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

网络与系统攻防技术

实验一实验报告

pwn1 程序控制流修改与
shellcode 注入

学生姓名:罗靖林

所在系别:网络空间安全系

专  业:网络空间安全

学  号:20242423

指导教师:王志强

实验环境:Kali Linux

2026—2027 学年第一学期

目 录

1 实验目的 1

2 实验内容及实验原理 1

2.1 实验环境与实验内容 1

2.2 基础知识与实验原理 1

3 实验步骤 2

3.1 实验文件准备和原始程序验证 2

3.2 手工修改可执行文件 4

3.3 利用 BOF 覆盖返回地址 8

3.4 注入 shellcode 并运行 8

4 实验思考与总结 12

4.1 问题及解决方案 12

4.2 实验总结 13

4.3 学习感悟与安全思考 13

参考资料 14

1 实验目的

通过分析 32 位 ELF 程序 pwn1,理解函数调用、相对地址、小端序和栈帧之间的关系,掌握利用反汇编定位关键指令及核对修改结果的方法。

分别完成修改可执行文件、缓冲区溢出覆盖返回地址和栈上 shellcode 注入,比较三种改变程序控制流的方法,并结合实验中的失败现象理解运行环境和安全保护机制对结果的影响。

2 实验内容及实验原理

2.1 实验环境与实验内容

本次实验对象是 pwn1。实验文件复制后命名为 pwn1_20242423,文件名中包含了自己的学号;Kali 终端中的主机名也已经改为 luojinglin,截图中可以看到对应主机名。

实验分为三个部分。第一部分是手工修改可执行文件中的机器码,把 main 中调用 foo 的指令改成调用 getShell。第二部分是不修改程序文件,而是利用 foo 中 gets 函数造成的缓冲区溢出,构造输入覆盖返回地址,使程序跳转到 getShell。第三部分是在输入中加入 shellcode 和 NOP 滑梯,让返回地址跳到栈上的输入内容中执行。

我的做法是先确认原程序的正常运行结果,再找出关键函数地址和 call 指令位置。第一部分通过修改字节验证静态改变控制流;第二部分通过覆盖返回地址验证 BOF;第三部分再处理栈地址、ASLR 和可执行栈等问题。

2.2 基础知识与实验原理

本次实验主要用到 ELF 文件、反汇编、栈帧、缓冲区溢出和 shellcode 这些知识。pwn1 是一个 32 位 Linux 可执行文件,程序运行时会先进入 main,再调用 foo。正常情况下 foo 只负责读取输入并打印出来,getShell 虽然在程序中存在,但不会被正常流程调用。

反汇编的作用是把二进制机器码翻译成汇编形式。通过 objdump -d 可以看到每条指令的地址、机器码和对应汇编语句。本实验中,我先用它找到 main、foo 和 getShell 的位置,再根据这些地址决定应该修改哪几个字节,或者 payload 中应该写入哪个地址。

NOP 指令的机器码是 90,执行后不会改变程序状态,只会继续执行下一条指令。JMP 是无条件跳转,常见短跳转是 EB xx,近跳转是 E9 xx xx xx xx。JE 或 JZ 在比较结果相等时跳转,短跳机器码常见为 74 xx;JNE 或 JNZ 在比较结果不相等时跳转,短跳机器码常见为 75 xx。CMP 用于比较两个操作数,本身不保存比较结果,而是改变标志位,所以常和 JE、JNE 等指令配合使用。

call 指令需要特别注意。32 位 x86 中,call 后面的 4 字节通常保存的是相对偏移,而不是目标函数的绝对地址。计算时要用目标地址减去 call 下一条指令的地址。程序中地址按照小端序存放,例如 0x0804847d 写入 payload 时要写成 7d 84 04 08。

缓冲区溢出发生在输入长度超过程序预留空间时。foo 中使用 gets 读取输入,gets 不检查输入长度,所以过长输入会继续覆盖栈上的内容。如果覆盖到返回地址,函数 ret 时程序就会跳转到攻击者写入的地址。shellcode 注入的思路是在输入中放入可以执行的机器码,再让返回地址跳到这段机器码附近。为了提高命中率,shellcode 前一般会放一段 NOP 滑梯。

3 实验步骤

3.1 实验文件准备和原始程序验证

进入桌面目录后,我先查看当前文件。截图中可以看到 pwn1 原始文件、复制后的 pwn1_20242423,以及后面第一部分生成的 pwn1_20242423_patch。file 命令显示 pwn1_20242423 是 ELF 32-bit LSB executable,说明它是 32 位 Linux 可执行文件,可以按 32 位程序的方式分析。

