how2heap_libc2.23_summary

1.first_fit:

unsortedbin中切割原则,常用来泄露地址

2.fastbin_dup:

1
2
3
4
5
#注释头

a=malloc(x),b=malloc(x),c=mallox(x)
free(a),free(b),free(a)
malloc(x)=a,malloc(x)=b,malloc(x)=a

3.fastbin_dup_into_stack:

1
2
3
4
5
6
7
8
9
10
11
#注释头

a=malloc(x),b=malloc(x),c=mallox(x)
free(a),free(b),free(a)

//malloc(x)得到a,此时a还在fastbin中,之后修改a的fd为fakechunk,使得:
fastbinsY.fd->b,b.fd->a,a.fd->fakechunk

//再次申请得到fakechunk
malloc(x)=b,malloc(x)=a,malloc(x)=fakechunk
//这里就可以使得fakechunk位于栈上,从而使得堆分配到栈中控制栈上的原先不可控数据。

4.fastbin_dup_consolidate

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

a=malloc(x),b=malloc(x)
free(a)//a进入fastbin中

c = malloc(0x400)
//申请large bin的时候已经执行了malloc_consolidate,使得fastbin中的a放入smallbin中

free(a)
//并不会报错,因为这个时候a已经被放到了smallbin之中,fastbin中没有a,之后a再次进入fastbin中

//此时的a中信息如下:
a.bk->smallbin
a.fd->0//由于处在fastbin的第一个,所以fd被清空

malloc(x) = a//这时候a中存在smallbin的信息,可以进行泄露
malloc(x) = a//可以再次申请,再次得到a

5.house_of_einherjar:

在当前chunk中构造fakechunk,利用溢出漏洞修改下一个chunk的presize和pre_inuse。之后释放下一个chunk连上fakechunk一起进入Bins中,再通过当前chunk修改进入bins中chunk的fd,bk,从而实现任意地址读写。

(1)申请三个chunk,chunk1,chunk2,chunk3

(2)在chunk1中制造fakechunk//这里需要绕过一些检查

①构造:

1
2
3
#注释头

fake_chunk->size = sizeof(chunk1)-0x10

绕过pre_size的检查:

1
2
3
4
#注释头

if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))
malloc_printerr ("corrupted size vs. prev_size");

这段代码意思就是当前chunk的size如果不等于下一个chunk的pre_size域,则出错

②构造:

1
2
3
4
#注释头

fake_chunk->fd = chunk1
fake_chunk->bk = chunk1

绕过双向链表的检查:

1
2
3
4
#注释头

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr ("corrupted double-linked list");

这段代码意思就是下一个chunk,也就是BK,其fd不等于当前chunk地址,或者上一个chunk,也就是FD,其bk不等于当前chunk地址,满足其中一个就出错。

(3)利用溢出漏洞,改掉chunk2的pre_size和pre_inuse,free掉chunk2,这样fakechunk+chunk2一起被放入bins中,形成chunkA

(4)通过chunk1修改chunkA的fd,bk,使其指向想更改的地址addr

(5)再malloc(sizeof(fakechunk+chunk2))就可以得到首地址为fakechunk,大小为fakechunk+chunk2的chunkA了。之后再malloc一次就得到addr的chunk

6.house_of_force:通过修改topchunk的size域获得任意读写的chunk

(1)修改topchunk的size域,使之变成一个大数。

(2)malloc(-size)。要确保这个-size<topchunk.size(补码形式)

(3)之后topchunk就会被抬升到-(size-0x10)的位置(chunk内存对齐的原因,具体调试一下就能判断被抬升到哪里了:p main_arena.top)然后才会从被抬升之后的topchunk开始分配size大小的chunk。

(4)这时候再正常malloc,就可分配到从被抬升之后分配完size的topchunk地址处切割的chunk了,实现任意地址读写。

7.house_of_lore:

针对small bin进行攻击的,如果题目设置malloc大小限制大于fastbin时,就可以利用了。

(1)分配一个small bin大小的chunk_ptr,和另一个chunk 用来间隔 top chunk。

(2)在栈上伪造两个地址fake chunk1和fake chunk2,从smallbin中申请需要满足以下条件:

①// 获取 small bin 中倒数第二个 chunk 。

bck = victim->bk;

▲需要设置chunk_ptr->bk = fake chunk1

②// 检查 bck->fd 是不是 victim,防止伪造

