缓冲区溢出(Buffer Overflow)攻击实验详解

本实验核心思想:利用程序漏洞,输入精心构造的超长字符串,导致程序执行植入的恶意代码,最终目标通常是获取一个 shell(命令行)。

实验流程与思路

第 1 步:分析漏洞源代码 (buffer.c)

首先需要找到漏洞。攻击起点是程序的弱点。以 buffer.c 为例:

char buf[BUFSIZE]; // BUFSIZE is 32
...
gets(buf);

gets() 函数非常危险。它从标准输入读取一行字符串并存入 buf,但完全不检查输入长度。如果输入超过 32 字节,多余数据会溢出,覆盖 buf 之后栈上的其他数据。


第 2 步:理解栈内存布局

要理解溢出能做什么,必须知道数据在栈(Stack)上的排列。以函数 foo 为例,其栈帧(Stack Frame)如下(地址由高到低):

| ...                   |
+-----------------------+
| 返回地址 (Return Address) | <--- foo() 执行完毕后,CPU跳转到这里
+-----------------------+
| 旧的帧指针 (Saved EBP)  |
+-----------------------+
| char buf[32]          | <--- 缓冲区
+-----------------------+
| const int fav_number  |
+-----------------------+
| ...                   |
  • 缓冲区 buf:可控区域
  • 返回地址 (Return Address):最重要!决定 foo 函数结束后 CPU 执行位置

攻击核心:输入足够长字符串,填满 buf,继续覆盖,最终精确覆盖“返回地址”,修改为指定值。


第 3 步:制定攻击策略

目标:让程序不返回 main,而是执行自定义代码(Shellcode,通常用来打开 shell)。

攻击数据(输入给 gets 的字符串)包含三部分:

  1. Shellcode:可打开 shell 的机器码
  2. 填充数据 (Padding):填满从 buf 到返回地址的空隙
  3. 新的返回地址:指向 buf 里 Shellcode 的起始位置

内存布局如下:

| ...                   |
+-----------------------+
| 新的返回地址 (指向 buf) | <--- 被覆盖
+-----------------------+
| 填充数据 (Padding)    | <--- 被覆盖
+-----------------------+
| Shellcode             | <--- 恶意代码,存在 buf 里
+-----------------------+
| ...                   |

当 foo 结束时,CPU 读取被篡改的“返回地址”,跳转到 buf 开头,执行 Shellcode,夺取程序控制权。


第 4 步:具体实施流程

  1. 获取 Shellcode
    • 需要一段可用 Shellcode。通常实验会提供 payload.txt 或需自行生成,内容为十六进制机器码。
  2. 确定偏移量 (Offset)
    • 关键:准确知道从 buf 到返回地址的字节数。可用调试器(如 GDB)辅助计算。
    • 步骤:
      • gcc -g -fno-stack-protector -z execstack buffer.c -o buffer 编译带调试信息、关闭栈保护、允许栈执行的版本
      • gdb ./buffer 启动调试
      • 在 gets 后设置断点,运行程序
      • 输入特殊、不重复的长字符串(如 AAAABBBBCCCC…)
      • 程序停下时,检查栈内容 (x/32wx $esp) 和寄存器 (info registers)
      • 查看哪个字符覆盖了返回地址,计算偏移量。例如返回地址被 “HHHH” 覆盖,”H” 是第 40-43 字节,则偏移量为 40
  3. 确定 Shellcode 的地址
    • foo 运行时,buf 地址会变化(因 ASLR)。为提高攻击稳定性,使用 NOP Sled 技术。
    • NOP Sled:在 Shellcode 前加一串 NOP 指令(机器码 0x90),CPU会滑过所有 NOP,最终执行 Shellcode
    • 返回地址只需指向 NOP Sled 区域即可,提高成功率。地址可通过 GDB 查看栈指针 $esp 估算
  4. 构造并执行 Exploit
    • exploit.c 用于生成攻击字符串。需修改其输出为 [NOP Sled] + [Shellcode] + [Padding] + [New Return Address]
    • exploit.c 输出字节序列到标准输出
    • 通过管道将 exploit 输出作为 buffer 输入执行攻击:

      ./exploit | ./buffer
      
    • 若成功,buffer 执行 shellcode,终端出现新 shell 提示符,攻击成功

总结

完整实验思路:

  1. 审计代码:发现 gets() 漏洞
  2. 理解原理:明白栈溢出可覆盖返回地址
  3. 设计载荷:构造 NOP Sled + Shellcode + Padding + New Return Address 攻击字符串
  4. 调试计算:用 GDB 确定偏移量和 buf 地址
  5. 编写利用程序:修改 exploit.c 生成攻击载荷
  6. 执行攻击:通过管道输入载荷,获取 shell