RTIS CTF 战队初建,需要大量对 CTF 感兴趣且有大量学习时间的小伙伴加入,急缺 pwn、逆向相关的小伙伴,欢迎有兴趣的同学将自己的个人简介发送到,如果能让我看到你的学习热情,我将拉你进我们的交流群,跟一群志同道合的小伙伴一起学习,一起打比赛,欢迎你的加入。这两天闲着无聊就在上找个小软件练练手,但是上面给的 poc 并能正常运行,无奈只好自己写了一个,顺便写篇文章把自己写的思路分享给大家。软件介绍及环境搭建漏洞软件:虚拟机:实验工具:软件下载地址及POC:https://pan.baidu.com/s/1kWdnKkZ这款软件有点年份了,并且开启了保护机制。我们的目的是通过使用技术绕过并执行,我会把文章重点着重放在链的构造上。开始调试并找到溢出位置漏洞的成因及利用:该播放器在打开文件时会调用的某个函数并发生栈溢出,我们通过构造超长字符串溢出覆盖指针和处理函数并触发异常执行我们的链,通过链使所在的内存区域变为可执行紧接着去调用。什么是.m3u文件?文件是音频文件的列表文件,是纯文本文件。播放器会根据它的记录找到网络地址进行在线播放。开始调试程序……分析这种文件型漏洞,一般有两种方法:1、对相应的文件后缀下断2、对函数下断先选择第一种载入软件,运行软件,搜索
可以找到两个,这里注意一下,还搜索出了两个(也是一种,只是它的编码格式是格式。用字符集编码),全部下断,接着点击软件左上角打开一个文件,程序断在了处
接着单步,你会进入到这样的一个死循环,emmmm……
对 windows 消息机制熟悉的人,一眼就能看出来,这是一个消息循环,消息循环的大致流程是这样的,先获取消息(),如果有消息到达就调用回调处理函数(),如果消息是,则退出消息循环。很明显这是一条死胡同,出师不利……只能换第二种方法了:接着我们对下断(),重新载入软件,软件直接断在了处………???我还没载入文件呢,怎么就直接断了?看了一眼堆栈。
??看了一眼屏幕的右下角,emmmm……大概知道原因了。但是我在任务管理器里面始终找不到的进程,可能是程序加载了的模块,而这个模块一直在循环(这难道是键盘记录嘛,不懂不懂,求大佬赐教)。还懒得卸载,折腾了一会,放弃了。哎,不溯源了,直接异常跟踪吧!换!先用脚本生成一个由 5000 个组成的文件,
file="myMplayer.m3u"
buf="\x41"*5000
fobj=open(file,"w")
fobj.write(buf)
fobj.close()
重启软件,附加,用软件打开该文件,触发异常,紧接着查看堆栈调用
可以看到,可以在这里下个断点,然后单步跟,在处,程序把文件所在路径和 5000 个复制到的栈中。如果字符串够长,淹没了整个栈,那么就会触发异常,执行异常处理函数。(如果你不想单步跟,直接在处下断即可)
这里要强调一点我的文件是放在 c 盘根目录的,想放到其他位置也可以,但是相应的缓冲区会发生变化,大家可以自行调试,接着我们把字符串增加到 6000 个,可以看到最后一个被覆盖,整个栈笼罩在的阴影之下。的范围是。
通过我们可以了解到程序发生异常时的值为,并不在范围内,也就是说,不在我们可控的范围之内。所以我们要覆盖的处理函数所执行的操作应该是使变大,使跳到我们的构造好的缓冲区内。在程序中寻找这些代码片段的地址,运气还行,找到了两个第一个位于,反汇编如下:
addesp,17cc
popebx
popesi
popedi
popebp
retn
第二个位于,反汇编如下:
addesp940
popebx
popesi
popedi
retn
我选择第一个,因为它有,详细原因构造链的时候再说。先了解一下什么是?ROP 技术简介:连续调用程序代码本身的内存地址,以逐步地创建一连串欲执行的指令序列。技术主要是对抗微软的保护机制的。什么是?的运行机制是,Windows 利用标记只包含数据的内存位置为非可执行(),当应用程序试图从标记为的内存位置执行代码时,Windows 的逻辑将阻止应用程序这样做,从而达到保护系统防止溢出。换句话说微软通过技术把数据和代码彻底的分离了。我们在栈中放置的在没有特殊处理的情况下是无法被执行的。怎么绕过呢?一般使用技术,常用的技术通过调用系统自带的 API 如函数(关闭)、函数(将指定的内存空间改为可执行)、函数(创建一段可执行的内存)来达到绕过的效果。之后微软引入了技术来对抗不过这都是后话了……这里我选择构造链使所在的内存区域变为可执行。ROP 链的设计把处理函数覆盖为,重新载入,并在处下断,执行 4 个之后,观察堆栈,可以看到变为了。
也就是说从位置开始就是链了,然后在链下面存放就行了先介绍一下函数吧:
BOOLVirtualProtect(
LPVOIDlpAddress,// 目标地址起始位置
DWORDdwSize,// 大小
DWORDflNewProtect,// 请求的保护方式
PDWORDlpflOldProtect// 保存老的保护方式
);
我们只需要处在低址,而处在高址,并且 size 的大小不超过就行。
BOOLVirtualProtect(
LPVOIDlpAddress,// 小于shellcode的起始位置
DWORDdwSize,// 大小不越界
DWORDflNewProtect,// 这个值设为0x40表示可读可写可执行
PDWORDlpflOldProtect// 随便一个可写低址就行
);
怎么构造ROP链呢?上面这些参数存到哪里呢?以我的经验,参数可以存到两个地方,一个是寄存器,一个是栈。当然的地址也要相应的存在寄存器或栈中。这里我选择寄存器,因为如果存在栈中的话你需要不断的改变mov [esp],某个寄存器并且不断调用这样的语句对栈的内容进行赋值,操作简单但是过于繁琐。最后会发现链会非常长,影响观看(不过有兴趣的可以试试,也是可以达到效果的)既然选择了寄存器,那么即使四个参数和函数地址都已经存入寄存器,我们该怎么执行呢?调用这个语句push adretn的含义是把依次入栈,这里我们不妨调试一下,首先我们找到的地址,通过插件进行搜索,找到了一组,地址为。把该地址放在链的首端,也就是把的值覆盖为(具体怎么覆盖,大家可以自行计算),顺便手动把中这几个寄存器的值都改变一下(的值不能动)如图:
寄存器的值依次为。接着单步跳到处执行,查看堆栈情况:
可以看到栈接下来会执行的内容,也就是的内容。那么我们把的值改成的地址(下的值为),重新调试。如下图:
相信大家都已经看明白了。如果选为函数地址的话:就是函数执行完的返回地址就是第一个参数就是第二个参数就是第三个参数就是第四个参数但是这样做可行吗?很明显不可行,因为的值也就是的值超出的范围了,函数肯定返回失败,那么就剩下两种方法了
eax
ecx//lpflOldProtect
edx//flNewProtect
ebx//dwSize
esp//lpAddress
ebp//返回地址
esi//VirtualProtect地址
edi
这里我们要巧妙的利用,由于的值不可改变,所以我们直接把的值当成返回地址,而且这个返回地址的内容正好被我们的覆盖了。那么的值就应该是的地址了。问题来了,怎么把的值变成地址呢?emmmm…… 还记得之前选择处理函数的时候,我选择了:
addesp,17cc
popebx
popesi
popedi
popebp
retn
看到没?只需要计算一下时栈的内容,我们将其覆盖成的地址就行了。接下来就是对四个参数进行运算了,先计算第一个参数()这个参数只要求小于的地址即可,由于本来就位于高址,而且是函数的返回地址,也是的起始地址,所以我们干脆把的值等于就行了,接着我在处找到了这样的指令:
pushesp
popebx
popesi
retn
关于,我们可以随便给弹一个值就行了计算第二个参数()我的想法是让做运算,最后把的值赋给。我在处找到给清零的指令xor eax,eax接着在处找到add eax,69retn我们连续执行这条指令三次把的值变为(十进制)。又在处找到了给赋值的指令mov edx,eaxmov eax,edxretnok!计算第三个参数()我们需要把的值变为,在这个参数上我思考了好久,一直没有找到解决方法,本来我是这么打算的
xoreax,eax
addeax,8
addeax,8
addeax,8
addeax,8
addeax,8
addeax,8
addeax,8
addeax,8
movcl,al
发现在执行的时候产生异常,莫名其妙的异常而且还不知道怎么解决,折腾了半天,就换了种思路……能不能利用?在处找到了mov cl,bl也就是说我们只需要把的值等于即可如何把的值变为呢?一开始我满脑子只有计算,既然上面的使得等于了,那么我直接或者不就行了吗?然而,我内存空间中找不到这样的指令……这就很麻烦了,难道还要用和?其实我们不妨跳出计算的思维,直接不就行了吗?请看这一段指令
addesp,17cc
popebx
popesi
popedi
popebp
retn
emmmmm…… 熟不熟悉?说实话之前选择处理函数的时候,并没有想到这段指令会帮我完成这么多复杂的操作。只需要的时候把栈的值覆盖为即可,接着让清零,执行就行了。计算第四个参数()这个就很简单了,直接,把的值变为一个可写地址就行,我选择的可执行地址是好了!接下来就开始组装了,这里要注意一点,也就是的操作一定要最后再处理,具体原因看下面的代码就懂了。下面是组装后的汇编指令
xoreax,eax//计算第二个参数
addeax,69
retn
addeax,69
retn
addeax,69
retn
movedx,eax
moveax,edx
retn
popecx//计算第三个参数,popecx使ecx等于FFFFFFFF,当然你也可以直接xorecx,ecx
retn
incecx
retn
movcl,bl
popeaxretn //计算第四个参数
pushesp//计算第一个参数
popebx
popesi
retn
pushad//最后push ad
retn
ok,按照这样的思想,我们在里面进行构造,下面只是中的片段
进行调试如图
这里有一个严重的问题,就是之后,会先执行的内容,而我们把它覆盖成了……emmmm…… 并且我们还想跳过执行的内容,有没有一个指令能帮我们呢?聪明的你已经想到了!对!那就是,我们只需要把的值改成的地址就行了修改后的 POC(部分)如下:
重新运行
完美!接着按f9运行就可以看到我们的计算器弹了出来
结束!小结这篇文章主要向大家展示了链的构造。如果大家没有看太懂,不妨通过我写好的,一步一步进行调试,体会的精髓。可能大家已经发现了,我写的文章会把自己当时出现的错误,以及思考的过程全部向大家展示出来,这样大家看的时候会有一种代入感,而且最重要的是对我来讲这是一种令人难忘的回忆。如果前辈们有什么不同的看法,欢迎大家在下面留言。
领取专属 10元无门槛券
私享最新 技术干货