pwn-kernel_前置知识

一、内核前置基础知识:

1.physmap:

physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址,而用户空间的虚拟内存也会映射到RAM。

所以可能会形成如下关系:

usr_data—>RAM—->physmap

那么physmap中就有可能会保存usr_data,那么就为提权代码放到内核空间提供了前置条件,同时有mmap就可能会导致条件竞争。

2.ioctl:系统调用,用于与设备通信

由于内核和用户空间隔离开,所以就需要一个接口来使得用户可以在一定情况下访问内核空间:

int ioctl(int fd, unsigned long request, …)

第一个参数为打开设备 (open) 返回的文件描述符,第二个参数为用户程序对设备的控制命令,再后边的参数则是一些补充参数,与设备有关。

3.状态转换相关操作:

当发生系统调用,产生异常,外设产生中断等事件时,会发生用户态到内核态的切换

ENTRY(entry_SYSCALL_64)

(1).用户态至内核态:

①swapgs指令触发,切换到kernel GS:

SWAPGS_UNSAFE_STACK

②保存栈值,设置内核栈:

movq %rsp, PER_CPU_VAR(rsp_scratch)

movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp

③压栈保存寄存器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#注释头 

/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx tuichu /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */

④判断类型并跳转:

1
2
3
4
5
#注释头

movq PER_CPU_VAR(current_task), %r11
testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11)
jnz entry_SYSCALL64_slow_path

1
2
3
4
5
6
7
#注释头

entry_SYSCALL64_slow_path:
/* IRQs are off. */
SAVE_EXTRA_REGS
movq %rsp, %rdi
call do_syscall_64 /* returns with IRQs disabled */

(2)内核态至用户态:

①swapgs恢复GS

②iretq(加上寄存器信息)或sysretq,如果使用 iretq 还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp 等)

kernel 的 crash 通常会引起重启

4.相关保护技术Mitigation:

(1)SMAP和SMEP保护技术:

(arm里面叫PXN(Privilege Execute Never)和PAN(Privileged Access Never))

①SMAP:禁止内核访问用户空间的数据(Supervisor Mode Access Prevention)

②SMEP:禁止内核执行用户空间的代码(Supervisor Mode Execution Prevention)

内核命令行中添加nosmap和nosmep禁用

(2)kernel canary:

编译内核时设置CONFIG_CC_STACKPROTECTOR,可以起到类似于stack canary的技术。

(3)KALSR:内核地址随机化

5.提权代码与函数结构体:

cred结构体:kernel用cred结构体记录进程权限(每个结构都有一个cred结构),保存了进程的相关信息,如果利用这个cred就可以提权。一般调用commit_creds(prepare_kernel_cred(0))完成提权然后用户态“着陆”起shell。

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
#注释头

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 __rcu *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 */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;

不同内核版本的cred结构体可能不太一样。

二、内核态函数及相关变化:

1.printf() -> printk(),但需要注意的是 printk() 不一定会把内容显示到终端上,但一定在内核缓冲区里,可以通过 dmesg 查看效果

2.malloc() -> kmalloc(),内核态的内存分配函数,和malloc()相似,但使用的是slab/slub分配器。

3.free() -> kfree(),同 kmalloc()

4.memcpy() -> copy_from_user()/copy_to_user()

5.copy_from_user() 实现了将用户空间的数据传送到内核空间

6.copy_to_user() 实现了将内核空间的数据传送到用户空间

7.提权相关函数:

1
2
3
4
#注释头

int commit_creds(struct cred *new)
struct cred* prepare_kernel_cred(struct task_struct* daemon)

执行commit_creds(prepare_kernel_cred(0))即可获得 root 权限

函数地址可在/proc/kallsyms,老版本/proc/ksyms中查看(cat|grep),权限一般需要root

三、保护绕过技术:

1.ret2usr:

在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr

2.ret2dir:

如果用户空间用mmap()把提权代码映射到内存RAM,那么就可以在physmap里找到其对应的副本,就可以修改EIP跳到副本执行,这种利用方式被称为ret2dir。

3.kernel canary:

绕过方法同用户空间的canary绕过大致相同,编译内核时设置CONFIG_CC_STACKPROTECTOR,可以起到类似于stack canary的技术。