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

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

1. 基础知识

本次实验涉及以下基础概念与技能:

(1)Linux 基本操作

  • 管道 |:把前一个命令的输出作为后一个命令的输入。

    例如 (cat input; cat) | ./pwn1,将 payload 文件内容传递给 pwn1 程序。

  • 输入重定向 <:把文件内容作为程序输入,如 ./pwn1 < input。

  • 输出重定向 >:把命令输出写入文件,如 perl -e '...' > input。

  • xxd:在任务一种将十六进制查看/转换文件内容。

  • perl -e:在命令行直接执行 Perl 代码,用 "\xNN" 表示一个字节,用于生成含二进制字节的 payload 文件。

(2)ELF 文件格式

本次实验对象 pwn1 为 ELF 可执行文件。用 file pwn1 查看,输出中的 32-bit 表明这是一个 32 位程序,LSB 表明采用小端字节序。

ELF 文件中 .text 段存放代码,objdump -d 命令可以把 .text 段的机器码反汇编成汇编指令,输出包括指令地址、机器码、汇编指令三列。依靠 objdump -d,从而定位 getShell 的入口地址(0x0804847d)以及 main 中 call foo 指令的机器码(e8 d7 ff ff ff)和它下一条指令的地址(0x080484ba),为任务一修改机器指令提供了依据。

+----------------------------------+
| ELF Header(魔数 7f 45 4c 46…)    |  ← readelf -h
+----------------------------------+
| Program Header Table(可选)      |  ← 加载器视角
+----------------------------------+
| Section Header Table(必须)       |  ← 链接器视角
+----------------------------------+
| 真正数据:                         |
|   .text   代码(只读,可执行)       |
|   .rodata 只读数据(字符串常量)     |
|   .data   已初始化全局/静态变量      | 
|   .bss    未初始化全局/静态变量      |(文件里不占空间,运行时才分配)
|   …                              |
+----------------------------------+

(3)汇编与机器指令
汇编指令:push、pop、mov、call、ret、leave 等,与机器码一一对应。
机器指令:CPU 真正执行的二进制字节。例如 call 指令机器码为 e8 + 4 字节相对偏移(小端)。
EIP(指令指针):保存下一条要执行的指令地址。
指令地址:每条指令在内存中的位置。32 位无 PIE 的 ELF 通常从 0x08048xxx 开始。

(4)Call 指令与偏移计算
call 使用相对偏移寻址:即目标地址 = call 下一条指令的地址(EIP)+ 4 字节补码偏移
例:0x80484ba + (-0x29) = 0x8048491(foo 地址)。想改成调用 getShell,只需把偏移改成 getShell - 下一条指令地址 的补码。

(5)栈与缓冲区溢出(BOF)原理
32 位函数栈帧结构(从低地址到高地址)为:buf[大小] → 保存的旧 EBP → 返回地址
函数返回时执行 ret:从栈顶取出返回地址赋给 EIP,程序跳过去。
若程序使用 gets 等不检查长度的函数,输入超长字符串即可覆盖返回地址,从而劫持执行流,这就是 BOF(Buffer Overflow)。
其中,小端字节序:写入栈的 4 字节地址在内存中要低字节在前,如 0x0804847d 写成 \x7d\x84\x04\x08。

(6)Shellcode 与三种攻击布局
Shellcode:一段用于注入的机器指令,常见功能为执行 execve("/bin/sh") 获取 shell。
N S R 布局:nop + shellcode + retaddr,shellcode 在返回地址前面。
R N S 布局:anything + retaddr + nop + shellcode,shellcode 在返回地址后面,可避免 push 覆盖未执行指令的问题(本实验采用)。

(7)BOF 防御技术
BOF 防御机制与本实验绕过方式

防御机制 原理 本实验处理方式
Stack Canary 编译器在 buf 和返回地址之间放一个随机值,函数返回前检查是否被改,被改则判定发生溢出并终止程序 编译时未开启(-fno-stack-protector),栈上无 canary,可随意溢出
DEP / NX 通过 CPU 页表机制把栈内存标记为不可执行 用 execstack -s pwn1 将栈设置为可执行
ASLR 操作系统每次加载程序时随机化代码段、栈、堆的地址 将 /proc/sys/kernel/randomize_va_space 设为 0,关闭地址随机化

2. 实验内容

本次实践的对象是一个名为pwn1的linux可执行文件。

该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下该代码不会被运行。本实验通过两种方法运行这个代码片段并尝试注入运行任何Shellcode。

三个实践内容如下:

  • 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
  • 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
  • 注入一个自己制作的shellcode并运行这段shellcode。

3. 实验过程

3.1 环境准备

  • 虚拟机:VMware + Kali(64 位,x86_64)

  • 主机名按要求设置为本人姓名拼音:liangruili

  • 通过复制粘贴把 pwn1 传入 Kali 桌面

2026-10-10-14-26-30-594ae08c-c084-4fd3-bd86-d4b7232f90be

试运行:


3.2 任务一:修改机器指令,调用 getShell

(1)反汇编,定位目标

得到关键信息:

  • getShell 入口地址:0x0804847d

  • main 中 call foo 指令:

    • call 机器码:e8 d7 ff ff ff

    • 下一条指令地址:0x080484ba

    • 相对偏移 d7ffffff = -0x29(补码)

(2)计算新偏移

要把 call 目标从 foo(0x8048491) 改为 getShell(0x804847d):

新偏移 = getShell - 下一条指令地址
= 0x804847d - 0x80484ba
= -0x3d
= 0xffffffc3(补码)
小端 = c3 ff ff ff

所以新机器码为:e8 c3 ff ff ff。

