通过实例学习ROP技术

这两天闲着无聊就在 上找个小软件练练手,但是 上面给的 poc 并能正常运行,无奈只好自己写了一个,顺便写篇文章把自己写 的思路分享给大家。

软件介绍及环境搭建

漏洞软件:

虚拟机:

实验工具:

软件下载地址及POC:

https://pan.baidu.com/s/1kWdnKkZ

这款软件有点年份了,并且开启了 保护机制。我们的目的是通过使用 技术绕过 并执行 ,我会把文章重点着重放在 链的构造上。

开始调试并找到溢出位置

漏洞的成因及利用:该播放器在打开 文件时会调用 的某个函数并发生栈溢出,我们通过构造超长字符串溢出覆盖 指针和 处理函数并触发异常执行我们的 链,通过 链使 所在的内存区域变为可执行紧接着去调用 。

什么是.m3u文件/strong>

开始调试程序……

分析这种文件型漏洞,一般有两种方法:

1、对相应的文件后缀下断

2、对 函数下断

先选择第一种

载入软件, 运行软件,搜索

可以找到两个 ,这里注意一下, 还搜索出了两个 ( 也是一种 ,只是它的编码格式是 格式。 用 字符集编码),全部下断,接着点击软件左上角打开一个 文件,程序断在了 处

接着单步,你会进入到这样的一个死循环,emmmm……

对 windows 消息机制熟悉的人,一眼就能看出来,这是一个消息循环,消息循环的大致流程是这样的,先获取消息( ),如果有消息到达就调用回调处理函数( ),如果消息是 ,则退出消息循环。

很明显这是一条死胡同,出师不利……只能换第二种方法了:

接着我们对 下断( ),重新载入软件,软件直接断在了 处………/p>

我还没载入 文件呢,怎么就直接断了了一眼堆栈。

看了一眼屏幕的右下角,emmmm……

大概知道原因了。但是我在任务管理器里面始终找不到 的进程,可能是程序加载了 的模块,而这个模块一直在循环 (这难道是键盘记录嘛,不懂不懂,求大佬赐教)。还懒得卸载,折腾了一会,放弃了。

哎,不溯源了,直接异常跟踪吧!换 !先用 脚本生成一个由 5000 个 组成的 文件,

重启软件, 附加,用软件打开该文件,触发异常,紧接着查看堆栈调用

可以看到 ,可以在这里下个断点,然后单步跟,在 处,程序把 文件所在路径和 5000 个 复制到 的栈中。如果字符串够长,淹没了整个栈,那么就会触发异常,执行 异常处理函数。(如果你不想单步跟,直接在 处下断即可)

这里要强调一点我的 文件是放在 c 盘根目录的,想放到其他位置也可以,但是相应的缓冲区会发生变化,大家可以自行调试,接着我们把字符串增加到 6000 个,可以看到最后一个 被覆盖,整个栈笼罩在 的阴影之下。

的范围是 。

通过 我们可以了解到程序发生异常时 的值为 ,并不在 范围内,也就是说, 不在我们可控的范围之内。所以我们要覆盖的 处理函数所执行的操作应该是使 变大,使 跳到我们的构造好的缓冲区内。

在程序中寻找这些代码片段的地址,运气还行,找到了两个

第一个位于 ,反汇编如下:

第二个位于 ,反汇编如下:

我选择第一个,因为它有 ,详细原因构造 链的时候再说。

先了解一下什么是 /p>

ROP 技术简介

:

连续调用程序代码本身的内存地址,以逐步地创建一连串欲执行的指令序列。 技术主要是对抗微软的 保护机制的。

什么是 /strong>

的运行机制是,Windows 利用 标记只包含数据的内存位置为非可执行( ),当应用程序试图从标记为 的内存位置执行代码时,Windows 的 逻辑将阻止应用程序这样做,从而达到保护系统防止溢出。换句话说微软通过 技术把数据和代码彻底的分离了。我们在栈中放置的 在没有特殊处理的情况下是无法被执行的。

怎么绕过呢/strong>

一般使用 技术,常用的 技术通过调用系统自带的 API 如 函数(关闭 )、 函数(将指定的内存空间改为可执行)、 函数(创建一段可执行的内存)来达到绕过 的效果。之后微软引入了 技术来对抗 不过这都是后话了……

这里我选择 构造 链使 所在的内存区域变为可执行。

ROP 链的设计

把 处理函数覆盖为 , 重新载入,并在 处下断,执行 4 个 之后,观察堆栈,可以看到 变为了 。

也就是说从 位置开始就是 链了,然后在 链下面存放 就行了

先介绍一下 函数吧:

我们只需要 处在低址,而 处在高址,并且 size 的大小不超过 就行。

怎么构造ROP链呢面这些参数存到哪里呢/strong>

以我的经验,参数可以存到两个地方,一个是寄存器,一个是栈。