图 1 查看桌面目录中的 pwn1_20242423,并确认其为 32 位 ELF 文件

为了确认程序原来的行为,我直接运行 pwn1_20242423 并输入 hello。程序输出的仍然是 hello,没有出现 shell 提示,也没有其他异常。这一步说明程序当前正常流程就是 main 调用 foo,foo 对输入内容进行回显。

图 2 未修改程序的运行结果,只对输入内容进行回显

3.2 手工修改可执行文件

接着我查看 main 函数的反汇编。图中 0x080484b5 处的指令是 call 8048491 <foo>,对应机器码为 e8 d7 ff ff ff。这里的 e8 是 call 指令,后面的 d7 ff ff ff 是相对偏移。

图 3 反汇编 main 函数,确认原本调用的是 foo

继续查看 getShell 和 foo 的地址,可以看到 getShell 位于 0x0804847d,foo 位于 0x08048491。因此本部分要做的不是新增代码,而是把 main 中这条 call 指令的目标从 foo 改为 getShell。

图 4 查看 getShell 与 foo 的入口地址

call 指令地址为 0x080484b5,指令长度为 5 字节,所以下一条指令地址为 0x080484ba。调用 getShell 时需要的偏移为:

0x0804847d - 0x080484ba = -0x3d

-0x3d 的 32 位补码是 0xffffffc3,小端序写入就是 c3 ff ff ff。原本调用 foo 的偏移是 d7 ff ff ff,所以实际只需要把一个字节 d7 改成 c3。因为 hexedit 安装时网络源连接失败,我最后用 dd 直接在文件偏移处写入 c3,然后再用 objdump 检查修改结果。

图 5 修改一个字节后,main 中的 call 目标已经变为 getShell

运行修改后的 pwn1_20242423_patch 后,程序不再只是等待普通输入回显,而是直接进入 shell。随后输入 whoami 和 id,返回结果显示当前用户为 kali,说明第一部分已经成功。

图 6 运行修改后的文件并进入 shell

3.3 利用 BOF 覆盖返回地址

第二部分使用原来的 pwn1_20242423,不再修改可执行文件。根据 foo 的栈帧结构,输入缓冲区到返回地址的位置相差 32 字节,所以 payload 的前 32 字节用 A 填充,后面跟 getShell 地址 0x0804847d。由于小端序,地址写为 \x7d\x84\x04\x08。

(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*32 + b"\x7d\x84\x04\x08" + b"\n")'; cat) | ./pwn1_20242423

执行后,程序先输出一串 A,说明输入内容确实被 foo 读取并打印。随后 foo 返回时返回地址已经被覆盖为 getShell 的地址,所以程序进入 shell。截图中 whoami 和 id 的输出均显示为 kali,说明 BOF 构造成功。

图 7 构造 BOF 输入覆盖返回地址后进入 shell

3.4 注入 shellcode 并运行

第三部分的目标不是跳到程序已有的 getShell,而是执行我放在输入中的 shellcode。开始前先关闭 ASLR,然后用 readelf 查看栈权限。GNU_STACK 一行显示 RWE,说明栈有读、写、执行权限,可以继续尝试在栈上执行 shellcode。图中前面的 broken pipe 和 segmentation fault 是退出上一部分管道后出现的提示,不影响前面实验结果。

图 8 关闭 ASLR,并检查 GNU_STACK 权限

为了找到输入内容在栈上的位置,我先构造了一个用于定位的输入文件 sc_find。这个输入由 32 个 A、4 个用于覆盖返回地址的字节、80 个 NOP 和 shellcode 组成。进入 GDB 后在 gets 返回后的 0x080484a2 处设置断点,再查看 $ebp+8 开始的内存。

第一次看到的栈地址在直接运行时并不稳定,所以我改用 set exec-wrapper env -i,让 GDB 调试时的环境变量情况和最终运行时更接近。重新查看后,$ebp+8 为 0xffffde00,从这里开始能看到大量 0x90,shellcode 大约在 0xffffde50 附近。

图 9 在 GDB 中查看输入内容、NOP 区域和 shellcode 位置

第一次尝试时,我使用 0xffffcf20 作为返回地址,直接运行后出现 illegal hardware instruction。这个结果说明程序跳转位置不合适,没有落入正确的 NOP 滑梯或 shellcode。这个失败也说明 shellcode 部分不能只看一次地址,运行环境变化会影响栈位置。

