作者:360 Core Security
博客:http://blogs.#/post/VBScript_vul_CH.html?from=timeline&isappinstalled=0

前言

近日,360核心安全事業部高級威脅應對團隊又發現若干vbscript漏洞的在野利用。其中包括CVE-2016-0189、CVE-2018-8373和另一個此前不為人所知的漏洞(我們暫未確定它的cve編號)。這三個漏洞,加上我們在今年4月發現的CVE-2018-8174,一共是4個vbscript在野利用。經過分析,我們發現這4個文件的混淆和利用手法都高度一致,我們懷疑背后有一個寫手(或團隊),一直在開發vbscript的0day利用并用于攻擊。

如下為我們抓到的4個漏洞的在野利用:

被遺漏的0day?

由于其他三個漏洞都已出現過分析文章,本文我們將重點分析未被公開過的第四個vbscript 0day。

該漏洞利用一直隱藏得非常隱蔽,我們發現該漏洞在2017年3月更新中被修復,微軟修復時沒有提到該漏洞被利用,我們推測這個漏洞可能是微軟并未發現利用而修復。可以定位到的最后一個可以觸發該漏洞的版本是 vbscript.dll 5.8.9600.18538,在vbscript.dll 5.8.9600.18616 中,該漏洞被修復。有意思的是,我們發現相關利用的出現時間早于2017年3月,這也意味著該漏洞在當時是一個0day。遺憾的是,我們并未找到其他廠商對該漏洞的分析文章。

下面我們將和大家分享該漏洞的成因和利用方式。

漏洞分析

概述

這個漏洞位于vbscript!rtJoin函數。當執行vbscript的join語句時,VbsJoin函數會調用rtJoin,rtJoin首先遍歷傳入的數組中的每個元素,并計算拼接后的字符串總長度(包括拼接字符,默認為unicode空格0x0020)。然后調用SysAllocStringLen分配相應的空間,用于保存拼接后的字符串。

實際分配的空間大小 = (要分配的字節數 + 0x15) & 0xfffffff0 (參見oleaut32!SysAllocStringLen及oleaut32!CbSysStringSize的實現)

字符串起始地址前4字節為字符串的字節長度(參見BSTR結構)。上述整個過程的偽代碼如下所示:

相應的棧回溯如下:

隨后解析流程會逐個拷貝字符串到新分配的空間,這個過程中會使用保存在棧上的字符串地址獲取每個字符串的長度,以作為memcpy的size參數。當數組元素里面有類對象時,會觸發類對象的Default Property Get回調獲取默認屬性,在回調中可以對數組中的其他成員進行操作,例如更改字符串大小。只要精確控制更改前后的字符串大小,通過(下圖中第一個)memcpy拷貝的數據大小就有可能超出前面由SysAllocStringLen分配的空間,從而導致堆溢出。上述整個過程的偽代碼如下所示:

PoC 我們構造了一個該漏洞的poc,供研究人員分析使用:

代碼分析

內存布局

原利用代碼首先進行內存布局(prepare),然后第一次利用漏洞(exp_1),覆蓋一個BSTR對象的長度域,得到一個超長BSTR對象,并借助該BSTR去獲取一塊之前準備好的內存地址;成功后,再次利用漏洞(exp_2)去覆蓋一個偽造的字符串的對象類型為數組(200c),從而得到一個數據起始地址為0,元素大小為1,元素個數為0x7fffffff的超長一維數組。

隨后借助第一次獲得的內存地址和第二次獲得的超長數組實現任意地址讀取,后續的利用方式和之前被披露的細節基本一致。

prepare上半部分代碼如下圖所示。

在這部分代碼中,str_h的字符串長度為0x4fec字節,SysAllocStringLen實際分配的空間為0x5000字節((0x4fec+0x15) & 0xfffffff0 = 0x5000),str_o的字符串長度為0x4ff6字節,SysAllocStringLen實際分配的空間為0x5000字節((0x4ff6+0x15) & 0xfffffff0 = 0x5000)。array_a和array_b是2個數組,每個數組的實際數據區域占的空間為0xa00*0x10 = 0xa000字節(每個元素為一個VAR結構體)。

