CVE-2016-0189是一个vbscript脚本引擎损坏漏洞,最初作为一个0day被用在针对韩国的APT攻击中,并在2016年3月10号于MS16-051中被修复。3个月后,国外安全人员通过补丁比对分析了漏洞成因,并将利用代码上传到Github。从此0189便被广泛纳入各种挂马,在今年的CVE-2018-8174出现之前,CVE-2016-0189一直是较新版本IE的挂马首选。
由于之前在调试这个漏洞时发现参考资料很少,特别是国内只看到一篇文章讨论这个漏洞,于是决定把调试过程写一下,不足之处请见谅。
在vbs解析引擎中,vbscript!AccessArray
函数用来访问数组成员。例如访问一个二维数组A的成员A(1,1)时,vbs解析引擎就会调用这个函数,根据传入的索引计算待访问的地址。
2015年Hacking Team的泄漏中有两个通过重载valueof函数来触发的flash 0day CVE-2015-5119 / CVE-2015-5122。这两个漏洞的思路是当赋值方是个对象,而被赋值方出于某种原因要求接收一个数值时,会调用该对象的valueof方法进行转换,而valueof
方法是可以被重载的,这样就可以在重载的valueof函数中进行一些自定义的操作,例如释放对象,改变数组大小等。
在vbscript!AccessArray
函数内,当访问语句为如下形式时,就有可能进入上面的情景,而事实确实如此。
具体的逻辑如下图所示,cp_var_index->vt
代表索引变量的类型,当索引类型为VT_I2
和VT_I4
时,直接返回该对象的值,而其他情况下会调用vbscript!rtVariantChangeTypeEx
函数,并在里面调用oleaut32!VariantChangeTypeEx
函数,随后会调用对象的valueof方法。
我们来看一下 VT_I2
和 VT_I4
代表什么:
The value of a VT_I2 type property MUST be a 2-byte signed integer
. It MUST be formatted in little-endian
byte order.
The value of a VT_I4 type property MUST be a 4-byte signed integer
. It MUST be formatted in little-endian
byte order.
当传入索引是有符号短整型和有符号长整型型时,就直接使用其值,而当索引为其他类型时(例如当传入对象为js对象时,变量类型为VT_DISPATCH),就调用vbscript!rtVariantChangeTypeEx函数将对象值转化为要求的类型,最终通过调用valueof方法返回该值。
如果我们在重载的valueof内改变数组的大小,当返回上层继续访问数组元素时,就会产生问题。poc中的思路是:先定义一个比较大的二维数组A(1, 2000),然后通过访问A(js_obj, 2)去调用重载的js_obj.valueof()方法将一维索引转化为合理的数组下标。在js_obj.valueof()内将数组缩小为A(1, 1),然后迅速用UAF进行占位,并在valueof方法的最后返回1,作为转换后的索引。转换完成后访问A(1, 2) 。然而这时候的A(1, 2)已经变成了占位后的内存。攻击者多次利用这一特性来操控内存,分别实现泄漏一个类对象地址,任意地址读,和任意地址写。在此基础上找到vb的安全选项开关,利用一个单精度浮点数(vbSingle)的类型值(4)去覆盖原来的安全开关属性值(0x0E
-> 0x?4
),从而开启上帝模式。
vbscript.dll内判断对象安全性的函数是COleScript::InSafeMode
,汇编代码如下图所示。可以看到test指令对 dword ptr [ecx+174] 的值与0x0B(00001011)进行与运算,若结果为0,即认为不处于SafeMode,放行对象执行。
windows 7 sp1 x86 无补丁
+ vbscript.dll/oleaut32.dll 5.8.7601.17514
+ windbg 6.11 x86
poc的主入口为exploit
函数,原代码的注释写的很清晰,可以看到exploit函数分为5个步骤:
getAddr
函数的逻辑又可以分为如几步:
遍历y数组,通过对比VarType找出s对象(s是一个空class实例)并返回其地址
VBScriptClass
继承了NameTbl,其头部是一个NameTbl
结构体,NameTbl结构体偏移0x08处的值是一个指向NameList
结构体的指针,NameList结构体偏移0x2C处是一个CDISPIDTable
结构体,而CDISPIDTable结构体偏移0x08处的值是一个指针数组,每个数组元素都指向一个VAR结构体,代表一个类成员在类对象中的实体,整个关系如下所示:
先来看一下resize后的aw.A:
再来看一下占位成功后的y数组情况:
下图分别以aw.A视角(红色框区域)和y视角(黄色框区域)看s对象,不难发现有4字节的错位:
理解上图之后,我们就可以根据条件查找包含s对象的y数组成员,调试时恒为y(0)。poc里面申请32次内存是确保释放后的内存一定被y中的某个成员再次使用,进而从32个成员里面找出包含s对象的那个成员。
泄漏了s对象的地址后,我们通过相关数据结构去获取vbscript!SafetyOption
的内存地址,并将其改写为0x00或0x04。查找过程如下:
下面对此进行说明。
poc中读取CSession
对象指针的代码如下所示:
由前面的分析已知addr是一个VBScriptClass实例指针,我们可以看到代码把addr+8的地址传入leakMem
函数,并通过Mid(mem, 3, 2)将CSession对象指针获取出来并转换为16进制。
为什么上述代码不写成如下形式?
为了回答这个问题,我们先来看一下leakMem函数的实现:
leakMem的基本逻辑是在占位内存中构造一个字符串地址(Data High
)为待读取地址的字符串对象,然后再次利用漏洞触发UAF,从而使aw.A(1, 2)处改写为一个VT_BSTR对象,随后读取该对象,从而使addr处的数据被当做定长字符串读出,最后在读取的字符串中定位CSession指针对应的部分并转化为对应的32位地址。
图片出处
我们来回顾一下BSTR
对象的结构:
图片出处
关键点在于字符串前面的4字节,这4字节是一个长度域,指定了后面待读取的unicode字符串长度。现在再来看一下前面问题的那个问题。
这是本次调试中getAddr函数返回的VBScriptClass实例:
原poc中传入的是addr+8,那么可以构造出如下的BSTR结构:
这样可以成功读取包含Csession地址的字符串。
如果代码这样写:
但当传入addr+c时,构造的BSTR如下:
此时构造的BSTR的长度域为0,数据无法正常读出。
poc中读取COleScript
对象指针的代码如下,原理和上一步完全相同,此处不再过多分析:
在IE8中,vbscript!SafetyOption位于COleScript对象的+0x174处。poc代码中通过调用overwrite函数去覆写这一值,如下:
代码中伪造一个type=0x400C
间接寻址对象,接着触发漏洞,随后将一个Csng
(单精度浮点)对象的写入COleScript+0x16C开始的16个字节处,COleScript+0x174处正好写入Csng的type值4,从而开启上帝模式。
开启上帝模式后,就可以弹出cmd窗口了。
下面通过逆向某安全软件来看一下对CVE-2016-0189的动态检测方案。
首先hook oleaut32!VariantChangeTypeEx函数
可以看到代码中对CVE-2016-0189的检测逻辑为:在调用oleaut32!VariantChangeTypeEx
函数前后检查rgsabound[0].cElements
所对应的第二维度的大小,若调用后的大小小于调用前的大小,则视为检出。
这里有一个疑问,MSDN对多维数组的rgsabound域解释.aspx)如下:
但调试时发现A(1, 2000)的rgsabound实际使用顺序和文档描述相反,看检测逻辑里面判断的也是rgsabound[0]->cElements。我们以实际调试结果为主。
特别感谢 Hu Jiang
,Xu Xilin
,Yang Kang
在调试过程中的指导
《CVE-2016-0189》 https://theori.io/research/cve-2016-0189
《theori-io/cve-2016-0189》 https://github.com/theori-io/cve-2016-0189
《Nebula漏洞利用包CVE-2016-0189漏洞利用分析》 http://www.freebuf.com/sectool/131766.html
《WinDbg 漏洞分析调试(三)之 CVE-2014-6332》 https://paper.seebug.org/240/
《Write Once, Pwn Anywhere》 https://www.blackhat.com/docs/us-14/materials/us-14-Yu-Write-Once-Pwn-Anywhere.pdf
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2019-3-26 13:08
被银雁冰编辑
,原因: