CVE-2014-6332浅析-Internet Explorer整数溢出漏洞

来源:互联网 发布:淘宝联盟链接转换 编辑:程序博客网 时间:2024/06/15 15:18

本文转载自
启明星辰CVE-2014-6332分析报告
WinDbg漏洞分析调试(三)之CVE-2014-6332

背景知识

VBScript在IE浏览器中使用时是受限制的,权限很低。但此受限是通过Safe Mode标识来控制的,具体在Window 7 IE10环境下即是COleCscript对象的偏移0x174地址处的变量。正常情况下,该变量值是0x0E,即是运行在Safe Mode下。但若人为修改为0x00,VBScript将可以做任何事情,为所欲为。在漏洞利用层面,DVE技术并不注重shellcode数据及其所在内存的精确分配与控制,而是单刀直入,直击要害,直接篡改Safe Mode标识,因此能够实现在Windows IE3~11全平台上的通杀。且绕过了微软所有的保护措施,包括DEP、ASLR、EMET、CFI等。

关键知识点的介绍

VBScript变量

VBScript变量在内存中占用0x10字节。其定义是VARIANT结构体,前两个字节是VARTYPE vt,用来标识变量类型。
这里写图片描述
如下定义两变量i和mystring,查看其内存。

<SCRIPT LANGUAGE="VBScript">dim i, mystringi = &h12345678mystring = "hahahehe hellow"IsEmpty(i)IsEmpty(mystring)</script>

这里写图片描述
上述两图分别是i和mystring变量分别赋值后的内存情况。绿色是类型域vt,黄色是保留域,红色是数据域。
这里写图片描述

数组内存分布

VBScript引擎里,有关数组的结构体定义是SAFEARRAY和SAFEARRAYBOUND。

