Bins
LOLOLO Lv3

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
struct malloc_state
{
/* Serialize access. */
mutex_t mutex;

/* Flags (formerly in max_fast). */
int flags;

/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];

/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;

/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;

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

/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];

/* Linked list */
struct malloc_state *next;

/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;

/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;

/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
  • fastbinY数组:大小为10。记录的是fast bin链。
  • bins数组:大小为126。记录的是unsorted bin(1)、small bin(263)、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
2
#define fastbin_index(sz) \ 
((((unsigned int)(sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)

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
    #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))

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 许可协议。转载请注明出处!
 评论