查看原文
其他

CVE-2018-15982 flash 0day漏洞分析报告

银雁冰 看雪学院 2019-05-25

前言


本文是这次flash 0day出来之后对这个漏洞的调试报告,文中的大部分都已经发布在核心安全技术博客的综合分析文章上。限于篇幅,原文中没有包含本文中的部分章节,特别是调试部分,即本文的“在调试器中看上述两个过程”对应章节,该部分对于理解这个漏洞的利用还是很有帮助的。

 

360威胁情报中心给出的分析部分shellcode分析得不错,可以两篇文章互补着看一下。

 

感谢核心安全成都分析团队对本次漏洞成因部分的精彩分析,感谢360高级威胁应对团队的大牛和小伙伴们对本文的指导。

 

水平有限,文中不足之处请见谅。由于截图较多,隐藏页边距时全文排版会好一点。



概述


该漏洞位于TVSDK内Metadata类的setObject函数,setObject在将String类型(属于RCObject)对象保存到Metadata类对象的keySet成员时,没有使用DRCWB(Deferred ReferenceCounted, with Write Barrier)。攻击者利用这一点,通过强制GC获得一个悬垂指针,在此基础上通过多次UAF进行多次类型混淆,随后借助两个自定义类的交互操作实现任意地址读写,在此基础上泄露ByteArray的虚表指针,从而绕过ASLR,最后借助HackingTeam泄露代码中的方式绕过DEP/CFG,执行shellcode。



我们完全逆向了整套利用代码,为方便读者阅读,下文所引述的代码均为重命名后的代码。



漏洞成因


1、Metadata类的setObject对应的Native函数如下图所示,实际实现在setObject_impl里。


2、在setObject_impl内,会直接将传入的键(String对象)保存到Metadata的keySet成员里。


Buffer结构体定义如下。(keySet成员的结构体与Buffer有一定差异,此处是为了便于说明)


3、add_keySet中保存传入的键(String对象)的汇编代码如下所示。

4、此时如果强制触发GC(垃圾回收机制),GC会认为传入的键未被引用,从而回收相应内存,然而Metadata对象的keySet成员中仍保留着指向被回收内存的指针。



UAF


准备工作


原利用代码首先申请0x1000个String对象,然后立即释放其中的一半,从而造成大量String对象的内存空洞,为后面的利用做准备。


第1次UAF


随后,利用代码定义了一个Metadata对象,借助setObject方法将key-value对保存到该对象中,Metadata对象的keySet成员保存着一个指向一片包含所有key(以String形式存储)的内存区域的指针。紧接着强制触发GC,此时keySet内保存着一个指向上述内存区域的(间接)悬垂指针(这里更准确的描述应该是:该内存区域内那些指向已被释放的String对象的指针没有被清零,导致可以通过keySet去操作已被释放的内存),随后读取keySet到arr_key数组,供后面使用。


得到悬垂指针后,利用代码立即申请256个Class5类对象保存到vec5(vec5是一个向量对象),由于Class5类对象的内存大小和String对象的内存大小一致(32位Class5对象为0x18字节),且相关对象分配在同一个堆内,根据MMgc内存分配算法,会有Class5对象占据之前被释放的(连续两个)String对象的内存空间。


其中Class5对象定义如下,可以看到该Class5有2个uint类型的成员变量,分别初始化为0x18和2200(0x898)。


随后遍历key_arr数组,找到其中长度变为0x18的字符串对象(在内存中,String对象的length字段和Class5的m_1成员重合),在此基础上判断当前位于32位还是64位环境,据此进入不同的利用分支。


第2次UAF


接上图,可以看到:在找到被Class5对象占用的String索引后,利用代码将arr_key的相关属性清零,这使得arr_key数组内元素(包括已占位Class5对象)的引用计数减少变为0,在MMgc中,对象在引用计数减为0后会立刻进入ZCT(zero count table)。随后利用代码强制触发GC,把ZCT中的内存回收,进入后续利用流程。下面我们主要分析32位环境下的利用流程。

 

在32位分支下,在释放了占位的Class5对象后,利用代码立即申请256个Class3对象并保存到另一个向量对象vec3中,此过程会重用之前被释放的某个(或若干)Class5对象的内存空间。


其中Class3对象的定义如下,它和Class5非常相似,两者在内存中都占用0x18字节。


可以看到Class3有一个m_ba成员和一个m_Class1成员,m_ba被初始化为一个ByteArray对象,m_Class1被初始化为一个Class1对象,Class1对象定义如下:


Class3对象占位完成后,利用代码立即遍历vec5寻找一个被Class3对象占用内存的原Class5对象。找到后,保存该Class5对象的索引到this.index_1,并保存该对象(已经变为Class3对象)的m_Class1成员到this.ori_cls1_addr,供后续恢复使用。



