前言
CVE-2018-8373是trendmicro(趋势科技)研究人员于2018年7月发现的VBScript引擎的UAF漏洞。它是同年2018-8174的兄弟版本漏洞。
漏洞原理
漏洞POC如下所示:
POC定义了MyClass类,包含一个array成员变量,两个成员函数,Class_Initialize以及Default Property Get P。前者是不建议使用的方法,现已被新的过程替代。对象初始化时,会被自动唤醒。POC中,Class_initialize是重载的。当VBScriptClass::InitializeClass时,重载此函数。
默认属性不需要特殊说明即可开源访问,POC中的Default Property Get函数会重载MyClass的默认属性。当调用被用来访问cls时,会处理重载的函数。
漏洞触发流程简化为下面三步:
1.cls=New MyClass
初始化MyClass时会重载Class_Initialize(通过vbscript!CScriptEntryPoint::Call派发进入到对应的编码者实现的Class_Initialize方法中)。在此函数中,ReDim array(2)会调用Vbscript!RedimPreservearray来修改数组,元素数为3,也就是修改array数组的长度:
1 | Private Sub Class_Initialize |
2.cls.array(2)**
由于VB引擎扫描代码从左至右,首先执行cls.array(2)操作。
调用函数vbscript!AccessArray判断对应array(2)是否可访问,首先拿到array内存对象。
中间经过一顿判断,esi此时为array内存对象地址。
分别取出cbElements(数组元素的大小)成员、rgsabound.cElements(元素个数)成员,pvData成员(数组缓冲区起始地址),【ebp+c】拿出目标数组成员下标,我们要访问的是array(2),因此此时下标为2。计算方式为数组成员下标cbElements+pvData = 2\0x10+0x08342fd0 =08342ff0 ,08342ff0 被拷贝到求值栈。
3. cls.array(2)=cls
接着执行等号右边的部分,对cls访问时将执行重载的Get P函数。
1 | Public Default Property Get P |
通过ReDim修改array数组的长度,Preserve属性表示对数组中已赋值的那些数据不清空 。修改array数组内存的操作,由Vbscript!RedimPreserveArray函数实现。
对array类型一顿判断后,最终调用SafeArrayRedim函数,原型如下:
1 | HRESULT __stdcall SafeArrayRedim(SAFEARRAY *psa, SAFEARRAYBOUND *psaboundNew) |
第一参数为array对象,第二参数为新array对象数组元素个数。
调用完成后,array对象的pvData被修改为新的数组缓冲区,同时元素个数也变为2。同时,原先的pvData缓冲区被释放。
1 | 0:005> !heap -p -a 07bd2fe0 //NEW |
继续执行,调用函数AssignVar完成赋值,如下所示。
在赋值之前,存在VAR::clear()函数调用,释放之前的引用,即释放Old array的array(2),由于之前已被释放,第二次访问则产生崩溃。
小结,cls.array(2) = cls语句,在赋值前保存array(2)地址到求值栈,而后Class的Default Property Get P内redim操作导致array对象数组内存被重新分配,而求值栈的地址被释放。最终赋值时二次重用导致崩溃。
漏洞利用
多维数组补充知识。
小例子如下所示
1 | Dim array2(0,1,2,3,4) |
可以看到在SAFEARRAY结构中从右至左分别为第一维,第二维等等,至少在内存布局中tagSAFEARRAYBOUND结构是这样体现的。
漏洞利用步骤同样分为三步:
- 利用漏洞修改二位指针数组长度为0x0FFFFFFF
- 实现任意地址读写
- 构造CONTEXT,执行shellcode
修改数组长度
首先,定义两个数组,分别记为array和array2,array就是原理poc中的array数组,array2数组为二维数组,其中每个元素的值都为3。
由于SAFEARRAY结构在堆中申请内存大小为0x30,根据调试来看,其在结构前有额外的0x10字节大小内存。我们首先在类初始化重载函数Class_Initialize函数中构造array(2)也即为其分配0x30的pvData数据缓冲区大小。
一样的,在重载函数Default Property Get函数释放array.pvData,将array2设置为新的array.pvData。
上面代码执行时,首先将array数组大小修改为100001,接着利用赋值语句循环申请大量array2结构的二维数组,即也要申请大量的SAFEARRAY结构体,其中有一个会占用已经被释放的原先array.pvData内存。
执行array(2)=cls后,Default Property Get函数返回值0x0fffffff将被写入SAFEARRAY.rgsabound[1].cElements成员,表示当前二维数组的第一为大小为0x0fffffff,也即我们修改了此数组的长度。
任意地址读写
在上一步中,我们有一个修改了长度的二维数组,导致使用这个数组可以进行越界访问。首先,我们需要找到这个二维数组在array数组的哪个位置,也就是需要找到此元素下标,我们记为index_vuln。
思路就是遍历array数组,找到一个array2的一维元素个数为0x0FFFFFFF,使用UBound函数则即最大上标为0x0FFFFFFE,如下所示,即可定位到此元素下标。
找到这个array2后,我们即可使用array(index_vuln)(a,b)来索引这个数组,其中a∈[0,0x0FFFFFFE],b∈[0,1],也就是说我们可以越界访问array(index_vuln)(a,b).pvData指向的一大片内存。
接下来,需要找到可用于类型混淆的数组元素。
由于在大量连续申请array2数组时,导致array2.pvData内存的连续,每个array2.pvData中间间隔8字节的堆头数据。我们可以利用这一点,通过越界读写,错位赋值来改变VARIANT结构的vt字段,最终构造一个基地址为0,元素大小为1,元素个数为0x7FFFFFFF的超级一维数组;在使用同样方法泄露出用来读写的地址,从而实现任意地址读写。(银师傅的总结过于精炼,直接使用)
那么,先要找到array(index_vuln)(a,b)中的某个元素,它的前一个元素由于堆分配问题,导致向后错位了8字节,存储的VARIANT数据与前面的短整形值为3不一样了,到了当前元素时,变为长整型值为2。我们将当前元素表示为array(index_vuln)(index_A,0),那么前一个元素表示为array(index_vuln)(index_A-1,0),也就是说,从当前元素开始,存储的都是长整型值为2的元素,接着,在经过一次堆分配,又变为正常的短整型值为3。同样的,我们需要另一个元素,它来和array(index_vuln)(index_A,0)实现类型混淆,可预见的最合适的便是从array(index_vuln)(index_A,0)向前的8字节,它是错位后的array2数组的第一个元素。
那么先找到index_A,利用VB的VarType函数,判断VARIANT的vt类型,通过分别定位类型为2及3定位到index_A,逻辑如下:
拿到index_A,如何定位index_B,思路是通过将array(index_vuln)(index_A,0)设置为一段字符串,如”AAAA”,那么将有一个array数组的元素array2的Data变为低两字节8,通过遍历array(i)(0,0)=8,即可拿到index_B。
以上,我们拿到了错位8字节的两个VARIANT结构,可用来进行类型混淆。
借助假的字符串数据类型混淆后可构造一个超大一维数组。
首先令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)变为长整型(vt=3),array(index_vul)(index_A+2,0)变为数组类型(vt=200c)。如下所示
rw_primit第一行语句执行完后,可以看到array(index_vul)(index_A+2,0)被覆盖为我们的fakearray字符串。
执行完rw_primit后
拿到了一个泄露块内存util_mem,一个可以实现32位内存读写的超大一维数组array(index_vul)(index_A+2,0)。
任意地址读
使用上面的两个工具进行封装即可。
和8174类似的,使用LenB函数特性,写入要读的地址+4,将其类型改为字符串类型8,即可拿到目标地址的值,读完后再将其类型改回长整型3。
任意地址写
通过array(index_vul)(index_A+2,0)访问内存块util_mem,设置类型为长整型3,在设置util_mem+8为要写的data值。
shellcode执行
采用和8174差不多的思路,即可实现利用。
总结
8373是我完整分析的第二个有关脚本引擎相关漏洞,总的来说,对基础语法不熟悉称为阅读poc和理解漏洞原理和利用思路的重大阻碍,幸好调试过程可以帮助我更好地理解代码执行流程,以及引擎内部处理流程。
后续需要继续加强此类基础语法的学习,另外总结漏洞的关联性,利用手法等等。
感谢前辈们的工作,得以很快的学习并理解,瑞思拜!
参考链接
[1]UAF Bug Affects Internet Explorer, Runs Shellcode
[2]CVE-2018-8373:VBScript引擎UAF漏洞
[3]记一次CVE-2018-8373利用构造过程
https://bbs.pediy.com/thread-246327.htm
[4]利用CVE-2018-8373 0day漏洞的攻击与Darkhotel团伙相关的分析
https://ti.qianxin.com/blog/articles/analyzing-attack-of-cve-2018-8373-and-darkhotel/
[5]CVE-2018-8174:从UAF到任意地址读写