一、实验内容
1.1 实践目标
- 实践对象:一个名为pwn1的linux可执行文件。
- 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
- 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
1.2 实践内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
1.3实验要求
- 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
- 掌握反汇编与十六进制编程器
- 能正确修改机器指令改变程序执行流程
- 能正确构造payload进行bof攻击
二、相关知识
2.1 Linux ELF可执行文件
- 实验对象
pwn1为Linux系统下的ELF格式可执行文件,包含代码段、数据段、程序栈等核心存储区域。 - 程序包含main、foo、getShell三个函数,正常执行流程为main调用foo实现用户输入字符串回显,getShell函数默认不执行。
- 实验核心目标:通过技术手段劫持程序执行流程,强制运行getShell函数或自定义代码片段。
2.2 核心汇编指令及机器码
- NOP:空指令,机器码
0x90,无实际操作,仅用于内存填充、指令对齐。 - CMP:比较指令,执行减法运算并修改状态标志位,为条件跳转提供判断依据。
- JE:相等跳转指令,标志位满足相等条件时跳转到目标地址执行代码。
- JNE:不相等跳转指令,标志位满足不相等条件时跳转到目标地址执行代码。
- JMP:无条件跳转指令,直接修改指令寄存器,强制跳转到指定地址执行代码。
2.3 反汇编与十六进制编程器
- 反汇编:使用objdump、gdb等工具,将二进制机器码转换为可读汇编代码,用于查看函数地址、指令偏移、程序逻辑。
- 十六进制编程器:如hexedit、010 Editor,可直接编辑可执行文件的二进制原始数据,修改机器指令以改变程序执行流程。
2.4 缓冲区溢出(BOF)漏洞原理
- 函数调用依赖程序栈,栈帧包含局部变量缓冲区、栈基址、函数返回地址等关键数据。
- foo函数存在缓冲区溢出漏洞:读取用户输入时未做长度校验,超长输入会超出缓冲区边界。
- 溢出数据可覆盖栈上的函数返回地址,函数执行结束后,CPU会跳转到被篡改的返回地址执行代码。
2.5 Payload构造
- Payload是针对漏洞构造的特制输入字符串,缓冲区溢出攻击的Payload结构为:填充字节 + 目标函数地址。
- 填充字节占满缓冲区,目标地址覆盖函数返回地址,实现程序执行流劫持。
2.6 Shellcode基础
- Shellcode是精简的二进制机器指令,用于调用系统接口获取交互Shell。
- 实验中可自行制作Shellcode,通过缓冲区溢出注入程序内存并执行,实现任意代码运行。
2.7 程序执行流劫持
- CPU通过EIP指令寄存器控制下一条执行指令的地址,默认按程序固有流程执行。
本次实验的三种方法均通过篡改EIP指向,让程序脱离正常流程,执行指定代码片段。
三、实验过程
3.1 手工修改可执行文件
(1)手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
-
先将pwn1拉到kali中,进入目标文件所在的文件夹,把pwn1改名。
-
输入命令
objdump -d pwnlyh | more实现反汇编。

-
程序结构:程序包含三个核心函数:main、foo、getShell。main函数仅主动调用foo实现用户输入回显;getShell是隐藏函数,正常流程不会被调用,其入口地址为0x0804847d,功能是调用system("/bin/sh")获取系统交互 Shell。
-
漏洞点:foo函数调用了无输入长度校验的gets()函数,存在典型栈溢出漏洞:用户超长输入可覆盖栈上的函数返回地址,实现程序执行流劫持。
-
攻击目标:利用栈溢出漏洞,构造 Payload 覆盖foo函数的返回地址为getShell的地址0x0804847d,使foo执行结束后跳转至getShell函数,最终成功获取系统 Shell。

-
打开pwnlyh文件,显示乱码后使用指令改成以16进制的形式打开。
vim pwnlyh
:%!xxd

修改 call 指令的偏移量,让函数返回时不再回到 main,而是直接跳去 getShell 函数。找到需要修改的地方,将d7 改为 c3。按ESC,输入:%!xxd -r 还原为原格式后再:wq保存退出。


恢复再次把pwn这个程序的汇编代码反汇编出来,发现确实已经修改。
objdump -d pwnlyh | more

执行该文件,之前修改的 main 函数里的 call 指令已经生效,程序成功跳转到了 getShell 函数。

3.2 foo函数的Bof漏洞
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
建议:第一个实验的文件在改个名字,用新的文件。
- 漏洞根源:foo函数调用了无长度校验的gets()函数,用户输入会直接写入栈缓冲区,导致栈溢出。
- 偏移计算逻辑:
lea -0x1c(%ebp),%eax:说明缓冲区距离栈基址ebp的偏移为0x1c(28 字节)。- 栈帧结构:缓冲区(28B) → 旧ebp(4B) → 返回地址(4B),因此覆盖返回地址需要填充28 + 4 = 32字节。
- 劫持流程:当foo执行完ret指令时,会读取被篡改的返回地址,跳转到getShell入口,成功获取系统 Shell。

3.3 注入自己制作的shellcode
四、遇到的问题以及解决方案
问题1:用%!xxd转成十六进制文本后,忘记执行%!xxd -r转回二进制,文件变成了十六进制文本,不再是可执行文件。

解决方案:file pwnlyh
发现ELF 文件头已经损坏。

重新来一遍
问题2:permission denied 表示你的 pwnlyh 文件没有可执行权限,系统不允许直接运行它。这是用 vim/xxd 修改二进制文件后很常见的问题,修改操作可能重置了文件的权限位。

解决方案:
#给文件添加可执行权限
chmod +x ./pwnlyh
./pwnlyh
问题解决

