pwn堆的结构及堆溢出理解
字数 1957 2025-08-23 18:31:34
Glibc堆管理机制及堆溢出利用详解
1. 堆的基本概念
堆是程序虚拟地址空间的一块连续的线性区域,具有以下特点:
- 由低地址向高地址方向增长(与栈相反)
- 内存可以动态分配和释放
- 全局可访问,通过指针引用
- 通过malloc和free函数管理内存
堆与栈的对比
| 特性 | 堆 | 栈 |
|---|---|---|
| 申请时机 | 运行时动态分配 | 程序运行前分配 |
| 释放方式 | 手动释放 | 自动释放 |
| 增长方向 | 低地址→高地址 | 高地址→低地址 |
| 内存分配 | 非线性、无序 | 线性、有序 |
2. Glibc堆管理机制
2.1 内存分配策略
为了避免频繁的系统调用,glibc会:
- 预先申请一大块内存区域
- 在这块区域内进行内存管理
- 当预分配的内存不足时再向操作系统申请更多空间
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否)
- 低3位用作标志位:
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的fdbk存储key用于安全检查cnt记录entry数量
4. 堆溢出攻击原理
堆溢出是指向堆块写入数据时超过了其边界,覆盖了相邻的高地址chunk。
4.1 溢出条件
- 程序允许向堆写入数据
- 写入数据大小未受控制
- 溢出的数据覆盖了关键结构
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 利用技术
通过堆溢出可以:
- 覆盖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命令可以直观显示堆结构,便于分析堆布局和漏洞利用。