1. 缓冲区溢出基础
任务目标
编写一个包含栈缓冲区溢出漏洞的C程序,通过传入超长字符串覆盖栈上相邻变量及返回地址,观察程序行为异常或崩溃;随后使用GDB调试器定位溢出发生的位置与原因。
漏洞程序实现
// vuln.c — 存在栈缓冲区溢出的程序
#include <stdio.h>
#include <string.h>
void vulnerable(char *input) {
char buffer[16];
int flag = 0;
strcpy(buffer, input); // 无边界检查,导致溢出
if (flag != 0) {
printf(" [!] flag 被覆盖为 0x%x\n", flag);
}
printf(" [*] buffer 内容: %s\n", buffer);
}
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法: %s <字符串>\n", argv[0]);
return 1;
}
vulnerable(argv[1]);
printf(" [*] 函数正常返回\n");
return 0;
}
编译时关闭栈保护并禁用ASLR以便稳定复现:
gcc -o vuln vuln.c -fno-stack-protector -z execstack -no-pie
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # 关闭ASLR(重启后恢复)
触发溢出与观察
输入16字节以内的字符串时程序正常。输入超过16字节的字符串时,strcpy会继续写入buffer之后的内存,首先覆盖flag变量(位于buffer高地址方向),随后覆盖保存的ebp和返回地址:
./vuln "AAAAAAAAAAAAAAAA" # 16字节,正常
./vuln "AAAAAAAAAAAAAAAAAAAA" # 20字节,flag被覆盖为0x41414141
./vuln "$(python3 -c 'print("A"*28)')" # 28字节,覆盖返回地址,触发段错误
预期输出:前两种情况打印被覆盖的flag值;第三种情况程序以Segmentation fault崩溃,且不会打印“函数正常返回”。
使用GDB定位溢出点
gdb -q ./vuln
(gdb) run $(python3 -c 'print("A"*28)')
(gdb) info registers eip # 查看EIP是否被0x41414141覆盖
(gdb) bt # 查看调用栈回溯
(gdb) x/20wx $esp-20 # 查看栈上数据,确认溢出写入范围
当EIP显示为0x41414141(即“AAAA”的ASCII码)时,说明返回地址已被精确覆盖。通过bt命令可以看到调用栈在vulnerable函数处中断,x/20wx可直观看到栈上从buffer起始到返回地址的连续内存布局。
验证判定标准
- 输入20字节字符串时,程序输出
flag 被覆盖为 0x41414141,证明局部变量被越界写入; - 输入28字节字符串时,程序以段错误终止,且不输出“函数正常返回”,证明控制流被破坏;
- GDB中EIP寄存器值为
0x41414141,确认溢出点位于strcpy调用处,溢出数据精确覆盖了返回地址。
常见问题
- 未出现段错误:检查是否使用
-fno-stack-protector编译,以及ASLR是否已关闭; - flag值不是0x41414141:不同编译器对局部变量布局可能调整,可先用GDB的
p &flag和p &buffer确认两者地址差,再调整输入长度; - GDB中EIP未被覆盖:输入长度需精确计算,
buffer(16) + flag(4) + 保存的ebp(4) = 24字节后才是返回地址,因此28字节输入中最后4字节写入EIP。