1
2
3
4
5
6
#注释头

if (__glibc_unlikely(bck->fd != victim)) {
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

▲需要设置fake chunk1->fd = chunk_ptr,同时由于之后申请fake chunk1时也需要满足这个条件,所以也需要设置fake chunk2->fd = fake chunk1,fake chunk1->bk = fake chunk2

(3)释放掉chunk_ptr,唯一需要漏洞利用的是设置victim->bk = fake chunk1,这里需要获取栈上的地址,并且存在类似UAF漏洞之类的来设置bk指针。设置之后现在smallbin链表中结构为fake chunk2->fake chunk1->chunk_ptr。

(4)之后申请sizeof(chunk_ptr),将之申请出来,再申请一次即可将fake chun1申请出来,实现任意地址申请堆块。

▲注意这里针对smallbin的攻击是不需要设置size位的,不存在这方面的条件。

8.house_of_orange:无free函数的泄露地址

(1)修改topchunk的size域,使之变小,但需要满足下列条件:

①伪造的 size 必须要对齐到内存页,例如topchunk_addr+topchunk_size=0x1000*x。

②size 要大于 MINSIZE(0x10)

③size 要小于之后申请的 chunk size + MINSIZE(0x10)

④size 的 prev inuse 位必须为 1

用于通过之后拓展topchunk的sysmalloc函数中的下列检查:

1
2
3
4
5
6
#注释头

assert((old_top == initial_top(av) && old_size == 0) ||
((unsigned long) (old_size) >= MINSIZE &&
prev_inuse(old_top) &&
((unsigned long)old_end & pagemask) == 0));

(2)申请一个大于伪造之后topchunk_size,小于128K(0x1f400)的chunk。前者是为了调用sysmalloc函数,后者是为了使得分配方式为brk拓展topchunk。

①brk是将数据段(.data)的最高地址指针_edata往高地址推,原先的topchunk被放入unsortedbin中,然后再从新的topchunk处开始分配。(一般会往高地址推0x21000这么大,基本就是再加一个初始topchunk大小)

②mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存,这样原先的topchunk不会被放到unsortedbin中,那么再分配就是从这个mmap中分配,没办法泄露地址了。

(3)再申请一块内存,就会从unsortedbin中的old_topchunk切割,得到的chunk的fd和bk都带有信息。如果程序没有将申请的chunk内存初始化,那么就可以泄露地址。另外由于在执行切割之前,old_topchunk会被先整理到smallbin或者largebin中(具体看old_chunk的size),所以申请回来的chunk中的fd和bk的内容应该是对应smallbin或者largebin链表头的地址,不是unsortedbin链表头main_aren+88的地址。这里需要具体调试一下好计算偏移,方便泄露地址。

9.house_of_roman:无leak的利用,爆破

(1)申请3个chunk,chunkA,chunkB,chunkC,分别为0x20,0xd0,0x70.(A用来触发单字节溢出,修改chunkB的size位)

(2)在chunkB+0x78处设置p64(0x61),用来过检查用的。(6+7=13(0xd))

(3)释放chunkB , chunkB进入unsortedbin , 这样chunkB的fd和bk处都有 main_arena 的地址。

(4)再次分配 0xd0 ,会分配到chunkB。以上步骤都是为了使得chunkB中带上main_arena的地址。

(5)再申请三个0x70大小的chunk,chunkD,chunkE,chunkF,chunkF用来占位用,防止被topchunk吞并

(6)释放掉chunkC,chunkD,使之进入fastbin中,为chunkD->chunkC。

(7)利用UAF修改chunkD的fd的低字节,使其指向chunkB,即将0xf0修改为0x20即可:

1
2
3
4
5
6
7
8
#注释头

0xn000 chunkA
0xn020 chunkB
0xn0f0 chunkC
0xn160 chunkD
0xn1d0 chunkE
0xn240 chunkF

这里能够通过检查,因为前面将chunkB的size设置成了0x70,并且chunkB+0x78处也被设置成了0x61,所以可以通过fastbin的检查。伪造了一个0x70的chunk。那么0x70处的fastbin就变成chunkD->chunkB

(8)修改chunkB的fd指针最后两个字节为\xaa,\xed使其指向malloc_hook - 0x23处。这里就需要用到爆破了,\xaa中的第一个a是不确定的,有16种可能性。所以直接设置成a,成功概率应该为1/16。

img

img

img

而这里需要指向malloc_hook - 0x23的原因是因为既要包含__malloc_hook,又要使得这个chunk的size位为0x7f,观察内存可得,这是最优选择,选取的是_IO_wide_data_0+304这个数据中的7f,其它的7f都会带上其它的数据。

(9)修改完chunkB的fd,就又成功伪造了一个0x70大小的chunk,那么0x70的fastbin中就变成:chunkD->chunkB->malloc_hook - 0x23

(10)现在连续申请三次0x70大小的chunk,最后一个chunk就得到了malloc_hook - 0x23处的chunk,那么现在就可以计算偏移修改malloc+hook的值,修改最后三个字节,劫持为one_gadget。最后12位Bits可以通过固定偏移算出来,不会改变,但是前面的12位bits会改变,需要爆破。这里可以通过刚才的\xaa中的a来减少爆破次数,\xaa中第一个a已经确定,那么就可以通过偏移算出来随机化之后的one_gadget中的倒数第二个字节中的第一个16位数,这样就只需要爆破倒数第三个字节即可。那么总共需要的数学期望就应该是(0xff+1)*(0xf+1)=4096次。

(11)爆破成功后还需要触发__malloc_hook,通过连续free两次同一个chunk,使得malloc检查到错误(主要会调用 __libc_message 来执行abort函数)。就可以触发malloc_printerr函数,从而调用__malloc_hook函数,进而getshell。

10.house_of_spirit:

(1)通过栈溢出修改栈上某个chunk指针,使其指向一个可控内存的栈地址,再修改该栈地址对应chunk的size位。

(2)通过修改下一个chunk的size绕过检查,然后free掉该chunk指针,再申请回来,就可以控制中间区域的内容了。

fakechunkA uncontral_data fakechunkB

中心思想就是将不可控的内存纳入一个可控的Chunk中,从而实现可控。

1
2
3
4
5
6
#注释头

//需要绕过的代码(_int_free函数)中:
if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))

