【Kernel】一些trick和struct记录(持续更新)
11月 21, 2023
pt_regs 用于保存用户态–>内核态 切换时的用户态信息,在内核态返回时会从pt_regs
恢复数据。因而若我们能控制rsp到达此位置,即可以使用用户态寄存器值辅助实现ROP
list_head结构体 简单的内核双链表结构,在内核中经常作为实现双链表结构的基础,定义如下
1 2 3 4 5 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 struct msg_queue { struct kern_ipc_perm q_perm ; time64_t q_stime; time64_t q_rtime; time64_t q_ctime; unsigned long q_cbytes; unsigned long q_qnum; unsigned long q_qbytes; struct pid *q_lspid ; struct pid *q_lrpid ; 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 struct msg_msg { struct list_head m_list ; long m_type; size_t m_ts; struct msg_msgseg *next ; void *security; };
实际结构如下:
进程描述符(process descriptor) 在内核中使用结构体 task_struct
表示一个进程,该结构体定义于内核源码include/linux/sched.h
中,代码比较长就不在这里贴出了.一个进程描述符的结构应当如下图所示:
注意到task_struct
的源码中有如下代码:
1 2 3 4 5 6 7 8 9 10 const struct cred __rcu *ptracer_cred ;const struct cred __rcu *real_cred ;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; void *put_addr; unsigned magic; #define CRED_MAGIC 0x43736564 #define CRED_MAGIC_DEAD 0x44656144 #endif kuid_t uid; kgid_t gid; kuid_t suid; kgid_t sgid; kuid_t euid; kgid_t egid; kuid_t fsuid; kgid_t fsgid; unsigned securebits; kernel_cap_t cap_inheritable; kernel_cap_t cap_permitted; kernel_cap_t cap_effective; kernel_cap_t cap_bset; kernel_cap_t cap_ambient; #ifdef CONFIG_KEYS unsigned char jit_keyring; struct key *session_keyring ; struct key *process_keyring ; struct key *thread_keyring ; struct key *request_key_auth ; #endif #ifdef CONFIG_SECURITY void *security; #endif struct user_struct *user ; struct user_namespace *user_ns ; struct group_info *group_info ; union { int non_rcu; struct rcu_head rcu ; }; } __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 结构体
区zone 在 Linux 下将一个节点内不同用途的内存区域划分为不同的区(zone)
,对应结构体 struct zone
节点node zone 再向上一层便是节点 ——Linux 将_内存控制器(memory controller)_作为节点划分的依据,对于 UMA 架构而言只有一个节点,而对于 NUMA 架构而言通常有多个节点,对于同一个内存控制器下的 CPU 而言其对应的节点称之为_本地内存_,不同处理器之间通过总线进行进一步的连接。如下图所示,一个 MC 对应一个节点:
tty struct /dev/ptmx
是一个特殊文件,用于在 Unix-like 系统中创建伪终端(pseudo-terminal)。伪终端是一种虚拟的终端设备,通常用于在程序之间提供类似于终端的交互界面,比如通过 SSH 远程登录、终端仿真器等。
当使用open()
打开该设备后,会在内核空间分配一个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