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。