需要注意的是,0x4fec2 + 0x18 + 0x22 = 0x9ff4,(0x9ff4+0x15) & 0xfffffff0 = 0xa000, 這些值在下文會提到。

prepare下半部分如下圖所示。

str_left_0大小為0x4ffa字節(get_left_str_a_by_size會將傳入的參數減6字節),SysAllocStringLen分配的空間為0x5000字節((0x4ffa + 0x15) & 0xfffffff0 = 0x5000); str_left_1大小為0x9ffa字節,SysAllocStringLen分配的空間為0xa000字節((0x9ffa + 0x15) & 0xfffffff0 = 0x5000)。

隨后將array2數組的每一個元素都賦值為str_left_1(實際內存大小為0xa000),將array3數組的每一個元素都賦值為實際內存大小為0xa000的array_b(見上文)。

到這里內存布局便完成了,之后只要先將array2(在exp_1中操作)或array3(在exp_2中操作)的部分元素進行釋放,就會有大量0xa000的內存空洞,此時立即申請0xa000字節大小就有可能對釋放的內存進行重用。

只要保證rtJoin函數中的SysAllocStringLen申請的大小為0xa000字節,結合上述漏洞就可實現對array2某個str_left_1對象或array3數組中某個array_b對象的數據覆蓋,這些會在后面詳細描述。

改寫BSTR長度

在exp_1中,第一次觸發漏洞,改寫一個BSTR對象的長度為0xfffffffe。

首先給array_c第1個和第2個元素賦值為str_h(字符串長度為0x4fec字節,實際占用的空間為0x5000字節,見上文),給第3個元素賦值為class_a的對象,而class_a的Default Property Get會返回一個長度為0x18字節的長度(0x1a-0x6+0x4 = 0x18),這樣array的三個元素加上分隔字符拼接后占用的長度為0x9ff4(0x4fec+0x4fec+0x18+0x2+0x2 = 0x9ff4)

在觸發漏洞前先調用make_hole_of_array2前釋放array2中的一半元素,以生成足夠的大小為0xa000的內存空洞。

make_hole_of_array2調用前后后對應的內存布局如下,可以發現array2中一半字符串內存均被釋放,對于下標在0x00-0x7F區間內的元素,偶數部分被釋放;對于下標在0x80-0xFF區間的元素,奇數部分被釋放:

隨后在rtJoin中的SysAllocStringLen會申請分配一個總長度為0xa000字節的BSTR((0x9ff4 + 0x15) & 0xfffffff0 = 0xa000)。由于windows的堆分配算法,該內存會從上圖右邊的空閑堆塊中重用一個。

在class的Default Property Get中,先釋放array_c的第1、2個元素(設為Nothing),并立即將它們賦值為str_o(字符串長度為0x4ff6字節,實際占用的空間為0x5000字節)。

這里需要注意兩點:

  1. 2次賦值為str_o的操作會重用剛釋放的2個0x5000內存塊(即原先兩個str_h占據的內存)。
  2. 重用后,相同地址處的字符串長度和內容發生了變化(一開始是str_h,長度為0x4fec字節,現在是str_o,長度為0x4ff6),所以在rtJoin中進行memcpy前重新獲取的字符串長度分別為0x4ff6,0x4ff6,0x18,再加上2次分隔字符的大小0x4,memcpy總共復制的數據長度為0xa008,相比0x9ff4字節多出了0x14字節,多出的字節中的最后4字節則會覆蓋array2中相鄰str_left_1對象的長度域,在利用代碼中,攻擊者將原str_left_1的長度覆寫為了0xfffffffe。

錯位過程如下圖所示:

隨后,借助超長字符串獲取前面準備的字符串地址,用于后續使用。

下圖為在prepare中準備的字符串:

構造超長數組

在exp_2中,第二次觸發漏洞,將fake_array對應字符串的類型改為0x200c,方法同覆蓋字符串長度一致,此處不再重復描述。

