LargeBin_Attack
背景知识
1 |
MALLOC_ALIGNMENT等于SIZE_SZ,即系统位数乘以2,64位即0x8*2=0x10,32位为0x4*2=0x8
largebin最小为(64-0)*0x10=0x400
large bin中一共63个bin,每个bin中的chunk大小不一致,而是处于一定区间范围内。这63个bin被分成了6组,每组bin中的chunk之间的公差一致。
large bin采取了分段的存储,比如第一个范围就是0x400到0xc00(48<<6),其中每个链表之间差0x40(1<<6),结果如下表
| index | size |
|---|---|
| 64 | [0x400,0x440]相差0x40 |
| 65 | [0x440,0x480)相差0x40 |
| … | …相差0x40 |
| 96 | [0xc00,0xc40)相差0x40 |
| 97 | [0xc40,0xe00)相差0x1c0 |
| 98 | [0xe00,0x1000)相差0x200 |
| … | …相差0x200 |
| … | … |
largebin的index1
2
3
4
5
6
7
8
9
10
11
12
large chunk结构
64位上大于0x400字节的chunk称为large chunk
被释放进large bin的chunk中包含fd,bk,fd_nextsize,bk_nextsize
- fd_nextsize,bk_nextsize只有chunk空闲的时候才使用
- fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针
- bk_nextsize指向后一个与当前chunk大小不同发的i的一个空闲块,不包含bin的头指针
- 一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历。
large bin插入顺序
在index相同的情况下
- 按照大小,从大到小排序(小的链接large bin块)
- 大小相同,则按照free的时间排序
- 多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
- size最大的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk,其余chunk的fd_nextsize指向比他小的最大heap,bk_nextsize指向比他大的最小heap

