20232327 2025-2026-1 《网络与系统攻防技术》实验一实验报告
1.实验内容
在本周的课程学习了缓冲区溢出和shellcode攻击的内容,以下是一些基本概念和解释:
- 缓冲区:连续的一段存储空间;
- 缓冲区溢出攻击BOF(Buffer Overflow,缓冲区溢出):写入缓冲区的数据量超过缓冲区能容纳的最大限度,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行的完整性;
- 一些缓冲区溢出漏洞:红色代码、冲击波病毒、震荡波病毒、心脏出血、勒索病毒...
- shellcode:为了获取一个交互式shell的一段机器指令
在实验之前需要掌握的一些知识和原理:
- GDB调试代码:设置断点:
break/clear
、启用/禁用断点:enable/disable
、运行程序:run
、继续运行:continue
、单步代码跟入函数:step(源代码层面)
、单步指令跟入函数:stepi(汇编代码层面)
...... - 小端序:多字节数据的 “低字节存低地址,高字节存高地址”,x86架构是小端序;
- 汇编语言
本次实验用到的寄存器:
EBP栈底指针寄存器
:当前堆栈的栈底指针
EIP指令寄存器
:指向下一条指令的地址 - 反汇编指令
objdump
本次实验用到的汇编指令:x86 架构下基础指令机器码表(32 位模式,无复杂操作数)
汇编指令 | 核心功能 | 机器码(十六进制) | 说明 |
---|---|---|---|
NOP | 空操作,无任何功能 | 90 | 固定 1 字节,常用于延时或指令对齐 |
JMP | 无条件跳转(短跳转) | EB xx | “xx” 为相对偏移量(8 位),总长度 2 字节 |
JMP | 无条件跳转(近跳转) | E9 xx xx xx xx | “xx xx xx xx” 为 32 位相对偏移量,总长度 5 字节 |
CMP | 比较两个操作数(如 AL 与 BL) | 38 D8 | 示例为CMP AL, BL ,操作数不同机器码不同 |
JE | 相等则跳转(短跳转) | 74 xx | 基于 CMP 结果,ZF 标志为 1 时跳转,“xx” 为 8 位偏移量 |
JNE | 不相等则跳转(短跳转) | 75 xx | 基于 CMP 结果,ZF 标志为 0 时跳转,“xx” 为 8 位偏移量 |
CALL | 调用子程序(近调用) | E8 xx xx xx xx | “xx xx xx xx” 为 32 位相对偏移量,总长度 5 字节 |
跳转 / 调用的偏移量:JMP、JE、JNE、CALL 的 “xx” 部分是相对偏移量,计算方式为 “目标地址 - 当前指令下一条地址”,
偏移量的计算遵循公式:偏移量(补码)= 目标函数地址 - 下一条指令地址
本次实验的说明
- 实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。(在实验中修改为文件名为20232327pwn)
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。 - 三个实践内容
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 注入一个自己制作的shellcode并运行这段shellcode。
2.实验过程
2.1 直接修改程序机器指令,改变程序执行流程
在安装好的kali虚拟机中,上传pwn1文件,解压后将其改名为pwn20232327
使用命令objdump -d pwn20232327 | more
将其进行反汇编,得到下图中的结果
找到main函数调用foo函数(call)指令的这一行,计算可知:
偏移量 = 0x08048491 - 0x080484ba = -0x29(十进制 - 41)
将 - 0x29 转换为 32 位补码:0xFFFFFFD7(补码计算规则:负数的补码 = 其绝对值的二进制取反 + 1,0x29 取反为 0xFFFFFFD6,加 1 后为 0xFFFFFFD7)
因此,CALL foo的机器码为E8 D7 FF FF FF,与文档中反汇编结果一致。
现在需要修改偏移量,让main函数中执行到这一行后,调用getShell
(入口 0x0804847d)函数,所以同样计算可知:
偏移量 = 0x0804847d - 0x080484ba = -0x3D(十进制 - 61)
所以接下来修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff,就可以完成getshell调用,完成指令流的改变;
复制一份原文件cp pwn20232327 pwn20232327_2
在vim编辑器中,使用命令%!xxd
将显示模式切换为16进制模式,在命令模式中,输入e8 d7
(注意中间空格)查找到对应内容(在000004b0行),按i
进入编辑模式,将d7修改为c3;按Esc退出编辑模式,%!xxd -r
转回原格式,保存并退出;
重新反汇编,查看是否正确调用
运行修改后的文件,会得到shell提示符,运行shell;
2.2 通过构造输入参数,造成BOF攻击,改变程序执行流
复制一份原文件cp pwn20232327 pwn20232327_3
重新反汇编这个文件,观察这段代码
foo函数存在BOF漏洞:foo 函数中调用了 gets 函数,而gets函数不会检查输入字符串的长度,会持续读取输入直到遇到换行符(\n)或文件结束符;
汇编中栈空间的分配:foo 函数在栈上为局部变量分配了 0x38 字节(56 字节)的空间,超过就会溢出;lea -0x1c(%ebp),%eax可知缓冲区大小为28字节,加上EBP4字节,一共需要32字节来覆盖返回地址;
调用foo之后的返回地址:call 8048491
使用GDB进行调试,输入111111112222222233333334444444455555555
,程序崩溃,EIP为0x35353535
;
再次运行,输入1111111122222222333333334444444412345678
,EIP值为0x34333231(1234)
所以这里只要把1234的值替换为getshell的内存地址(0x0804847d)即32字节+\x7d\x84\x04\x08
,(x86架构小端序)就可以调用getshell函数;
使用输出重定向“>”将perl生成的字符串存储到文件input中,然后将input的输入,通过管道符“|”,作为pwn1的输入。
成功进行getshell函数的调用
或者直接使用命令
(perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"';cat) | ./pwn1
作为输入,完成getshell函数的调用
2.3 注入Shellcode并执行
2.3.1 准备工作
设置堆栈可执行,关闭地址虚拟化
2.3.2 尝试使用nop+shellcode+retaddr
方法构造payload(此方法不可行)
构造payloda并注入攻击buf
找到对应的进行号,并对其进行断点调试;
找到shellcode的地址
此时运行,会发现payload失败,所以此处使用nop+shellcode+retaddr
的方法不可行
2.3.3重新开始,使用结构:anything+retaddr+nops+shellcode
和上面一样的步骤,先随机进行注入,找到esp指向的地址;
看到0x01020304,在之后的就是就是返回地址的位置,计算可知:
0xffffcf5c+4=0xffffcf60,所以改payload内容为
perl -e 'print "A" x 32;print "\x60\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\x90\x00\xd3\xff\xff\x00"' > input_shellcode
成功完成注入!
BOF防御
- 防止注入,确保堆栈上特定值不变;
- 将堆栈设置不可执行;
- ALSR地址随机化,每次运行都用不同的地址;
- 编写代码时,注意边界检测,使用安全的库函数;
- 64位操作系统地址空间大,注入难度大;
3.问题及解决方案
- 问题1:xxd缺失
- 问题1解决方案:xxd是vim的其中一个包,输入无效时可以
apt install xxd
下载一个(不知道我的vim编辑器为什么会缺少),或者使用wxhexeditor16进制编辑器; - 问题2:kali系统没有网卡,连接不到网络:
- 问题2解决方法:https://blog.csdn.net/weixin_44937163/article/details/147394267
这里使用上面的解决方法,启动wth0网卡,设置dhcp自动获取ip,虚拟机设置为NAT方式连接网络; - 问题3:
apt install
公钥缺失问题:
- 问题3解决方法:这是由于kali软件源的GPG公钥缺失导致,这里使用A豆包给出的解决方法:
使用命令sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ED65462EC8D5E4C5
从服务器获取并安装公钥,就可以解决上面的问题 - 问题4:在使用GDB调试确定输入字符串中哪些字符会覆盖到程序的返回地址时,为什么输入都是8个1、8个2、8个3,而不是4个或者16个?
- 问题4解答:询问豆包得到的回答:x86 架构的 “栈对齐” 特性;x86 处理器的栈操作默认以 4 字节(32 位)或 8 字节(64 位) 为对齐单位(即栈上的数据地址通常是 4 或 8 的倍数)。在 32 位系统中,函数调用时的栈帧(Stack Frame)通常按 4 字节对齐,但为了兼容更多场景(如 64 位指令、SSE 指令等),也常以 8 字节作为实际调试中的 “观察粒度”。
- 问题五:在2.3 注入Shellcode并执行这一部分中,关闭文件的堆栈可执行,
- 问题5解决方案:但是execstack工具下载很麻烦,可以使用patchelf来进行代替;(其实也可以不用,两种方法查看后发现在编译时已经设置为堆栈可执行,其实这一部可以省略)
4.学习感悟、思考等
- 安装kali虚拟机的时候,最好直接下载vmware版本,不要下载镜像装载,否则就会出现上面遇到的各种奇怪问题(室友使用vmdk文件就不会出现上面的一些问题);
- 应该平时注重积累掌握一些常用的linux命令,在做实验的过程中帮助很大;GDB调试命令不是很熟悉,应该学习掌握一些;对汇编知识掌握了解不多,在接下来的学习过程努力掌握;
- 在实验过程中感受到调用函数时,栈的增长变化,通过 GDB 调试观察函数调用过程,清晰看到栈从高地址向低地址增长的实际变化 ;
- 实际体会感受缓冲区溢出漏洞的危害性之大,谨慎使用fgets等不检查输入边界的函数;
参考资料
- kali无eth0网卡
- kali如何设置中文
- ...