0CTF/TCTF 2021 Music writeup
接手这个题的时候从 kjdy 得知这个题是个 0day 题,并且给了这个二进制的文档,特别注意了 INCLUDE 操作。最重要的是,kjdy 提示这个人的代码风格及其离谱,不像是个正常人。(kjdy yyds)
文档: https://mirror-hk.koddos.net/CTAN/support/m-tx/doc/mtxdoc.pdf
文档中提到 voice 行数 > 256 时会触发崩溃。经测试,这是 bss 上的溢出引发 segfault,只能溢出一点点,没用。
INCLUDE 功能似乎能直接 INCLUDE: /home/pwn/flag
,本地可以打印 flag,远程不行,考虑 getflag 是 setuid 程序。
之后开始读逻辑,审源码,以及在 ida 中审计二进制文件(许多有问题的代码被优化掉了)。几个小时后审出来 sprintf 的溢出。图中 v4, v5都能到256的长度。但是这个二进制开了 FORTIFY 保护,没办法溢出。
再之后发现了可以通过 INLUCDE: /proc/self/maps
来泄露 libc,但是泄露完程序会直接崩溃。读文档,发现 Enable: ignoreErrors
可以阻止崩溃,而且开启 debugMode
和 beVerbose
之后能够获得更加详细的 log。
把一些有用没用的选项加进来,泄露地址的mtx文件长这样。
1 | Title: Net soos ek is |
之后需要找一个任意地址写,继续审代码。既然 sprintf 行不通,最危险的函数就是 strcpy 了。于是在 ida 里面搜索所有 strcpy 的引用。
首先找到的是 addUptext 函数,里面如果 voice 能够超长的话,可以有个 strcpy 可以溢出。构造了两个小时,没办法构造,放弃。
之后在审到头秃的时候,找到了关键的漏洞点。
pushFile 中 v4->actualfile_NAME
只有 120 字节大小,文件名却可以有 255 字节大小。这就造成了堆上的溢出,实际上文件名最多可以有 200 字节,能够溢出 80 字节。再加上 Enable: ignoreErrors
,即使文件不存在也不会直接死掉。再通过套娃 INCLUDE ,就可以一定程度上控制 malloc、free 的顺序。所以就可以开始堆风水了。
程序中有 4 个地方会用到堆分配:
- 上传文件的缓冲 buffer,size 和上传的数据大小有关,calloc 分配,分配后会被释放。
- pushFile 中的malloc,分配 file_node 结构体(溢出点), size: 0x1a0
- 打开一个新文件产生的 FILE 结构体, size: 0x1e0
- io 输入缓冲区,size: 0x1010
正常的话,file_node 结构体溢出的下一块 chunk 要么是 io 缓冲区 (unsorted bin),要么是 FILE 结构体。 Unsorted bin 非常难用,FILE 结构体分配过后控制不了写的内容,就算打 FSOP 也过于麻烦。所以还是考虑能不能通过 upload 构造一下堆布局。
如果 upload 一个 0x190 大小的文件,就会在 tcache 里面留下一个 0x1a0 的 chunk。多分配几个就可以有内存连续的 0x190 大小的 chunk 在 tcache 里面成链。但是难顶的链里的 chunk 是从内存高地址排向内存低地址的 … (这样就没办法通过前面的 chunk 改后面的 chunk 的 fd 指针,达到任意地址分配)
之后就是一通玄学操作(我不想回忆了),最后构造出了内存中连续的 0x1a0, 0x1e0, 0x1a0 的 freed chunk。分别叫 chunk1, chunk2, chunk3 吧。
chunk1 通过溢出改写 chunk2 的 size 为 0x1a0,经过 free 后,tcache 里会有 freelist -> ... -> chunk2 -> chunk3 -> ...
的情况。
再次分配通过 chunk2 改 chunk3 的 fd,到 freehook,然后用 one gadget 拿到 shell。
堆风水还有很多细节坑,不想回忆了。。。
exp:
1 | from pwn import * |
test.mtx:
1 | Title: Net soos ek is |
test1.mtx
1 | Title: Net soos ek is |
注意 test1.mtx 下面两个空行是必须的。