即fakechunkA的nextchunk,也就是fakechunkA+fakechunkA->size = fakechunkB的size位满足大于2*SIZE_SZ(64位程序中SIZE_SZ为8),小于av->system_mem(在main_arena中,默认为128kb),一般简单设置后都能满足。

11.large_bin_attack_1:任意可写地址写堆地址

(1)申请三个chunk,chunkA,chunkB,chunkC,大小依次为0x80,0x400,0x400,其中还需要插入其它的chunk防止合并,然后free掉chunkB,使其进入unsortedbin。

chunkA用来分割,chunkB和chunkC用来放入largebin中

(2)申请一个小于chunkA的chunk,触发unsortedbin整理,将chunkB整理到largebin中。然后chunkA被分割成chunkA_1,chunkA_2,chunkA_1返回给用户,chunkA_2进入unsortedbin中。

(3)free掉chunkC,使其进入unsortedbin中。

(4)利用漏洞修改chunkB的size,使其小于0x410。

(5)修改chunkB的bk为target_addr1-0x10,bk_nextsize为target_addr2_0x20。

(6)分配一个比chunA_2小的chunk,再次触发整理,将chunkC整理至largebin中。

(7)由于chunkB的size小于chunkC的size,所以会将chunkC插入largebin大小排列链表的头部,即以下代码:

▲大小排列链表的代码赋值过程:

1
2
3
4
#注释头

victim->bk_nextsize = fwd->bk_nextsize;
victim->bk_nextsize->fd_nextsize = victim;

这里的victim就是chunkC,fwd就是chunkB,那么fwd->bk_nextsize就等于target_addr2-0x20。

①第一行代码就是:

chunkC->bk_nextsize被赋值为target_addr2-0x20

②第二行代码就是:

(target_addr2-0x20)->fd_nextsize被赋值为chunkC_addr,相当于

((target_addr2-0x20)+0x20)=chunkC_addr,即(target_addr2)=chunC_addr

▲释放顺序排列链表的代码赋值过程:

1
2
3
4
5
6
7
8
9
#注释头

bck = fwd->bk;
// ......
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

以上就相当于是*(target_addr1)=chunC_addr

★综上所述,largebin attack的第一种利用方式就是将目的地址的值修改成一个堆地址。

12.large_bin_attack_2:实现overlap chunk

