堆利用-3

一些零碎的东西

fastbin attack

主要漏洞侧重于利用 free 函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击,或故意修改 fd 指针,直接利用 malloc 申请指定位置 chunk 进行攻击。

直接改写freelist中的fd指针的话,不要求地址对齐

free释放假chunk需要地址对齐

fastbins取出时,会进行该chunk的大小所对应的索引和当前链索引是否相同

Fastbin Double Free

概要

简介: fastbin double free 我们可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。 如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。

利用条件:能多次free同一chunk

利用效果:得到两个指向同一chunk的指针

有效版本:ALL

原理

fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

Alloc to Anywhere

概要

简介:该技术的核心点在于劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上

利用条件:能劫持chunk的fd指针

利用效果:任意分配chunk

有效版本:ALL

限制:2.32及以上版本fastbin加密,写fd指针需要能够泄露堆地址,并进行一些额外处理

原理

利用了UAF,再free后又去改写该chunk的fd指针,使其指向内存中一个可以被视为fastbin_chunk的内存空间(size域合适等)

指向的chunk要绕过该检测,该chunk的大小计算出的idx要与该链的索引idx相匹配

1
2
3
4
5
6
7
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim));
return NULL;
}

unsortedbin attack

概要

简介:

利用条件:uaf,能够劫持chunk的bk指针

利用效果:任意地址写上unsorted+96(+88)

有效版本:2.29以前

失效原因:2.29新增unsortedbin取出时bk链完整性

基础

什么情况下会被放入unsortedbin

  1. 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。(即last remainer)
  2. 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
  3. 当进行 malloc_consolidate 时,若合并后不与top chunk相邻,则会将合并后的chunk放入unsortedbin。

使用情况

  1. unsortedbin采用的是FIFO,即插入的时候插入到 unsorted bin 的头部,取出的时候从链表尾获取。加入的时候利用fd,取出的时候利用bk
  2. 在程序 malloc 时,如果在 fastbin,small bin 中找不到对应大小的 chunk,就会尝试从 Unsorted Bin 中寻找 chunk。如果取出来的 chunk 大小刚好满足,就会直接返回给用户,否则就会把这些 chunk 分别插入到对应的 bin 中。

原理

Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。

Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。

以及辅助泄露main_arena地址

其利用的函数片段为

1
2
3
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

即将取出的chunk的上一个chunk的fd指针写为unsorted_chunks (av)

不难看出要攻击就要能修改某个chunk的bk指针,达到的效果是将&bins[0]-2会被写入指定位置

这里可以看到 unsorted bin attack 确实可以修改任意地址的值,但是所修改成的值却不受我们控制,

  • 通过修改循环的次数来使得程序可以执行多次循环。
  • 可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。

注意:

当进行完unsorted bin attack

unsortedbin都会损坏

但分两种情况:

  1. 原本unsortedbin中只有一个chunk,利用完后unsorted的取出与放入都将被禁止
  2. 原本不止一个chunk,利用完后取出操作被禁止

另一种利用方法:

修改bk,使其指向一个fakechunk,并且fakechunk

  1. size符合申请大小
  2. fd字段可写
  3. bk字段指向可写区域

这样当被构造的unsortedbin被取出后下一次取出就会取出fakechunk,且大小合适的话就会直接退出

最好大小合适因为不合适的话就找不到出口了

失效

加了一个检测

1
2
3
4
5
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

largebin attack

概要

简介:这种攻击方式主要利用的是 chunk 进入 bin 中的操作,在 malloc 的时候,遍历 unsorted bin 时,对每一个 chunk,若无法 exact-fit 分配或不满足切割分配的条件,就会将该 chunk 置入相应的 bin 中,而此过程中缺乏对 largebin 的跳表指针的检测。

利用条件:

  1. 可以修改一个large bin chunk的data

利用效果:任意地址写上chunk地址,辅助 Tcache Stash Unlink+ 攻击,可以修改 _IO_list_all 便于伪造 _IO_FILE 结构体进行 FSOP。是诸多house系列的重要前提

