pwn堆的结构及堆溢出理解
字数 1957 2025-08-23 18:31:34

Glibc堆管理机制及堆溢出利用详解

1. 堆的基本概念

堆是程序虚拟地址空间的一块连续的线性区域,具有以下特点:

  • 由低地址向高地址方向增长(与栈相反)
  • 内存可以动态分配和释放
  • 全局可访问,通过指针引用
  • 通过malloc和free函数管理内存

堆与栈的对比

特性
申请时机 运行时动态分配 程序运行前分配
释放方式 手动释放 自动释放
增长方向 低地址→高地址 高地址→低地址
内存分配 非线性、无序 线性、有序

2. Glibc堆管理机制

2.1 内存分配策略

为了避免频繁的系统调用,glibc会:

  1. 预先申请一大块内存区域
  2. 在这块区域内进行内存管理
  3. 当预分配的内存不足时再向操作系统申请更多空间

2.2 Chunk结构

堆内存被划分为多个chunk,每个chunk包含:

+-------------------+-------------------+
|     prev_size     |       size        |  <-- chunk header
+-------------------+-------------------+
|                   data                |
|                                       |
+---------------------------------------+

字段说明

  • prev_size:前一个chunk的大小(仅当前一个chunk空闲时有效)
  • size:当前chunk的大小(包括头部),按8字节对齐
    • 低3位用作标志位:
      • A (bit 0x4):是否属于主线程(1不属于,0属于)
      • M (bit 0x2):是否由mmap分配(1是,0否)
      • P (bit 0x1):前一个chunk是否被分配(1是,0否)

2.3 Chunk类型

  1. Allocated chunk:已分配的chunk
  2. Free chunk:已释放的chunk
  3. Top chunk:堆顶剩余的chunk

2.4 Chunk合并机制

当释放一个chunk时:

  • 如果相邻的前一个或后一个chunk也是空闲的,会进行合并
  • 通过检查size字段的P位判断前一个chunk是否空闲
  • 通过检查下一个chunk的P位判断后一个chunk是否空闲

3. Bins管理机制

glibc使用多种bin来管理空闲chunk:

3.1 Fast Bins

  • 管理小于0x80字节的free chunk
  • 共有7个(0x20-0x80)
  • 特点:
    • 单链表结构,使用fd指针连接
    • 释放时不修改下一chunk的P
    • 不进行合并操作(提高分配速度)

3.2 Small Bins

  • 管理较小尺寸的空闲chunk(小于504字节)
  • 双向链表结构,使用fdbk指针
  • 按大小分类存放,便于快速查找

3.3 Large Bins

  • 管理大于504字节的free chunk
  • 双向链表结构
  • 分配策略更复杂,可能涉及分割操作

3.4 Unsorted Bin

  • "垃圾桶",存放刚释放还未分类的chunk
  • 双向链表结构
  • 在分配时优先检查,提高内存重用率
  • 后续会被转移到合适的small/large bin

3.5 Tcache (per-thread cache)

  • 自glibc 2.26引入的线程本地缓存
  • 更高效的小内存分配机制
  • 结构特点:
    • fd指向上一个chunk的fd
    • bk存储key用于安全检查
    • cnt记录entry数量

4. 堆溢出攻击原理

堆溢出是指向堆块写入数据时超过了其边界,覆盖了相邻的高地址chunk。

4.1 溢出条件

  1. 程序允许向堆写入数据
  2. 写入数据大小未受控制
  3. 溢出的数据覆盖了关键结构

4.2 溢出示例

#include <stdio.h>
int main(void) {
    char *chunk;
    chunk=malloc(24);
    puts("Get input:");
    gets(chunk);  // 危险函数,不检查输入长度
    return 0;
}

堆布局:

0x602000: 0x00000000 0x00000000 0x00000000 0x00000021  <== chunk
0x602010: 0x00000000 0x00000000 0x00000000 0x00000000
0x602020: 0x00000000 0x00000000 0x00000000 0x0020ffe1  <== top chunk

溢出后(写入0x100字节'A'):

0x602000: 0x00000000 0x00000000 0x41414141 0x41414121
0x602010: 0x41414141 0x41414141 0x41414141 0x41414141
0x602020: 0x41414141 0x41414141 0x41414141 0x41414141  <== top chunk被覆盖

4.3 利用技术

通过堆溢出可以:

  1. 覆盖chunk的header信息(prev_size, size)
  2. 修改free chunk的fd/bk指针
  3. 实现任意地址读写
  4. 最终控制程序执行流

5. 危险函数列表

以下函数容易导致堆溢出:

输入函数

  • gets:直接读取一行,忽略'\x00'
  • scanf/vscanf:格式化输入

输出函数

  • sprintf:格式化输出到字符串

字符串操作

  • strcpy:字符串复制,遇'\x00'停止
  • strcat:字符串拼接,遇'\x00'停止
  • bcopy:内存拷贝

6. realloc的特殊行为

