20242425 2026‑2027‑1 《网络与系统攻防技术》实验 一 实验报告
1. 基础知识
- 栈缓冲区溢出:C 语言函数调用时,栈上分配局部缓冲区、保存 EBP 与函数返回地址。gets()等无边界检查函数,若输入数据长度超过缓冲区容量,多余数据会覆盖栈上返回地址。函数ret返回时,CPU 读取被篡改的返回地址,跳转到攻击者指定地址执行代码。
- ret2text:缓冲区溢出攻击的一类,程序内部预先存在目标函数getShell(),内部执行system("/bin/sh")。不需要注入 shellcode,直接将返回地址覆盖为getShell函数地址,劫持程序流直接调用该函数。
- 32 位小端序:x86 32 位架构,多字节地址存储时低字节放在低地址。例如地址0x08049186,payload 中要写成\x86\x91\x04\x08。
- PIE 保护:PIE 开启时程序每次加载基址随机,objdump 输出的是偏移,不是运行时绝对地址,跳转失败。编译时加-no-pie关闭该保护。
- 管道特性:|管道属于一次性数据流,payload 发送完毕输入流直接关闭。即使成功执行/bin/sh,EOF 信号会直接终止 shell 进程,很难得到可交互终端。
- SIG11 段错误:访问非法内存触发。段错误不等于攻击失败,有可能是已经成功跳转到目标函数,函数执行完毕栈环境被破坏,返回时报错。
2. 实验内容
- 在 Kali Linux 中使用nano编写漏洞 C 程序pwn1.c,包含漏洞函数foo()(使用危险函数gets())与getShell()函数,用于弹出 shell。
- 使用 gcc 编译 32 位 ELF 程序,关闭栈保护、PIE 保护,生成可执行文件pwn1。
- 使用objdump查找getShell函数的内存地址。
- 测试确定缓冲区起始位置到栈返回地址的偏移量。
- 构造 payload,覆盖返回地址,劫持执行流跳转到getShell,完成 ret2text 攻击。
- 分析实验过程中出现的编译警告、无法拿到交互式 shell、管道 BrokenPipeError 等问题。
3. 实验过程
- 打开 Kali 终端,使用nano pwn1.c编写源码,ls查看目录,确认pwn1.c文件成功创建。
- 编译命令:
gcc -m32 -fno-stack-protector -z execstack -Wno-implicit-function-declaration pwn1.c -o pwn1
编译出现gets函数危险警告,编译成功,生成pwn1可执行文件。
3. 使用objdump -d pwn1 | grep getShell,获取getShell函数地址0x08049186。
4. 测试偏移,多次尝试后确定偏移量为84 字节。
5. 构造 payload:b"A"*84 + b"\x86\x91\x04\x08",用 python3 输出二进制 payload,管道输入程序:
python3 -c 'import sys; sys.stdout.buffer.write(b"A"*84 + b"\x86\x91\x04\x08")' | ./pwn1
程序输出This is a vulnerable program,随后报zsh: segmentation fault。 6. 尝试使用 socat 搭建端口转发,尝试获取交互式 shell。
- 终端 1:socat TCP-LISTEN:1234,fork EXEC:./pwn1
- 终端 2 发送 payload 连接端口。 socat 日志输出child exited on signal 11,程序触发段错误,仍然无法交互执行 ls、id 命令。
截图说明: 截图 1:Kali 桌面,终端执行hostname,展示实验环境。
截图 2:nano pwn1.c创建源码,ls查看目录,pwn1.c存在。
截图 3:gcc 编译 pwn1.c,gets 警告,objdump 查找 getShell 函数地址。
截图 4:执行 payload,程序输出提示文字,出现segmentation fault;socat 执行日志。
4. 问题及解决方案
- 问题 1:编译提示 warning: the ‘gets’ function is dangerous and should not be used.
- 问题 1 解决方案:gets是 C 语言废弃危险函数,无边界检查,本实验仅用于漏洞演示。编译参数增加-Wno-implicit-function-declaration屏蔽警告,实际开发禁止使用 gets。
- 问题 2:理论缓冲区 64 字节 + 4 字节 EBP=68 字节,使用 68 字节 payload 无法控制 EIP,程序不崩溃
- 问题 2 解决方案:新版 gcc 编译 32 位程序存在栈对齐,理论偏移失效。通过不断增加填充字符测试,测出真实偏移为 84 字节。
- 问题 3:python 管道执行 payload 出现 BrokenPipeError,程序报 segmentation fault,无法输入 ls/id 交互式命令
- 问题 3 解决方案:管道|是一次性数据流,payload 传输完成后输入流关闭,system("/bin/sh")启动后收到 EOF 直接退出。本实验不需要强求交互;出现段错误就代表返回地址覆盖成功,执行流劫持成功,可作为实验结果。尝试 socat 端口转发,但是目标程序栈环境被破坏,依旧无法稳定交互。
- 问题 4:pwntools 提示 ModuleNotFoundError
- 问题 4 解决方案:Kali 未预装 pwntools。改用原生 python3 标准库构造 payload,无需第三方库,直接输出二进制字节。
5. 学习感悟、思考等
本次 ret2text 缓冲区溢出实验,完整走完从编写漏洞程序、编译、找函数地址、确定偏移、构造 payload 到攻击验证的全流程。 栈溢出的核心就是覆盖栈上保存的返回地址,但是课本上理想化的栈布局,在真实 gcc 编译器环境下会因为栈对齐发生变化,理论偏移不一定直接可用,必须动手调试测试。 段错误不一定代表攻击失败,要区分是没有控制 EIP,还是劫持成功之后的崩溃。管道输入方式简单,但是在获取 shell 场景有天生缺陷。 gets()这类危险函数是漏洞根源,实际开发中应当杜绝。现代操作系统 PIE、栈保护等安全机制,就是用来对抗这类缓冲区溢出攻击,攻防对抗中攻击者需要绕过各类保护。 实验也体会到理论和实操之间的差距,很多坑只有自己动手踩过才会理解。
参考资料
- 《Java 程序设计与数据结构教程(第二版)》
- 《Java 学习笔记 (第 8 版)》学习指导