how2heap学习笔记

时间:2023-03-08 17:17:04

github源代码地址

这里只分析glibc2.25堆分配的特性,为了方便调试编译时使用

gcc -g -no-pie <input_file_name> -o <output_file_name>

0.fastbin_dup_consolidate

  glibc在分配一个large chunk时会先检查是否存在fastbins,如果存在则合并fastbins到unsortedbins,并根据unsortedbins大小将其划入small bins或large bins。所以第一次free后p1会划入small bins,第二次可以free的原因是double free只检查fastbins的头节点和所释放的堆是否一致。malloc两次依次从fastbins和smallbins取出p1

1.fastbin_dup_into_stack

 源代码中11行要输出的值和48行的指针赋值不一致,都改成+或-就行了

  double free的一种利用,这种攻击的本质是我们可以控制一个存在于fast bin的堆的内容。free(a),free(b),free(a).然后malloc两次,这时fastbins中只有a,但此时a的chunk内容我们可控,所以此时伪造a的fd,在栈中伪造size,此时fastbin内容就变为a->a.fd,此时我们malloc两次,第二次就会得到a.fd的地址,造成任意地址写。

PS:关于Fastbin的LIFO。malloc fastbin大小的堆时取fastbin头结点直接指向的堆,向fastbin链表添加堆时也是添加到头结点的位置。如果是取堆和放堆都在链表结束位置,则取堆和放堆都需要遍历一遍fastbin,O(n)复杂度;而直接在fastbin头部取堆和放堆只需要修改头结点指针就行,O(1)复杂度

how2heap学习笔记

2.house_of_einherjar

  off by one的一种利用。假设我们有两个分配的堆p0和p1,其中p0位于物理低地址,此时p0和p1共享p1的prev_size域;假设此时存在一个off by one,则我们可以覆盖p1 size的pre_inuse为0(空闲)。由于glibc为了减少碎片化会进行后向合并,所以会得到下一个空闲堆为chunk_at_offset(p1,(long)prev_size),由于p1的prev_size域我们可控,随意修改prev_size为p1和ptr的偏移offset就可以得到ptr地址处的堆。修改后得到下一个空闲堆的效果为chunk_at_offset(p1,(long)offset),offset=p1-ptr

3.house_of_force

  top_chunk域可以溢出的一种利用。具体操作是覆盖top_chunk的size域为一个大整数(-1),以保证我们无论申请多大的内存都不需要调用mmap。计算malloc返回需要任意地址写的地址和top_chunk的偏移offset,这里得到的offset一定是一个负数(相当于一个整型溢出),然后此时我们malloc(offset),由于offset大小的chunk属于largebins,此时unsortedbins和largebins都为空,所以从topchunk得到这个chunk,所以new_ptr会得到top_chunk的地址。

offset的计算过程:

/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/

  此时我们再次malloc一个任意大小的堆,第二次malloc时top_chunk如下。所以此时malloc会得到dest的原因是与av->top的更新有关,以下有计算过程


pwndbg> print ctr_chunk


$2 = (void *) 0x602080 <bss_var>


pwndbg> print ptr_top


$3 = (intptr_t *) 0x603110


pwndbg> x/20xg 0x603110


0x603110:    0x0000000000000000      0xffffffffffffef61


0x603120:    0x0000000000000000      0x0000000000000000


0x603130:    0x0000000000000000      0x0000000000000000


0x603140:    0x0000000000000000      0x0000000000000000


0x603150:    0x0000000000000000      0x0000000000000000


0x603160:    0x0000000000000000      0x0000000000000000


0x603170:    0x0000000000000000      0x0000000000000000


0x603180:    0x0000000000000000      0x0000000000000000


0x603190:    0x0000000000000000      0x0000000000000000


0x6031a0:    0x0000000000000000      0x0000000000000000



av->top=chunk_at_offset(victim,nb)


0x603110+0xffffffffffffef50+4*sizeof(long)=0x602080

 

一个细节的地方:malloc(256)而malloc_usable_size()=264的原因是当前chunk数据域+next_chunk'pre_size。

