Linux堆内存分配与管理机制详解及其利用方法
字数 1854 2025-08-24 16:48:07
Linux堆内存分配与管理机制详解及其利用方法
堆介绍
堆(Heap)是一个用于动态内存分配的数据结构,进程可以在运行时通过系统调用(如malloc和free)向操作系统请求和释放内存。堆与栈不同,栈是用于自动变量的快速内存分配,而堆则用于需要灵活大小和生存期的动态数据。
程序可以使用如malloc、calloc、realloc等函数在堆上动态分配内存。当内存不再需要时,使用free函数释放。
堆的工作方式
堆内存通过malloc函数分配,分配时会包含元数据信息:
- 0x49-1:表示分配的空间大小(0x48),最低位1表示当前空间已被使用
- 0x00020fd9:剩余可分配的堆空间大小
- 实际分配大小 = 请求大小 + 元数据大小(通常为8字节)
堆的常见漏洞及原理
内存泄漏
当程序分配内存后忘记释放,未被释放的内存将无法再被使用,导致内存泄漏。长时间运行的程序如果不断泄漏内存,最终会导致可用内存耗尽。
void memoryLeakExample() {
char *leak = (char *)malloc(1024);
snprintf(leak, 1024, "This is a memory leak example");
// 漏掉了free(leak)
}
使用已释放的内存(Use-After-Free)
在释放内存后,继续使用该内存区域。这通常会导致未定义行为,可能被攻击者利用。
void useAfterFree() {
char *data = (char *)malloc(100);
strcpy(data, "Hello, World!");
free(data);
printf("Data after free: %s\n", data); // UAF
}
双重释放(Double Free)
试图再次释放已经释放过的内存块。这可能会破坏堆的内部管理结构。
void doubleFree() {
char *data = (char *)malloc(100);
free(data);
free(data); // Double Free
}
元数据破坏(Heap Metadata Corruption)
堆管理器使用元数据来跟踪内存块的分配和释放状态。如果程序修改这些元数据,会导致堆管理器无法正确管理内存。
堆利用技术
unlink攻击
unlink操作是从双向链表中移除一个空闲内存块的过程。攻击者可以控制fd和bk指针来覆盖任意内存。
#define unlink(P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
FD->bk = BK; \
BK->fd = FD; \
}
House of系列攻击技术
- House of Prime:通过修改堆块的大小和边界,破坏堆的管理结构
- House of Force:通过修改堆块的大小,使得内存分配器分配不正确的内存块
- House of Mind:通过修改堆块的指针,使得内存分配器在错误的位置进行读写操作
- House of Lore:通过修改堆块的元数据,使得内存分配器在错误的位置进行分配操作
- House of Spirit:利用堆管理器中的链表结构,通过伪造链表指针进行攻击
- House of Chaos:通过破坏堆管理器的内部状态,使得堆管理器的行为异常
- House of Underground:通过修改堆块的指针和大小,使得堆管理器在错误的位置进行内存分配和释放
- House of Orange:利用伪造chunk的size和prev_size字段,造成unlink操作被利用
tcache机制
tcache(线程缓存)是ptmalloc中用于优化小内存分配和释放的机制。每个线程都有独立的tcache结构,避免锁争用。
tcache_entry结构:
typedef struct tcache_entry {
struct tcache_entry *next;
struct tcache_perthread_struct *key;
} tcache_entry;
tcache分配和释放过程:
- 分配时首先检查对应大小的桶是否有可用块
- 释放时将块添加到对应大小的桶中
- 使用单链表结构管理空闲块
tcache攻击技术
- Double Free:通过覆盖key值绕过双重释放检查
- tcache污染:通过破坏tcache_entry->next指针,可能导致非法内存访问或控制程序流
chunk与metadata
chunk结构
在内存管理中,块(chunk)是分配器管理的内存区域的基本单位,包含元数据和实际数据。
chunk元数据:
- mchunk_prev_size:前一个块的大小
- mchunk_size:当前块的大小(包含标志位)
- fd/bk:空闲块的双向链表指针
ptmalloc的缓存机制
- tcache bin:线程局部缓存,16-1032字节
- fast bin:单链表,最多160字节
- unsorted bin:双链表,临时存储释放块
- small bin:双链表,最多512字节
- large bin:双链表,超过512字节
实际利用示例
覆盖GOT表
通过堆溢出修改函数指针(如printf的GOT表项),使其跳转到攻击者控制的代码。
import struct
# 覆盖puts@GOT为winner函数地址
buf3 = ''
buf3 += '\xff'*4
buf3 += struct.pack('I', 0x804b128-12) # puts@GOT-12
buf3 += struct.pack('I', 0x804c040) # shellcode地址
负数size利用
使用-4(0xfffffffc)作为块大小,绕过检查并触发unlink操作。
buf2 += struct.pack('I', 0xfffffffc)*2 # 覆盖prev_size和size
防御措施
- 及时释放内存并置空指针
- 使用安全的内存管理函数(如calloc代替malloc)
- 启用堆保护机制(如GNU Libc的防护措施)
- 使用内存安全语言或静态分析工具检测漏洞
堆漏洞利用需要对内存管理机制有深入理解,攻击者通过精心构造的输入可以绕过各种防护措施实现代码执行。防御方需要结合多种防护手段来降低风险。