返回博客

新文章

2026-03-194 min 阅读

PWN 专题

堆管理器 和 堆漏洞


堆内存

堆内存是一种允许在程序运行过程中动态分配和使用的内存区域,相比于栈内存,堆没有固定的生命周期和固定的内存区域,程序可以动态地申请和释放不同大小的内存,被分配后,如果没有进行明确的释放操作,该堆的内存区域都是一直有效的。

+------------------------+  高地址
|      命令行参数         |  (argc, argv[])
+------------------------+
|      环境变量           |  (environ)
+------------------------+
|      栈 (Stack)        |
|  - 函数参数             |
|  - 局部变量             |
|  - 返回地址             |
|  - 保存的寄存器         |
|  (向下增长)             |
+------------------------+
|      空闲区 (可选)      |
+------------------------+
|      堆 (Heap)          |
|  - malloc分配的内存      |
|  (向上增长)             |
+------------------------+
|      BSS段 (.bss)       |
|  - 未初始化的全局/静态变量
+------------------------+
|      数据段 (.data)     |
|  - 已初始化的全局/静态变量
+------------------------+
|      代码段 (.text)     |
|  - 程序的机器指令       |
+------------------------+  低地址

对于不同的应用来说,由于内存的需求各不相同等特性,因此目前堆的实现有很多种,具体如下

dlmalloc  – General purpose allocator
ptmalloc2 – glibc
jemalloc  – FreeBSD and Firefox
tcmalloc  – Google
libumem   – Solaris

这里我们主要以 glibc 中 ptmalloc2 堆的实现为主进行介绍。


堆结构

Ptmalloc2 分配的最基础的内存结构为 chunk,其定义如下

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;

  /* Only used for large blocks: pointer to next larger size.  */
  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
  struct malloc_chunk* bk_nextsize;
};

下面我们来看 chunk 结构体,各个字段的具体的解释如下:

1. prev_size

  • 含义

    • 如果物理相邻的前一个 chunk(即低地址方向,地址差值为前一个 chunk 大小)是空闲的,则此字段记录前一个 chunk 的总大小(包括 chunk 头部)。
    • 如果前一个 chunk 已被分配,该字段可以用于存储用户数据
  • 用途

    • 当前一个 chunk 空闲时,用来辅助合并空闲块。

2. size

  • 含义

    • 当前 chunk 的总大小(包含头部),64位系统中,chunk 最小为 32 字节,且 必须是 8 的整数倍。
  • 低 3 位标志位(从高到低):

    标志位 含义
    NON_MAIN_ARENA 是否属于主线程(1:不属于;0:属于主线程)
    IS_MAPPED 是否通过 mmap 方式分配(1:是;0:否)
    PREV_INUSE 前一个 chunk 是否正在使用(1:已使用;0:空闲)
  • 注意事项

    • 第一个分配的内存块通常会将 PREV_INUSE 位置 1,以避免访问非法内存。
    • 如果 PREV_INUSE = 0,可以通过 prev_size 字段确定前一个 chunk 的地址及大小,便于空闲块合并。

3. fdbk

  • 含义(仅空闲 chunk 时使用):

    • fd:指向下一个(非物理相邻的)空闲 chunk。
    • bk:指向上一个(非物理相邻的)空闲 chunk。
  • 说明

    • 空闲 chunk 被链接到对应的空闲管理链表中。
    • 当 chunk 处于分配状态时,从 fd 开始的区域为用户数据区。

4. fd_nextsizebk_nextsize

  • 适用范围:仅适用于空闲的large chunk(较大的空闲块)。

  • 含义

    • fd_nextsize:指向前一个大小不同的空闲块(不包含 bin 的头指针)。
    • bk_nextsize:指向后一个大小不同的空闲块(不包含 bin 的头指针)。
  • 目的

    • 支持按照 chunk 大小由大到小的顺序组织 large chunk,减少遍历次数,提高分配效率。
+------------+
| prev_size  |  // 仅当前一个 chunk 空闲时有效
+------------+
| size       |  // 含标志位
+------------+
| fd         |  // 空闲时: 指向下一个空闲 chunk
+------------+
| bk         |  // 空闲时: 指向上一个空闲 chunk
+------------+
| fd_nextsize|  // 仅 large chunk 使用
+------------+
| bk_nextsize|  // 仅 large chunk 使用
+------------+
| User Data  |  // 分配状态: 用户数据开始
|            |
+------------+

