实验一:逆向破解与BOF实验报告
实验基本信息
- 实验名称: 逆向破解与 Buffer Overflow(BOF)实验
- 学号: 20242329
- 姓名: wangxiaoxuan
- 课程名称: 网络与系统攻防技术
实验概述
本实验围绕 Linux 环境下 32 位 ELF 可执行文件的逆向分析与漏洞利用展开,实验对象为程序 pwn1。
实验主要包含三个部分:
- 通过分析程序反汇编结果,定位关键机器指令,并修改二进制文件中的指令数据,改变程序原有执行流程;
- 利用程序中存在的缓冲区溢出漏洞,通过构造输入数据覆盖函数返回地址,实现程序控制流改变;
- 在缓冲区溢出的基础上进一步构造 Shellcode Payload,使程序执行注入的机器代码。
通过本实验,可以深入理解 ELF 文件结构、汇编指令执行机制、函数调用栈结构以及缓冲区溢出漏洞利用的基本原理。
一、实验目的
本实验旨在通过对 Linux 环境下 32 位 ELF 可执行文件进行逆向分析和漏洞利用实践,理解程序执行流程、机器指令修改以及内存安全漏洞利用的基本原理。
通过本实验,需要达到以下目标:
1. 掌握ELF文件分析与汇编代码阅读方法
通过对实验程序 pwn1 进行反汇编分析,了解 ELF 可执行文件的基本结构,掌握利用反汇编工具查看程序执行流程的方法。
能够通过汇编代码分析:
- 程序入口函数;
- 函数调用关系;
- 关键指令位置;
- 程序控制流程。
2. 理解机器指令修改对程序执行流程的影响
通过分析程序中的 CALL 指令,理解 x86 架构下函数调用的实现方式。
掌握:
- CALL 指令机器码组成;
- 相对地址计算方法;
- 指令偏移修改方法。
通过修改二进制文件中的指定机器码,使程序跳转到目标函数执行,从而理解底层机器指令对程序行为的影响。
3. 掌握缓冲区溢出漏洞利用过程
通过分析实验程序中的输入处理函数,理解由于缺少输入长度检查而产生的缓冲区溢出漏洞。
掌握:
- 函数栈结构分析方法;
- 返回地址覆盖原理;
- EIP 控制流程;
- Payload 构造方法。
通过构造特殊输入数据,实现对程序执行流程的控制。
4. 理解Shellcode注入基本原理
通过构造包含 Shellcode 的攻击数据,进一步理解代码注入攻击的基本过程。
掌握:
- Shellcode 的作用;
- NOP 滑道原理;
- 返回地址跳转方式;
- Payload 内存布局设计。
通过实验观察 Shellcode 从注入到执行的完整过程。
5. 认识漏洞利用与安全防护之间的关系
通过实验过程中对 ASLR、NX、Stack Canary 等安全机制的分析,理解现代操作系统针对内存安全漏洞采取的防护措施。
认识到漏洞利用不仅依赖攻击代码本身,还受到程序运行环境和系统安全机制的影响。
二、实验环境
2.1 实验平台
本实验在 Linux 环境下完成,主要针对 32 位 ELF 可执行文件进行逆向分析和漏洞利用实验。
实验环境如下:
| 项目 | 配置 |
|---|---|
| 操作系统 | Kali Linux |
| 系统架构 | x86 32位环境 |
| 实验对象 | pwn1 ELF可执行文件 |
| 调试环境 | Linux终端 |
| 分析工具 | objdump、gdb等 |
2.2 实验工具
实验过程中使用了多种 Linux 下的程序分析和调试工具,各工具作用如下:
| 工具 | 作用 |
|---|---|
| objdump | 对 ELF 文件进行反汇编,将机器码转换为汇编代码,用于分析程序结构和函数调用关系 |
| gdb | 对程序进行动态调试,查看寄存器状态、内存变化以及程序执行流程 |
| xxd | 查看文件的十六进制内容,辅助分析和定位二进制数据 |
| execstack | 修改 ELF 文件堆栈执行权限,用于 Shellcode 注入实验 |
| perl | 生成包含特殊字节数据的输入文件,用于构造实验 Payload |
2.3 实验对象介绍
本实验主要围绕程序 pwn1 展开分析。
该程序为一个 32 位 Linux ELF 可执行文件,正常执行流程中:
main函数负责程序入口逻辑;foo函数负责接收用户输入;getShell函数可以调用系统 Shell,但正常情况下不会被主动执行。
通过反汇编分析可以发现,程序中存在影响执行流程的关键位置。
本实验主要围绕以下三个目标展开:
- 修改程序中的机器指令,使程序执行流程跳转到
getShell; - 利用
foo函数中的缓冲区溢出漏洞,覆盖返回地址控制程序执行; - 注入自定义 Shellcode,使程序执行指定机器代码。
通过上述三个任务,逐步分析程序从正常执行到控制流被改变的过程。
三、基础知识
3.1 ELF文件结构与程序分析基础
ELF(Executable and Linkable Format)是一种 Linux 系统中常用的可执行文件格式,被广泛应用于可执行程序、目标文件以及共享库。
一个 ELF 文件包含程序运行所需的代码和数据,不同区域承担不同功能:
| 区域 | 主要作用 |
|---|---|
| .text段 | 保存程序实际执行的机器指令 |
| .data段 | 保存已经初始化的全局变量和静态变量 |
| .bss段 | 保存未初始化的全局变量和静态变量 |
| .rodata段 | 保存只读数据,例如字符串常量 |
| 符号信息 | 保存函数、变量等相关信息 |
程序运行时,操作系统会根据 ELF 文件中的结构信息,将相关内容加载到进程虚拟地址空间中。
但是,CPU 无法直接理解 C 语言等高级语言代码,只能够执行由机器码表示的指令。因此,在进行程序分析时,需要将 ELF 文件中的机器指令转换为可读的汇编代码。
这种将机器码转换为汇编代码的过程称为反汇编。
本实验中,通过对 pwn1 程序进行反汇编分析,可以观察:
- 程序入口位置;
- 函数调用关系;
- 关键控制流指令;
- 目标函数地址。
反汇编分析是后续修改机器指令和漏洞利用的基础。
3.2 x86体系结构与关键寄存器
本实验基于 32 位 x86 架构,因此需要了解程序运行过程中几个重要寄存器的作用。
3.2.1 EIP寄存器
EIP(Instruction Pointer)表示当前 CPU 下一条需要执行的指令地址。
程序运行过程中,CPU 会不断读取 EIP 指向位置的指令并执行。
当程序发生:
- 函数调用;
- 跳转;
- 函数返回;
等操作时,本质上都是修改 EIP 的值。
因此,在漏洞利用过程中,控制 EIP 就意味着控制程序执行流程。
3.2.2 ESP寄存器
ESP(Stack Pointer)表示当前栈顶地址。
程序执行函数调用、保存临时数据以及存储局部变量时,都需要使用栈空间,因此 ESP 会随着程序运行不断变化。
在调试过程中,通过观察 ESP 的变化,可以分析当前栈空间状态。
3.2.3 EBP寄存器
EBP(Base Pointer)通常用于表示当前函数栈帧的基地址。
在传统函数调用过程中,EBP主要用于:
- 定位局部变量;
- 访问函数参数;
- 保存函数调用环境。
典型函数建立栈帧过程:
push ebp
mov ebp, esp
函数返回时:
leave
ret
其中:
leave恢复之前的栈环境;ret从栈中取出返回地址,并恢复程序执行。
由于返回地址位于函数栈结构中,因此 EBP 附近的数据也是缓冲区溢出分析的重要内容。
3.3 汇编指令与程序控制流程
汇编语言是机器指令的可读表示形式,每一条汇编指令最终都会对应 CPU 执行的机器码。
本实验涉及的主要指令如下:
| 指令 | 作用 |
|---|---|
| NOP | 空操作,不改变程序状态 |
| JMP | 无条件跳转 |
| JE | 条件满足时跳转 |
| JNE | 条件不满足时跳转 |
| CMP | 比较两个操作数 |
| CALL | 调用函数 |
| RET | 函数返回 |
其中,CALL 和 RET 是影响程序控制流程的重要指令。
程序正常运行时,控制流由编译器生成的指令顺序决定。
而在逆向分析和漏洞利用过程中,通过修改这些控制流相关数据,可以改变程序原本的执行路径。
3.4 CALL指令与函数调用机制
CALL 指令用于实现函数调用。
当程序执行 CALL 指令时,CPU 会完成两个操作:
- 保存当前执行位置,作为函数返回地址;
- 跳转到目标函数地址执行。
例如:
call 8048491
表示调用地址为 0x8048491 的函数。
在 x86 架构中,CALL 指令通常采用相对地址方式进行跳转。
其计算方式为:
目标地址 = 下一条指令地址 + 偏移量
其中:
- 下一条指令地址表示 CALL 指令执行完成后的地址;
- 偏移量保存在 CALL 指令机器码中。
因此,如果修改 CALL 指令中的偏移字段,就可以改变程序实际跳转的位置。
在本实验第一个任务中,正是利用这一特点,通过修改 CALL 指令对应机器码,使程序从原来的函数调用流程跳转到指定目标函数。
3.5 函数调用栈与缓冲区溢出原理
在程序运行过程中,函数调用依赖栈(Stack)保存函数执行所需的数据。
在 32 位 x86 程序中,一个典型函数栈结构如下:
高地址
函数返回地址
保存的EBP
局部变量空间
函数参数
低地址
其中:
- 函数返回地址用于记录函数执行结束后程序继续执行的位置;
- 保存的 EBP 用于恢复上一级函数栈环境;
- 局部变量区域用于存储函数内部使用的数据。
当一个函数被调用时,程序会创建新的栈帧;当函数执行结束时,CPU 通过 ret 指令读取栈中的返回地址,并跳转回原来的执行位置。
缓冲区溢出(Buffer Overflow,BOF)漏洞产生的主要原因是:
程序向固定大小的内存区域写入超过其容量的数据,而没有进行有效的边界检查。
例如:
char buffer[32];
gets(buffer);
其中:
buffer只能存储有限长度的数据;gets()函数不会检查输入数据长度。
当输入长度超过缓冲区大小时,多余数据会继续覆盖后续内存区域。
在函数栈中,覆盖过程通常表现为:
缓冲区
↓
保存的EBP
↓
返回地址
由于返回地址直接决定函数执行结束后的跳转位置,因此攻击者可以通过修改返回地址改变程序执行流程。
本实验中的 BOF 任务正是利用这一特点,通过构造特殊输入覆盖返回地址,使程序跳转到目标函数。
3.6 返回地址覆盖与小端序
在缓冲区溢出攻击中,确定返回地址的位置后,需要将目标地址写入返回地址区域。
但是,在实际构造 Payload 时,还需要考虑处理器的数据存储方式。
x86 架构采用小端序(Little Endian)存储方式。
小端序的特点是:
低位字节存放在低地址位置,高位字节存放在高地址位置。
例如:
目标地址:
0x0804847d
正常书写顺序:
08 04 84 7d
实际内存存储顺序:
7d 84 04 08
因此,在构造攻击数据时,需要按照小端序格式写入地址。
如果地址排列顺序错误,程序从栈中读取返回地址时会得到错误的跳转位置,从而导致攻击失败。
3.7 Shellcode注入与NOP滑道
Shellcode 是一段可以直接由处理器执行的机器代码。
在漏洞利用过程中,攻击者可以将 Shellcode 写入程序可控的内存区域,然后通过控制程序执行流程,使 CPU 跳转到 Shellcode 所在位置执行。
Shellcode 注入通常包含以下步骤:
- 将 Shellcode 放入目标进程内存;
- 构造攻击输入覆盖关键控制数据;
- 修改返回地址或跳转地址;
- 执行注入的机器代码。
与直接跳转到程序已有函数相比,Shellcode 注入能够执行攻击者自定义的代码,因此具有更高的灵活性。
在实际利用过程中,由于程序运行环境可能导致地址存在一定偏差,攻击者通常会在 Shellcode 前加入大量 NOP 指令。
例如:
NOP + NOP + NOP + Shellcode
NOP 指令不会改变寄存器状态,也不会产生实际计算效果,只会让 CPU 继续执行下一条指令。
连续的 NOP 指令区域称为 NOP 滑道(NOP Sled)。
当返回地址落在 NOP 区域时,CPU 会沿着 NOP 区域继续执行,最终进入 Shellcode,从而提高攻击成功概率。
3.8 Linux漏洞防护机制
现代操作系统为了降低内存漏洞带来的风险,通常会采用多种安全保护机制。
本实验涉及的主要保护机制包括:
3.8.1 ASLR
ASLR(Address Space Layout Randomization)即地址空间布局随机化。
该机制会随机改变程序运行时的内存地址,包括:
- 栈地址;
- 堆地址;
- 共享库地址。
由于目标地址不再固定,攻击者难以提前确定跳转位置,从而增加漏洞利用难度。
3.8.2 NX
NX(No Execute)机制用于限制内存区域的执行权限。
例如:
默认情况下,栈空间通常只能保存数据,不能直接执行代码。
因此,即使攻击者将 Shellcode 写入栈区域,如果该区域没有执行权限,程序也无法运行其中的代码。
3.8.3 Stack Canary
Stack Canary 是一种用于检测栈溢出的保护机制。
编译器会在返回地址附近加入特殊检测值。
当缓冲区溢出导致栈数据被修改时,Canary 值会发生变化,程序可以检测异常并终止运行。
本实验为了便于观察漏洞产生过程和分析程序执行行为,在实验环境中关闭部分安全机制。
但在真实系统环境中,ASLR、NX 和 Stack Canary 等机制能够有效提高漏洞利用难度,降低攻击成功概率。
四、实验内容与总体流程
4.1 实验内容概述
本实验围绕 Linux 环境下 32 位 ELF 程序的逆向分析与漏洞利用展开,主要研究程序控制流修改、缓冲区溢出攻击以及 Shellcode 注入三个方面。
实验对象为可执行文件 pwn1,通过静态分析与动态调试相结合的方法,逐步分析程序内部结构,并验证不同方式改变程序执行流程的过程。
本实验主要包括以下三个任务:
| 实验任务 | 实验内容 | 实验目标 |
|---|---|---|
| 任务一:修改机器指令 | 分析并修改程序中的关键机器码 | 改变程序原有执行流程 |
| 任务二:缓冲区溢出攻击 | 利用栈溢出漏洞覆盖返回地址 | 实现程序控制流劫持 |
| 任务三:Shellcode注入 | 构造包含机器代码的Payload | 实现注入代码执行 |
4.2 实验总体流程
本实验按照由浅入深的方式进行,整体流程如下:
(1)程序静态分析
首先对目标程序 pwn1 进行分析,通过反汇编工具查看程序内部结构。
主要分析内容包括:
- ELF 文件基本信息;
- 函数调用关系;
- 关键汇编指令;
- 目标函数地址。
通过静态分析,了解程序正常运行时的控制流程,为后续修改和漏洞利用提供基础。
(2)程序控制流修改
在掌握程序结构后,进一步分析影响程序执行流程的关键指令。
通过计算 CALL 指令目标地址偏移,并修改对应机器码,使程序执行路径发生改变。
该过程主要验证:
- 汇编指令与机器码之间的对应关系;
- 修改二进制文件对程序行为的影响。
(3)缓冲区溢出利用
通过分析程序输入处理过程,发现程序中存在由于输入长度限制不足导致的缓冲区溢出问题。
实验过程中:
- 分析函数栈结构;
- 确定返回地址覆盖位置;
- 构造攻击输入;
- 修改程序返回流程。
通过该过程理解栈空间数据被覆盖后对程序控制流产生的影响。
(4)Shellcode注入
在缓冲区溢出的基础上,进一步尝试将自定义机器代码注入程序运行空间。
实验主要包括:
- 配置实验环境;
- 准备Shellcode;
- 构造Payload;
- 调试分析执行过程;
- 优化攻击数据结构。
最终验证程序执行注入代码的过程。
4.3 实验流程总结
本实验从程序分析开始,逐步深入到漏洞利用:
程序静态分析
↓
定位关键执行位置
↓
修改程序控制流程
↓
分析栈结构
↓
利用缓冲区溢出漏洞
↓
注入并执行Shellcode
通过上述过程,可以完整理解二进制程序分析、漏洞发现以及漏洞利用之间的关系。
五、实验过程
5.1 实验准备与程序初步分析
5.1.1 实验文件准备
在正式进行漏洞分析之前,首先需要确认实验环境以及目标程序文件。
本实验主要围绕 Linux 环境下的 32 位 ELF 可执行文件 pwn1 展开,通过对该程序进行静态分析和动态调试,观察程序执行流程以及可能存在的安全问题。
首先检查实验目录中的相关文件,确认目标程序已经准备完成。
通过查看文件信息,可以确认目标程序属于 ELF 格式的可执行文件。