有效版本:all/2.30以前

基础

在index相同的情况下:

  1. 一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历

  2. 如果大小相同,chunk放置在处于nextsize链上的chunk的下一个

  3. 多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
  4. size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk

空闲chunk的结构与unsorted和small的差别就在于多了fd_nextsize和bk_nextsize,分别指向下一个大小的chunk和上一个大小的chunk,且chunk在largebin free list中按从大到小排序是重要细节

原理

unsorted处理将chunk放入largebin中的操作(2.38)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
     else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

largebin attack就是出现在这个提取chunk的过程中

由于两者大小相同的时候只会使用如下的方法插入,所以此时无法利用。

1
2
3
4
if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;

两种利用方法

  • 当unsorted chunk的size小于largebin链上最小的chunk时
1
2
3
4
5
6
7
8
9
10
          if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;

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

如果修改nextsize链上最大的chunk的bk_nextsize字段为addr,那么victim->bk_nextsize->fd_nextsize = victim;就会将addr+0x20的位置写上victim

  • 当unsorted chunk的size大于largebin链上最小的chunk时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
             else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))//!!no
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}

这两个在2.30中新增加的检查使得这种利用方法不再可行

但在此之前

  1. 如果修改第一个小于unsorted chunk的size的chunk的bk_nextsize字段为addr,victim->bk_nextsize->fd_nextsize = victim;会使得addr+0x20写为victim
  2. 如果修改第一个小于unsorted chunk的size的chunk的bk字段为addr,bck = fwd->bk;与之后的bck->fd = victim;会使得addr+0x10写为victim

tcache attack

tcache使得性能提高的同时,安全性大幅下降,操作优先级高且检查并不严谨,利用更为简单

tcache poisoning

概要

简介: 通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。任意地址作为chunk操作

利用条件:能劫持chunk的next字段

利用效果:任意地址分配chunk

有效版本:ALL

限制:2.32及以上版本tcachebin加密,写next字段需要能够泄露堆地址,并进行一些额外处理

tcache dup

概要

简介: 类似 fastbin dup,不过利用的是 tcache_put() 的不严谨,因为没有任何检查,所以我们可以对同一个 chunk 多次 free,可以连续释放同一个chunk,造成 cycliced list。也就是造成一个chunk可以被无数次取出利用

利用条件:能多次free同一chunk

利用效果:获得多个指向同一chunk的指针

有效版本:2.29以前,2.29tcache引入key验证机制

技巧:2.29及以后虽然tcache引入key验证机制,但如果能破坏chunk的key字段,就能绕过

tcache perthread corruption

概要

简介: 在开启tcache情况下,堆上第一个chunk就是结构体tcache_perthread_struct,大小为0x290,该技术就是通过tcache posioning将tcache链中的某个chunk指向tcache_perthread_struct本身,直接修改tcache_perthread_struct进行攻击

利用条件:能劫持chunk的next字段

利用效果:控制tcache_perthread_struct结构体

有效版本:同tcache posioning

tcache house of spirit

概要

简介: 与fastbin的house of spirit一个原理,因为没有检查反而更简单,不需要伪造nextsize等,只要size字段符合tcache标准即可

利用条件:能free指定地址

利用效果:获得一个任意地址的chunk

有效版本:ALL

fakechunk须满足的条件

  1. 地址对齐
  2. ISMMAP 位不能为 1
  3. 大小对齐,大于MINSIZE小于system_mem,处在tcache范围内

概要

简介: 在smallbin中取出chunk后,如果在该 smallbin 链中还有剩余的空闲块的时候,会同时将同大小的其他空闲块,放入 tcache 中,此时也会出现解链操作,但相比于 unlink 宏,缺少了链完整性校验。

利用条件:能劫持chunk的bk字段

利用效果:

有效版本:ALL

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}

修改victim的bk字段为目标地址,两个效果

  1. 使得包含目标地址的chunk被放入tcache
  2. 目标地址的fd字段被写为该smallbin头

注意:

其实最好需要控制tcache已有的数量尽量多(理论最好是6,这样能够在放入目标chunk后就退出循环,但实际上不行因为第一个chunk的完整性检查过不了,因此实际上最好为5)

否则可能会访问到错误地址,从而报错

但是tcache中有数量的话则申请的时候会直接从tcache中取出

因此除非能使目标地址的bk字段为其自身,不然都比较难使用,毕竟要一直保证每一次的bk字段都存放一个可写地址并不轻松

概要

简介: 利用smallbin中的chunk转移到tcache的漏洞

利用条件:能劫持chunk的bk字段

利用效果:

有效版本:ALL

原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
 if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin)
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

这种攻击利用的是 tcache bin 有剩余 (数量小于 TCACHE_MAX_BINS ) 时,同大小的 small bin 会放进 tcache 中

这种情况可以用 calloc 分配同大小堆块触发,因为 calloc 分配堆块时不从 tcache bin 中选取。

在获取到一个 smallbin 中的一个 chunk 后会如果 tcache 仍有足够空闲位置,会将剩余的 small bin 链入 tcache ,在这个过程中只对第一个 bin 进行了完整性检查,后面的堆块的检查缺失。

当攻击者可以写一个 small bin 的 bk 指针时,其可以在任意地址上写一个 libc 地址 (类似 unsorted bin attack 的效果)。构造得当的情况下也可以分配 fake chunk 到任意地址。

示例

同时还要在 fake_chunk_addr->bk 处提前写一个可写地址 writable_addr 。调用 calloc(size-0x10) 的时候会返回给用户 chunk0 (这是因为 smallbin 的 FIFO 分配机制),假设 tcache[sz] 中有 5 个空闲堆块,则有足够的位置容纳 chunk1 以及 fake_chunk 。在源码的检查中,只对第一个 chunk 的链表完整性做了检测 __glibc_unlikely (bck->fd != victim) ,后续堆块在放入过程中并没有检测。

因为 tcache 的分配机制是 LIFO ,所以位于 fake_chunk->bk 指针处的 fake_chunk 在链入 tcache 的时候反而会放到链表表头。在下一次调用 malloc(sz-0x10) 时会返回 fake_chunk+0x10 给用户,同时,由于 bin->bk = bck;bck->fd = bin; 的 unlink 操作,会使得 writable_addr+0x10 处被写入一个 libc 地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <stdio.h>
#include <stdlib.h>

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;

// stack_var emulate the fake_chunk we want to alloc to


stack_var[3] = (unsigned long)(&stack_var[2]);


//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}

//put 7 tcache


for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}


//last tcache bin
free(chunk_lis[1]);
//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);

//convert into small bin


malloc(0xa0);//>0x90


malloc(0x90);
malloc(0x90);



//change victim->bck
/*VULNERABILITY*/
chunk_lis[2][1] = (unsigned long)stack_var;
/*VULNERABILITY*/

//trigger the attack


calloc(1,0x90);


//malloc and return our fake chunk on stack
target = malloc(0x90);

return 0;
}

因此利用时,最好保证

  1. tcache的数量最好是5
  2. smallbin链上至少要有两个正常的chunk以绕过第一个chunk的完整性检查
  3. 目标地址的fd字段要可写,bk字段应该指向一个可写的地方,确保bck->fd = bin;不报错

Fastbin reverse into tcache

概要

简介: 利用fastbin中的chunk转移到tcache的漏洞

利用条件:能劫持chunk的bk字段

利用效果:

有效版本:ALL

原理

原理于上述两个利用差不多,都是利用chunk转移到未满tcache链上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
     size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}

可以看到仅仅是检测了chunk的地址是否对齐,并没有对size进行检查

那么只要能够控制fast chunk的fd字段就能够做到任意写

但因为目标地址处的内容不完全可控

所以控制流程结束最稳妥的方式是通过将tcache加满,即加入fake chunk之后tcache刚好填满

个人觉得不是很实用,都能够做到控制fastbin的fd字段了,不如干脆直接控制tcachebin的next字段