图 10 使用不稳定地址时出现 illegal hardware instruction

重新按 env -i 环境确认地址后,我把返回地址改为 0xffffde20。这个地址位于 NOP 区域中,写成小端序就是 \x20\xde\xff\xff。最后 payload 中先写 32 个 A,再写返回地址,然后接 80 个 NOP 和 shellcode。

(python3 -c 'import sys; shellcode=b"\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"; sys.stdout.buffer.write(b"A"*32 + b"\x20\xde\xff\xff" + b"\x90"*80 + shellcode + b"\n")'; cat) | env -i /home/kali/Desktop/pwn1_20242423

再次运行后,输入 whoami 和 id 都能正常返回 kali 相关信息,说明程序最终执行到了栈上的 shellcode,第三部分完成。

图 11 重新选择返回地址后,shellcode 注入成功

4 实验思考与总结

4.1 问题及解决方案

问题1:一开始我把 Windows 路径 C:\Users\11219\Documents 直接输入到了 Kali 终端中,结果终端提示 command not found。

问题1解决方案:确认 Linux 和 Windows 的路径写法不同,Kali 中应该进入 ~/Desktop 查找实验文件。切换到桌面目录后,后续命令就能正常执行。

问题2:第一部分原本准备安装 hexedit 修改十六进制字节,但 apt 安装时提示网络不可达,无法下载软件包。

问题2解决方案:本次只需要修改一个字节,所以改用 dd 在文件偏移处直接写入 c3。写入后再通过 objdump 反汇编确认 main 中的 call 目标已经改为 getShell。

问题3:shellcode 部分第一次使用 0xffffcf20 作为返回地址,运行后出现 illegal hardware instruction。

问题3解决方案:重新检查后发现 GDB 和普通运行环境中的栈地址不完全一致。之后使用 env -i 减少环境变量影响,并在 GDB 中设置 exec-wrapper env -i 重新定位,最终选择 0xffffde20 作为返回地址后成功。

问题4:修改主机名后,sudo 有时提示 unable to resolve host luojinglin。

问题4解决方案:这是主机名和 /etc/hosts 没同步造成的,对本次实验结果没有直接影响。后续可以把 127.0.1.1 对应的主机名改成 luojinglin。

4.2 实验总结

本次实验围绕 pwn1 完成了三种控制流修改:第一部分将 call 指令的相对偏移由 d7 ff ff ff 修改为 c3 ff ff ff,使 main 转而调用 getShell;第二部分使用 32 字节填充并覆盖返回地址,使 foo 返回后进入 getShell;第三部分在输入中加入 NOP 滑梯与 shellcode,重新定位栈地址后完成执行。三部分均通过实验中的 whoami 和 id 输出验证进入了 shell。

实验中的关键在于把反汇编结果、地址计算和实际运行现象相互核对。shellcode 首次运行失败后,通过统一环境变量条件重新定位,并将返回地址调整到 NOP 区域,最终得到预期结果。这说明调试器中的地址必须结合实际运行环境验证,不能直接照搬。

4.3 学习感悟与安全思考

这次实验让我对“程序执行流程可以被改变”这件事有了比较直观的认识。第一部分只是修改一个字节,但前提是要知道 call 指令的相对偏移如何计算;第二部分只是构造一串输入,但必须先弄清楚缓冲区到返回地址之间到底有多少字节。

我觉得 shellcode 注入是这次实验里最容易出错的地方。刚开始我以为 GDB 中看到的地址可以直接使用,但直接运行时却失败了。后来改用 env -i 重新定位,才发现运行环境对栈地址影响很明显。这一步让我感觉到漏洞利用不是机械复制命令,而是要根据自己机器上的结果不断调整。

从安全防护角度看,这个实验也能对应到很多实际机制。输入长度检查可以避免 gets 这类函数带来的溢出问题,ASLR 会增加地址预测难度,NX 可以阻止栈上代码直接执行。实验环境中为了观察原理弱化了部分保护,但也正是这样才能更清楚地看到漏洞利用的基本过程。

参考资料

《网络与系统攻防技术》课程实验指导书:pwn1 与 Shellcode 注入实验

GNU Binutils objdump 使用说明。https://sourceware.org/binutils/docs/binutils/objdump.html

GDB Documentation。https://sourceware.org/gdb/documentation/

Linux man-pages:file、dd、readelf。https://man7.org/linux/man-pages/

Intel 32 位 x86 指令集相关资料。https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html

posted @ 2026-10-12 10:49  娄瑾澜  阅读(4)  评论(0)    收藏  举报