Bins(为了更好的学习Large bin attack)
一、bin链的介绍
- bin是一个由struct chunk结构体组成的链表。
- 前面介绍过,不同的chunk根据特点不同分为不同的chunk,为了将这些chunk进行分类的管理,glibc采用了bin链这种方式管理不同的chunk。
- 不同的bin链是由arena管理的。
- bin链中的chunk均为free chunk。
二、bin链分类
- fast bin,单链表,除了fast bin其他都是循环双链表
- unsorted bin
- small bin
- large bin
三、bin链的保存(struct malloc_state结构体)
1 | struct malloc_state |
- fastbinY数组:大小为10。记录的是fast bin链。
- bins数组:大小为126。记录的是unsorted bin(1)、small bin(2
63)、large bin链(64126)。
四、Fast bin
- 概念:chunk的大小在32字节~128字节(0x20-0x80)的chunk称为“fast chunk”(大小不是malloc时的大小,而是在内存中struct malloc_chunk的大小,包含前2个成员)。
- fast bin链表的个数为10个。
- 不会对free chunk进行合并:鉴于设计fast bin的初衷就是进行快速的小内存分配和释放,因此系统将属于fast bin的chunk的PREV_INUSE位总是设置为1,这样即使当fast bin中有某个chunk同一个free chunk相邻的时候,系统也不会进行自动合并操作,而是保留两者。虽然这样做可能会造成额外的碎片化问题,但瑕不掩瑜。
fastbinsY数组存储fastbins的规则
- 每个fast bin链表都是单链表(使用fd指针)。因此,fast bin中无论是添加还是移除fast chunk,都是对“链表尾”进行操作,而不会对某个中间的fast chunk进行操作。
- 单个fastbin链表中的chunk大小都是相同的,各个fastbin链表中的chunk大小是不同的。
- fastbinY数组中的每个bin链表的排序,是按照链表元素的大小进行排序的。数组的第一个元素的fast bin链表中的每个chunk的大小是32字节的,数组的第二个元素的fast bin链表中的每个chunk的大小是48字节的……每个元素都比前面的fast bin链大16字节,以此类推进行排序。
链表索引宏定义:(fastbin_index)
- 功能:通过此宏能判断某一个fastchunk属于哪一个fastbin链表。
- 参数:某一个chunk的大小。
1 |
|
malloc操作与fastbins的初始化:
- 当应用层通过malloc函数第一次申请的chunk属于16字节~80字节之间时,因为初始化的时候fast bin支持的最大内存大小以及所有fast bin链表都是空的,所以它也不会交由fast bin来处理,而是向下传递交由small bin来处理,如果small bin也为空的话就交给unsorted bin处理。
- fast bin如何进行初始化
- 当我们第一次调用malloc(fast bin)的时候,系统执行_int_malloc函数,该函数首先会发现当前fast bin为空,就转交给small bin处理,进而又发现small bin 也为空,就调用malloc_consolidate函数对malloc_state结构体进行初始化。malloc_consolidate函数主要完成以下几个功能:
- a.首先判断当前malloc_state结构体中的fast bin是否为空,如果为空就说明整个malloc_state都没有完成初始化,需要对malloc_state进行初始化。
- b.malloc_state的初始化操作由函数malloc_init_state(av)完成,该函数先初始化除fast bin之外的所有的bins,再初始化fast bins。
- 那么当再次执行malloc(fast chunk)函数的时候,此时fast bin相关数据不为空了,就可以使用fast bin。
- 这个操作很简单,主要分为两步:先通过chunksize函数根据传入的地址指针获取该指针对应的chunk的大小;然后根据这个chunk大小获取该chunk所属的fast bin,然后再将此chunk添加到该fast bin的链尾即可。整个操作都是在_int_free函数中完成。
五、Unsorted bin
- 何时使用:当不存在tcache时,只要释放的堆块大于0x80,就会首先放入unsorted bin中去。
- 特性
- unsorted bin只有一个
- unsorted bin是一个由free chunks组成的循环双链表。
- 在unsorted bin中,对chunk的大小并没有限制,任何大小的chunk都可以归属到unsorted bin中。
- unsortedbin采用的遍历顺序是FIFO,即是先进先出。
六、Small bin
- 概念:小于1024字节(0x400)的chunk称之为small chunk,small bin就是用于管理small chunk的。
- small bin链表的个数为62个。
Small Bin链表的特性
每个smallbin也是一个由对应free chunk组成的循环双链表。
small bin采用FIFO(先入先出)算法:内存释放操作就将新释放的chunk添加到链表的front end(前端),分配操作就从链表的rear end(尾端)中获取chunk。
单个smallbin链表中的chunk大小都是相同的,各个smallbin链表中的chunk大小是不同的,跟fastbinsY数组存储fastbin链的原理是相同的。
bins数组存储small bin链时:第一个small bin链中chunk的大小为32字节,后续每个small bin中chunk的大小依次增加两个机器字长(32位相差8字节,64位相差16字节)…….以此类推,跟fastbinsY数组存储fastbin链的原理是相同的(用下图表示)。
bin链存储的大小与数组下标的关系:chun_size=2 * SIZE_SZ * index。
malloc操作与small bin的初始化
类似于fast bins,最初所有的small bin都是空的,因此在对这些small bin完成初始化之前,即使用户请求的内存大小属于small chunk也不会交由small bin进行处理,而是交由unsorted bin处理。
如果unsorted bin也不能处理的话,glibc malloc就依次遍历后续的所有bins,找出第一个满足要求的bin,如果所有的bin都不满足的话,就转而使用top chunk,如果top chunk大小不够,那么就扩充top chunk,这样就一定能满足需求了。
注意遍历后续bins以及之后的操作同样被large bin所使用,因此,将这部分内容放到large bin的malloc操作中加以介绍。
那么glibc malloc是如何初始化这些bins的呢?因为这些bin属于malloc_state结构体,所以在初始化malloc_state的时候就会对这些bin进行初始化,代码如下:
将bins数组中的第一个成员索引值设置为了1,而不是我们常用的0(在bin_at宏中,自动将i进行了减1处理)。
从下面代码可以看出在初始化的时候glibc malloc将所有bin的指针都指向了自己——这就代表这些bin都是空的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 static void
malloc_init_state (mstate av)
{
int i;
mbinptr bin;
/* Establish circular links for normal bins */
for (i = 1; i < NBINS; ++i)
{
bin = bin_at (av, i);
bin->fd = bin->bk = bin;
}
......
}过后,当再次调用malloc(small chunk)的时候,如果该chunk size对应的small bin不为空,就从该small bin链表中取得small chunk给malloc使用。
free操作
- small的free比较特殊。当释放small chunk的时候,先检查该chunk相邻的chunk是否为free,如果是的话就进行合并操作:将这些chunks合并成新的chunk,然后将它们从small bin中移除,最后将新的chunk添加到unsorted bin中,之后unsorted bin进行整理再添加到对应的bin链上(后面会有图介绍)。
七、Large bin
- 概念:大于等于1024字节(0x400)的chunk称之为large chunk,large bin就是用于管理这些largechunk的。
- large bin链表的个数为63个,被分为6组。
- largechunk使用fd_nextsize、bk_nextsize连接起来的。
Large Bin链表的特性
同一个largebin中每个chunk的大小可以不一样,这些chunk根据一定的范围存储在一个larbin链表中。
large chunk可以添加、删除在large bin的任何一个位置。
在这63个largebins中:第一组的32个largebin链依次以64字节步长为间隔,即第一个largebin链中chunksize为1024-1087字节,第二个large bin中chunk size为1088~1151字节。第二组的16个largebin链依次以512字节步长为间隔;第三组的8个largebin链以步长4096为间隔;第四组的4个largebin链以32768字节为间隔;第五组的2个largebin链以262144字节为间隔;最后一组的largebin链中的chunk大小无限制。
在同一个largebin中:每个chunk的大小不一定相同,因此为了加快内存分配和释放的速度,就将同一个largebin中的所有chunk按照chunksize进行从大到小的排列:最大的chunk放在一个链表的front end,最小的chunk放在rear end;相同大小的chunk按照最近使用顺序排序。
链表索引宏定义:(largebin_index)
参数为链表能存储的chunk大小,宏定义中有简介调用其他宏定义。
例如:第一个largebin的起始大小为1024,那么1024>>6=16,所以其在bins数组中的下标为48+16=64。
1
2
3
4
5
6
7
8
9
10
11
12
malloc操作与large bin的初始化
- 初始化完成之前的操作类似于small bin。
- 下面讨论large bins初始化完成之后的操作:
- 首先确定用户请求的大小属于哪一个large bin,然后判断该large bin中最大的chunk的size是否大于用户请求的size(只需要对比链表中front end的size即可)。如果大于,就从rear end开始遍历该large bin,找到第一个size相等或接近的chunk,分配给用户。如果该chunk大于用户请求的size的话,就将该chunk拆分为两个chunk:前者返回给用户,且size等同于用户请求的size;剩余的部分做为一个新的chunk添加到unsorted bin中。
- 如果该large bin中最大的chunk的size小于用户请求的size的话,那么就依次查看后续的large bin中是否有满足需求的chunk,不过需要注意的是鉴于bin的个数较多(不同bin中的chunk极有可能在不同的内存页中),如果按照上一段中介绍的方法进行遍历的话(即遍历每个bin中的chunk),就可能会发生多次内存页中断操作,进而严重影响检索速度,所以glibc malloc设计了Binmap结构体来帮助提高bin-by-bin检索的速度。Binmap记录了各个bin中是否为空,通过bitmap可以避免检索一些空的bin。如果通过binmap找到了下一个非空的large bin的话,就按照上一段中的方法分配chunk,否则就使用top chunk来分配合适的内存。
free操作类似于small bin
- 本文标题:Bins
- 本文作者:LOLOLO
- 创建时间:2022-03-03 13:00:45
- 本文链接:https://lololo-pwn.github.io/2022/03/03/bins/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!