Large Bin Attack源码分析

背景

在高版本glibc中,由于在Tcache, fastbin等位置引入了较严格的安全检查,很多低版本glibc中可以使用的攻击手法都难以利用。Large bin attack可以将任意位置覆盖一个可控堆地址,成为了有力的攻击手段。在取消各种hook的高版本glibc下,使用large bin attack进行fsop等攻击也成为了经典的攻击手段。

分析

当free掉一个chunk的时候,如果范围不在Tcache范围内,或Tcache已满且不在fastbin范围内,并且不与top chunk相邻,chunk会进入unsorted bin

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
clear_inuse_bit_at_offset(nextchunk, 0);

/*
Place the chunk in unsorted chunk list. Chunks are
not placed into regular bins until after they have
been given one chance to be used in malloc.
*/

bck = unsorted_chunks(av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("free(): corrupted unsorted chunks");
p->fd = fwd;
p->bk = bck;
if (!in_smallbin_range(size))
{
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
bck->fd = p;
fwd->bk = p;

set_head(p, size | PREV_INUSE);
set_foot(p, size);

check_free_chunk(av, p);

只有在下一次malloc更大的chunk时,符合large bin大小的chunk才会被放入large bin

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* 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);

/* Take now instead of binning if exact fit */

if (size == nb)
{
/* ... */
}

/* place chunk in bin */

if (in_smallbin_range (size))
{
/* ... */
}
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;

注意到为了使同一large bin中的chunk有序,插入chunk时对大小进行了判断。而当(unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))即在要插入的chunk比large bin中已有的chunk小的时候,进行了没有检查的赋值操作fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;,只要更改victim->bk_nextsize,而victim->bk_nextsize在前面被赋值为fwd->fd->bk_nextsize。因此只要更改fwd->fd->bk_nextsizeaddr-0x20,就可以在addr处写入victim的堆地址。

利用

  1. 申请处于同一large bin范围的一大一小两个堆块chunk1, chunk2(两个chunk间需要分隔,与top chunk也要分隔)

  2. 释放chunk1后申请比其更大的堆块使其从unsorted bin进入large bin

  3. 更改chunk1->bk_nextsize为addr-0x20

  4. 释放chunk2后申请比其更大的堆块使其从unsorted bin进入large bin,完成攻击