看了趋势的博文后尝试手动构造了一下CVE-2018-8373的expoit,大概花了我一个下午的时间,晚上回来记录了一下整个过程。
本文仅作分享,不足之处请见谅。
内容见下文。(原文为:内容见附件)
CVE-2018-8373 是趋势科技上个月抓到的一个 vbscript UAF 0day,它是前几个月 CVE- 2018-8174 的兄弟漏洞。由于趋势的博文已经把漏洞原理和整个利用过程讲得十分清楚, 所以看完文章后的我只有一个问题:如何写一个 exp?
趋势公布的 hash 在 VT 上是没有的,而且文章中只给了 PoC 和部分高度混淆的在野利用片段,出于这个原因,我想自己构造一个 exp,供自己和组内的小伙伴分析调试使用。
由于这个漏洞的触发原理有一点点 0189 的影子,又有一点 8174 的影子,利用部分还有一点点 6332 的影子。所以在熟悉前 3 个漏洞的前提下,写出这个漏洞的 exp 应该不难,于是我白天花了几个小时试着写了个 exp。
鉴于此漏洞潜在的危害性,完整代码不予公开,本文仅记录构造过程中的一些细节。
UAF 的原理趋势已经讲的很清楚了,由于从页堆信息来看,每个二维数组的 tagSAFEARRAY 结构占内存空间为 0x30(实际的 tagSAFEARRAY 前面还多申请了 0x10 字节)。可以仔细看趋势的截图,或者自己动手做一下。
所以为了顺利 UAF,我们需要设计 pvData 部分大小为 0x30 的一维数组,也就是有 3 个元素的一维数组,令其为 array(2)。
我们对照趋势的图 4/ 图 11 , Get P 回调中对 array 数组的大小进行了更改: array(2)->array(100000),随后利用 array(i) = array2 循环申请大量小空间的二维数组 array2 , array2 被定义为 array2(0, 6)。此过程需要申请大量二维数组的tagSAFEARRAY 结构体,其中有一个会占用原来 array 的 pvAata 内存,并在 Get P 返回之后将 tagSAFEARRAY. rgsabound[1].cElement 覆盖为 0x0fffffff。
所以下一步就是找出哪个array2 的头部占用了原来 array 的 pvAata 内存,这个过程可以参考趋势图 16。
思路其实很简单,我们遍历新的 array 数组,找出其中的一维大小为 0x0ffffffe 的array2。我们假设这个 array2 在 array 中对应的下标为 index_vul。如下:
找到 array2 之后,我们可以用 array(index_vul)(a, b)来索引这个数组,其中 a∈
[0,0xffffffe],b∈[0,1]。这个数组可以越界读写 array(index_vul)(a, b).pvData开始的一大片内存。
现在我们拥有一个可以越界读写的数组,最终目的是要去实现任意地址读写。下一步如何进行?
从趋势的图 15/图 16 可以看到,这个漏洞借助了和 6332 一样的思路来进行利用,即借助大量连续内存申请来实现多个 array2.pvData 内存的连续,每个 array2.pvData 中间间隔 8 字节的堆数据。我们利用越界读写能力通过对 8 字节的错位进行赋值来改变 variant 变量在内存中的含义,从而构造一个基地址为 0,元素大小为 1,元素个数为 0x7ffffff 的一维数组;同时再泄露一块内存的地址供读写使用。从而实现任意地址读写。
现在的问题是:如何找出趋势图 14/15 中的 index_a 和 index_b?关于这点,原文中说得含糊其辞,而且从混淆的片段(图 14)看去步骤较为复杂,还涉及到部分没有出现在代码片段中的函数。
我在放弃理解图 14 的代码片段后,转而开始思考有没有简单一点的办法。从趋势图 15 我们可以看到index_a 和 index_b 所需要满足的位置。调试器中,我们可以看到 index_a 具有如下特征。
可以看到array(index_vul)(index_a-8, 0)至array(index_vul)(index_a-1, 0)都存储着短整型的3对应的variant结构,到了array(index_vul)(index_a, 0),由于堆结构数据,错位了8个字节,导致array(index_vul)(index_a, 0)代表的variant和前面后不一样。到了array(index_vul)(index_a+1, 0)。由于我们之前给每个array2元素都设置为3,见趋势图10。所以array(index_vul)(index_a+1, 0)开始代表的都是存储着长整型2对应的variant结构。
VB 有一个函数 VarType,可以用来判断 variant 的类型:
我们可以借助其实现如下逻辑:
这样我们就找到了满足条件的 index_a。
现在我们已经找到了 index_a,如何找到它对应的 index_b 呢?这个则更简单,我们可以利用越界写将array(index_vul)(index_a, 0)设置为一段字符串,例如“AAAA”,这样某一个 array2 的Data 域就变成了 8( 低 2 字节)。我们遍历 array 数组, 找出array(index_b)(0, 0)=8 的 index_b 即可,如下:
当然,上述做法存在一定的失败率,我相信原 exp 中的校验一定会更保险。但是这样的 exp对于本地调试来说,足够了。
在取得 index_a 和 index_b 之后。我们就得到了两个可以用来进行类型混淆的 variant 结构。后续就是借助假的字符串数据和类型混淆构造一个超长一维数组了。
我们模仿yuange的6332利用代码,令array(index_vul)(index_a, 0)处存储我们构造的字符串地址,令array(index_vul)(index_a+2, 0)处存储我们构造的用来伪造数组的字符串地址。然后借助array(index_b)(0, 0)和array(index_b)(2, 0)去分别改动两处的类型,令array(index_vul)(index_a, 0)处的variant 转化为长整型,令array(index_vul)(index_a, 0)处的variant转化为数组,整个过程只需要如下几句代码。从而获得一个超长数组和一个被泄露的字符串地址。
我们在调试器中看一下上述过程前后 array(index_vul)(index_a+2, 0)的数据变化。
rw_primit 第一句代码执行后:
执行rw_primit后:
现在,我们有了一个块泄露的内存 util_mem,一个可以实现 32 位下用户态任意地址读写的一维数组array(index_vul)(index_a+2, 0),接下来就是利用这两个工具封装任意地址读写函数了。
任意地址读:
任意地址写是一样的原理,此处不再贴出。相关原理在其他分析 6332/0189/8174 的文章中已经有很多详细说明,此处不再提及。
借助上述封装好的函数,将 8174 的利用代码稍微改造一下就可以实现后续步骤:
当然 8174 这种利用方法并不能躲避 ROP 检测,还是当年的 GodMode 比较经典。
anyway,经过几个小时的努力,我成功地在打了 2018 年 7 月份全补丁的 win7 x86 sp1IE11上弹了一个计算器:
像 CVE-2018-8174 和 CVE-2018-8373 这两个漏洞,思路上基本是 yuange 的漏洞利用技术+TK 的利用技术,威力可见一斑。
参考链接:
https://blog.trendmicro.com/trendlabs-security-intelligence/use-after- free-uaf-vulnerability-cve-2018-8373-in-vbscript-engine-affects-
本文原作者:银雁冰(看雪ID)于 2018/08/17发布在看雪论坛
原文链接:[原创]记一次CVE-2018-8373利用构造过程-『二进制漏洞』-看雪安全论坛
转载请注明转自看雪论坛。