任意地址读写


32位下的任意地址读写


两轮UAF之后,利用代码紧接着利用上述保存的index_1索引,借助vec5[index_1]去修改被重用的Class3对象的m_Class1成员。随后立即遍历vec3去寻找被修改的Class3对象,将该对象在vec3中的索引保存到this.index_2。


到目前为止,利用代码已经得到两个可以操纵同一个对象的vector(vec5和vec3),以及该对象在各自vec中的索引(index_1和index_2)。接下来利用代码将在此基础上构造任意地址读写原语。

 

我们来看一下32位下任意地址读写原语的实现,从下图可以看到,借助两个混淆的Class对象,可以实现任意地址读写原语,相关代码在上图和下图的注释中已经写得很清楚,此处不再过多描述。关于减去0x10的偏移的说明,可以参考我们之前对cve-2018-5002漏洞的分析文章。



64位下的任意地址读写


64位下的任意地址读写原语和32位下大同小异,只不过64位下将与Class5混淆的类对象换成了Class2和Class4。此外还构造了一个Class0用于64位下的地址读取。


以下是这三个类的定义。


以下是64位下的任意地址读写原语,64位下的读写原语一次只能读写32位,所以对一个64位地址需要分两次读写。



在调试器内看上述两个过程(32位)


前面讲了那么多,还是调试器里见真章。我们在调试器内看一下32位环境下的两次UAF以及任意地址读写原语的工作过程。


第1次UAF


某次调试时,申请的Metadata对象在内存中如下:

 

第一次UAF之后,Class5对象进行了内存重用,我们在内存中看一下vec5以及其对应Class5对象:


可以看到上面两幅图中绿色高亮的对象地址由String对象变为了Class5对象。vec5中某个Class5对象重用了arr_key内第0n10(以及第0n11)个String对象的内存。


第2次UAF


随后,Class3对象会重用上述Class5对象的内存地址,从下图可以看到vec3中的第0n256个Class3对象重用了vec5中第0n33个Class5对象的内存地址。从这里我们已经可以得知index_1 = 0n33,index_2 = 0n256。


重用内存的Class3对象的初始元素如下,我们可以看到它的两个成员变量及它们对应的对象类型。


在执行下述代码后,上述Class3对象的m_Class1成员被修改。


在内存中查看被修改的Class3对象前后对比(m_Class1成员变成m_ba-1):


随后利用代码查找被修改后的Class3定位index_2,即0n256。



任意地址读写


借助index_1和index_2,分别用Class5对象和Class3对象对同一片内存区域进行操作,可以构造出任意地址读写原语,下面的调试日志展示了利用任意地址读原语读取ByteArray对象虚表指针的过程。


假设我们要读取addr地址处的4个字节,将m_Class1对象设为addr-0x10。利用代码中需要读取ByteArray对象首地址开始的4个字节,所以将m_Class1对象设为m_ba-0x10 = 0x0815e3f0。


可以看到ByteArray虚表指针已被成功读取:


任意地址写的过程和上述原理一致,此处不再描述。



Bypass ASLR/DEP/CFG


泄露ByteArray虚表指针Bypass ASLR


将ByteArray对象首地址传入任意地址读原语,从上一小节的图中已经看到ByteArray对象的虚表地址被读出并返回。在此基础上,借助任意地址读原语搜索32位下内存空间,找到flash模块基地址,从而绕过了ASLR。


利用代码还借助任意地址读原语构造了一系列功能函数,并借助这些功能函数最终读取kernel32.dll的VirtualProtect函数地址,供后面Bypass DEP使用。


利用HackingTeam的方法Bypass DEP/CFG


利用最终采用与HackingTeam完全一致的手法来Bypass DEP/CFG。由于相关过程在网上已有描述,此处不再过多解释。


Shellcode


原样本32和64位下的shellcode分别放在的Class6和Class7两个类内,我们用scdbg工具对32位下的shellcode做一个简单模拟,结果如下:


shellcode最终调用cmd启动WINRAR相关进程,相关命令行参数如下:



结语


本次flash 0day和今年年初的CVE-2018-4878一样都是TVSDK内的UAF问题。这类漏洞触发稳定,而且本次0day并未使用此前已经被增加额外校验的ByteArray利用方式,而采用另一种较为通用的利用手法实现任意地址读写。后面极有可能再次出现TVSDK内其他对象的UAF漏洞利用。


我们也预计该漏洞样本一旦被披露,会被立即加入各大EK,请大家做好防范准备。





- End -


看雪ID:银雁冰                              

https://bbs.pediy.com/user-628056.htm


本文由 银雁冰 原创

转载请注明来自看雪社区


新书推荐:

立即购买!


热门技术文章推荐:




公众号ID:ikanxue

官方微博:看雪安全

商务合作:wsc@kanxue.com


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存