typedef struct tagSAFEARRAY {USHORT cDims; //数组维度USHORT fFeatures;ULONG cbElements; //一个元素所占字节ULONG cLocks;PVOID pvData; //数据的BufferSAFEARRAYBOUND rgsabound[1];} SAFEARRAY, *LPSAFEARRAY;typedef struct tagSAFEARRAYBOUND {ULONG cElements; //元素个数LONG lLbound; //索引的起始值} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;

利用下边的VBScript代码,仔细观察下两个数组内存的布局。

<SCRIPT LANGUAGE="VBScript">dim aa()dim ab()redim aa(5)redim ab(5)redim Preserve aa(6)redim ab(6)aa(0) = &h12345678aa(1) = &habcdef12ab(0) = "hellow hehe"ab(1) = "whoami haha"IsEmpty(aa)IsEmpty(ab)</script>

这里写图片描述
0x02708c70即是数组aa对应的内存,0x02708c74处为0x00000010即是cbElements,表示数组中一个元素占用0x00000010字节;0x02708c80处值是0x00000007,对应rgsabound.cElements,也即共有七个元素;0x02708c84处值是0x00000000,对应rgsabound.lLbound,即表示数组下标从0开始。再以word字节查看。

0:007> dw 02708c7002708c70 0001 0880 0010 0000 0000 0000 5dc8 002602708c80 0007 0000 0000 0000 a866 3462 0045 8000

0x02708c70处的0x0001即是cDims,表示aa是一维数组;0x02708c72处的0x0880对应fFeatures,根据msdn是FADF_VARIANT|FADF_HAVEVARTYPE。0x02708c7c处是0x00265dc8,对应pvData,指向真正的数组元素数据地址,查看0x00265dc8,很明显正好对应了aa(0)、aa(1)。两个元素都是长整型,故0x00265dc8和0x00265dd8处都是0x0003,0x00265dd0和0x00265de0正是我们在VBScript里的赋值:aa(0)=&h12345678,aa(1)=&habcdef12。
这里写图片描述
F5继续运行,仍然断在vbscript!VbsIsEmpty,此时显示ab数组内存。
这里写图片描述
0x02708c40即是数组ab对应的内存,各结构不再详细介绍,可参考上边aa的说明。0x02708c4c处是0x00265e40,对应pvData,也即ab数组元素数据的地址。查看0x00265e40内存,很明显对应了元素ab(0)、ab(1),因为都是字符串,所以0x00265e40和0x00265e50都是0x0008,即VARTYPE vt=VT_BSTR。
这里写图片描述
现在请仔细观察下两个数组的pvData,aa是0x00265dc8,ab是0x00265e40,aa有7个元素,每个元素占用0x10字节,0x00265dc8+0x10*7=0x00265E38。而0x00265E38再加8就是0x00265e40。也就是说,ab的pvData和aa的pvData只隔了8个字节。再次查看aa的0x00265dc8,用dd 00265dc8 l30,从下图可以很直观看出来。
这里写图片描述
红色是aa的合法数据,绿色是ab,深绿色即是ab(0)元素。但若aa越界访问,则00265e38、00265e48分别是aa(7)、aa(8)元素。同时00265e48前8个字节还是ab(0)元素的数据域。对ab(0)赋值,则影响了aa(7)的数据域和aa(8)的类型域。总之aa和ab错位重叠,通过给ab元素的赋值,来修改某越界的aa(i)元素类型域。

求值栈及COleScript地址

在VBScript虚拟机中,像赋值或求值等操作,都要用到一个栈来保存中间及临时结果,这个栈被称为求值栈。如a=10,虚拟机先把10解析为VARIANT对象,然后放进求值栈中。随后是赋值操作,取出求值栈中10的VARIANT,调用vbscript!AssignVar函数,赋值给a。另外根据调试发现,CScriptRuntime对象的偏移0xB0处即是求值栈指针。

<SCRIPT LANGUAGE="VBScript">dim adim bdim cIsEmpty("try to AssignVar")a = &h11223344b = &h44332211c = a+bdocument.write(c)</script>

在vbscript!VbsIsEmpty下断点,其实0x009af220就是求值栈,是把try to AssignVar解析为VARIANT对象并放入求值栈,作为参数传给VbsIsEmpty。
这里写图片描述
接下来执行a=&h11223344语句,将解析0x11223344。下硬件写断点ba w4 009af220。F5继续运行,在039044e5处断下,此时0x009af220的4字节数据已经是0x00000003,即长整型。
这里写图片描述
等继续执行到039044fb,求值栈009af220内容已经是00000003 00000000 11223344 00000000了,也即已经把0x11223344解析为VARIANT对象并放入求值栈。随后调用vbscript!AssignVar,把其赋值给a。对于b的情况也是如此。
这里写图片描述
a+b的结果也是放在了求值栈,然后再赋值给c。
这里写图片描述
在VBScript里,无法获取函数地址,但是当尝试把函数地址赋值给变量时,求值栈会发生很神奇的事情。

<SCRIPT LANGUAGE="VBScript">On Error Resume Nextsub testaa()end subdim iIsEmpty("try to get COleCscript")i=testaai=null</script>

首先虚拟机获取testaa地址,放入求值栈中,随后准备赋值给i,但是检测类型域时,发现求值栈中是函数地址,将直接返回错误,不会执行赋值。又因为设置了On Error Resume Next,所以继续向下执行i=null。对于null,虚拟机并不会向求值栈放入数据,仅仅把求值栈里类型域设置为VT_NULL,然后赋值给i。但是数据域仍残留着testaa的地址。最终i保存了testaa的地址,其实是CScriptEntryPoint对象地址,虽然类型只是VT_NULL。
这里写图片描述
虚拟机获取了testaa函数的地址,放在求值栈009af378里,注意009af378前两个字节是004C,即VT_FUNC。查看009af3d0,对其前4个字节03904e48查看符号,正好匹配了CScriptEntryPoint::vftable,也就是说,009af3d0其实是CScriptEntryPoint对象地址。
这里写图片描述
009af3d0偏移8处是009ae578,009ae578+0x10处存放的就是COleScript对象地址009ae140。顺便看下COleScript对象偏移0x174的Safe Mode标识,确实是0x0E。偏移是0x174有代码为证,检测Safe Mode标识的函数代码如下。
这里写图片描述

数组内存重新分配

现在起,分析exploit触发的技术细节以及流程,环境是Win 7 IE 10。分享一个小技巧:IsEmpty函数可传入任何参数,因此在相关地方加入IsEmpty调用,可及时查看变量对象的值,也用来打印调试信息。在vbscript!VbsIsEmpty下条件断点,若参数类型为字符串,打印并继续运行,否则中断到调试器,以查看变量或对象数据。

bp vbscript!VbsIsEmpty "r $t1=poi(@esp+c);r $t2=poi($t1)&0xF;.if ($t2=0x8) {.printf \"%mu\",poi($t1+8);.echo;gc;}.else{}"

现在已经知道,aa和ab内存可能仅隔8个字节。在VBscript中动态定义的数组,可以用redim来重新分配内存,最终由OLEAUT32.dll里的SafeArrayRedim函数来实现。

HRESULT SafeArrayRedim(_Inout_ SAFEARRAY *psa,_In_ SAFEARRAYBOUND *psaboundNew);

SafeArrayRedim的两个参数里,psa即是要改变大小并重新分配内存的数组,psaboundNew里的cElements即是改变后的大小。笔者调试时还发现一个规律,利用redim Preserve aa(a0)修改aa大小为a0时,在调用SafeArrayRedim时,实际传入参数psaboundNew->cElements大小是a0+1,也即默认大小会加1。SafeArrayRedim先获取psa数组的大小,计算方法:cbElements*cElements。
这里写图片描述
随后把psaboundNew->cElements赋值给psa->rgsabound[0].cElements。
这里写图片描述
此后按照cElements新的值0x0800000026计算大小,结果保存到[ebp-8]。
这里写图片描述
然后计算两者之差,结果是80000260-00000260=80000000,强调下cElements是无符号数,但是却用了jge指令,把80000000当做有符号数来比较。自然转入小于0的分支。在小于0的分支里,以80000000为参数申请内存,申请失败,这时直接返回8007000Eh了,SafeArrayRedim结束了。
这里写图片描述
如上图,psa的cElements仍然是0x0800000026,内存分配失败时,psa的cElements没被修改回来。但psa所指内存块02dac720并没变大,造成了SAFEARRAY索引越界,这是漏洞根本原因。总结起来,SafeArrayRedim函数有两处Bug。

  1. 元素个数的变量cElements本来定义为ULONG,无符号数。但在比较两次所需内存大小时,差值用了jge指令,相当于按有符号数比较差值。导致80000000这样的数比较时转入小于0的分支。
  2. 在小于0分支里,对于内存申请失败的情况处理不完善,前面过早修改了psa的cElements,申请失败后却没有改回来,直接返回了。

ab的pvData在aa的8个字节之后,只是一种可能性,如何判断内存布局确是如此呢?ab(0)=1.123456789012345678901234567890,此浮点数值编码后在内存里就是d3746f66,3ff1f9ad。02dac988即是ab(0)的内存,ab(0)赋值浮点数使02dac990开始的连续8个字节分别是d3746f66和3ff1f9ad。而02dac990算是aa(a1)元素,该元素的类型应该是0x6f66,但VarType函数取该值后会和0xBFFF逻辑与,0x6f66&0xBFFF=0x2F66,所以用 type1=&h2f66作为aa和ab确实错位重叠的依据。
这里写图片描述
这里写图片描述
从ab数组角度看ab(0)元素。
这里写图片描述
对ab(0)赋值,修改了aa(a1)元素类型域。

Setnotsafemode函数

Over函数完成错位重叠后,开始执行Setnotsafemode,调用mydatai=testaa i=null来初始化相关数据。i=testaa i=null即利用求值栈获取testaa地址,其实是CScriptEntryPoint对象地址,不再详细分析。aa(a1)=i之后,aa(a1)数据域是01fcf090,即CScriptEntryPoint对象地址。
这里写图片描述
02dac990即aa(a1)元素,其类型还是VT_NULL,因此需要修改类型,才能操作其数据域01fcf090。我们需要是的01fcf090偏移8处的01fbe320,即图中绿色部分。ab(0)=6.36598737437801E-314,此浮点数的内存布局即是0300000003000000,成功把aa(a1)类型修改为长整型。下图红色部分即是ab(0)元素。
这里写图片描述
从ab数组角度看ab(0)元素。
这里写图片描述
aa(a1+2)=myarray,myarray是一份伪造的数组,后边用到。
这里写图片描述
从ab数组角度看ab(2)元素。
这里写图片描述
02dac9b0即是aa(a1+2)元素,此时类型是VT_BSTR,ab(2)=1.74088534731324E-310,其内存布局是0c2000000c200000, aa(a1+2)的数据类型将会被修改为0x200C,即VT_VARIANT|VT_ARRAY,数组类型。
这里写图片描述
从ab数组角度看ab(2)元素。
这里写图片描述
接着aa(a1)作为返回值赋值给i,i为长整型,数据域内容01fcf090,即i=01fcf090,正是我们想要的数据。
这里写图片描述
我们需要是其实是01fcf090+8处的01fbe320,那么怎么根据01fcf090获取其偏移8处的01fbe320呢?这就需要ReadMemo函数出马了。先以i+8作为参数调用readmemo函数,并把结果赋值给i。

ReadMemo函数

ReadMemo函数可以读取参数所指定的内存地址的数据,如何实现的呢?就以i+8做参数调用说明,即i=readmemo(i+8)。i+8的值是01fcf090+8即是01fcf098,那么aa(a1)=add+4即是把01fcf09c这样的长整型数据赋值给aa(a1)元素。执行完aa(a1)=add+4语句后,aa(a1)=01fcf090。
这里写图片描述
再次看下之前的图,我们需要的是01fcf090+8处的值01fbe320。
这里写图片描述
对ab(0)赋值,ab(0)=1.69759663316747E-313,其内存布局是0800000008000000,恰好把aa(a1)类型修改为VT_BSTR,也即vbscript此后会把aa(a1)看做BSTR类型,把01fcf09c看做是指向字符串的内存。根据BSTR定义,vbscript会认为其向前4字节即01fcf098就是存放长度的地址内存。
这里写图片描述
所以lenb(aa(a1)),相当于去把01fcf09c作为字符串的内存,去01fcf098读取4字节作为aa(a1)的长度,恰好就是01fcf098处的值01fbe320。为了验证,对01fcf098下硬件读断点,确实在VbsLenB函数里断下,见下图。
这里写图片描述
01fbe320作为返回值赋值给i,继续调用i=readmemo(i+16),很明显这次获取的将是01fbe330的数据,具体过程不再分析。见下图,同样在VbsLen中获取读取到01fbe330的数据01f8d968,作为返回值赋值给i。
这里写图片描述
那么01f8d968到底是啥呢?
这里写图片描述
通过ln命令验证,01f8d968确实是COleScript对象地址,且其偏移0x174处正是Safe Mode标识0x0000000e。

开启Godmode

前边千辛万苦,费尽心机终于得到COleScript对象地址并保存在i里了。下边就是想办法把其偏移0x174处的0x0000000e清0。一旦实现,VBScript即可为所欲为,可以做一切事情了,即所谓上帝模式。以下一条语句即可实现。

aa(a1+2)(i+&h170)=ab(4)

这里写图片描述
ab(4)即是上图黄色部分,很明显是0。02dac9b0即是aa(a1+2)元素,其数据类型是200c(VT_VARIANT|VT_ARRAY)即数组,020d4bdf4伪造的数组:一维数组,元素有7fff0000个,每个元素占用1字节,元素内存地址为0。所以该数组可访问的内存空间为0x00000000到0x7fff0000*1。索引值i+&h170结果是01f8d968+170=01f8dad8。对这样一个索引值,真实元素地址就等于内存地址0+01f8dad8=01f8dad8。等赋值完成后,01f8d968偏移174处的Safe Mode已经是0了。
这里写图片描述
Godmode已经开启,F5之后,记事本弹出来了。

总结

CVE-2014-6332漏洞通过数组越界问题达到了读写任意内存的目的,又通过读写任意内存成功修改某对象的关键标志位,成功绕过了微软的安全防御体系。当然,微软目前已经放弃了VBScript,但我们学习的目的在于举一隅以三隅反,因此理解其原理还是很有必要的。

阅读全文
0 0
原创粉丝点击