有点老的题目——19年的 house of prime。算是 MIPS 入门题。环境为 uLibc,是用于嵌入式系统和移动设备的基于 Linux 内核的操作系统(摘自wiki)。说白了就是小型设备都用它,我理解为精简版的 glibc,有一点点变化,但相似度还是蛮高的en,但是 qemu user调着好像有点问题,先鸽一会会😭😰,后续补上
参考链接(🙏):
- kirin: TCTF Finals 2019 Embedded_heap
- h4lo: 2019 0CTF embedded-heap 题目复现
- e3pem: 0ctf2019 Final embedded_heap题解
题目分析
chunk 结构大致如下:
程序一开始使用上面的函数分配了若干个(随机数)chunk,大小也是 0~0x100 随机。接着打印出每个 chunk 的 content 大小(非实际 chunk 大小)。
菜单题:
- update:往已分配的 chunk 中(+8 ptr 位置)写任意 size 的 content。
- view:打印 content 处 size 个字符。
- pwn:执行完后退出程序。其中 delete 两次,update 一次。
只有一个堆溢出漏洞,但足以使 chunk 的所有字段可控。
接下来看题目给的 libc 和 ld 文件。显然,使用的是 uClibc 的 0.9.33.2 版本。但是这里给了四个文件,不难看出它们之间的链接关系:
我们试着用 qemu 运行一下,发现在 lib 中有两套链接的情况下,默认选择的是 ld-uClibc.so.0。但是报错说,文件架构不匹配。
1 | ch331n@ubuntu:~/cs/practices/embedded_heap$ qemu-mips -L ./ ./embedded_heap |
也许真正匹配的是 0.9.33.2 版本的 uClibc,用 mv 命令把默认的 ld-uClibc.so.0 替换成 ld-uClibc-0.9.33.2.so 就可以了,libc 的替换同理。换好后再运行就正常了。
1 | ch331n@ubuntu:~/cs/practices/embedded_heap/lib$ mv ld-uClibc-0.9.33.2.so ld-uClibc.so.0 |
堆管理机制分析
堆溢出具体要修改哪里、改成什么,还得看 uClibc 的管理机制是怎么写的。从官网上下载源码:https://www.uclibc.org/。malloc 的位置:/(your_path)/uClibc-0.9.33.2/libc/stdlib/malloc-standard
。
malloc_state 结构体定义如下:
1 | struct malloc_state { |
其中,关于 malloc_chunk 的定义,跟 glibc 是相似的:
1 | struct malloc_chunk { |
不同的是,uClibc 把 max_fast 写进了管理所有堆布局的 malloc_state 中,就在 fastbin 的上方。如果想修改 max_fast 的大小,最直接的应该是通过其相邻的 fastbin 进行修改,当然不排除也有其他的做法。
以下是 free.c 中关于 fastbin 的代码。可以看到,当准备 free 的 chunk 符合 fastbin 的大小时,会以它的 size 大小去寻找合适的 fastbin 链,也就是 fastbin_index(size) 这一步。
1 | /* |
关于 fastbin 下标的计算,在 malloc.h 中有定义:
1 | /* offset 2 to use otherwise unindexable first 2 bins */ |
前面说到在这个题目中,chunk 的所有字段都可控。 size 可控,通过 fastbin_index(sz) 计算出来的下标也可控。巧就巧在当 size==8 时,(8 >> 3) -2 = -1
,而在 malloc_state 中,fastbin[-1] 就是 max_fast 的位置。从而执行完 free 后, max_fast 存储的是堆地址,相当于扩大了 fastbin 的范围。
正常情况下 size 不可能为 8,该手法的利用前提之一就是 size 可修改。free 完 这个 size 为 8 的 chunk 后, max_fast 的值变成了 0x5555……..(chunk 地址,十二位 hex 数,很大)。之后再 free 一个 “huge size” (在 chunk 地址范围内)的 chunk,也会被合法地当作 fastbin 处理,放入由 fastbin_index(size) 算出来的合适偏移处。由于fastbin + huge idx 的范围也很广,现在也算能实现任意地址写了。因此利用的第二个前提是,能实现两次 free。至于往任意地址写什么,因题而异。
漏洞利用
刚好这个题目,size可控,有两次 free。现在的问题是往什么地址写什么。考虑到程序执行完 pwn 函数后直接结束,打 exit 流。
en 大家貌似都是用 qemu-system 调试的,都只是提了下 qemu-user 的调试方法,没有人用 user 来调?貌似有点问题,调了几天都没调好, 待我找下问题在哪……
关于 uClibc
uClibc 的初始发行都是 2000 年的事了。这道题目 embedded_heap 所用的 0.9.33.2 版本,是 2012年发布的最终版本。此后没再发布新版本,官方给出的理由是维护者缺乏任何沟通 (?