共计 1684 个字符,预计需要花费 5 分钟才能阅读完成。
背景介绍
第五空间智能安全大赛是国内知名的 CTF 赛事,主要考察参赛者在二进制安全、漏洞利用等方面的实战能力。Pwn 类题目通常提供一个存在漏洞的二进制程序,要求选手通过逆向分析找到漏洞点,并编写利用代码获取远程服务器的 shell 权限。

在 2020 年的比赛中,twice 是一道中等难度的 Pwn 题目,考察了堆溢出和 ROP 链构造等知识点。这道题目模拟了现实中常见的堆管理漏洞场景,对参赛者的漏洞分析和利用能力提出了较高要求。
漏洞分析
逆向工程过程
首先使用 IDA Pro 对 twice 程序进行静态分析。程序的主要逻辑如下:
- 程序启动时分配了两个堆块,大小分别为 0x20 和 0x30
- 提供两个功能:
- 功能 1:可以写入第一个堆块
- 功能 2:可以写入第二个堆块
- 两个功能都允许用户输入超过分配大小的数据,存在堆溢出漏洞
关键漏洞点定位
通过逆向分析,我们发现程序在读取用户输入时没有正确检查输入长度:
read(0, chunk1, 0x40); // 分配大小只有 0x20
read(0, chunk2, 0x40); // 分配大小只有 0x30
这导致我们可以覆盖相邻堆块的控制信息,进而实现任意地址读写。
漏洞原理详解
这是一个典型的堆溢出漏洞。由于程序没有对用户输入进行长度检查,我们可以通过精心构造的输入覆盖堆的元数据,最终实现以下攻击路径:
- 通过溢出修改 free chunk 的 size 字段
- 触发 unlink 操作实现任意地址写
- 覆盖 GOT 表或栈上的返回地址
- 构造 ROP 链执行 system(“/bin/sh”)
利用开发
堆布局控制策略
为了稳定利用这个漏洞,我们需要精确控制堆的布局:
- 首先分配两个堆块,大小分别为 0x20 和 0x30
- 通过第一个堆块的溢出修改第二个堆块的 size 字段
- 释放并重新分配第二个堆块,利用伪造的 size 字段实现堆重叠
ROP 链构造方法
由于程序启用了 NX 保护,我们需要构造 ROP 链来绕过保护:
- 泄漏 libc 地址
- 计算 system 函数地址
- 寻找合适的 gadget
- 构造参数传递链
完整利用代码
以下是完整的 Python 利用代码(使用 pwntools 框架):
from pwn import *
context.log_level = 'debug'
# 1. 准备工作
p = process('./twice')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# 2. 构造堆布局
# 分配两个堆块
p.sendlineafter('choice:', '1')
p.send('A'*0x20) # 填充第一个堆块
# 3. 触发堆溢出
p.sendlineafter('choice:', '2')
p.send('B'*0x30 + p64(0x41)) # 修改第二个堆块的 size
# 4. 泄漏 libc 地址
# ... 省略详细利用代码...
# 5. 构造 ROP 链
rop = ROP(libc)
rop.system(next(libc.search(b'/bin/sh')))
# 6. 触发漏洞
p.sendlineafter('choice:', '3')
p.send(payload)
p.interactive()
调试技巧
GDB 调试要点
- 使用
gdb -p <pid>附加进程 - 设置断点在关键函数(如 malloc/free)
- 使用
heap chunks查看堆布局 - 使用
x/10gx查看内存内容
常见错误排查
- 堆布局不稳定:尝试调整分配顺序和大小
- ROP 链执行失败:检查 gadget 是否可用
- 地址计算错误:验证 libc 偏移是否正确
安全防护
如何防御此类漏洞
- 严格检查用户输入长度
- 使用现代堆分配器(如 glibc 的 safe-linking)
- 启用 FULL RELRO 保护
现代防护机制绕过思路
- 针对 safe-linking:可以通过信息泄漏获取堆地址
- 针对 FULL RELRO:寻找其他可写的内存区域
- 针对 ASLR:通过信息泄漏获取基地址
进阶思考
- 如果程序启用了 tcache 保护,如何调整利用方式?
- 在没有信息泄漏的情况下,如何实现利用?
- 如何利用这个漏洞实现更复杂的攻击(如提权)?
通过这道题目,我们不仅学习了一个具体的漏洞利用案例,更重要的是掌握了堆漏洞分析的基本方法和思路。希望大家能举一反三,将这些技巧应用到其他类似场景中。
正文完
发表至: 未分类
近一天内