realloc在调整内存大小时有以下行为:

  1. 缩小分配

    • 如果新旧size差距不足以插入最小chunk,保持原状
    • 否则分割chunk并free多余部分
  2. 扩大分配

    • 如果与top chunk相邻,直接扩展
    • 否则相当于free旧chunk + malloc新chunk

7. 防御与检测

  1. 使用安全函数替代危险函数
  2. 严格检查输入长度
  3. 使用堆保护机制(如glibc的security checks)
  4. 利用现代防护技术(ASLR, DEP等)

8. 调试工具

推荐使用pwndbg插件,其中的parseheap命令可以直观显示堆结构,便于分析堆布局和漏洞利用。

Glibc堆管理机制及堆溢出利用详解 1. 堆的基本概念 堆是程序虚拟地址空间的一块连续的线性区域,具有以下特点: 由低地址向高地址方向增长(与栈相反) 内存可以动态分配和释放 全局可访问,通过指针引用 通过malloc和free函数管理内存 堆与栈的对比 | 特性 | 堆 | 栈 | |------|----|----| | 申请时机 | 运行时动态分配 | 程序运行前分配 | | 释放方式 | 手动释放 | 自动释放 | | 增长方向 | 低地址→高地址 | 高地址→低地址 | | 内存分配 | 非线性、无序 | 线性、有序 | 2. Glibc堆管理机制 2.1 内存分配策略 为了避免频繁的系统调用,glibc会: 预先申请一大块内存区域 在这块区域内进行内存管理 当预分配的内存不足时再向操作系统申请更多空间 2.2 Chunk结构 堆内存被划分为多个chunk,每个chunk包含: 字段说明 : prev_size :前一个chunk的大小(仅当前一个chunk空闲时有效) size :当前chunk的大小(包括头部),按8字节对齐 低3位用作标志位: A (bit 0x4):是否属于主线程(1不属于,0属于) M (bit 0x2):是否由mmap分配(1是,0否) P (bit 0x1):前一个chunk是否被分配(1是,0否) 2.3 Chunk类型 Allocated chunk :已分配的chunk Free chunk :已释放的chunk Top chunk :堆顶剩余的chunk 2.4 Chunk合并机制 当释放一个chunk时: 如果相邻的前一个或后一个chunk也是空闲的,会进行合并 通过检查 size 字段的 P 位判断前一个chunk是否空闲 通过检查下一个chunk的 P 位判断后一个chunk是否空闲 3. Bins管理机制 glibc使用多种bin来管理空闲chunk: 3.1 Fast Bins 管理小于0x80字节的free chunk 共有7个(0x20-0x80) 特点: 单链表结构,使用 fd 指针连接 释放时不修改下一chunk的 P 位 不进行合并操作(提高分配速度) 3.2 Small Bins 管理较小尺寸的空闲chunk(小于504字节) 双向链表结构,使用 fd 和 bk 指针 按大小分类存放,便于快速查找 3.3 Large Bins 管理大于504字节的free chunk 双向链表结构 分配策略更复杂,可能涉及分割操作 3.4 Unsorted Bin "垃圾桶",存放刚释放还未分类的chunk 双向链表结构 在分配时优先检查,提高内存重用率 后续会被转移到合适的small/large bin 3.5 Tcache (per-thread cache) 自glibc 2.26引入的线程本地缓存 更高效的小内存分配机制 结构特点: fd 指向上一个chunk的 fd bk 存储key用于安全检查 cnt 记录entry数量 4. 堆溢出攻击原理 堆溢出是指向堆块写入数据时超过了其边界,覆盖了相邻的高地址chunk。 4.1 溢出条件 程序允许向堆写入数据 写入数据大小未受控制 溢出的数据覆盖了关键结构 4.2 溢出示例 堆布局: 溢出后(写入0x100字节'A'): 4.3 利用技术 通过堆溢出可以: 覆盖chunk的header信息(prev_ size, size) 修改free chunk的 fd / bk 指针 实现任意地址读写 最终控制程序执行流 5. 危险函数列表 以下函数容易导致堆溢出: 输入函数 : gets :直接读取一行,忽略'\x00' scanf / vscanf :格式化输入 输出函数 : sprintf :格式化输出到字符串 字符串操作 : strcpy :字符串复制,遇'\x00'停止 strcat :字符串拼接,遇'\x00'停止 bcopy :内存拷贝 6. realloc的特殊行为 realloc 在调整内存大小时有以下行为: 缩小分配 : 如果新旧size差距不足以插入最小chunk,保持原状 否则分割chunk并free多余部分 扩大分配 : 如果与top chunk相邻,直接扩展 否则相当于free旧chunk + malloc新chunk 7. 防御与检测 使用安全函数替代危险函数 严格检查输入长度 使用堆保护机制(如glibc的security checks) 利用现代防护技术(ASLR, DEP等) 8. 调试工具 推荐使用 pwndbg 插件,其中的 parseheap 命令可以直观显示堆结构,便于分析堆布局和漏洞利用。