缓冲区溢出(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 的字符串)包含三部分:
- Shellcode:可打开 shell 的机器码
- 填充数据 (Padding):填满从
buf到返回地址的空隙 - 新的返回地址:指向
buf里 Shellcode 的起始位置
内存布局如下:
| ... |
+-----------------------+
| 新的返回地址 (指向 buf) | <--- 被覆盖
+-----------------------+
| 填充数据 (Padding) | <--- 被覆盖
+-----------------------+
| Shellcode | <--- 恶意代码,存在 buf 里
+-----------------------+
| ... |
当 foo 结束时,CPU 读取被篡改的“返回地址”,跳转到 buf 开头,执行 Shellcode,夺取程序控制权。
第 4 步:具体实施流程
- 获取 Shellcode
- 需要一段可用 Shellcode。通常实验会提供
payload.txt或需自行生成,内容为十六进制机器码。
- 需要一段可用 Shellcode。通常实验会提供
- 确定偏移量 (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
- 用
- 关键:准确知道从
- 确定 Shellcode 的地址
foo运行时,buf地址会变化(因 ASLR)。为提高攻击稳定性,使用 NOP Sled 技术。- NOP Sled:在 Shellcode 前加一串 NOP 指令(机器码 0x90),CPU会滑过所有 NOP,最终执行 Shellcode
- 返回地址只需指向 NOP Sled 区域即可,提高成功率。地址可通过 GDB 查看栈指针
$esp估算
- 构造并执行 Exploit
exploit.c用于生成攻击字符串。需修改其输出为[NOP Sled] + [Shellcode] + [Padding] + [New Return Address]exploit.c输出字节序列到标准输出-
通过管道将 exploit 输出作为 buffer 输入执行攻击:
./exploit | ./buffer - 若成功,buffer 执行 shellcode,终端出现新 shell 提示符,攻击成功
总结
完整实验思路:
- 审计代码:发现 gets() 漏洞
- 理解原理:明白栈溢出可覆盖返回地址
- 设计载荷:构造 NOP Sled + Shellcode + Padding + New Return Address 攻击字符串
- 调试计算:用 GDB 确定偏移量和 buf 地址
- 编写利用程序:修改 exploit.c 生成攻击载荷
- 执行攻击:通过管道输入载荷,获取 shell