强网杯2025-filesystem赛后解
赛中和这题搏斗了好长时间,没有搞定,赛后1小时在本地打通了,docker没通,不知道为啥。这两天终于腾出时间再看了一下,docker也搞通了,遂写博客总结一下题解
本题逆向不算太难,至少比那个大冒险友好多了。函数功能很好判断,这里给出其储存file的结构体供各位师傅参考:
1 | |
本题漏洞点在edit和show这两个功能上。这两个功能都有负溢出,但有两个难题:
- show的leak有一定的限制。输出filename有0截断,输出input段又会检查size,而它储存size的那段空间恰好是标准file结构中未使用的字段,我们无法控制。因此,我们无法通过
stdin或stdout来leak libc,需要想别的方法leak - 机会只有两次
因此,我们首先要考虑的就是修改chance。毕竟2次的有限制读写看起来是什么都做不到的。
我们首先调试一手,容易观察到在heaplist前是有一个自指的地址的(0x5008处):
只要edit(-11)编辑这里,就可以在heaplist附近写任意地址,进而实现任意地址读写。
可惜这个任意地址读写需要用到两次机会,但我们现在还没有拿到任何地址,必须先读再写,那改chance就需要读+写的4次机会,看来这条路是行不通的。
这里我们回想到比赛中另一道题目bph的攻击手法。那一题通过修改_IO_2_1_stdin_来实现任意地址写,我们这题是不是可以再用类似的手法打呢?
这里我们选择打_IO_2_1_stdout_。赛中笔者是随便乱试试出来的,赛后详细看了一下,发现libc中有这样一段代码(libc2.41 libio/fileops.c line431):
1 | |
我们主要关注这几行:
1 | |
这里的setg定义如下:
1 | |
也就是说这个函数最后会把_IO_read_base、_IO_write_base等一系列读写缓冲区指针设置为_IO_buf_base。
那怎么调用到这个函数呢?其实,只要调用输出相关的函数,就一定会调用此函数,感兴趣的读者可以自行调试看看具体调用链,这里只需要知道输出一定会调用此函数就可以了。
那么,我们的思路就是覆写_IO_2_1_stdout_的_IO_buf_base为$rebase(0x5010)(即chance的地址),然后在一次输出后,_IO_write_base等会被覆写成这个地址,再一次输出后,这个地址的值会被覆写成输出的最后一个字符,通常是\n。这样,我们就完成了对chance的篡改。
这部分的POC如下:
1 | |
POC运行结束并输出menu后,就可以看到chance被覆写成0xa了。
好了,现在我们有了pie地址,还有了任意地址读写,接下来的事情想必不难了。读取got表即可leak libc,随便添加一个文件再读取就可leak heapbase。有所有地址,又有无限次任意地址读写,攻击想必不难。
这里笔者还是考虑利用程序本身的漏洞,关注程序退出时执行的sub_2210函数,它会关闭自己打开的flag_stream文件,我们把这个文件覆写成我们的fake_io就可以getshell了。这里的fake_io可以套house of apple的板子,但要注意,vtable项要设置为libc_base + libc.sym["_IO_wfile_jumps"] -0x70,因为fclose是通过__close触发的,我们得把__close对应的vtable偏移对上_IO_wfile_overflow,才能顺利触发house of apple调用链。完整exp如下:
1 | |
笔者赛时有点犯糖,很多东西写麻烦了,读者可以自行简化()
另外有一个诡异的问题,笔者没有搞清楚。笔者在使用patchelf和fedora虚拟机打时都能正常打通,但打docker不通,笔者调试了一下docker中的程序,发现docker中libc各项的偏移都不一致。可笔者patch的libc是从docker中docker cp得到的,不应该发生这种事情。如有懂这个的大佬麻烦在评论区或issue不吝赐教。