插入时利用
在申请大堆块后,unsorted bin不满足size要求,会将其放入large bin中
how2heap演示
glibc版本2.23
调试的程序中chunk大小与网上文章中的略有不同,在从unsorted bin卸下chunk时会有点不一样
要修改的两个位于栈上的值
stack_var1 (0x7fffffffdc90): 0
stack_var2 (0x7fffffffdc98): 0
先布置堆块
malloc(0x420) p1
malloc(0x20)
malloc(0x500) p2
malloc(0x20)
malloc(0x500) p3
malloc(0x20)
free(p1)
free(p2)
unsorted bin: p2->p1
第一次malloc(0x90)
从unsorted bin卸下chunk
首先拿到victim为p1,bck为p2(vicitm->bk)1
2
3
4
5while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
...
...
然后把victim(p1)从unsorted bin中取出1
23516 unsorted_chunks (av)->bk = bck;
3517 bck->fd = unsorted_chunks (av);
查看对应index(64)的largebin中是否有chunk(比较fwd和bck)
发现没有,于是让victim(p1)的fd_nextsize和bk_nextsize指向自己
1 | 3584 else |
然后把victim(p1)链入large bin中1
2
3
4
53588 mark_bin (av, victim_index);
3589 victim->bk = bck;
3590 victim->fd = fwd;
3591 fwd->bk = victim;
3592 bck->fd = victim;
接下来继续while循坏,此时unsorted bin中只剩下p2,victim即为p2victim=unsorted_chunks(av)->bk,bck则为main_arena上的地址
把victim(p2)从unsorted bin中取出
然后检查对应indx(68)的large bin中有无chunk
发现没有,让victim(p2)的fd_nextsize和bk_nextsize指向自己
最后把victim(p2)放进对应大小的large bin中
至此p1和p2均被放进了largebin中1
2
3largebins
0x400: 0x603000 —▸ 0x7ffff7dd5f68 (main_arena+1096) ◂— 0x603000
0x500: 0x603460 —▸ 0x7ffff7dd5fa8 (main_arena+1160) ◂— 0x603460 /* '`4`' */
从large bin中拿出chunk
现在需要拿出0x90的chunk
会计算获得对应的bin,取出,此时victim为last(bin),即p1
获取p1的大小,计算remainder_size
执行unlink把p1从large bin中卸下
接下来进行分割
计算切割后的remainder
将remainder插入unsorted bin中
并判断在small bin范围,标记为remainder
最后设置victim的size位(3765~3766)
设置remainder的size位(3767)
修改remainder的prevsize(3768)1
2
3
43765 set_head (victim, nb | PREV_INUSE |
3766 (av != &main_arena ? NON_MAIN_ARENA : 0));
3767 set_head (remainder, remainder_size | PREV_INUSE);
3768 set_foot (remainder, remainder_size);
执行void *p = chunk2mem(victim),即返回p1+0x10地址,即指向chunk数据区的指针
然后返回指针p完成分配
1 | unsortedbin |
修改large bin中p2里的指针
free(p3)
unsorted bin: p3->p1(left)
修改位于large bin中的chunk p2的内容
修改前1
2
3
4
50x603460: 0x0000000000000000 0x0000000000000511
0x603470: 0x00007ffff7dd1fa8 0x00007ffff7dd1fa8
0x603480: 0x0000000000603460 0x0000000000603460
0x603490: 0x0000000000000000 0x0000000000000000
0x6034a0: 0x0000000000000000 0x0000000000000000
vulnerability
p2.size 0x511->0x3f1
p2.fd ->0
p2.bk ->stack_var1-0x10
p2.fdnextsize ->0
p2.bknextsize ->stack_var2-0x20
修改后1
2
3
4
50x603460: 0x0000000000000000 0x00000000000003f1
0x603470: 0x0000000000000000 0x00007fffffffdc80
0x603480: 0x0000000000000000 0x00007fffffffdc78
0x603490: 0x0000000000000000 0x0000000000000000
0x6034a0: 0x0000000000000000 0x0000000000000000
第二次malloc(0x90)
同理先对unsorted bin中的chunk进行整理
取出p1_left为victim,p3为bck
从unsorted bin中卸下p1_left
卸下p1_left,由于此时其大小为0x390,被放入small bins中1
2
3
4
5
6
7
8unsortedbin
all: 0x6039a0 —▸ 0x7ffff7dd5b78 (main_arena+88) ◂— 0x6039a0
smallbins
0x390: 0x6030a0 —▸ 0x7ffff7dd5ef8 (main_arena+984) ◂— 0x6030a0
largebins
0x500 [corrupted]
FD: 0x603460 ◂— 0x0
BK: 0x603460 —▸ 0x7fffffffdc80 ◂— 0x0
从unsorted bin中卸下p3
卸下p3,大小为0x510不属于small bin的大小,因而被放进相应index(68)的large bin中
由于此时index=64的large bin中已经存在chunk p2,会进入if (fwd != bck)中
判断是否比最小的空闲large bin还小if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
由于我们修改了p2的size,而上面的size为0x511,bck->bk->size为0x3f1,故不会进入
而是执行else里的语句。其中1
2
3
4
5while ((unsigned long) size < fwd->size)
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}
用来查找该bin中比size大的最小large bin。当前fwd为p2,fwd->size为0x3f1,size为0x511,不会进入该while循环中。
然后if判断size与fwd的size是否相等,如果相等则插入。这里不相等,进入else中1
2
3
4
5
6
73574 else
3575 {
3576 victim->fd_nextsize = fwd;
3577 victim->bk_nextsize = fwd->bk_nextsize;
3578 fwd->bk_nextsize = victim;
3579 victim->bk_nextsize->fd_nextsize = victim;
3580 }
利用1
这四行就是我们利用的第一个地方了
p3的fd_nextsize指向p2
p3的bk_nextsize指向p2_bknextsize,即stack_var2-0x20
p2的bk_nextsize指向p3
p3的bk_nextsize为stack_var2-0x20,也就是stack_var2-0x20的fd_nextsize写入victim地址,也就是stack_var2被写入p3的地址
利用2
接下来就是利用的第二个地方
bck赋为fwd->bk,即p2->bk,为stack_var1-0x10
然后执行
1 | 3588 mark_bin (av, victim_index); |
p3的bk指向stack_var1-0x10
p3的fd指向p2
p2的bk指向victimstack_var1-0x10的fd被写入victim,即p3的地址
至此利用结束,位于栈上的两个变量被写入了堆的地址
效果
stack_var1 (0x7fffffffdc90): 0x6039a0
stack_var2 (0x7fffffffdc98): 0x6039a0