5.1.2 查看程序基本信息
为了进一步了解目标程序结构,使用 Linux 下的文件分析工具查看程序类型。
通过分析结果可以确认:
- 程序为 ELF 可执行文件;
- 运行环境为 Linux;
- 程序采用 32 位 x86 架构。
32 位程序的特点是:
- 使用 EIP 保存当前执行位置;
- 使用 ESP 和 EBP 管理函数栈空间;
- 函数调用过程依赖栈结构完成。
这些特点为后续分析 CALL 指令以及缓冲区溢出漏洞提供基础。

5.1.3 程序运行情况分析
在进行静态分析之前,首先运行目标程序,观察程序默认执行过程。
通过运行程序,可以了解:
- 程序是否能够正常启动;
- 程序是否需要输入数据;
- 程序正常情况下的执行流程。
经过初步运行发现,程序会接收用户输入,并根据输入内容继续执行。
后续实验将围绕该输入过程展开分析,进一步研究其中可能存在的控制流修改以及内存安全问题。

5.1.4 反汇编分析准备
为了分析程序内部执行逻辑,需要将 ELF 文件中的机器指令转换为汇编代码。
本实验使用 objdump 工具进行反汇编分析:
objdump -d pwn1
通过反汇编结果,可以观察程序中的:
- 函数入口;
- 指令执行顺序;
- 函数调用关系;
- 关键跳转位置。
后续实验中的机器指令修改、缓冲区溢出分析以及 Shellcode 注入,都需要基于反汇编结果进行。

