20242415 2026-2027-1 《网络与系统攻防技术》实验一 实验报告
《网络与系统攻防技术》实验一 实验报告
学号:20242415
学期:2026-2027-1
1. 基础知识
本次实验的目标对象 pwn1 是一个 Linux 可执行文件,正常流程是 main 调用 foo,foo 回显用户输入;文件中另有一段正常情况下不会执行的 getShell 代码,我们的任务就是想方设法运行它。
实验用到的基础知识:
- 函数调用与栈帧:
call指令会把下一条指令的地址(返回地址)压入栈,ret指令再从栈顶弹出返回地址送到 EIP。因此,只要能篡改栈中被保存的返回地址,ret时程序就会跳转到攻击者指定的位置。foo函数入口先是push %ebp; mov %esp,%ebp; sub $0x38,%esp,局部缓冲区位于-0x1c(%ebp),即从栈顶往下 28 字节处,返回地址保存在%ebp+4。 - 缓冲区溢出(BOF):
gets函数不检查输入长度,会把超长输入一直写入栈上的缓冲区。当写入超过 28 字节缓冲区并继续覆盖掉保存的ebp和返回地址后,foo返回执行ret时 EIP 就被劫持为攻击者输入的数据,从而改变程序执行流程。 - 相关汇编指令及机器码(实验要求掌握):
NOP(空操作):90,常用来填充 NOP 滑梯(sled),让跳转落到滑梯内任意位置都能滑到真正的 shellcode。CMP(比较):比较两个操作数并设置标志位,机器码如39(寄存器/寄存器)、80/81(与立即数比较)。JE(相等则跳转):74,条件跳转;JNE(不等则跳转):75。JMP(无条件跳转):短跳转EB(8 位偏移),近跳转E9(32 位相对偏移)。本实验用到的call近调用是E8,后跟 32 位相对偏移,偏移 = 目标地址 − 下一条指令地址。
- 反汇编与十六进制编程器:用
objdump -d反汇编查看机器码与符号地址;用十六进制编辑器(如 ghex)直接修改可执行文件中的机器指令字节;用gdb动态调试观察寄存器与栈内容。 - Shellcode 注入:构造一段能执行
execve("/bin/sh")的机器码,利用栈溢出把返回地址改到栈中 shellcode 的起始位置;配合NOP滑梯提高命中率,并在程序前通过env -i清空环境变量、ulimit -c unlimited保留 core 文件来获取稳定的栈地址。
2. 实验内容
围绕“运行 getShell 代码片段”,完成三个实践:
- 手工修改可执行文件:用反汇编定位
getShell与main中call foo的位置,通过十六进制编辑器改写机器码,把调用目标改为getShell,直接改变程序执行流程。 - BOF 攻击:利用
foo中gets的缓冲区溢出漏洞,构造一段攻击输入字符串覆盖返回地址,触发getShell。 - 注入 Shellcode:制作一段自己的 shellcode(执行
/bin/sh),通过溢出将其注入栈并跳转执行。
3. 实验过程
任务一:手工修改可执行文件,改变执行流程跳转到 getShell
-
反汇编定位函数地址。用
objdump -d pwn20242415 | grep -E "(main|foo|getShell)"查看三个关键符号的地址:getShell在0804847d,foo在0804847f,main在080484af。![任务1-1 反汇编定位函数地址]
![alt text]()
-
反汇编
main函数,找到调用foo的指令。objdump -d pwn20242415 | grep -A 25 "<main>:"显示main在地址80484b5处执行e8 d7 ff ff ff call 8048491 <foo>。可见调用foo用的是call指令,机器码e8后跟 32 位相对偏移d7 ff ff ff。![任务1-2 反汇编 main 函数]
![alt text]()
-
用十六进制编程器修改机器码。目标是把这条
call foo改为call getShell。相对偏移的计算方法:目标地址0x0804847d减去下一条指令地址0x080484ba,得到-0x3d,即c3 ff ff ff。因此把e8 d7 ff ff ff改为e8 c3 ff ff ff。用十六进制编辑器打开二进制文件,定位到偏移000004b0处找到e8 d7 ff ff ff并修改。![任务1-3 十六进制编辑器修改机器码]
![alt text]()
-
保存为带学号的新文件并运行验证。将修改后的文件另存为
pwn20242415_task1,执行./pwn20242415_task1,程序不再回显而是直接进入 shell,执行whoami返回kali,说明执行流程已被成功改到getShell。![任务1-4 运行修改后程序成功获取 shell]
![alt text]()
-
反汇编
foo分析漏洞成因。objdump -d pwn20242415 | grep -A 20 "<foo>:"看到foo用sub $0x38,%esp分配栈空间,缓冲区地址为-0x1c(%ebp)(28 字节),随后调用gets@plt读入、puts@plt回显,gets不检查长度即为溢出点。![任务1-5 反汇编 foo 函数]
![alt text]()
-
用 gdb 动态验证溢出可行性。
gdb ./pwn20242415后run,输入一串超长字符,程序发生Segmentation fault,EIP 被覆盖为0x35353535(即 ASCII “55555555”),证明返回地址确实可以被输入覆盖。![任务1-6 gdb 验证返回地址被覆盖]
![alt text]()
任务二:利用 BOF 漏洞构造 payload,触发 getShell
-
构造 payload。缓冲区 28 字节 + 保存的
ebp4 字节 = 32 字节填充,之后放置getShell的地址0x0804847d(小端序为\x7d\x84\x04\x08)。用 perl 生成:perl -e 'print "111111122222222333333344444444\x7d\x84\x04\x08"' > input20242415_bof文件名包含学号
20242415。![任务2-1 构造攻击输入文件]
![alt text]()
-
注入并执行。
gets之后foo还会调用puts继续从标准输入读数据,因此用(cat input20242415_bof; cat) | ./pwn20242415把输入文件喂入并保持管道打开,程序返回地址被覆盖为getShell,成功进入 shell。![任务2-2 注入 payload 后获取 shell]
![alt text]()
任务三:注入自制 Shellcode 并执行
-
构造 shellcode 输入文件。准备一段执行
execve("/bin/sh")的 shellcode,前面用若干\x90(NOP)作为滑梯。先ulimit -c unlimited允许生成 core 文件,然后生成输入并运行程序使其崩溃,以便后续用 core 文件定位栈地址。![任务3-1 生成 shellcode 输入并触发 core]
![alt text]()
-
用 gdb 分析 core 定位栈地址。
gdb ./pwn20242415 $(ls -t core.* | head -1) -batch -ex "info registers esp" -ex "x/40bx $esp-64",从 core 中看到程序崩溃在0x41414141,并观察到esp附近的栈上有一大片0x90,即 NOP 滑梯所在位置,据此确定 shellcode 的跳转目标地址。![任务3-2 分析 core 定位栈中 NOP 滑梯]
![alt text]()
-
把返回地址改为栈中 NOP 滑梯地址并重新生成输入。将 payload 中的返回地址改为上一步得到的栈地址(小端
\x20\xde\xff\xff),重新用 perl 生成,并用xxd查看十六进制确认 NOP 滑梯与 shellcode 布局正确。![任务3-3 重新生成并核对输入]
![alt text]()
-
执行验证。用
(cat input; cat) | env -i ./pwn20242415注入运行(env -i清空环境变量,使栈地址稳定、减少对溢出的干扰),执行whoami返回kali,说明注入的 shellcode 成功运行,获得了 shell。![任务3-4 注入成功执行 whoami]
![alt text]()
4. 问题及解决方案
- 问题1:任务一中直接把
call foo的机器码改成call getShell时,第一次改了地址但程序仍执行原来的foo,跳转没有生效。- 问题1解决方案:认识到
call的e8后跟的是相对偏移而非绝对地址。重新按公式“偏移 = 目标地址 − 下一条指令地址”计算,0x0804847d − 0x080484ba = -0x3d,把d7 ff ff ff改为c3 ff ff ff后保存,程序即正确跳转到getShell。
- 问题1解决方案:认识到
- 问题2:任务二中执行
./pwn20242415 < input20242415_bof后 shell 一闪而过,无法交互。- 问题2解决方案:因为
gets之后foo还会调用puts继续读输入,输入重定向的管道会在读完文件后关闭。改用(cat input20242415_bof; cat) | ./pwn20242415,用后面的cat一直占用标准输入保持管道打开,从而维持 shell 交互。
- 问题2解决方案:因为
- 问题3:任务三直接按之前猜的栈地址跳转,程序反复崩溃,shellcode 没有被执行。
- 问题3解决方案:栈地址受环境变量等因素影响并不固定。先在程序前执行
ulimit -c unlimited生成 core 文件,再用 gdb 从 core 中读出程序崩溃时的真实esp和栈中 NOP 滑梯的位置,把返回地址精确指向 NOP 滑梯,并在运行时用env -i清空环境变量保证栈布局稳定,最终成功执行 shellcode。
- 问题3解决方案:栈地址受环境变量等因素影响并不固定。先在程序前执行
5. 学习感悟、思考等
这次实验让我第一次把“会读代码”和“能攻破代码”打通了。任务一让我真正理解了 call/jmp 的相对寻址:改动机器码时不是抄个地址就完事,而是要看指令之间的相对偏移,这比背机器码表深刻得多。任务二的 BOF 攻击把课本上的栈帧模型变成了亲眼可见的事实——gets 不检查长度,输入一旦越过 28 字节缓冲区,就把返回地址盖掉了,ret 一出,程序就“身不由己”。任务三的 shellcode 注入则综合考验了栈布局、NOP 滑梯和地址随机化的应对,稍有偏差就段错误,反复调试才成功。
整个过程也提醒我:漏洞的根子在于编写代码时对输入长度的疏忽。写代码时随手用 fgets 替换 gets、开启栈保护、NX 和 ASLR,就能让大部分此类攻击失效。攻防是一体两面,站在攻击者角度做一遍,反过来更能体会防御设计每一条规则背后的意义。












浙公网安备 33010602011771号