0.堆内存
pwn中的堆一般指堆内存,而非数据结构中的堆。
堆是由一堆堆称为chunk的堆块组成的。
在64位系统中,堆块是以8字节对齐的,意味着如果申请一个长度为12字节的chunk,实际分配给我们的可控数据区域大小为16字节。
但是,在实际的管理中,一个堆块除了用户区域(user data)外,还有头部字段。头部字段的大小为16字节。因此在64位系统中,一个最小的chunk为32字节(包括头部字段)。所以当我们申请一块1字节大小的chunk,实际给我们的是32字节的chunk。
一个chunk如下图所示:
size 表示当前chunk的大小, prev_size 表示前一个chunk的大小。它们各占8字节,共计16字节,构成了chunk的头部字段。
后面的user data区域是用户可以输入数据的地方。
chunk的大小是8字节对齐的,对于分配器而言,0x80、0x81、0x82的大小的chunk都是一样的,都为0x80。
为了节省空间,将size的最低三个位设置为标志位,从高到低分别为non_main_arena、is_mmap、prev_inuse。
- non_main_arena用来记录当前的chunk是否属于主线程,1为不属于,0为属于。
- is_mmap用来表示当前chunk是否是由mmap分配的,1为是,0为否。
- prev_inuse用来表示前面紧邻的chunk是否正在被使用,1为前面的chunk正在被用户使用,0为前面的chunk已被free。
prev_size记录前面一个chunk大小。prev_size只有在前面的chunk被free掉后才生效,即只有在prev_inuse为0时系统才会把prev_size字段当成prev size看待。如果前面的chunk正在被使用,那么这个prev_size字段就会被当成前一个chunk的user data区,充分利用空间。
这也就意味着,当用户申请一个0x80大小的区域时,系统实际给我们的区域是0x90大小的(0x10头部);而当用户申请0x88大小的区域时,系统同样也会给我们0x90大小的区域(算头部)。剩下的8字节则使用next chunk的prev_size区域。
有一种特殊的chunk:topchunk。
最开始时,程序的堆还未被使用,整个堆区域属于一个很大的堆块叫做topchunk。
当已经被使用的空间不够时,程序会从topchunk中分割出一块来给程序使用。
1.堆块的管理
为保证程序的快速运行,而且方便系统内存管理,因此ptmallic将free后的堆块根据其大小分成不同的bin:
- fastbin:大小范围为0x20~0x80。
- smallbin:大小范围为0x90~0x400。
- largebin:大小范围为0x410以上。
- unsortedbin:是未被归类的bin,用于临时储存,存放的堆块大小不定。
被free后的chunk如下图所示:
可以看出,由于chunk被free了,按常理来说用户无法访问到这个chunk。
因此在user data区域存放一些用于管理内存的指针信息。
对于fastbin而言,它是单链表结构,只有fd。
对于smallbin和unsortedbin而言,它们是双向链表结构,有fd和bk。
对于largebin而言,它是双向链表结构,不仅有fd和bk,还有fd_next_size和bk_next_size.
2.堆块的合并
当我们free掉一个chunk,可能会触发向前合并和向后合并。
- 向前合并:检查当前chunk的prev_inuse位,如果为0,则根据当前chunk的prev_size找到prev chunk的头,两chunk合并。
- 向后合并:检查当前chunk的next next chunk的prev_inuse位(因为要确定next chunk的状态需要检查next next chunk的prev_size位,通过size寻找。),然后根据next chunk地状态决定是否合并。
如下图所示:

(注:向后合并的图有误,左中间chunk的prev_inuse位应为0。)
3.各类bins及其管理
arena是一块结构体,用于管理bins。
主线程创建的arena称之为main_arena,其它的叫thread_arena。
如下图:
各类的bins如下图:
- fastbin
管理fastbin free chunk,单链表结构,FILO。
总共有十个fastbin链表,每个链表中fastbin的size一样为0x10递增。
大小属于fastbin的chunk被free掉时不会改变next chunk的prev_inuse位,也就是意味着不会被合并。
如图:
- unsortedbin
管理usorted chunk,只有一个双向链表。
所有大小大于fastbin的chunk都会先被暂时放入unsortedbin中,链表中的chunk大小不一。
如图:
- smallbin
管理small chunk,由62个双向链表组成,每个链表中的chunk大小一样,以0x10递增。
(图和unsortedbin的差不多。)
- largebin
管理large chunk,由63个双向链表组成,FIFO。
同一个双向链表中的chunk大小可不一样。但是在一定范围内,bins大小由小到大排列。
如图:
那么,当我们调用malloc时,程序都干了什么呢?
下面用伪代码来表示:
1 | 计算真正chunk的大小(算上头部长度,对齐); |
当我们调用free时,程序干了什么?
下面也用伪代码表示:
1 | if(free的chunk大小属于fastbin) |
4.总结
难,多复习,多练。