5.1.5 初步分析总结
通过实验准备阶段,可以确定:
- 实验对象为 32 位 ELF 可执行文件
pwn1; - 程序可以正常运行并接受用户输入;
- 通过反汇编能够查看程序内部执行逻辑;
- 后续可以基于程序结构进一步分析控制流和漏洞位置。
在完成程序初步分析后,下面分别进行机器指令修改、缓冲区溢出利用以及 Shellcode 注入实验。
5.2 任务一:修改机器指令改变程序执行流程
5.2.1 反汇编分析程序执行流程
第一个实验任务要求通过修改程序中的机器指令,改变程序原有的执行流程。
由于 ELF 文件中保存的是机器指令,无法直接通过源代码方式观察程序内部逻辑,因此首先需要对目标程序 pwn1 进行反汇编分析。
使用 objdump 工具查看程序中的汇编代码:
objdump -d pwn1
通过反汇编结果,可以观察程序内部函数结构以及不同函数之间的调用关系。
分析过程中重点关注:
main函数入口;- 函数调用指令;
- 目标函数地址;
- 程序执行路径。
从反汇编结果可以发现,程序中存在多个关键函数,其中包括负责程序流程控制的 main 函数,以及后续实验需要跳转执行的目标函数。

5.2.2 定位关键CALL指令
在反汇编结果中,进一步分析 main 函数内部的调用关系。
程序通过 CALL 指令调用其他函数,例如:
call xxxx
CALL 指令负责实现函数调用,其执行过程包括:
- 保存当前执行位置;
- 根据目标地址跳转到指定函数。
本实验需要修改程序原有调用关系,因此需要定位对应的 CALL 指令位置。
通过分析可以确定:
- 当前 CALL 指令调用目标函数;
- 需要修改的是 CALL 指令中的偏移数据;
- 修改后程序将跳转至指定目标函数。

