house of water : 一种无泄露情况下的堆利用手法
| 0 | 前言
一种可以在没有显式打印函数存在情况下,将libc指针写入tcachebins的手法
虽然条件苛刻但是思路十分巧妙
| 1 | 利用条件
- 我们可以对堆块进行
use-after-free操作 - 能够
malloc/free一个较大堆块(0x9000)
无需溢出与泄露内存即可完成攻击
我们在攻击的过程中需要几枚悬空指针,如果在真实ctf环境中要先堆好风水,预留好指针的位置
| 2 | 攻击演示
0x00设置伪造chunksize
当我们申请了0x3d0与0x3e0两种堆块并释放后,在tcachebins_struct中会出现一个0x10001
1 | add(0,0x3d8) |
如下

这个可以作为我们伪造的size,让ptmalloc误认为这里存在一个大小为0x10000,已被申请的堆块
假如这个大堆块伪造成功,这个堆块的内容便与tcachebins_struct中的指针数组重叠(0x20,0x30的指针)
如将libc写入,我们就可以从tcachebins中申请libc地址

0x01填充big_chunk
1 | for i in range(7): |
七个堆块不仅要用来填充0x10000的伪造chunk,还需要堵住tcachebins中0x90的位置,让其他释放的0x90堆块不进入tcachebins
剩下的三个0x88的堆块在后续中的作用关键,它们需要进入unsort_bins中,所以使用0x20的堆块将它们隔开
最后使用0xf000不满0x10000的堆块,并在最后申请的小堆块中伪造pre_size与size
此时big_chunk的前(pre_inuse:0x10001)后(presize:0x10000)都设置好了
0x02填充tcachebins
1 | for i in range(7): |
前面说的,将tcachebins中的0x90部分填满7
0x03伪造big_chunk的fd,bk
我将chunk[9,11,13]分别命名为usort_start,usort_middle与usort_end
1 | usort_start=0x000055555555be60 |
如果我们想将libc指针写入bigchunk,我们就要利用unsortedbins中堆块被申请后脱钩时不会清空fd,bk指针的操作
而我们不能直接free掉big_chunk(会产生double free or corruption (!prev))
我们就想能不能通过末尾覆盖fd/bk,将一个合法的unsortedbins修改为big_chunk
此时我们通过以上free操作,将usort_start-0x8与usort_end-0x8写入了teachebins的0x20/0x30指针处,也就是 big_chunk 的fd/bk处

图中白框处同时是big_chunk的fd/bk
0x04伪造unsortedbins的fd/bk
1 | free(13) |
现在我们只需要再将usort_start与usort_end放入unsortedbins并修改着两个堆块的fd/bk指针为heap_base+0x80就可以手动形成一个unsortbins链
在未修改前,unsortedbins的结构
1 | unsortedbin |
其中0x55555555be50为usort_start,0x55555555bfb0为usort_end
通过uaf部分写usort_start.fd与usort_end.bk,会将unsortedbins的结构修改为(只差了三个字节,所以还得爆破4bits)
1 | unsortedbin |
可以看到0x55555555b080(big_chunk)已经成功取代0x55555555bf00(usort_middle)

0x05将libc印在teachebins
如果此时我们如果申请一个大于0x90的堆块,ptmalloc就会从big_chunk中取出堆块,并在脱钩时将libc写入申请的chunk的fd/bk位
1 | add(20,0x388) |

成功在未泄露的情况下申请到libc指针
在glibc源码中
✅ malloc 为了防止“被重用的 freed chunk”再次参与链表,防御 unsorted bin attack,这里会手动清除 fd/bk。
glibc 的开发者用一种”自指向 main_arena 的方式”来标识:这个 chunk 已经被取出,不应该再进 bin。
| 3 | 总POC
1 |
|
1 | from pwn import * |