Blaze 2016 - dmail Writeup
Blaze CTF, 不知道是谁举办的,不过这个dmail确实有点麻烦……
程序逻辑较为简单,只有send mail, read mail, delete mail这3个功能,程序首先分配了一个256字节的数组(32个元素)在堆上,依靠一个Bitmap来记录每个位置是否已经使用。每次send mail时就malloc一块小于256字节的块存放到之前的数组中,delete mail则free其指向的块并将其清0. read mail, delete mail时都会检查Bitmap对应位是否为1, 而send mail时则会检查对应块是否为0.程序开启所有保护。
漏洞
漏洞还是比较容易发现,因为程序没对输入的idx做检查,故只要对应的Bitmap位是0就可以随便输入idx,程序会把malloc得到的指针写到idx指向的地方,这就相当与有了一个任意内存写的功能,但不能随意控制写的内容。程序比较神奇的是它对于大的idx能够检查出对应位(idx % 32)是否为1,给接下来的利用带来不小的麻烦,至于为什么我也没太搞清楚……
Leak
先分配mail 0到堆上,然后分配mail 34,此时程序正好将malloc得到的堆地址写到了mail 0指向的位置,此时read mail 0就读出了mail 34的地址,也即leak出了heap的位置。
libc基址的leak则相对麻烦一些,我感觉自己搞的比较绕。分配一个大块mail 16,大小为0xb0,预先在其中布置一个fake chunk,大小为0x50。接着将mail 0 free,让其进入fast bin,此时再次分配mail 34,此时覆盖掉之前free掉的mail 0的fd,这个指针正好指向mail 34的用户数据,于是我们在写mail 34数据时再伪造一个chunk,大小为0x50,fd为mail 16中fake chunk的位置。接着通过2次分配0x50的块mail 17, mail 18,此时fast bin中就剩下了mail 16中的fake chunk。再次分配mail 19即可指向mail 16中的fake chunk,接着将mail 16 free,此时其被投入unsorted bin中,再次取回mail 16,这次大小为0x60,于是unsorted bin中0xb0大小的块将分裂为0x60和0x50两块,而剩下的块又将投入unsorted bin中,正好它的fd和bk指针写到了mail 19的用户数据里,这时read mail 19即可得到unsorted bin的地址,也即leak出了libc的位置。(当然,是libc偏移已知的情况下)
1 | [mail 0] [mail 16] [mail 34] |
Exploit
这部分感觉脑洞比较大,因为程序没有edit这种功能,传统的unlink不太好用,只能通过伪造fd来取得想要写的位置。但Fast bin需要正确的size,其他bin则有link list完整性检查,都不太好利用。在程序保护全开的情况下,一般的想法是overwrite free hook to system来进行利用,而且这个题程序bss上的指针就一个,还有一个Bitmap数据,感觉没有太多利用价值。
想了许久,结果想到了前不久的zerostorage……于是我决定试一下overwrite global_max_fast,之前提到可以通过idx来进行任意内存写,我们就把global_max_fast写成一个堆地址,此时所有chunk都会被当成Fast chunk来处理。
在main_arena中,top是跟在fastbin之后的成员,所以通过适当控制chunk的大小(64bit下大小为0xc0),就可以控制top的值。具体来说,事先需要准备一个0xc0大小的mail 20,在改写global_max_fast之后free mail 20,此时mail 20被当作fast chunk插入top这个”fast bin”中。接着使用之前leak相似的伎俩,利用对应的idx构造fake chunk并改写mail 20的fd指向free_hook - 16,然后取回mail 20和构造的fake chunk,即可使top指向free_hook - 16.随后分配一个fast bin里没有的大小,触发top chunk code,即可将system写入free_hook中。在这之前,还需要向free_hook - 8的位置也做一次任意内存写,让”top chunk”的size足够大才可以。
Puzzle
实际操作中,比较麻烦的一个是Bitmap,在构造chunk的时候需要妥善处理idx,不能与之后的任意写有冲突否则就写不进去。而且31这个idx比较神奇,一写它就导致bitmap的高4个bytes都变成了1,之后任意写就写不了了。remote的free_hook - 8恰恰就对应idx 31,于是我将fake top chunk的位置向前推,发现free_book之前一些位置会被fgets使用,乱写的话会崩掉,于是又往前找,最后找到了free_hook - 56的地方终于没事了……
另一个比较麻烦的就是libc,题目没给libc,只说运行在Ubuntu 14.04。于是我搜了一个Ubuntu 14.04LTS的libc deb下来,发现unsorted bin的位置一样,于是觉得应该没错。结果远程试了几次发现不行,找了一下客服,他们说需要leak一下,似乎libc不太一样。于是我就想办法leak了一下,之前说过如果写idx 31将导致无法任意内存写,但相对地,可以任意内存读,所以我在某个mail中放上想要leak的地址,然后read对应的idx就可以读出内容。
从计算的地址处读出一些数据后,我发现有点奇怪,数据段的地址应该没错,唯独system计算错了,而且我发现libc的基址竟然比计算的往前推了1个page!于是只能leak出一些数据,然后做一个修正,最后终于搞成了。拖下libc来一看,果然不一样,数据段的offset都多了0x1000也就是1个page,而system则往后推了0xd40,非常神奇……
小结
现在的heap的脑洞原来越大,利用越来越复杂了,感觉自己有点跟不上时代的脚步了呢……XD
利用代码,仅供参考
1 | #!/usr/bin/env python |