5.2.3 分析CALL指令偏移计算方式
x86 架构中的 CALL 指令通常采用相对地址寻址方式。
目标地址计算公式为:
目标地址 = 下一条指令地址 + 偏移量
其中:
- 下一条指令地址由当前程序位置决定;
- 偏移量保存在 CALL 指令机器码中。
因此,如果希望 CALL 指令跳转到新的目标函数,需要重新计算目标地址对应的偏移量。
计算过程如下:
新偏移量 = 目标函数地址 - CALL下一条指令地址
得到新的偏移量后,将其转换为机器码格式,并替换原 CALL 指令中的偏移字段。
5.2.4 修改程序中的机器指令
确定需要修改的机器码后,对 ELF 文件中的对应字节进行修改。
由于实验目标是修改二进制文件中的机器指令,因此修改过程中需要保证:
- 修改位置准确;
- 不破坏 ELF 文件结构;
- 修改后程序仍然能够正常运行。
实验过程中采用命令行方式定位并修改目标字节。
这种方式可以直接针对二进制数据进行操作,更适合对 ELF 文件进行精确修改。
修改完成后,需要重新对程序进行反汇编,确认目标 CALL 指令已经发生变化。

5.2.5 修改结果验证
完成机器码修改后,重新运行修改后的程序,并观察程序执行流程变化。
通过验证发现:
修改前:
main
↓
原目标函数
修改后:
main
↓
getShell
说明修改后的 CALL 指令已经生效,程序执行路径发生改变。
该实验验证了:
- 汇编指令与机器码之间存在对应关系;
- 修改二进制文件可以改变程序行为;
- 程序控制流可以通过修改底层指令实现控制。

