CVE-2014-6332是微软2014年11月11日(数字很吉利)公布的一个IE浏览器漏洞(同时公布的还有CVE-2014-6352),影响IE版本为IE3-IE11。根据漏洞利用方法,官方定义为远程代码执行漏洞。究其原因是因为vbs引擎在执行Redim array(nNum)时,即重定义数组时,会调用OLEAUT32.dll模块里面SafeArrayRedim函数,该函数内部处理逻辑不严谨,使用了错误的条件跳转指令,而且当传入的nNum足够大时,函数内部的数组空间申请会失败、SafeArrayRedim函数不做任何处理直接返回,导致返回时已经将nNum写入数组结构,后续对该数组便可以随意“越界”访问。
VARIANT结构是脚本语言中常用的一种结构,用于封装对象结构。参见官方说明:https://docs.microsoft.com/zh-cn/windows/desktop/api/oaidl/ns-oaidl-tagvariant
简化一下该结构,这是一个0x10大小的结构:
VARTYPE结构参考:https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/3kfz157h(v=vs.84)
列出常用的几种类型如下表所示:
接下来看一下Vbscript中的数组结构,参见:https://docs.microsoft.com/zh-cn/windows/desktop/api/oaidl/ns-oaidl-tagsafearray
我们测试一下这些结构在内存中的存储情况
在实际调试中发现,数组元素的类型是可以不一致的;使用redim重定义数组时,数组实际的元素个数为传入的nNum+1。
Vbs中将函数指针赋值给变量时,先将函数类型的VARIANT结构压入求值栈,然后将求值栈中结构再传递给变量,但当检测到求值栈中的数据类型为0x400C(即函数类型)时,并不会传递此类型,引发相关异常。如果当前代码流设置了On Error Resume Next,则会忽略当前异常,执行下面的代码。
SafeMode是Windows操作系统中针对安全的一种特殊模式,即安全模式。在Vbs引擎中同样存在这样一个安全属性值,正常情况下该属性值为0xE。默认情况下Vbscript脚本执行权限是非常低的,正是因为safemode安全属性的限制,如若我们能通过一定方法修改掉此属性值(改为0),即可绕过安全权限检查,为所欲为,即进入上帝模式。而且,经过调试我们发现在COleScript对象偏移0x174位置正是Safe Mode标志。
所以我们对该漏洞利用的核心就是找到safemode标志的位置并修改它。
我们选用附件中一个弹计算器的Exp进行分析(stage2.html)。
分享一片帖子上的调试方法(博客链接见文末):由于Vbscript脚本由Vbs引擎解释执行,如果我们盲目跟踪解析流程非比较低效,我们调试的目的是为了复现漏洞位置的代码bug,所以定位到漏洞模块的解析位置是关键,跟踪关键数据的解析过程即可。前面我们为了查看测试变量的内存结构手动加入了IsEmPty()函数的调用,在WinDbg中模糊搜索该函数,可找到其符号为Vbscript.dll!VbsIsEmpty,我们将想查看的对象作为参数传给VbsIsEmpty()函数,即可断在该函数进行对象结构的查看。此为为了方便我们判断脚本执行的流程,可以将自定义的日志信息字符串传给VbsIsEmpty()函数,然后下条件断点:
上述指令的意思是:下断VbsIsEmpty函数,如传入参数为字符串(即我们自定义的日志信息)则打印字符串,然后继续执行;否则就断下来(此时我们就可以查看特定的变量结构了)。
我们重点跟踪一下redim操作,在redim Preserve arrX(ofnumele)之前跟进redim流程。搜索vbscript.dll中redim相关符号,定位到一个可疑的函数Vbscript.dll!RedimPreserveArray,下断该函数并结合IDA进行动态跟踪验证,程序成功断下。我们发现在RedimPreserveArray内部会调用SafeArrayRedim函数,传入的两个参数(下图中为psa、&psaboundNew)分别为原数组对象指针和新的SAFEARRAYBOUND结构指针。注意到SafeArrayRedim函数位于OLEAUT32.dll模块内。此后将通过SafeArrayRedim函数的返回值(注意图中的v4是一个有符号数)判断是否redim成功,若小于零则抛出异常结束RedimPreserveArray函数。
继续跟进OLEAUT32.dll中的SafeArrayRedim函数。
我们直接反编译SafeArrayRedim函数,该模块自带符号,简单分析一下该函数的伪代码,即可快速定位该函数中的逻辑bug(如下图所示),结合动态验证也正是如此。
总结一下该漏洞成因主要有两点:
A、SAFEARRAYBOUND结构中的cElements字段(即数组元素个数)是一个ULONG类型,即无符号长整形。但是在SafeArrayRedim函数里面处理新旧数组操作(此处说的处理是指上两图中对于变动的数组空间进行开辟新的缓冲区进行保存备份操作)时,条件跳转判断选择了jge有符号跳转指令(源代码中应该是定义错了数据类型)。这样就存在一种可能:如果psaboundNew-> cElements - psa-> rgsabound.cElements>=0x08000000,即数组元素个数增加的值大于或等于0x08000000,其变动的大小就会大于等于0x80000000,该数值就会占用有符号数的符号位、使得图中的v5恒小于0,随后便会产生错误的跳转。
B、SafeArrayRedim函数里面的处理流程是先将psaboundNew结构赋给psa-> rgsabound,随后按照数组空间变动的大小去申请缓冲区,如果申请失败则会返回0x8007000退出,然后外层的Vbscript.dll!RedimPreserveArray函数将会抛出异常退出。这里的处理存在一个非常严重的问题:RedimPreserveArray函数redim失败之后却并未还原psa-> rgsabound.cElements。这让后续的psa数组“越界”合法化。
至此漏洞成因已分析完毕。
接下来,我们分析一下附件中的漏洞利用文件stage2.html,学习一下该Exp的编写技巧及思路。前面的分析中提到该漏洞的常规利用方法是修改vbs引擎中的SafeMode标志开启上帝模式,而且我们知道COleScript对象偏移0x174位置正是Safe Mode标志。所以我们分析的重点是如何找到Safe Mode标志的具体位置并且合法的去修改该标志。
笔者在调试stage2.html时,简单总结了一下代码流程:
1. 初始化数组arrX()、arrY()。(即Init()函数)。
2. 通过循环redim尝试使得arrX和arrY空间“连续”(准确来讲使得原始数组arrX和arrY之间只间隔8字节)。(Exploit()函数)
3. 触发漏洞修改arrX的cElements字段,使得arrX数组可“越界”访问arrY数组元素,让两个数组后续空间重叠。(Trigger()函数)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)