pt_regs

用于保存用户态–>内核态切换时的用户态信息,在内核态返回时会从pt_regs恢复数据。因而若我们能控制rsp到达此位置,即可以使用用户态寄存器值辅助实现ROP

img

list_head结构体

简单的内核双链表结构,在内核中经常作为实现双链表结构的基础,定义如下

1
2
3
4
5
//	https://elixir.bootlin.com/linux/latest/source/include/linux/types.h#L184

struct list_head {
struct list_head *next, *prev;
};

setxattr系统调用

从a3✌的d3kheap中学到的东西,a3✌也太超人了吧!!!原文链接如下:

【CTF.0x06】D^ 3CTF2022 d3kheap 出题手记 - arttnba3’s blog

setxattr 是一个十分独特的系统调用族,抛开其本身的功能,在 kernel 的利用当中他可以为我们提供近乎任意大小的内核空间 object 分配

观察setxattr源码,发现如下调用链:

1
2
3
SYS_setxattr()
path_setxattr()
setxattr()

setxattr()函数中有如下逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
size_t size, int flags)
{
//...
kvalue = kvmalloc(size, GFP_KERNEL);
if (!kvalue)
return -ENOMEM;
if (copy_from_user(kvalue, value, size)) {

//,..

kvfree(kvalue);

return error;
}

这里的 value 和 size 都是由我们来指定的,即我们可以分配任意大小的 object 并向其中写入内容,完成写入之后该 object 又会通过 kvfree 被释放掉,因此我们便可以通过 setxattr 多次修改 victim 的内容

msg_queue消息队列

同样来自a3✌的blog, a3✌简直是我Kernel的启蒙老师😭😭😭

【CTF.0x06】D^ 3CTF2022 d3kheap 出题手记 - arttnba3’s blog

The syscall about system V message queue

  • msgget:create a msg queue
  • msgsnd:send msg to a msg queue
  • msgrcv:recive msg from a msg queue

System will create a structure named msg_queue when we create a message queue. Here is the definition of this structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* one msq_queue structure for each present queue on the system */
struct msg_queue {
struct kern_ipc_perm q_perm;
time64_t q_stime; /* last msgsnd time */
time64_t q_rtime; /* last msgrcv time */
time64_t q_ctime; /* last change time */
unsigned long q_cbytes; /* current number of bytes on queue */
unsigned long q_qnum; /* number of messages in queue */
unsigned long q_qbytes; /* max number of bytes on queue */
struct pid *q_lspid; /* pid of last msgsnd */
struct pid *q_lrpid; /* last receive pid */

struct list_head q_messages;
struct list_head q_receivers;
struct list_head q_senders;
} __randomize_layout;

When we try to call msgsnd to send a message to a specified message queue, such a srturcture is created in kernel space:

1
2
3
4
5
6
7
8
9
/* one msg_msg structure for each message */
struct msg_msg {
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
struct msg_msgseg *next;
void *security;
/* the actual message follows immediately */
};

实际结构如下:

image.png

进程描述符(process descriptor)

在内核中使用结构体 task_struct 表示一个进程,该结构体定义于内核源码include/linux/sched.h中,代码比较长就不在这里贴出了.一个进程描述符的结构应当如下图所示:

image.png

注意到task_struct的源码中有如下代码:

1
2
3
4
5
6
7
8
9
10
/* Process credentials: */

/* Tracer's credentials at attach: */
const struct cred __rcu *ptracer_cred;

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;

/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;

Process credentials 是 kernel 用以判断一个进程权限的凭证,在 kernel 中使用 cred 结构体进行标识,对于一个进程而言应当有三个 cred:

  • ptracer_cred:使用ptrace系统调用跟踪该进程的上级进程的cred(gdb调试便是使用了这个系统调用,常见的反调试机制的原理便是提前占用了这个位置)
  • real_cred:即客体凭证objective cred),通常是一个进程最初启动时所具有的权限
  • cred:即主体凭证subjective cred),该进程的有效cred,kernel以此作为进程权限的凭证

一般情况下,主体凭证与客体凭证的值是相同的

例:当进程 A 向进程 B 发送消息时,A为主体,B为客体

进程权限凭证:cred结构体

对于一个进程,在内核当中使用一个结构体cred管理其权限,该结构体定义于内核源码include/linux/cred.h中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
/* RCU deletion */
union {
int non_rcu; /* Can we skip RCU deletion? */
struct rcu_head rcu; /* RCU deletion hook */
};
} __randomize_layout;

我们主要关注cred结构体中管理权限的变量

用户ID & 组ID

一个cred结构体中记载了一个进程四种不同的用户ID

  • 真实用户ID(real UID):标识一个进程启动时的用户ID
  • 保存用户ID(saved UID):标识一个进程最初的有效用户ID
  • 有效用户ID(effective UID):标识一个进程正在运行时所属的用户ID,一个进程在运行途中是可以改变自己所属用户的,因而权限机制也是通过有效用户ID进行认证的,内核通过 euid 来进行特权判断;为了防止用户一直使用高权限,当任务完成之后,euid 会与 suid 进行交换,恢复进程的有效权限
  • 文件系统用户ID(UID for VFS ops):标识一个进程创建文件时进行标识的用户ID

在通常情况下这几个ID应当都是相同的

用户组ID同样分为四个:真实组ID保存组ID有效组ID文件系统组ID,与用户ID是类似的,这里便不再赘叙

页->区->节点三级结构

页page

Linux kernel 中使用 page 结构体来表示一个物理页框,每个物理页框都有着一个对应的 page 结构体

image.png

区zone

在 Linux 下将一个节点内不同用途的内存区域划分为不同的区(zone),对应结构体 struct zone

自己画的图.png

节点node

zone 再向上一层便是节点——Linux 将_内存控制器(memory controller)_作为节点划分的依据,对于 UMA 架构而言只有一个节点,而对于 NUMA 架构而言通常有多个节点,对于同一个内存控制器下的 CPU 而言其对应的节点称之为_本地内存_,不同处理器之间通过总线进行进一步的连接。如下图所示,一个 MC 对应一个节点:

image.png

tty struct

/dev/ptmx 是一个特殊文件,用于在 Unix-like 系统中创建伪终端(pseudo-terminal)。伪终端是一种虚拟的终端设备,通常用于在程序之间提供类似于终端的交互界面,比如通过 SSH 远程登录、终端仿真器等。

当使用open()打开该设备后,会在内核空间分配一个tty_struct结构体,如图

tty_struct

对我们来说,最关键的便是其中的const struct tty_operations *ops;,由结构体的名称tty_operations我们也能猜测出其为表状结构。该结构体定义在linux/tty_driver.h,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t count);
int (*put_char)(struct tty_struct *tty, u8 ch);
void (*flush_chars)(struct tty_struct *tty);
unsigned int (*write_room)(struct tty_struct *tty);
unsigned int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, const struct ktermios *old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *m, void *driver);
} __randomize_layout;

拥有丰富的函数指针,其中open, read, write等等函数也放置其中。常见利用手法便是劫持tty_operation的函数指针,这样我们在对/dev/ptmx设备进行读写操作时,便可调用劫持后的指针地址。


下面这些留个坑,有空补上

pipe_buffer

seq operation