5.2.6 实验小结
通过本任务,完成了对 ELF 文件中机器指令的分析和修改。
实验过程中,首先通过反汇编定位关键 CALL 指令,然后根据 x86 架构下相对地址计算方式确定新的偏移量,最后修改对应机器码并验证程序执行结果。
该过程加深了对程序控制流、汇编指令以及二进制文件修改方法的理解。
5.3 任务二:缓冲区溢出漏洞利用
5.3.1 分析存在漏洞的函数
在第二个实验任务中,需要利用程序中存在的缓冲区溢出漏洞,通过修改函数返回地址改变程序执行流程。
首先对程序中的关键函数进行反汇编分析。
通过查看 foo 函数的汇编代码,可以发现该函数存在用户输入操作。
程序在接收输入数据时,没有对输入长度进行有效限制,当输入数据超过预留缓冲区大小后,可能会覆盖栈中的其他数据。
根据函数调用栈结构分析,foo 函数运行过程中,栈空间中保存了:
- 局部变量;
- 保存的 EBP;
- 函数返回地址。
其中返回地址决定函数结束后程序继续执行的位置,因此成为本实验控制程序流程的关键位置。

5.3.2 分析函数栈结构
在正常程序执行过程中,函数调用会创建对应的栈帧。
典型结构如下:
高地址
返回地址
保存的EBP
局部变量缓冲区
低地址
当程序向缓冲区写入超过容量的数据时,超出的部分会继续覆盖栈中的其他内容。
覆盖过程通常为:
输入数据
↓
缓冲区
↓
保存EBP
↓
返回地址
如果返回地址被攻击者控制,函数执行结束时,ret 指令会读取攻击者构造的地址,并跳转到指定位置。
因此,本实验需要首先确定:
- 缓冲区长度;
- 返回地址所在位置;
- 需要覆盖的数据内容。
5.3.3 确定返回地址覆盖位置
为了确定输入数据对程序执行状态的影响,使用 gdb 对程序进行动态调试。
通过输入测试数据,并观察程序异常后的寄存器状态,可以判断输入数据覆盖的位置。
重点关注寄存器:
EIP
EIP 保存当前程序下一条执行指令地址。
如果输入数据能够影响 EIP 的值,说明已经成功控制了程序执行流程。
通过分析 EIP 中保存的数据,可以确定输入内容已经覆盖到返回地址区域。
由此确定后续 Payload 构造所需要的偏移长度。

5.3.4 获取目标跳转地址
确定返回地址位置后,需要选择程序跳转目标。
本实验选择程序中的目标函数作为跳转位置。
通过反汇编分析,可以获得目标函数地址:
getShell地址
由于 x86 架构采用小端序存储方式,因此在构造输入数据时,需要按照低字节在前的形式写入地址。
例如:
目标地址:
0x0804847d
实际写入:
\x7d\x84\x04\x08
如果地址排列方式错误,程序读取返回地址时会跳转到错误位置,导致实验失败。

5.3.5 构造缓冲区溢出Payload
根据前面的分析,最终需要构造能够覆盖返回地址的输入数据。
Payload基本结构为:
填充数据 + 目标地址
其中:
- 填充数据用于填满缓冲区以及覆盖返回地址之前的数据区域;
- 目标地址用于替换原始返回地址。
构造Payload时,需要保证:
- 数据长度能够准确覆盖返回地址;
- 地址按照小端序排列;
- 覆盖后的返回地址指向目标位置。

5.3.6 执行攻击并验证结果
完成Payload构造后,将输入数据传递给目标程序进行测试。
运行程序后,观察程序执行结果。
如果返回地址修改成功,程序执行流程将发生改变:
正常流程:
main
↓
foo
↓
返回原位置
修改后流程:
main
↓
foo
↓
目标函数
实验结果表明,通过缓冲区溢出漏洞可以修改程序栈中的关键控制数据,从而实现程序控制流改变。

5.3.7 任务二实验小结
通过本任务,完成了对缓冲区溢出漏洞利用过程的分析。
实验过程中,通过分析函数栈结构确定漏洞产生原因,通过调试确定返回地址覆盖位置,并根据目标函数地址构造攻击输入。
该过程验证了:
- 栈空间数据布局对程序执行的重要影响;
- 返回地址作为控制流关键数据的作用;
- 缓冲区溢出漏洞改变程序执行流程的基本原理。
5.4 任务三:Shellcode注入实验
5.4.1 实验环境配置
在前两个任务中,主要通过修改已有程序逻辑以及覆盖返回地址改变程序执行流程。
本任务进一步尝试将自定义机器代码注入程序运行空间,并控制程序执行该代码。
由于现代 Linux 系统通常会启用多种安全保护机制,例如:
- ASLR(地址空间布局随机化);
- NX(不可执行保护);
- Stack Canary(栈保护)。
这些机制会影响漏洞利用过程中地址定位以及代码执行。
因此,在进行 Shellcode 注入实验之前,需要根据实验要求调整相关环境。
首先检查程序当前的栈执行权限,并根据实验需求设置堆栈执行权限。
随后检查地址随机化状态,并根据实验环境要求关闭 ASLR。
完成环境配置后,可以进一步分析 Shellcode 注入过程。

5.4.2 Shellcode准备与Payload设计
Shellcode 是一段可以直接由处理器执行的机器代码。
在漏洞利用过程中,攻击者通常将 Shellcode 放入目标程序可以访问的内存区域,然后通过修改程序执行流程,使 CPU 跳转到 Shellcode 所在位置。
本实验中的 Payload 主要由以下部分组成:
填充数据 + 返回地址 + NOP区域 + Shellcode
其中:
- 填充数据用于覆盖缓冲区空间;
- 返回地址用于控制程序跳转位置;
- Shellcode用于执行目标机器代码。
为了提高跳转成功概率,在 Shellcode 前加入 NOP 指令区域。
其结构类似:
NOP区域 + Shellcode
NOP 指令不会改变程序运行状态,当返回地址落入该区域时,CPU 可以继续向后执行,最终进入 Shellcode。

5.4.3 第一次Shellcode执行尝试
根据前面缓冲区溢出实验确定的控制方式,首先构造包含 Shellcode 的输入数据。
完成 Payload 后运行程序:
./pwn1
观察程序执行结果。
第一次测试中,程序没有按照预期执行 Shellcode,而是出现异常情况。
通过现象分析可以确定:
- 程序控制流程已经受到影响;
- 但 Shellcode 没有正常执行。
因此需要进一步通过调试工具分析程序运行状态。


5.4.4 使用gdb分析执行失败原因
为了定位 Shellcode 执行失败原因,使用 gdb 对程序运行过程进行调试。
调试过程中重点观察:
- EIP 当前执行位置;
- ESP 栈指针变化;
- Shellcode 所在内存区域;
- 程序异常位置。
通过单步执行和寄存器分析,可以判断程序是否成功跳转到目标区域。
分析发现,第一次 Payload 失败的主要原因与内存布局有关。
在 Shellcode 注入过程中,不仅需要控制程序跳转地址,还需要保证:
- Shellcode存放位置合理;
- 栈空间不会破坏关键数据;
- 返回地址能够稳定指向目标区域。
因此,需要进一步调整 Payload 结构。

5.4.5 调整Payload结构
针对第一次执行失败的问题,对 Payload 结构进行优化。
调整前:
原始Payload结构
存在的问题:
- Shellcode位置不稳定;
- 地址控制精度不足;
- 执行过程中可能影响代码区域。
调整后:
填充数据 + 返回地址 + NOP区域 + Shellcode
新的结构可以:
- 先覆盖返回地址控制程序跳转;
- 利用NOP区域提高地址容错能力;
- 进入Shellcode执行区域。
重新设计后的 Payload 更符合实际漏洞利用过程中对内存布局的要求。

