之前在做SUCTF的note时接触到了house of orange的原理,即将top的大小修改为一个比较小的值,再分配一个比size大的值时,原来的top就会free并放入到unsorted bin里,重新产生一个新的top。而note这道题由于可以自己创造出unsorted bin,就没有修改top的大小。今天简单看了一下sysmalloc的源码,重新认识了house of orange。ps:在网上找了一些大佬讲原理的blog,发现各位大佬都分析的是非主分配区部分的源码,而实际上多数用到的是main arena的源码,虽然原理类似,但是金牛座就愿意钻牛角尖=。=
一个用来调试的栗子
1 |
|
栗子参考了ctf-wiki,将top的大小改为了0x1fe1,然后再分配0x2000大小的chunk,由于_int_malloc的其他环节都不能处理了,由此调用sysmalloc
sysmalloc的源码
根据上面的栗子,在第二个malloc
处对sysmalloc
的源码逐步分析的
1 | static void * |
av是当前的分配区,mmap_threshold
是能够使用mmap分配的阈值,n_mmaps_max
是mmap分配的内存块设定的最大值,调试查看这两个值:
显然当前我们输入的0x2000不满足这个条件,执行下一个分支。
1 | old_top = av->top; |
接下来获取原来的top信息:起始地址、大小、终止地址。并且要求满足两个assert的条件:
- 第一次调用
sysmalloc
函数,top还没有初始化;或者已经初始化,top的大小大于MINSIZE(0x20),前一个chunk处于inuse状态,以及top chunk的结束地址是页对齐的。 - 原top的大小小于当前要分配chunk的大小。
1 | if (av != &main_arena) |
之后是判断av是主分配区还是配主分配区,大部分大佬分析的都是非主分配区这部分,我这里就主要看主分配区了。先是重新计算需要分配的size。然后判断当前分配区是否连续,并将size按照页对齐。当size>0时,通过MORECORE
(sbrk
)分配连续的内存。
contiguous函数定义为:
1 |
1 | if (brk != (char *) (MORECORE_FAILURE)) |
如果sbrk
分配成功,并且MORECORE
的hook函数存在,调用hook函数。(还不清楚hook用来干嘛)
1 | if (brk != (char *) (MORECORE_FAILURE)) |
如果sbrk_base
还没有初始化,就根据brk修改sbrk_base
的值。更新当前分配区的内存分配总量。
如果新的brk和旧的top结尾是同一地址,也就是说新分配的内存与原top是挨着的,就直接更新原top头中的大小和PREV_INUSE位,相当于直接扩大了top。
如果brk小于原top结尾,则出错。
而对于house of orange,由于我们修改了top大小,top_end也提前了,此时分配的brk和top_end不是同一地址,而是从未修改前的top结尾开始分配的内存,即以上两个条件都不满足,从而进入到下面的这个分支。
1 | else |
先有一些对齐操作,这里省略掉了,其中aligned_brk
就是处理好之后的内存地址。
重新设置av->top
的地址,为重新分配的aligned_brk
,然后通过set_head
设置top的大小和标志位,更新分配区的总分配内存量。
接着将top chunk切分为fenceposts
和空闲块两部分,设置切分出空闲chunk大小为old_size。最终通过int_free释放掉old_top。
执行前后heap的大小比对:
以及top地址的变化:
第二个malloc执行结束后,unsortedbin中存放的即为原来的top地址
总结
将sysmalloc的流程简化为以下流程(针对上面的栗子):