House of storm
house of storm 是 largebin attack 的方法之一,在国赛分区赛中遇到过。
例题是0ctf_2018_heapstorm2
逆向分析
保护全开
Allocate、Update、Delete和View四个功能,普通的增删查改
init()
把0x13370800的一片地址先填满了随机数和随机数异或后的数
这题比较特殊的地方就是自己写了两个异或函数,大部分堆块的指针操作都是通过这两个函数,分析时直接看成是堆块结构体指针就好
mallopt(1, 0)
禁用了fastbin
mallopt int mallopt(int param,int value) param的取值分别为MMXFAST,value是以字节为单位。 MMXFAST:定义使用fastbins的内存请求大小的上限,小于该阈值的小块内存请求将不会使用fastbins获得内存,其缺省值为64。下面我们来将M_MXFAST设置为0,禁止使用fastbins
allocate
一共可申请16个chunk,用的是calloc,故无法劫持malloc_hook
结构体比较简单,只有size和ptr
update
限制了content的字符数最大为size-12,但是strcpy((char *)(size + chunkptr), "HEAPSTORM_II");
造成了一个off-by-null。
view
会检测(head[1].size ^ head[1].ptr) != 0x13377331
思路
我们希望使用view来leak,因此我们需要让view可用
Chunk Shrink to overlap
update存在off-by-null,可以触发chunk overlap
先布局
1 | alloc(0x18) #0 update 0 to trigger off by null |
然后触发off by null
1 | free(1) |
将chunk1 分割
1 | alloc(0x18) #1 split the original chunk 1 into 2 parts |
1 | free(1) |
由于禁用了fastbin,现在的chunk2被认为是SB chunk的size,当free(2)时,会通过pre_size来寻找上一个chunk(也就是会找到chunk1)查看它是否空闲,空闲就合并。合并之后被放入UB,踩出来了main_arena+88
于是我们的chunk7就被包裹在了这个UB chunk中,可以实现overlap了
1 | alloc(0x38) #1 |
此时2与7 overlap(7比2大0x20)。
把刚才的流程再走一遍
1 | free(4) |
此时left和8 overlap
重头戏
1 | free(2) |
unsortedbin
all: 2 —▸ left —▸ main_arena+88◂— 2
1 | allocalloc(0x4e8) #2 |
寻找0x4f0大小的chunk,不符合的按大小归类,left被放入LB
largebins
0x4c0: left —▸ main_arena+1144 ◂— left
1 | free(2) |
2落入UB
unsortedbin
all: 2 —▸ main_arena+88 ◂— 2
largebins
0x4c0: left —▸ main_arena+1144 ◂— left
回到我们的目的(head[1].size ^ head[1].ptr) != 0x13377331
上来,我们需要控制head来使条件成立。刚刚trigger 的 overlap 就可以用来 fake chunk。
1 | head = 0x13370000 + 0x800 |
可见此时的2和left的bk都已经被改变
原理分析
原理分析放在这里,这样就知道啥条件下可以用house of storm了。
一旦我们再次calloc,calloc会如何操作unsortbin和large bin?这个时候就要看看 __libc_malloc
源码了:
1 | mem = _int_malloc (av, sz); |
调用了_int_malloc
,我们的chunk是LB chunk size的,对应代码如下
1 | //#define unsorted_chunks(M) (bin_at (M, 1)) |
我们主要关注双向链表的插入
1 | victim->fd_nextsize = fwd; |
1 | victim->bk = bck; |
2次插入操作的重要结果:
1 | fwd->bk_nextsize->fd_nextsize = victim |
fwd就是large bin里的唯一(在这题中)chunk heap地址。 它的bk_nextsize
是我们随意控制的,wp把它写成了 0x13370800 -0x20-0x18-5
。 那么*(0x13370800 -0x20-0x18-5 + 0x20) = victim
。
类似的
1 | fwd->bk = victim |
我们也可以控制 fwd->bk,将其写为 0x13370800 -0x20+8。 *( 0x13370800 -0x20+8 ) = victim
。
插入操作结束后,又回到外面那层循环,又去寻找下一个unsortbin chunk是不是满足要求或者需要分割 。 下一个chunk的地址就是 0x13370800 -0x20
。
这个时候这个地址已经有值了,看看是啥值?
1 | 前面说 `*(0x13370800-0x18-5 = 0x133707e3 ) = victim`, |
__int_malloc
在拿到chunk后返回到__libc_malloc
,__libc_malloc
会对chunk的进行检查,这里如果有错的话会直接crash,但是由于程序有随机化,多运行几次总能有一次成功的。
1 | /* |
这里只有chunk_is_mmapped(mem2chunk(victim))有可能满足,也就是只有size为0x56时满足条件。
这里需要用异常处理会比较好(特别是在AWD中)
1 | try: |
leak & get shell
1 | #! /usr/bin/python |
总结
House_of_storm
可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk,造成任意地址写。
漏洞利用条件
- glibc版本小于2.30,因为2.30之后加入了检查
- 需要攻击者在
large_bin
和unsorted_bin
中分别布置一个chunk 这两个chunk需要在归位之后处于同一个largebin
的index中且unsorted_bin
中的chunk要比large_bin
中的大 - 需要
unsorted_bin
中的bk指针
可控 - 需要
large_bin
中的bk指针和bk_nextsize
指针可控