5.4.6 Shellcode执行验证
完成 Payload 调整后,重新运行程序进行测试。
程序执行过程中:
- 输入数据覆盖目标位置;
- 返回地址被修改;
- 程序跳转到Shellcode区域;
- Shellcode开始执行。
最终实验现象表明,程序能够按照预期执行注入代码。

5.4.7 任务三实验小结
通过 Shellcode 注入实验,进一步理解了漏洞利用过程中控制流和内存布局之间的关系。
实验过程中第一次执行失败,通过调试分析定位问题,并通过调整 Payload 结构解决。
该过程说明:
漏洞利用并不是简单地修改某一个地址,而需要综合考虑:
- 程序内存布局;
- 指令执行过程;
- 地址稳定性;
- 系统安全机制。
通过本实验,加深了对代码注入、Shellcode执行以及漏洞调试方法的理解。
六、实验问题分析与解决方法
6.1 二进制文件修改方式选择问题
在任务一的机器指令修改过程中,需要直接修改 ELF 文件中的机器码内容。
由于 ELF 文件属于二进制格式,目标修改内容并不是普通文本,而是程序实际执行的机器指令字节。我一开始采用普通文本编辑方式进行修改,发现非常容易导致修改位置不准确,甚至破坏文件结构。
因此,在实验过程中在询问AI以后学习采用命令行环境下对除第一次以外的二进制数据进行定位和修改的方法。
具体过程包括:
- 根据反汇编结果确定需要修改的指令位置;
- 分析对应机器码字节;
- 修改目标位置的数据;
- 重新进行反汇编验证修改结果。
这种方式能够直接作用于程序底层数据,更符合二进制程序分析和漏洞研究过程中的实际操作方式。
6.2 CALL指令偏移计算问题
在修改 CALL 指令过程中,最关键的问题是确定正确的跳转偏移。
由于 x86 架构中的 CALL 指令采用相对地址寻址方式,因此不能简单地将目标函数地址直接写入指令中,而需要根据当前指令位置重新计算偏移量。
计算过程中需要注意:
- CALL 指令长度;
- 下一条指令地址;
- 目标函数地址。
如果计算基准位置错误,会导致程序跳转到错误地址,使修改后的程序无法正常执行。
通过重新分析反汇编结果,并按照相对地址计算公式重新计算偏移,最终确定正确修改内容。
6.3 缓冲区溢出实验中返回地址定位问题
在进行 BOF 实验初期,需要确定输入数据覆盖返回地址的位置。
如果没有准确确定偏移量,直接构造 Payload 可能会出现:
- 无法覆盖返回地址;
- 覆盖错误数据;
- 程序异常退出。
因此,通过 gdb 动态调试程序运行过程,观察输入数据对寄存器和栈空间的影响。
分析重点包括:
- EIP寄存器变化;
- 栈中数据排列;
- 输入长度与覆盖范围。
通过调试结果确定返回地址所在位置,为后续构造攻击数据提供依据。
6.4 Shellcode第一次执行失败问题
在 Shellcode 注入实验中,第一次构造 Payload 后程序没有按照预期执行。
初次实验结果表明:
程序的控制流程已经受到影响,但是 Shellcode 并未成功执行。
针对该问题,通过 gdb 对程序执行过程进行分析,重点观察:
- 当前执行地址;
- Shellcode所在位置;
- 栈空间变化。
经过分析发现,失败原因主要与 Payload 内存布局有关。
在漏洞利用过程中,不仅需要保证返回地址能够正确跳转,还需要合理安排 Shellcode 存放位置。
如果内存布局不合理,可能导致:
- Shellcode未执行完整;
- 栈数据覆盖关键内容;
- 程序异常退出。
6.5 Payload结构优化问题
针对第一次 Shellcode 执行失败的问题,对 Payload 结构进行了调整。
优化过程中主要考虑:
- 返回地址是否能够准确控制程序跳转;
- Shellcode存放位置是否稳定;
- 是否需要增加NOP区域提高容错能力。
通过重新调整 Payload 结构,使程序跳转位置更加稳定,并提高 Shellcode 执行成功率。
这一过程说明,在实际漏洞利用过程中,Payload设计需要综合考虑程序运行环境、内存布局以及指令执行过程,而不是简单修改某一个地址即可完成攻击。
6.6 实验过程中的经验总结
通过本次实验可以发现,二进制漏洞利用过程通常需要不断分析和验证。
实验中的每一个步骤都需要结合程序实际运行状态:
- 静态分析用于理解程序结构;
- 动态调试用于观察运行状态;
- 修改验证用于确认分析结果。
当实验结果与预期不一致时,需要根据程序执行现象定位问题,而不是简单重复操作。
通过对错误原因的分析和修正,可以更加深入理解程序控制流、内存布局以及漏洞利用之间的关系。
七、实验结果分析
本实验围绕程序控制流修改、缓冲区溢出利用以及 Shellcode 注入三个方向展开,通过逐步分析和验证,完成了从程序逆向分析到漏洞利用的完整过程。
7.1 机器指令修改实验结果分析
在任务一中,通过对 pwn1 程序进行反汇编分析,定位到了影响程序执行流程的关键 CALL 指令。
通过分析 CALL 指令的机器码组成,并根据 x86 架构中的相对地址计算方式重新计算目标偏移,完成了对程序控制流程的修改。
实验结果表明:
- 程序执行流程不仅可以通过源代码改变;
- 直接修改二进制文件中的机器指令同样可以影响程序行为;
- 理解汇编指令与机器码之间的对应关系,是进行二进制分析的重要基础。
该实验验证了底层机器指令对于程序控制流的重要作用。
7.2 缓冲区溢出实验结果分析
在任务二中,通过分析 foo 函数的输入处理过程,发现程序存在缓冲区溢出风险。
实验过程中,通过动态调试观察栈空间变化,确定了输入数据对返回地址区域的影响,并进一步构造攻击数据改变程序执行流程。
实验结果表明:
- 函数返回地址属于程序控制流中的关键数据;
- 如果缺少有效的输入边界检查,攻击者可能通过覆盖返回地址控制程序执行;
- 栈结构分析和寄存器状态观察是漏洞定位的重要方法。
该实验验证了缓冲区溢出漏洞利用的基本原理。
7.3 Shellcode注入实验结果分析
在任务三中,在缓冲区溢出的基础上进一步尝试执行自定义 Shellcode。
实验过程中,第一次构造 Payload 后未能达到预期效果,通过调试分析程序运行状态,定位到 Payload 内存布局方面的问题。
经过调整后,重新设计 Payload 结构,使程序能够按照预期跳转到 Shellcode 所在位置并执行代码。
实验结果表明:
Shellcode 注入不仅需要控制程序执行流程,还需要综合考虑:
- Shellcode存放位置;
- 返回地址准确性;
- 栈空间变化;
- 程序运行环境。
该实验进一步体现了漏洞利用过程中程序控制流与内存布局之间的关系。
7.4 综合实验结果分析
通过三个实验任务,可以发现漏洞利用过程具有明显的递进关系。
第一阶段:
通过修改机器指令,直接改变程序已有的执行流程。
第二阶段:
利用缓冲区溢出漏洞,通过覆盖程序运行时保存的控制数据,实现动态控制流劫持。
第三阶段:
进一步注入自定义机器代码,实现更灵活的程序行为控制。
整个实验过程说明:
程序运行过程中的关键控制数据一旦受到非法修改,就可能导致程序执行逻辑发生改变。
因此,在实际软件开发过程中,需要通过输入检查、内存安全设计以及系统保护机制等方式降低此类漏洞风险。
八、实验总结与体悟
通过本次逆向破解与 BOF 实验,对 Linux 环境下二进制程序分析、程序控制流修改以及漏洞利用过程有了浅薄的了解。
在实验开始阶段,通过对 ELF 可执行文件进行分析,掌握了利用反汇编工具观察程序内部结构的方法。通过分析汇编代码,可以了解函数调用关系以及关键指令对程序执行流程的影响。
在机器指令修改实验中,通过定位 CALL 指令并分析其偏移计算方式,理解了汇编指令、机器码以及程序控制流之间的关系。实验表明,即使不修改程序源代码,直接修改二进制文件中的关键指令数据,也可以改变程序实际执行行为。
在缓冲区溢出实验中,通过分析函数栈结构,理解了局部变量、保存的 EBP 以及返回地址之间的关系。利用输入数据覆盖返回地址的过程,使原本抽象的控制流劫持概念转化为具体的内存变化过程。
在 Shellcode 注入实验中,通过构造 Payload 并结合调试工具分析执行过程,进一步理解了代码注入攻击中程序跳转、内存布局以及执行环境之间的关系。实验过程中遇到的问题也说明,漏洞利用并不是简单修改某一个地址,而需要综合考虑程序运行状态和内存结构。
同时,通过实验过程中对 ASLR、NX、Stack Canary 等安全机制的分析,认识到漏洞利用和安全防护之间存在持续对抗关系。攻击者需要分析程序弱点,而开发者则需要通过安全机制降低漏洞被利用的可能性。
总体来看,本实验将程序逆向分析、汇编指令理解、缓冲区溢出利用以及 Shellcode 注入等知识有效地结合起来。
AI时代,获取到大量的知识和按照计划书迅速地推进实验并不是什么难事,但是如何将AI所知晓的知识学到自己脑子里这件事却不简单,如果自己不具备良好的基础,不但对AI内容的正误无法判断,甚至无法清楚地对AI提出自己的需求和想法。本次实验我也从一开始简单地复制粘贴命令,到多次试错和重复锻炼,再到后期对于一定的步骤渐渐能意识到了下一步可能需要的操作,在试错中,学习和AI配合,通过AI学习知识、阅读资料,这是一次很不错的实验经历。
浙公网安备 33010602011771号