国赛决赛PWN赛后复现:
- ezheap: 入门堆题,存在 UAF 和任意大小溢出
- anime: 非栈上的格式化字符串漏洞
ezheap
程序分析
程序环境为 2.31
,二进制保护全开:
1 | $ checksec ./ezheap |
程序在读入用户输入后,马上就进行了神秘的解析操作。遇到这种情况,我们有以下解决方案:
- 盲猜是 JSON,或根据解析函数中的一些硬编码的字符发现是 JSON(比如
'{'
) - 求助具有丰富逆向经验的队友,发现使用了 cJSON 库
- 使用提前准备的签名进行匹配,如下图所示:
关于 Binary Ninja 如何制作与匹配二进制签名,可以参考 官方文档。
除了神秘的解析之外,本题就是一个入门级菜单堆题。漏洞点有两个:
- Delete 函数中存在 dangling pointer(指针未清零);
- Modify 函数中存在任意大小溢出;
利用思路
由于 new 函数中对堆块大小做了 0x400 的限制,且一共只能分配 7 个堆块,因此如果想要泄露 libc 地址,需要通过溢出把堆块 size 改大,来把堆块 free 进 unsorted bin 中。
在此以后,直接 view 还拿不到 libc 地址。由于 cJSON 的实现,在解析用户的指令时,它会申请一系列的堆块。由于刚刚释放进 unsorted bin 的堆块足够大,所以它会被切割数次,导致原来位置上不再是一个指向 unsorted bin 的指针。
但这种情况也很好解决,堆上还有很多遗留的地址,可以先 edit 然后顺带把地址给读出来。(感谢 V3rdant 师傅)
之后就是使用 tcache poisoning 来将 __free_hook
劫持为 system
,然后执行 free("/bin/sh")
来拿到 shell 了。
Exploit Script
1 | #!/usr/bin/python3 |
Patch
由于出题人的检验脚本非常恶心,所以本题修复难度非常大。我和队友尝试了非常多种方法后,最后发现把 malloc
的参数硬编码为 0x1000
就可以通过检测。
anime
程序分析
程序环境为 GLIBC 2.31
,二进制保护全开:
1 | $ checksec ./pwn |
在 main
函数中,程序读取用户输入,并使用硬编码的 key 对输入进行 AES 解密。解密后的消息将会直接被使用 printf
打印出来,存在格式化字符串漏洞。程序会循环执行上述过程三次,然后从 main
函数返回。
本题的利用难点在于:存储用户输入的缓冲区位于堆上,且限制了我们只有三次攻击机会。
利用思路
关于非栈上的格式化字符串,可以先去找找别的资料看。
3 次机会一定是不足以打穿非栈的格式化字符串溢出的,必须想办法进行更多次攻击。在程序中,我们可以发现表示剩余次数的循环变量 i
保存在栈上,因此一开始仅有的这三次机会可以用来进行 i
的劫持。
- 泄漏栈地址、Libc 基址;
- 劫持一个栈上的栈指针,使其指向
i
; - 劫持
i
为一个更大的数。
1 | sa(b'3 times', aes128_encrypt(b'.%6$p.%15$p.\0', key)) |
在此之后就是常规的攻击了。我选择将栈上的返回地址劫持为 libc 中的 one_gadget。
Exploit Script
我使用 Cryptodomex
库进行 AES 加密。如果有安装 Cryptodome
库,也可以直接将脚本中所有 Cryptodome
直接替换为 Crypto
。
1 | #!/usr/bin/python3 |
Patch
最简单的方式是直接把 printf
直接改成 puts
。
我们比赛时 patch 的方法是跳转到 .eh_frame
段的代码中,执行 printf("%s", buf)
。