(3)用 vi + xxd 修改文件

在 vi 中:

  1. ESC

  2. :%!xxd 切换十六进制

  3. /e8d7 搜索,定位到 000004b0 行:

  4. 更改e8为c3

  5. :%!xxd -r 转回二进制

  6. :wq 保存退出

(4)验证

如上图call机器码变为e8 c3 ff ff ff,call 指令是否已正确调用 getShell,说明修改成功。

id、ls 均可执行。


3.3 任务二:BOF 覆盖返回地址,调用 getShell

(1)查看 foo 的栈布局

关键行:

8048494: 83 ec 38 sub $0x38,%esp
8048497: 8d 45 e4 lea -0x1c(%ebp),%eax ← buf 起始 = ebp - 0x1c
804849d: e8 8e fe ff ff call 8048330 <gets@plt >
80484ae: c3 ret

  • buf 大小 = 0x1c = 28 字节

  • 从 buf 到返回地址 = 28 + 4(旧 ebp)= 32 字节

(2)用 gdb 验证偏移

输入 40 个字符:1111111122222222333333334444444412345678,程序 SIGSEGV 后:

输出

eip 0x34333231 ← " 1234"
ebp 0x34343434 ← "4444"

说明:

  • 第 29~32 字节(4444)→ 覆盖旧 ebp

  • 第 33~36 字节(1234)→ 覆盖返回地址(EIP)

  • 即偏移 = 32 字节

(3)构造 payload

返回地址 = getShell = 0x0804847d,小端 = \x7d\x84\x04\x08

输出:

00000000: 4141 ... (32 字节 'A')
00000020: 7d84 0408 0a

与预期一致

(4)注入,拿 shell

如上图,id 输出 uid=1000(kali) ...,说明跳转成功。


3.4 任务三:注入 shellcode 并执行

(1)关闭防护

(2)准备 shellcode

25 字节 execve("/bin/sh"):

\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

(3)RNS 布局探测 shellcode 地址

先生成占位 payload(返回地址为\x01\x02\x03\x04):

终端 A:

程序停在 gets 等输入(因为结尾未加 \x0a)。

终端 B:

如上图,PID为43726

gdb 内:

完成后回终端 A 按回车,gdb 停在断点。查看:

如上图,栈上 01 02 03 04 后紧跟 90 90 90 90 90 90 + shellcode。

因此 shellcode 起始地址 = esp + 4 = 0xffffcf60。

(4)构造最终 payload

布局为:

[ 'A'×32 ][ 60 cf ff ff ][ 90×6 ][ shellcode ]

(5)注入,执行 shellcode


4. 问题及解决方案

  • 问题 1:执行 sudo hostnamectl set-hostname 时,输入 sudo 密码报 Sorry, try again。

    问题 1 解决方案:sudo 密码即 Kali 登录密码,我一开始不知道。正确输入后命令执行成功,主机名修改完成。

  • 问题 2:execstack 在 Kali 源里报 Unable to locate package。

  • 问题 2 解决方案:手动下载 .deb 安装包安装:

  • 问题 3:执行 sudo apt install gdb 安装 gdb 时,提示 Package 'gdb' has no installation candidate,无法安装。

    问题 3 解决方案:这是软件源列表未更新导致的。先执行 sudo apt update 更新源,再重新安装:

  • 问题 4:vi 中 /e8d7 搜索不到目标字节。

  • 问题 4 解决方案:xxd 视图中每 2 个字节间可能有空格分隔,改用更短的 /e8 搜索,或按已知文件偏移用 :goto 偏移 跳转,或先 xxd pwn1 | grep "e8" 在终端定位偏移。

  • 问题 5:任务三中用 gdb attach 正在运行的 pwn1 进程时,执行 disassemble foo 报 No symbol table is loaded;且之前 attach 了一个不存在的 PID(输错了),报 ptrace: No such process。

    问题 5 解决方案:

    PID 看错:ps -ef | grep pwn1 会输出两行,一行是 pwn1 进程本身,一行是 grep 命令自己。要取命令列显示 ./pwn1 的那一行的 PID(左侧小数字),不要取 grep 那一行。

    修改后可正常显示:


5. 学习感悟、思考等

通过本次实验,我完整地实践了三种逐层递进的攻击方式,对二进制漏洞利用有了比较直观的理解:

(1)从"改文件"到"发输入"的攻击演进
任务一只需改一个字节(d7 → c3)就改变了程序执行流程,让我体会到可执行文件里机器指令与汇编的严格对应关系;

任务二仅靠构造输入覆盖返回地址就劫持了执行流;

任务三进一步把任意机器码注入栈上并执行,攻击能力最强。

(2)防护机制的对比
实验中我关掉了三道防护,从而攻击成功,分别如下:

  • Canary:防止输入溢出到栈上的关键位置;

  • NX/DEP:即使注入成功也不让栈上的代码执行;

  • ASLR:让预先获得的地址失效。
    这三道防线只要有一道开启,本实验的攻击就会失败。

在具体实验过程中,由于缺乏耐心,任务一第一次复制pwn并更改文件时,对文件造成不可逆破坏于是我又复制了一个,这提醒我慢慢来才会比较快。

任务三中,在NSR布局中,shellcode 放在返回地址前面(NSR 布局)会因 push 使栈顶向低地址增长、覆盖尚未执行的指令而失败;而RNS布局把 shellcode 放在返回地址后面,为了避免“掉入坑”,采用 RNS 布局。

我明白了三个任务本质完成同一件事,即让程序下一步跳去攻击者想要的地方。


参考资料

posted @ 2026-10-10 16:03  小鱼爱大葱  阅读(16)  评论(0)    收藏  举报