当然 的地址也要相应的存在寄存器或栈中。

这里我选择寄存器,因为如果存在栈中的话你需要不断的改变

mov [esp],某个寄存器

并且不断调用这样的语句对栈的内容进行赋值,操作简单但是过于繁琐。最后会发现 链会非常长,影响观看(不过有兴趣的可以试试,也是可以达到效果的)

既然选择了寄存器,那么即使四个参数和函数地址都已经存入寄存器,我们该怎么执行呢用这个语句

push ad

retn

的含义是把 依次入栈,这里我们不妨调试一下,首先我们找到 的地址,通过 插件进行搜索,找到了一组,地址为 。

把该地址放在 链的首端,也就是把 的值覆盖为 (具体怎么覆盖,大家可以自行计算),顺便手动把 中这几个寄存器的值都改变一下( 的值不能动)如图:

寄存器的值依次为 。接着单步跳到 处执行 ,查看堆栈情况:

可以看到栈接下来会执行 的内容,也就是 的内容。那么我们把 的值改成 的地址( 下 的值为 ),重新调试。如下图:

相信大家都已经看明白了。

如果选 为 函数地址的话:

就是函数执行完的返回地址

就是第一个参数

就是第二个参数

就是第三个参数

就是第四个参数

但是这样做可行吗/strong>

很明显不可行,因为 的值也就是 的值超出 的范围了,函数肯定返回失败,那么就剩下两种方法了

这里我们要巧妙的利用 ,由于 的值不可改变,所以我们直接把 的值当成返回地址,而且这个返回地址的内容正好被我们的 覆盖了。

那么 的值就应该是 的地址了。

问题来了,怎么把 的值变成 地址呢/strong>

emmmm…… 还记得之前选择 处理函数的时候,我选择了:

看到 没/p>

只需要计算一下 时栈的内容,我们将其覆盖成 的地址就行了。

接下来就是对四个参数进行运算了,

先计算第一个参数 ()

这个参数只要求小于 的地址即可,由于 本来就位于高址,而且 是函数的返回地址,也是 的起始地址,所以我们干脆把 的值等于 就行了,接着我在 处找到了这样的指令:

关于 ,我们可以随便给 弹一个值就行了

计算第二个参数 ()

我的想法是让 做运算,最后把 的值赋给 。

我在 处找到给 清零的指令

xor eax,eax

接着在 处找到

add eax,69

retn

我们连续执行这条指令三次把 的值变为 (十进制 )。

又在 处找到了给 赋值的指令

mov edx,eax

mov eax,edx

retn

ok!

计算第三个参数()

我们需要把 的值变为 ,在这个参数上我思考了好久,一直没有找到解决方法,本来我是这么打算的

发现在执行 的时候产生异常,莫名其妙的异常而且还不知道怎么解决,折腾了半天,就换了种思路……

能不能利用 /strong>

在 处找到了

mov cl,bl

也就是说我们只需要把 的值等于 即可

如何把 的值变为 呢/strong>

一开始我满脑子只有计算,既然上面的 使得 等于 了,那么我直接 或者 不就行了吗而,我内存空间中找不到这样的指令……

这就很麻烦了,难道还要用 和 /p>

其实我们不妨跳出计算的思维,直接 不就行了吗看这一段指令

emmmmm…… 熟不熟悉实话之前选择 处理函数的时候,并没有想到这段指令会帮我完成这么多复杂的操作。

只需要 的时候把栈的值覆盖为 即可,接着让 清零,执行 就行了。

计算第四个参数()

这个就很简单了,直接 ,把 的值变为一个可写地址就行,我选择的可执行地址是

好了!接下来就开始组装了,这里要注意一点, 也就是 的操作一定要最后再处理,具体原因看下面的代码就懂了。

下面是组装后的汇编指令

ok,按照这样的思想,我们在 里面进行构造,下面只是 中 的片段

进行调试如图

这里有一个严重的问题,就是 之后,会先执行 的内容,而 我们把它覆盖成了 ……

emmmm…… 并且我们还想跳过 执行 的内容,有没有一个指令能帮我们呢/p>

聪明的你已经想到了!对!那就是 ,我们只需要把 的值改成 的地址就行了

修改后的 POC(部分)如下:

重新运行

完美!接着按f9运行就可以看到我们的计算器弹了出来

结束!

小结

这篇文章主要向大家展示了 链的构造。如果大家没有看太懂,不妨通过我写好的 ,一步一步进行调试,体会 的精髓。

可能大家已经发现了,我写的文章会把自己当时出现的错误,以及思考的过程全部向大家展示出来,这样大家看的时候会有一种代入感,而且最重要的是对我来讲这是一种令人难忘的回忆。

如果前辈们有什么不同的看法,欢迎大家在下面留言。

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2022年2月22日
下一篇 2022年2月22日

相关推荐