fake_array是個字符串,它實際為一份偽造的tagSAFEARRAY結構。

以下為尋找類型混淆后的超長數組,用于后面使用:

任意地址讀

隨后樣本借助前面獲得的字符串地址和超長數組封裝了一組任意地址讀取的功能函數,供后面使用。

構造輔助函數

具備了任意地址讀取能力后,利用封裝了若干輔助函數:

隨后通過以下方式泄露CScriptEntryPoint對象的虛表地址

隨后借助封裝好的輔助函數獲取vbscript.dll基地址,再通過遍歷vbscript.dll導入表獲取msvcrt.dll基地址, msvcrt.dll又引入了kernelbase.dll、ntdll.dll,最后獲取了NtContinue、VirtualProtect等函數地址,整個過程如下:

執行shellcode

原利用代碼在windows 7和windows 8環境中,執行shellcode的方式與之前CVE-2018-8174相同。在windows 8.1和windows 10環境中所用的方式與低版本系統中略有不同。

動態調試

內存布局

prepare函數中內存布局完成后array2、array3和array_c的pvData分別如下所示。

內存重用

首先是Public Default Property Get回調中str_o字符串對str_h字符串內存的重用。重用后整體內存大小不變,字符串長度發生變化。

然后是SysAllocStringLen申請0xa000大小內存時對array2中某個被釋放的0xa000字符串的重用。從下圖中可以看到,第一次觸發漏洞前重用的內存是剛被釋放的array2(0x81)。隨后array2(0x82)對應字符串的長度將被覆寫。

改寫BSTR長度

在exp_1中第一次觸發漏洞,改寫某個str_left_1字符串的長度域。

構造超長數組

在exp_2中再次進行內存重用,此時的SysAllocStringLen申請的0xa000內存重用的是array3(0x81)剛釋放的內存(釋放方式與array2相同),隨后array3(0x82)相關內存的首部將被改寫。

第二次觸發漏洞,將精心準備的fake_array字符串的type由0008改寫為200c,從而得到一個超長一維數組。

執行shellcode

在windows 7和windows 8下的shellcode執行細節可參考我們之前寫的CVE-2018-8174分析文章。 在window 8.1和windows 10環境中,樣本用了一些其他技巧來Bypass CFG(在我們的測試中,該方式可以在早期版本的windows 8.1和windows 10中成功)。關于這部分的更多細節,我們會在后面的文章中進行披露。

補丁分析

以下是補丁前后Bindiff工具的比對結果。

可以看到,補丁文件中在拷貝每個數組元素到join_list之前,會先通過SysAllocString將字符串數據保存一份,這樣即使在后面回調中更改了初始的字符串長度,在執行memcpy進行內存拷貝時也會使用SysAllocString函數拷貝的那份數據,從而使SysAllocStringLen申請的內存大小和memcpy拷貝的數據大小相同,從而修復了漏洞。

與APT-C-06的關聯分析

我們對四個vbscript漏洞的shellcode進行了關聯分析,我們發現cve-2016-0189、本次漏洞和cve-2018-8174所用的shellcode除配置的CC外基本一致,cve-2018-8373的shellcode略有不同,但也非常相似。我們推測本次漏洞也是APT-C-06(又名Darkhotel)武器庫中的一個。

福利

讀者有沒有發現rtJoin函數中還存在一處整數溢出點,如下:

我們查找了vbscript里面join系列函數相關的整數溢出漏洞,發現有一個漏洞是CVE-2017-11869,我們對該漏洞修復前后的vbscript.dll做了一次補丁比對,并且發現了一些有意思的修改點,如下:

有興趣的讀者可以深入研究一下CVE-2017-11869。

結論

本文我們分享了本年度發現的第三個vbscript的漏洞細節,其利用手法和之前幾個同樣精彩。我們相信vbscript里面還有其他類似問題,同時推測相關開發團伙手上還有其他類似的漏洞利用,請廣大用戶提高警惕。

參考鏈接

http://blogs.#/post/cve-2018-8174.html

https://www.zerodayinitiative.com/advisories/ZDI-17-916/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/743/