被遺漏的0day?APT-C-06組織另一網路武器庫分析揭祕
前言
近日,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結構體)。
需要注意的是,0x4fec 2 + 0x18 + 0x2 2 = 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裡面還有其他類似問題,同時推測相關開發團伙手上還有其他類似的漏洞利用,請廣大使用者提高警惕。
參考連結
ofollow,noindex" target="_blank">http://blogs.360.cn/post/cve-2018-8174.html
https://www.zerodayinitiative.com/advisories/ZDI-17-916/
宣告:本文來自360核心安全技術部落格,版權歸作者所有。文章內容僅代表作者獨立觀點,不代表安全內參立場,轉載目的在於傳遞更多資訊。如需轉載,請聯絡原作者獲取授權。