DEFCON 29 CTF Qualifier coooinbase and coooinbase-kernel Write-up
coooinbase
题目描述:
a simple service backed by special hardware for buying bitcoin: our beta testing server is live at http://52.6.166.222:4567 - this time attack the kernel!
图:1 题目服务首页
从题目的首页的 custom hardware
处可以下到题目的固件包。
图: 2 下载题目固件
可以看到固件包里包以下文件:
1 | ➜ coooinbase tar -xvzf src |
其中 x.rb
是 web 的后端服务,我们需要关注的代码逻辑如图:
图3: x.rb 代码
阅读代码,我们可以知道一下几点:
- 当我们访问
/buy
api 的时候, 代码会请求HTTP_POST
地址处的的/gen-bson
api, 当获取到/gen-bson
api 返回的数据后,会将数据写入pwn
文件中,然后以重定向的形式喂入./x.sh
文件 /gen-bson
这个 api 会调用valid_credit_card
和valid_association
函数分别校验填入的 cardnumber 的合法性。 但是值得注意的是,这两个函数均会调用to_s.gusb(/\D/, '')
将传入的number
变量中的非数字给去掉,但是在 44 -处的number
却是仍然带有字符串的,因此此处我们可以传入其他非数字的值 (6011000000000004 这个cardnmumber 可以过校验)gen-bson
在45-46 行处会将参数转成 bson 格式,且 base64 编码, 然后返回
(注: 此处还有有个点,我在一开始的时候没注意到,暂且不提)
x.sh
的代码内容如下:
1 | timeout 1 qemu-system-aarch64 -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 |
用 qemu 跑起一个服务, 内核为: coooinbase.bin
以及有对应的 rootfs.img , 通过以下命令可以将文件系统 mount 出来
1 | modprobe nbd max_part=8 |
可以看到 文件系统中有三个文件:
1 | ➜ rootfs ls |
其中 bin 和 run , 通过逆向发现是一样的文件, flg 是flag 文件
猜测 bin (run) 就是要 pwn 的用户态程序, 通过启动命令,我们知道架构为 aarch64, cpu 为 cortex-a57, 我们使用 IDA Pro 打开该文件, 设置如下:
图4:IDA 加载
图4:IDA分析截图
然后就必然发现 IDA 什么函数都没有分析出来, 所以我们需要修正下我们的 IDB,修复出函数
图5:修复后的 IDA 截图
在 bsion_find_string
中我们发现了一处动态分配栈空间的逻辑
图5:动态分配栈空间
在地址 0xB6C 处, X1 为传入的字符串大小, 此处判断需要动态分配的栈的大小 。
图5:mapping 截图
但是这里存在一个问题, 这个没有判断传入的字符串大小是不是太大,如果太大的话, 例如我传入 0xf000 大小的字符串,那么此时将分配 0xf000 大小的栈, 即 SP = SP - 0xffff
, 由于栈在程序代码段的下方,此时将导致栈会被分配到代码段上,而且由于是 qemu 启动的程序,所有的段都是可写可执行的。
因此,这个题目的思路如下:
构造足够长的字符串,将栈分配之后将执行的代码段位置, 写入 shellcode 然后最后执行 shellcode. 由于程序有现成 open read write 的函数, 因此 shellcode 编写方便了许多,我们只需直接 call 函数即可。
shellcode:
1 |
|
但是这里会出现一个坑点:
ruby to bson 的时候得是 UTF-8 的字符集,这意味这在 x.rb 代码中的(见图4) 45 是过不去的, 然后在比赛的时候一度陷入试图把我的 shellcode 的修改为全为 UTF-8 字符集的艰苦工作中。 然后 peanuts 发现, HTTP_POST
是由 HTTP header 中的 HOST字段控制的, 这以为我们不需要通过后端自身的 /gen_bson
api 传入构造好的 payload , 我们只需搭建我们自己的服务, 当接收到 /gen_bson
请求后, 传回我们的 payload。
coooinbase-kernel
内核实现了几个syscall
其中 write 限制了读取的地址的范围
但是 read 中没有限制写入的地址的范围
因此这个题的思路为:
在已经完成的用户态任意代码执行的基础上
- open bin 文件,找到一个无意义的代码
- lseek 到该处
- read 该处的代码
- 通过 read 向内核的 write 的判断地址范围的地方写掉
- 调用 write 将内核地址中的 flag 打印出
1 | # open |