(1)申请四个chunk,chunkA,chunkB,chunkC,chunkD之后构造一个largebin大小排列的链表chunkA->chunkB,其中chunkA为0x420,chunkB为0x400,chunkC为0x400,未释放,chunkD用来占位防止合并。

(2)利用漏洞将chunkB的bk_nextsize伪造指向chunkC,同时将C的fd与bk构造好,将C的fd_nextsize与bk_nextsize赋值为0。

(3)当再次申请0x410大小的内存chunkE时,遍历chunkB->bk_nextsize会指向C,且C的大小满足需求,因此会调用unlink将chunkC从双链表取下,返回给用户。

(4)那么申请出来的chunkE的地址会为chunkC的地址,即chunkE和chunkC为同一内存块,实现overlap chunk的构造。

13.mmap_overlapping_chunks:暂时不好找

14.overlapping_chunks:

(1)free之前修改size,吞并邻块进入bin

(2)free之后修改size,吞并邻块申请出来。

15.poison_null_byte:实现overlap chunk

(1)malloc三个chunk,chunkA,chunkB,chunkC。

(2)利用漏洞,通过chunkB修改chunkC的pre_size位和pre_inuse位,使得chunkC的pre_size位为chunkA_size+chunkB_size。

(3)free掉chunkC,此时chunkC的pre_inuse位为free状态,触发向上合并,将chunkA,chunkB,chunkC一起free掉。从而实现chunkB的overlap。

但是ubuntu16.04打了补丁,需要绕过检查才行。

16.unsafe_unlink:通常存储chunk的结构体全局指针来进行unlink攻击。

(1)找到题目中的chunklist位置,并分清结构体中是size在前还是chunk在前。

(2)这里假设我们想要控制chunklist[0]中的chunk。申请chunk0,chunk1,chunk2。在chunk0中构造fakechunk,并设置:

1
2
3
4
#注释头

fakechunk->fd = chunklist_addr-0x18
fakechunk->bk = chunklist_addr-0x10

(3)通过堆溢出或者off-by-one将chunk1的pre_size设置成fakechunk_size,将chunk1的size设置成fakechunk_size+chunk1_size。

(4)free掉chunk1,这样就会触发向上合并,将fakechunk和chunk1合并。同时,由于合并过程中调用了unlink函数,那么chunklist[0].chunk就会指向chunlist_addr-0x18,对应的就是我们的chunk0指向chunklist_addr-0x18。

(5)现在修改chunk0就相当于修改*(chunklist_addr-0x18),从而将chunk0指向任意地址,在任意地址实现读写。

17.unsorted_bin_attack:任意地址写&main_arena+0x58

(1)利用漏洞,修改处在unsortedbin中将要被拿出来的chunk的bk指针,使其指向traget-0x10

(2)申请这个将要被拿出来的chunk,会有如下代码被运行:

1
2
3
4
5
6
7
#注释头

bck = victim->bk;
..................................................
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

①第一行代码中victim就是该chunk,那么bck就会指向traget_addr-0x10。

②第四行代码就是将bck的值赋值给unsortedbin的bk指针,使其指向traget_addr-0x10。

③第五行代码就是将unsortedbin的地址赋值给bck->fd,也就是*(target_addr-0x10+0x10)被赋值为unsortedbin的地址,即&main_arena+0x58。

★综上,unsortedbin attack就是将任意地址处的值修改成&main_arena+0x58。

▲一般unsortedbin attack是用来配合fastbin attack来使用的。因为fastbin attack需要伪造fakechunk的size,使其等于0x71或0x7f用来绕过检查.那么这时候就可以用到unsortedbin attack任意地址写&main_arena+0x58这个技巧了,可以使得fakechunk的size等于0x7f,从而绕过检查。

18.unsorted_bin_into_stack:

(1)利用漏洞,修改unsortedbin中位于链表尾部的chunk_last的bk指针使其指向栈上伪造的一个fakechunk,使其size不等于原先unsortedbin中chunk_last的size。

(2)申请fakechunk的size大小的chunk,从unsortedbin链表尾部的chunk开始查找,首先chunk_last的size不符合,接着查找chunk_last->bk,也就是fakechunk,发现size符号,那么就返回该fakechunk给用户。

这里的fakechunk就可以伪造到栈上,从而控制栈上原先不可控的内容。

参考资料:

ctfwiki

https://github.com/shellphish/how2heap

https://bbs.pediy.com/thread-259269.htm#msg_header_h2_12