背景知识

1
2
3
4
5
#define NBINS             128
#define NSMALLBINS 64
#define SMALLBIN_WIDTH MALLOC_ALIGNMENT
#define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
#define MIN_LARGE_SIZE ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)

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的index

1
2
3
4
5
6
7
8
9
10
11
12
#define largebin_index_64(sz)                                                \
(((((unsigned long) (sz)) >> 6) <= 48) ? 48 + (((unsigned long) (sz)) >> 6) :\
((((unsigned long) (sz)) >> 9) <= 20) ? 91 + (((unsigned long) (sz)) >> 9) :\
((((unsigned long) (sz)) >> 12) <= 10) ? 110 + (((unsigned long) (sz)) >> 12) :\
((((unsigned long) (sz)) >> 15) <= 4) ? 119 + (((unsigned long) (sz)) >> 15) :\
((((unsigned long) (sz)) >> 18) <= 2) ? 124 + (((unsigned long) (sz)) >> 18) :\
126)

#define largebin_index(sz) \
(SIZE_SZ == 8 ? largebin_index_64 (sz) \
: MALLOC_ALIGNMENT == 16 ? largebin_index_32_big (sz) \
: largebin_index_32 (sz))

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
5
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
...
...

然后把victim(p1)从unsorted bin中取出

1
2
3516           unsorted_chunks (av)->bk = bck;
3517 bck->fd = unsorted_chunks (av);

查看对应index(64)的largebin中是否有chunk(比较fwd和bck)
发现没有,于是让victim(p1)的fd_nextsize和bk_nextsize指向自己

1
2
3584               else
3585 victim->fd_nextsize = victim->bk_nextsize = victim;

然后把victim(p1)链入large bin中

1
2
3
4
5
3588           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
3
largebins
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
4
3765                   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
2
3
4
5
6
unsortedbin
all: 0x6030a0 —▸ 0x7ffff7dd5b78 (main_arena+88) ◂— 0x6030a0
smallbins
empty
largebins
0x500: 0x603460 —▸ 0x7ffff7dd5fa8 (main_arena+1160) ◂— 0x603460 /* '`4`' */

修改large bin中p2里的指针

free(p3)
unsorted bin: p3->p1(left)
修改位于large bin中的chunk p2的内容
修改前

1
2
3
4
5
0x603460:	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
5
0x603460:	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
8
unsortedbin
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
5
while ((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
7
3574                       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
2
3
4
5
3588           mark_bin (av, victim_index);
3589 victim->bk = bck;
3590 victim->fd = fwd;
3591 fwd->bk = victim;
3592 bck->fd = victim;

p3的bk指向stack_var1-0x10
p3的fd指向p2
p2的bk指向victim
stack_var1-0x10的fd被写入victim,即p3的地址

至此利用结束,位于栈上的两个变量被写入了堆的地址

效果
stack_var1 (0x7fffffffdc90): 0x6039a0
stack_var2 (0x7fffffffdc98): 0x6039a0