20232303 2025-2026-1 《网络与系统攻防技术》实验一实验报告
20232303 2025-2026-1 《网络与系统攻防技术》实验一实验报告
1. 实验内容
1.1 本章学习内容总结
通过本章目前的学习,我首先了解了安全漏洞、缓冲区溢出是什么与为什么,以及一些具体案例与安全事件。接着学习了缓冲区溢出的基础知识,了解了编译器与调试器、汇编语言、进程内存管理、函数调用过程的基础知识。之后我们进行了实验一的内容讲解与实践操作。然后学习了缓冲区溢出的三种实例(栈溢出、堆溢出、内核溢出),分别了解了它们的原理与缓冲区溢出攻击实例。接着开始学习shellcode技术,目前学习到缓冲区溢出的三种模式:NSR模式、RNS模式、RS模式,通过实际代码理解了其原理。
1.2 本次实验内容
本次实践的总体目标是想办法运行正常情况下不会运行的getShell代码片段,具体实现起来有两种方法,对应实验内容的前两条,第三条实验内容实现了在此基础上学习如何注入运行任何Shellcode。三条实验内容如下:
-
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数——运行原本不可访问的代码片段
-
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数——强行修改程序执行流
-
注入一个自己制作的shellcode并运行这段shellcode——注入运行任意代码
2. 实验过程
2.1 环境准备
在VMware里配置好kali虚拟机,把pwn1文件复制到虚拟机里:
(1)用WinSCP连接上虚拟机
(2)将文件拖入虚拟机
(3)在虚拟机内查看文件
(4)修改文件命名
2.2 直接修改程序机器指令,改变程序执行流程
2.2.1 通过objdump -d pwn20232303_1 | more
对文件进行反汇编
找到主函数调用foo函数部分
这里Call指令(机器码e8)是如何通过“EIP + 偏移量”改变程序执行流程的呢?
(1)EIP 的核心作用
EIP(指令指针寄存器)是 CPU 用来 “记住下一条要执行的指令地址” 的寄存器。正常流程中,执行完当前指令后,CPU 会自动从 EIP 指向的地址取指令执行,之后 EIP 再更新为 “下下条指令地址”。
(2)Call 指令的特殊逻辑(机器码 e8)
当遇到 Call 指令(用于调用函数,这里是调用 foo 函数)时,CPU 不按 “默认更新 EIP” 的逻辑走,而是做两步关键操作:
第一步:先把 “当前 Call 指令的下一条指令地址”(这里是 80484ba)存到栈里(后续函数执行完要靠这个地址 “返回”);
第二步:计算 “新的 EIP 值”—— 公式是:当前 EIP(下条指令地址 80484ba) + Call 指令后带的偏移量(d7ffffff),然后让 EIP 指向这个新地址,从而跳转到目标函数执行。
(3)偏移量 “d7ffffff” 的本质
“d7ffffff” 是 32 位二进制补码(补码是计算机表示负数的方式):
补码 “d7ffffff” 转十进制是 -41(也等于十六进制 - 0x29);
代入公式计算:80484ba(下条指令地址) + (-41) = 80484ba - 0x29 = 8048491,而 8048491 正是目标函数 foo 的地址 —— 这样就实现了 “从当前流程跳转到 foo 函数”。
想要调用getShell,只要修改“d7ffffff”为,"getShell-80484ba"对应的补码就行。
804847d-80484ba=ffffffc3,机器指令里写成c3ffffff
2.2.2 修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff
复制pwn20232303_1文件为pwn20232303_2,在vi内进行编辑:
找到要修改的d7ffffff:
修改为c3ffffff:
再对pwn20232303_2文件反汇编一下,看看call指令是否正确调用getShell:
给修改后的文件pwn20232303_2添加执行权限,运行该文件,得到shell提示符$,进入了shell,说明运行getShell函数成功:
2.3 通过构造输入参数,造成BOF攻击,改变程序执行流
2.3.1 反汇编pwn20232303_1,了解程序基本功能
目标是触发getShell函数,发现foo函数有Bof漏洞:
函数通过 sub $0x38, %esp 在栈上开辟 56 字节栈帧,其中 ebp-0x1c 到 ebp 之间的区域是预留的字符串缓冲区。
ebp 是栈帧基址,-0x1c 表示从基址向上偏移 28 字节(0x1c 转十进制为 28),因此系统仅预留了 28 字节 缓冲区
foo 函数调用 read 类函数(文档中 call 0x8048330,对应读取用户输入)时,未检查输入字符串的长度,若用户输入超过 28 字节,前 28 字节会正常填充预留的缓冲区,超出的字节会 “溢出” 到缓冲区后方的栈空间 —— 而栈上缓冲区后方,恰好存储着函数的 返回地址(EIP 要指向的下一条指令地址)。溢出的字节会覆盖这个返回地址,导致函数执行完后,CPU 不再跳回原本的调用处(如 main 函数),而是跳转到被覆盖的地址,从而改变程序执行流。
主函数调用foo函数后,我们要在堆栈上压上getShell函数的返回地址0804847d
2.3.2 确认输入字符串哪几个字符会覆盖到返回地址
用gdb工具对pwn20232303_1进行调试:
输入1111111122222222333333334444444455555555可以看到eip被5555覆盖了:
进一步将55555555替换为12345678输入,发现eip被1234替换。1234 那四个数最终会覆盖到堆栈上的返回地址,进而CPU会尝试运行这个位置的代码。那只要把这四个字符替换为 getShell 的内存地址,输给pwn20232303_1,就会运行getShell
2.3.3 确认用什么值来覆盖返回地址
getShell的内存地址为0804847d,通过观察实际存储顺序,实际应该输入的是11111111222222223333333344444444\x7d\x84\x04\x08
2.3.4 构造输入字符串
因为无法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以先生成包括这样字符串的一个文件。\x0a表示回车。
用16进制查看指令xxd查看input文件的内容:
通过管道符|将input20232303的输入作为pwn_20232303_1的输入:
可以看到图片中成功获得shell并用ls命令查看了当前目录下的文件,说明getShell函数成功调用。
2.4 注入Shellcode并执行
2.4.1 准备一段shellcode
shellcode就是一段机器指令(code)
通常这段机器指令的目的是为获取一个交互式的shell(像linux的shell或类似windows下的cmd.exe),所以这段机器指令被称为shellcode。
在实际的应用中,凡是用来注入的机器指令段都通称为shellcode,像添加一个用户、运行一条指令。
按照实验指导书,本实验采用的shellcode为\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\
2.4.2 准备工作
安装execstack(详见3.问题与解决方案部分),修改设置:
execstack:是 Linux 下专门用于查询 / 修改 ELF 格式可执行文件 “GNU_STACK” 头部标志的工具
-s:是 execstack 的关键参数,全称 “set-execstack”,功能是设置文件的堆栈为可执行状态;
在漏洞利用(如Shellcode 注入攻击)场景中,攻击者常将 Shellcode(可执行的攻击代码)注入到程序的堆栈段。若堆栈默认 “不可执行”,CPU 会拒绝运行堆栈中的代码,攻击失败;
-q:是 execstack 的 “查询(query)” 选项,作用是读取目标文件的堆栈权限标识,不做任何修改。后续输出的X pwn20232303_1表示 pwn20232303_1 的堆栈被设置为 “可执行”(允许注入的 shellcode 在堆栈中运行)
more /proc/sys/kernel/randomize_va_space
是 Linux 系统中用于查看内存地址随机化(ALSR,Address Space Layout Randomization)当前配置的命令。输出值2代表完全随机化,在 1 的基础上,额外对堆(heap)区域的基地址进行随机化(系统默认通常为 2,安全性最高)
echo "0" > /proc/sys/kernel/randomize_va_space
用于关闭 Linux 系统的内存地址随机化机制(ASLR,Address Space Layout Randomization)。再次查看内存地址随机化后输出值为0表示完全关闭 ASLR,程序每次运行时,代码段、堆栈、库的内存地址均固定不变。
2.4.3 构造要注入的payload
采用nsr模式构造攻击buf:
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"' > input_shellcode_20232303
最后的\x4\x3\x2\x1将用于得到shellcode的地址后覆盖堆栈上的返回地址的位置。
接下来确定\x4\x3\x2\x1应该填什么。
在当前终端注入这段攻击buf:
再打开另外一个终端,用gdb调试pwn20232303_1这个进程:
在原来的终端按下回车
在另一个终端里继续调试查找esp寄存器内容:
发现了01020304,应该替换的地址便是0xffffcf2c + 4 = 0xffffcf30
重新构造输入文件:
通过管道符|将input_shellcode_20232303_1的输入作为pwn_20232303_1的输入:
可以看到图片中成功获得shell并用ls命令查看了当前目录下的文件,说明实验成功。
3. 问题及解决方案
- 问题1:想通过WinSCP把本地下载的pwn1文件复制到虚拟机里,但是一开始连接不上虚拟机
- 问题1解决方案:通过以上截图询问AI,分析出问题不在于IP地址配置错误(因为已经正确获取了虚拟机的IP并填写了),而在于Kali虚拟机内部的SSH服务未运行。Kali Linux 默认不开启SSH服务,是出于安全考虑。因此需要先在虚拟机内启动并配置SSH服务。接下来我根据步骤安装并启动SSH服务,检查SSH服务状态,检查防火墙,完成以上所有步骤后,就成功连接了。
具体步骤如下:
- 问题2:运行程序提示权限不够
- 问题2解决方案:在实验场景中,由于从外部传入虚拟机的文件默认没有可执行权限(仅读 / 写),直接运行会报错,因此需通过下方图片中的命令赋予其执行权限,才能后续进行反汇编、漏洞利用等操作
- 问题3:无法正确安装execstack
- 问题3解决方案:寻求同学的帮助,从ubuntu官网进行了下载安装,具体见图片
4. 学习感悟与思考
本次实验从动手实践层面串联起了整个第二章目前所学习的内容。在做实验的过程中能够感受到自己所学知识被实际运用的通畅,同时好多只看概念与图示理解没有那么清晰的知识也在实际计算与实现中加深了理解与体悟。例如实验中对汇编指令、寄存器等的实际运用使PPT里抽象的概念变得鲜活。再如第一个实践内容改变程序执行流程也让我在实际操作中对函数调用过程理解的更加清晰。同时第三个实践内容对于shellcode的操作也与刚讲的内容结合的十分紧密,虽然这部分仍需要我继续研究一下,但本次实验给出了一个非常直观、可操作性强的理解渠道,对我后续继续理解很有帮助。