20242406 2025-2026-1 《网络与系统攻防技术》实验一实验报告
20242406 2025-2026-1 《网络与系统攻防技术》实验一实验报告
一.基础知识
- NOP 指令
机器码:0x90,单字节指令。作用是空操作,CPU 执行这条指令不做任何实质性运算,仅消耗一个指令周期,常用于 shellcode 填充、指令对齐,漏洞利用中常用 NOP sled(NOP 滑板)。
- CMP 比较指令
CMP 用于两个操作数做减法比较,只修改标志寄存器,不改变原操作数的值。后续 JE/JNE 等条件跳转指令依据 CMP 设置的 ZF 零标志位判断是否跳转。 例:CMP eax,ebx,机器码会根据寄存器 / 内存操作数不同而变化,是条件分支的前置指令。
- JE 相等则跳转(Jump if Equal)
条件跳转指令,ZF=1(上一步比较结果相等)时发生跳转;ZF=0 则顺序执行。短跳转机器码为0x74 偏移,后面 1 字节是相对跳转偏移量。
- JNE 不相等则跳转(Jump if Not Equal)
ZF=0(比较结果不相等)时跳转;ZF=1 顺序执行。短跳转机器码为0x75 偏移。
- 反汇编:把程序二进制机器码翻译成可读汇编指令的过程。可使用 objdump、gdb、IDA Pro 等工具。 作用:查看可执行文件内部真实指令,获取指令对应的机器码字节,定位需要修改的指令地址,为修改二进制打好基础。
- 十六进制编程器(十六进制编辑器):直接读写修改可执行文件原始二进制字节。可以直接修改文件里机器码,替换指令。 实验用途:不修改源代码,直接修改程序二进制,替换 JE/JNE/JMP/NOP 的机器码字节,篡改程序的分支逻辑,改变程序运行流程。
二.实验内容
1.手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
2.利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
3.注入一个自己制作的shellcode并运行这段shellcode。
三.实验过程
(1)任务一
1.下载虚拟机,使用winSCP工具将pwn二进制文件传入虚拟机,按要求修改主机名和pwn文件名:

执行命令objdump -d pwn20242406 | grep -E "main|foo|getShell",反汇编筛选关键函数地址:

2.执行命令objdump -d pwn20242406 | grep -A 30 "<foo>:",从反汇编可以看到,main 函数里调用 foo 的指令:

- e8 是 call 指令的操作码,后面 4 字节是相对偏移量
- 相对偏移 = 目标函数地址 - call 指令下一条指令的地址
- 只需要把后面的偏移量改成指向 getShell,就能让 main 直接调用 getShell
3.计算新的机器码
- call 指令内存地址:0x080484b5
- call 指令长度 5 字节,下一条指令地址:0x080484b5 + 5 = 0x080484ba
- 目标 getShell 地址:0x0804847d
计算相对偏移
新偏移 = 0x0804847d - 0x080484ba = -0x3D 32 位补码小端序表示为:c3 ff ff ff
计算文件偏移
根据之前的.text 段参数:
- .text 虚拟起始:0x08048380
- .text 文件起始偏移:0x000380
目标指令文件偏移 = 0x080484b5 - 0x08048380 + 0x380 = 0x4b5
安装十六进制编辑器:

修改文件,将D7改为C3:

执行命令objdump -d pwn20242406 | grep -A 10 "<main>:"查看call指令:

运行程序验证效果,直接弹出 # 或 sh-# 提示符,代表成功跳转到getShell,任务 1 完成。

(2)任务二
1.恢复原始未修改数据:

2.执行objdump -d pwn20242406 | grep -E "<getShell>|<foo>|<main>" -A 15查看关键函数汇编:

确认信息:
- 目标函数:getShell 入口地址为 0x0804847d,内部调用 system 函数,执行后可以拿到 shell
- 漏洞点:foo 函数使用无边界检查的 gets 读取输入;缓冲区位于 ebp-0x1c,大小为 28 字节
- 栈偏移计算:缓冲区起始 → 栈上保存的旧 ebp(4 字节) → 函数返回地址 覆盖返回地址的总偏移量 = 28 字节缓冲区 + 4 字节保存 ebp = 32 字节 即输入的第 33~36 个字节,会精准覆盖栈上的函数返回地址
3.安装gdb调试工具:

偏移量验证结果,eip = 0x34333231,对应 ASCII 字符 4 3 2 1,正好是输入的 1234 的小端序,证明 32 个 A 之后正好就是栈上的返回地址,偏移量 32 字节完全正确:

4. 执行完 payload 之后,输入ls成功列出目录文件,已经拿到 shell,缓冲区溢出攻击完成。

(3)任务三
1. 安装 execstack 工具,开启栈可执行,输出大写X,栈可执行设置成功。

2.关闭内核 ASLR 地址随机化。ASLR 会让栈地址每次运行都变化,无法写死 shellcode 地址,临时关闭

输出0,关闭成功
3.调试获取shellcode所在栈地址,终端A生成占位payload文件

终端B设置断点

当程序运行到断点时,我们发现可以发现esp寄存器的内容为:

修改跳转地址,执行溢出拿shell:

任务三成功。
四.问题及解决方案
问题1:任务一修改文件时,多次搜索(leave,ret)会跳转到文件的不同位置,无法精确定位到foo函数末尾的正确位置。
问题1解决方案:通过询问AI工具得知,(leave; ret)在整个二进制文件里不止一处,很多函数结尾都会用 leave+ret,hexedit 的 Ctrl+S 搜索是从当前光标位置向后找,光标位置不同,搜到的匹配结果就不一样,所以多次跳到不同地址。AI提供的解决方法时直接计算文件偏移,执行命令查看代码段信息:

计算文件偏移后精确定位需要修改的位置。
问题2:payload 送入时机错误,提前回车导致程序直接跑完。执行(cat input_shellcode; cat) | ./pwn20242406之后,在还没有完成 gdb 附加进程、设置断点的情况下就按下回车,payload 一次性全部输入给gets()函数。函数执行完毕,函数 ret、程序直接运行结束退出。后续再执行ps -ef已经找不到该进程 PID,无法使用gdb attach附加调试,不能查看栈上 payload 的内存布局,拿不到 shellcode 的跳转地址,后续攻击无法开展。
问题2解决方案:严格遵守操作时序:先执行管道命令,让程序阻塞在gets()等待输入,此时不要按回车,使用ps -ef | grep pwn20242406获取阻塞进程的 PID,使用gdb attach PID附加目标进程,在 foo 函数的ret返回指令处设置断点,输入c让 gdb 继续等待触发,全部调试准备工作完成之后,再回到运行管道的终端按下回车,送入 payload 命中断点。若已经误操作提前回车,进程已经退出,需要重新执行管道命令,回到阻塞状态,完整重复上述调试流程。
五.学习感悟与思考
本次缓冲区溢出实验完成了修改二进制劫持函数、覆盖返回地址、注入 shellcode 获取 shell 等任务,让我把课本中栈、函数调用的理论知识落到实操当中。
实验过程遇到不少问题,文件权限、地址偏移混淆、GDB 调试与实际运行地址存在差异,多次出现攻击失败、程序崩溃的情况,我也体会到纸上理论和真实环境之间存在差距,需要借助调试工具反复排查。
通过实验我理解了缓冲区溢出的原理:程序未对输入做边界检查,溢出数据能够覆盖返回地址,篡改程序执行流程。也认识到 ASLR 等保护机制对抵御溢出攻击的作用,不安全的编码会带来严重安全隐患。同时熟练了 GDB 调试、构造 payload 等操作。
攻防技术是双刃剑,作为网安专业学生,技术应当用于合法的安全防护。本次实验也发现我对栈内存底层掌握尚有不足,今后会加强底层知识学习,重视安全编码,树立正确的网络安全观念。
参考资料

浙公网安备 33010602011771号