堆漏洞挖掘中的bins分类(fastbin、unsorted bin、small bin、large bin)

一、bin链的介绍

  • bin是一个由struct chunk结构体组成的链表。
  • 前面介绍过,不同的chunk根据特点不同分为不同的chunk,为了将这些chunk进行分类的管理,glibc采用了bin链这种方式管理不同的chunk。
  • 不同的bin链是由arena管理的。
  • bin链中的chunk均为free chunk。

二、bin链分类

  • 根据bin链成员的大小不同,分为以下几类:
    • fast bin是单链表,其他都是双向链表。
    • Fast bin。
    • Unsorted bin。
    • Small bin。
    • Large bin。

三、bin链的保存(struct malloc_state结构体)

typedef struct malloc_chunk* mchunkptr;
typedef struct malloc_chunk *mfastbinptr;


struct malloc_state
{ 
  ...
  /*other member*/
  ...

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  ...
  /*other member*/
  ...
}; 
  • 前面说过,不同的bin链是由arena管理的。因此一个线程中会有很多的bin链,这些bin链都有arena所表示的struct malloc_state结构体的以下成员保存:
    • fastbinY数组:大小为10。记录的是fast bin链。
    • bins数组:大小为129。记录的是unsorted bin(1)、small bin(2~63)、large bin链(64~126)。

四、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的大小。

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。

free操作

  • 这个操作很简单,主要分为两步:先通过chunksize函数根据传入的地址指针获取该指针对应的chunk的大小;然后根据这个chunk大小获取该chunk所属的fast bin,然后再将此chunk添加到该fast bin的链尾即可。整个操作都是在_int_free函数中完成。

五、Unsorted Bin

  • 何时使用:当释放较小或较大的chunk的时候,如果系统没有将它们添加到对应的bins中,系统就将这些chunk添加到unsorted bin中。
  • 目的:这主要是为了让“glibc malloc机制”能够有第二次机会重新利用最近释放的chunk(第一次机会就是fast bin机制)。利用unsorted bin,可以加快内存的分配和释放操作,因为整个操作都不再需要花费额外的时间去查找合适的bin了。
  • Unsorted bin的特性如下:
    • unsorted bin的个数: 1个。
    • 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比larger bin快,但比fast bin慢。
  • 合并操作:相邻的free chunk需要进行合并操作,即合并成一个大的free chunk。具体操作见下文free(small chunk)介绍。

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都是空的。
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连接起来的。
  • 合并操作:类似于small bin。

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。

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 chunk。

  • 我是小董,V公众点击"笔记白嫖"解锁更多【堆漏洞挖掘】资料内容。

相关推荐
©️2020 CSDN 皮肤主题: 护眼 设计师:闪电赇 返回首页