chunk 在管理器中分为 3 中形式,分别为 allocated chunk free chunk top chunk

当用户申请一块内存后,就返回一个 allocated chunk

当内存被释放后,就得到了一个 free chunk

top chunk是分给堆区域的一大块内存,如果没有可用的 free chunk,就会从 top chunk 中分出一块 allocated chunk


Bin 结构分类

在 ptmalloc2 中,根据 chunk 大小的不同,空闲的 chunk 被组织在不同的 Bin 结构中,分别为 Fast Bin、Small Bin、Large Bin 和 Unsorted Bin。

1. Fast Bin

  • 范围:chunk 大小为 32~128(0x20~0x80)字节。
  • 特点
    • 如果 chunk 在释放时发现其大小符合要求,则将该 chunk 放入 Fast Bin。
    • 被放入 Fast Bin 后,不修改前一个 chunk 的 PREV_INUSE 标志位。
    • Fast Bin 以单链表形式组织,不同大小的 Fast Bin 存储在对应大小的单链表结构中。
    • Fast Bin 的取出机制为 LIFO(后进先出)。
  • 注意
    • 新释放的 chunk 指向链表头,形成 Fast Bin 链。

2. Small Bin

  • 范围:chunk 大小为 32~1024(0x20~0x400)字节。
  • 特点
    • 每个 Small Bin 作为双向链表,不同大小的 chunk 存放在对应的链表中。
    • Small Bin 的存储和连接方式是 FIFO(先进先出)。
  • 补充
    • 由于是双链表结构,相比 Fast Bin,Small Bin 的速度稍慢。

3. Large Bin

  • 范围:chunk 大小 > 1024(0x400)字节。
  • 特点
    • Large Bin 结构最复杂,速度也最慢。
    • 使用 fdbk 指针串联,同一大小的 Large Bin 使用普通链表连接。
    • 不同大小的 Large Bin 通过 fd_nextsizebk_nextsize 按大小顺序链接。

4. Unsorted Bin

  • 作用
    • 相当于 ptmalloc2 堆管理器的垃圾桶
  • 特点
    • 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。

    • 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。

    • 堆管理器首先在 Unsorted Bin 查找是否有合适 chunk。

    • 如果 Fast Bin 和 Small Bin 中都没有合适的 chunk,才在 Unsorted Bin 中查找。

    • 如果找到符合申请大小的 chunk,则直接取出或拆分使用。

5. Tcache Bin

  • 作用

    • Tcache(Thread Cache)是 glibc 2.26 后引入的一种堆优化机制。
  • 特点

    • 每个线程都有一个独立的 Tcache 结构(线程私有,不同线程互不影响)。
    • 小块(一般小于 0x410 字节)的空闲 chunk 可以存入 Tcache。
    • Tcache Bin 采用 单链表组织,同一大小的 chunk 放入对应大小的 Tcache Bin。
    • 取出方式为 LIFO(后进先出),即最近释放的 chunk 最先被再次使用。
  • 限制

    • 每个大小类别的 Tcache Bin 有一个最大容量(一般默认是 7 个 chunk)。
    • 超过容量后,新的释放 chunk 不再放入 Tcache,而是走正常的 Fast Bin/Small Bin 等流程。
  • 优点

    • 由于线程本地存储,无需加锁,大大提高了 malloc/free 的性能。
    • 减少了线程间同步带来的开销。
  • 与其他 Bin 的关系

    • 如果 Tcache 中有符合条件的 chunk,malloc 时优先从 Tcache 取出。
    • 如果 Tcache 中没有合适的 chunk,再从主堆管理器(Fast Bin/Small Bin 等)分配。

堆漏洞

未初始化读

被释放的堆块被重新分配时,如果没有清空内存,就可能泄露之前存储的内容

堆溢出

分为堆块内溢出和堆块外溢出

  • 堆块内溢出可以修改堆块内的其他内容,比如字符串指针或函数指针,从而实现读取任意地址或执行任意函数。

  • 堆块外溢出可以操纵 bin 中 free chunk 的指针,使其指向错误的区域,从而实现任意地址读写

image.png

释放后使用

堆块被释放后,仍然保留和使用指向堆块的指针,就能够直接向被释放的堆块写入内容。

之后操作同堆溢出。


评论

0/1000