从零攻破CSAPP buflab:手把手构建六层缓冲区溢出攻击链

张开发
2026/5/10 20:23:43 15 分钟阅读

分享文章

从零攻破CSAPP buflab:手把手构建六层缓冲区溢出攻击链
1. 缓冲区溢出攻击基础入门第一次接触CSAPP的buflab实验时我被这个看似简单却暗藏玄机的缓冲区溢出实验深深吸引。记得当时盯着屏幕上的段错误提示完全不明白为什么输入长字符串会导致程序崩溃。后来才知道这背后隐藏着计算机系统安全中最经典的漏洞之一。缓冲区溢出就像往杯子里倒水杯子容量有限但如果我们不顾限制一直倒水就会溢出到桌面上。在计算机内存中栈区存放着函数调用的重要信息比如返回地址和局部变量。当向栈上的缓冲区写入数据时如果没检查长度多出来的数据就会溢出到其他内存区域覆盖掉关键数据。在IA-32架构下栈是从高地址向低地址增长的。每次函数调用时系统会依次压入参数、返回地址和ebp寄存器值然后为局部变量分配空间。getbuf函数中的buf数组就是这样一个局部变量。当使用不安全的gets函数时用户可以输入任意长度的字符串突破buf的边界覆盖上层的返回地址。我第一次成功完成smoke级别时的场景至今难忘。通过objdump反汇编找到smoke函数的地址精心构造攻击字符串当看到Smoke!: You called smoke()的输出时那种成就感简直无法形容。这让我深刻理解了函数调用时栈帧的结构以及如何通过覆盖返回地址劫持程序流程。2. 实验环境搭建与工具解析工欲善其事必先利其器。在开始buflab实验前需要准备好以下环境Ubuntu系统推荐使用WSL2或原生Ubuntu 22.04这是最接近实验原始环境的配置GCC工具链安装gcc-multilib以支持32位程序编译GDB调试器配合gdb-peda插件可以更方便地查看栈状态实验文件包包含bufbomb、makecookie和hex2raw三个关键程序其中hex2raw工具的作用很特别。因为攻击字符串中经常包含不可打印字符直接输入很困难。hex2raw可以将十六进制编码的文本转换成原始字节流。比如echo 68 65 6c 6c 6f | ./hex2raw会输出hello的ASCII码。makecookie则根据用户ID生成唯一的8字节cookie值这在fizz和bang级别中会用到。使用方式很简单./makecookie 0809NJU064输出类似0x420e0c1b这样的值。调试时我发现一个实用技巧在gdb中使用x/40xw $esp可以查看栈内存disas getbuf能反汇编函数结合使用可以清晰看到缓冲区与返回地址的位置关系。3. 六层攻击链逐级突破3.1 Level 0smoke初体验smoke级别是缓冲区溢出的Hello World。目标是通过溢出使getbuf返回时跳转到smoke函数而非原调用者test。具体步骤使用objdump -d bufbomb找到smoke函数地址比如0x080493d5确定缓冲区大小通过反汇编可以看到lea -0x67(%ebp),%eax说明buf大小是0x67(103)字节构造攻击字符串103字节填充4字节任意值(覆盖ebp)4字节smoke地址(小端序)关键点在于理解栈帧结构。当getbuf执行ret时会弹出栈顶的值作为返回地址。我们通过溢出覆盖这个值就能控制程序流程。3.2 Level 1fizz参数传递fizz级别增加了参数校验需要让fizz函数接收到正确的cookie值。与smoke不同之处找到fizz地址0x08049402攻击字符串需要额外4字节存放cookie值这个cookie值要放在fizz的ebp8位置即返回地址后4字节通过这个级别我学会了函数参数在栈上的传递方式。x86架构下参数是通过栈传递的第一个参数位于ebp8的位置。3.3 Level 2bang与shellcodebang级别难度陡增需要注入可执行代码。目标是修改全局变量global_value并跳转到bang函数。攻击步骤编写汇编代码将cookie存入global_value然后跳转到bang使用gcc编译后objdump获取机器码将shellcode放在缓冲区开头返回地址设置为缓冲区起始地址(通过gdb调试获取)这里有个坑点现代系统通常有NX保护(不可执行栈)但实验中的bufbomb禁用了这个保护允许栈上代码执行。3.4 Level 3rumble与字符串参数rumble级别需要传递字符串参数并让eval2equal函数验证通过。关键发现eval2equal内部会将cookie转为字符串形式需要在攻击字符串中包含这个字符串字符串地址要正确指向缓冲区中的对应位置这个级别教会我如何在shellcode中处理字符串参数以及参数内存布局的重要性。3.5 Level 4boom完美隐身boom级别要求攻击后程序能正常返回到test函数就像什么都没发生过一样。解决方案在shellcode中将cookie存入eax(返回值寄存器)精确恢复原来的ebp值(通过gdb调试获取)返回到test中getbuf调用后的地址这模拟了真实攻击中最危险的情况——程序看似正常运行实则已被暗中操控。3.6 Level 5kaboom终极挑战kaboom在Nitro模式下运行每次栈地址都随机变化。解决方案是使用大量nop(0x90)指令构成滑板将shellcode放在nop区域末尾返回地址指向nop区域中部动态计算并恢复ebp值这个技巧在实际漏洞利用中很常见称为nop sled可以应对地址随机化(ASLR)保护。4. 调试技巧与实战心得在完成这些实验过程中我总结了一些实用调试技巧GDB可视化调试使用layout asm和layout regs同时查看汇编和寄存器栈内存检查x/40xw $esp查看栈内容info frame查看当前栈帧断点设置在getbuf和Gets函数设断点观察缓冲区变化VSCode集成配置launch.json实现图形化调试遇到最多的问题是地址计算错误。有次因为没注意小端序导致跳转地址完全错误。后来养成了习惯所有内存数据都先用printf %08x格式化检查。另一个教训是关于字符串中的零字节。gets函数遇到0x0A(\n)会终止读取所以攻击字符串中要避免这个字节。可以用man ascii查看字符编码。缓冲区溢出攻击看似简单但要精准控制每一个字节需要极其细心。有时候差一个字节就会导致段错误需要反复调试检查。这也让我更加理解为什么安全编程中要始终使用带长度检查的函数如fgets替代gets。

更多文章