这种利用方式能成功的原因是unsortedbins和largebins为空时申请largechunk会从top_chunk分配,利用一个leak得到dest和top_chunk的偏移就可以在下一次malloc时实现dest的写。

4.house_of_lore

  需要控制victim(smallbin最后一个chunk)的bk和stack_buffer的fd(stack中伪造堆)。具体操作是申请一个>fastbin大小的堆victim(例,64bit100;另,victim堆头起始记为victim_header),stack_buffer的fd覆盖为victim_header(绕过smallbin的双向链表检测)。

smallbin的双向链表检测

// 获取 small bin 中倒数第二个 chunk 。
bck = victim->bk;
// 检查 bck->fd 是不是 victim,防止伪造
if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
// 设置 victim 对应的 inuse 位
set_inuse_bit_at_offset(victim, nb);
// 修改 small bin 链表,将 small bin 的最后一个 chunk 取出来
bin->bk = bck;
bck->fd = bin;

  此时我们malloc()一个chunk为了防止free(victim)时victim和top_chunk合并(victim释放后放入unsortedbin的条件是size>max(fastbin)&&victim不与top_chunk紧邻)。此时free(victim) victim链入unsortedbin,然后我们malloc()一个和victim大小不等且大小大于smallbin的堆,这样victim会被链入smallbin。我们修改victim->bk=stack_buffer,这样smallbin的变为stack_buffer->victim,此时malloc一个victim大小的堆就会得到victim,然后再次malloc就会得到stack_buffer。

这种利用的原理还是伪造堆利用smallbin分配FIFO,只不过如何绕过victim和top_chunk合并,如何让victim链入smallbin,都是比较细节的问题,还是需要对glibc堆分配有很深的理解

5.house_of_orange

 无法使用free的一种堆利用。利用当前top_chunk不满足申请堆大小,old top chunk会被链入unsortedbin(glibc在尝试得到申请大小堆的时候会依次检测fastbin、smallbins、unsortedbin、largebins,从这些bin得不到申请大小的堆会从topchunk获得,但这里topchunk也不满足,所以执行topchunk拓展;因为我们需要以brk方式扩展,所以接下来就是绕过brk扩展的check)

        ) Top chunk + size has to be page aligned
) Top chunk's prev_inuse bit has to be set.

即伪造的size页对齐,size的PRE_INUSE位置1。如此得到链入unsortedbin的old top chunk

  然后修改old top chunk的bk指针为io_list_all,利用一次任意地址写修改io_list_all为system,修改io_list_all前八字节为"/bin/sh"得到shell。

这里是如何触发一次任意地址写没看懂,有时间补,QAQ

6.house_of_spirit

  这个针对fastbin的攻击是在栈上构造fake_chunk,伪造堆指针free(fake_chunk)实现fake_chunk地址写。难点在于构造fake_chunk过程需要绕过一些检测

1)因为堆大小是fastbin范围,所以pre_inuse位固定;但是ISMMAP要覆盖为1(MMAP分配的堆和SBRK处理方式不一样),例子中NON_MAIN_ARENA位是0

2)fake_chunk 16字节对齐,size在fastbin范围内

3)fake chunk的next chunk的大小范围:> 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena),以绕过nextsize的完整性检查

  满足以上条件构造fake_chunk后free(fake_chunk)再malloc() fake_chunk'size-chunk_header大小的堆就可以实现fake_chunk地址处的写

7.overlapping_chunks

  malloc三个unsortedbin范围堆P1,P2,P3;假设有一个溢出可以导致覆盖P2的size为P2+P3大小,free(P2),malloc(P2+P3-8)使malloc返回P2,由于此时P2堆大小为P2+P3,所以此时P3内容可控。

8.overlapping_chunks_2

  跟上一个的区别是先修改溢出堆的size,使其包含下一chunk,记fake_chunk,然后free(fake_chunk),malloc()fake_chunk size域大小的堆实现堆的覆写。利用的过程中注意不要使free的堆和top_chunk合并