宋曉斌 穆 源 朱 濤 馬陳城
(中國人民解放軍61660部隊(duì) 北京 100093)
隨著近年來網(wǎng)絡(luò)攻擊事件呈爆發(fā)增長趨勢,網(wǎng)絡(luò)安全問題已經(jīng)成為業(yè)界關(guān)注和討論的熱點(diǎn),正逐步引起各方重視.木馬、僵尸網(wǎng)絡(luò)、釣魚網(wǎng)站等傳統(tǒng)網(wǎng)絡(luò)安全威脅有增無減,分布式拒絕服務(wù)攻擊、高級持續(xù)性威脅(advanced persistent threat, APT)攻擊等新型網(wǎng)絡(luò)攻擊手段應(yīng)用愈發(fā)普遍.從傳統(tǒng)的感染型病毒、遠(yuǎn)控木馬,到近些年常用的社會工程、0day漏洞、勒索軟件等發(fā)起的有針對性的APT攻擊,攻擊特征更趨向于高級化、隱蔽化及持久化,并且能夠輕易繞過傳統(tǒng)的安全防護(hù)體系.其中DLL(dynamic link library)注入技術(shù)作為實(shí)現(xiàn)隱蔽滲透的一項(xiàng)主流技術(shù)已經(jīng)給傳統(tǒng)安全防護(hù)體系帶來極大的挑戰(zhàn).
DLL注入最初被用于改進(jìn)程序、修復(fù)Bug,后來由于其隱蔽及難以檢測的特性逐步被應(yīng)用于滲透攻擊.DLL注入目前作為惡意攻擊者或紅隊(duì)人員經(jīng)常使用的一門技術(shù),經(jīng)過多年的發(fā)展已十分成熟.DLL注入的本質(zhì)就是將DLL放進(jìn)某個目標(biāo)進(jìn)程的地址空間里,使其成為目標(biāo)進(jìn)程的一部分.DLL被加載到進(jìn)程后會自動運(yùn)行DllMain函數(shù),攻擊者可以把具有攻擊行為的代碼放到DllMain函數(shù)中,當(dāng)通過某種手段加載該DLL時,添加的代碼就會被執(zhí)行.與一般的DLL加載的區(qū)別在于目標(biāo)進(jìn)程在正常運(yùn)行過程中并不調(diào)用該DLL中的任何函數(shù),屬于被動加載行為.
圖1 導(dǎo)入表邏輯結(jié)構(gòu)
在通常情況下,程序加載DLL的時機(jī)主要有以下3個:一是在進(jìn)程創(chuàng)建階段加載輸入表中的DLL,即靜態(tài)輸入;二是通過調(diào)用系統(tǒng)API函數(shù)LoadLibrary主動加載,即動態(tài)加載;三是由于系統(tǒng)機(jī)制的要求,必須加載系統(tǒng)預(yù)設(shè)的一些基礎(chǔ)服務(wù)模塊,如網(wǎng)絡(luò)服務(wù)接口模塊、輸入法模塊等.因此,DLL注入也通過上述3種手段進(jìn)行.
1) 干預(yù)PE加載過程注入技術(shù).通過在完全加載PE文件之前對程序進(jìn)行人工干預(yù),為PE文件中的輸入表增加1個指向待注入的DLL項(xiàng),當(dāng)程序主線程在輸入表初始化階段時就會主動加載目標(biāo)DLL.主要包括導(dǎo)入表注入、篡改原始DLL注入等.
2) 動態(tài)注入技術(shù).通過改變程序執(zhí)行流程使其主動加載目標(biāo)DLL.主要包括通過創(chuàng)建遠(yuǎn)程線程注入、APC注入、依賴可信任進(jìn)程注入、反射式DLL注入等.
3) 利用系統(tǒng)機(jī)制加載注入技術(shù).基于操作系統(tǒng)提供的某些系統(tǒng)機(jī)制需要依賴基礎(chǔ)服務(wù)模塊來實(shí)現(xiàn),當(dāng)進(jìn)程主動或被動觸發(fā)這些系統(tǒng)機(jī)制時,就會主動加載這些模塊.因此,可以定制符合相關(guān)規(guī)范的DLL,將其注冊為系統(tǒng)服務(wù)模塊達(dá)到注入的目的.主要包括使用注冊表注入、輸入法注入、消息鉤取注入、LSP劫持注入等.
當(dāng)程序被加載時,系統(tǒng)根據(jù)程序?qū)氡硇畔⒓虞d需要的DLL,導(dǎo)入表結(jié)構(gòu)如圖1所示.導(dǎo)入表注入的原理就是修改程序的導(dǎo)入表,將自定義的DLL添加到程序的導(dǎo)入表中,程序運(yùn)行時可以將自定義的DLL加載到程序的進(jìn)程空間.
具體過程為:①將需要注入DLL的程序?qū)懭氲絻?nèi)存中,并新增1個節(jié);②復(fù)制原來的導(dǎo)入表到新節(jié)中;③在新節(jié)復(fù)制的導(dǎo)入表后新增1個導(dǎo)入表項(xiàng)IMAGE_IMPORT_DESCRIPTOR;④增加8 B的INT(import name table)表和8 B的IAT(import address table)表;⑤存儲需要注入DLL的名稱;⑥增加1個IMAGE_IMPORT_BY_NAME結(jié)構(gòu),并將函數(shù)名稱存入結(jié)構(gòu)體第1個變量后的內(nèi)存中;⑦將IMAGE_IMPORT_BY_NAME結(jié)構(gòu)地址的相對虛擬地址(relative virtual address, RVA)賦值給INT表和IAT表第1項(xiàng);⑧將DLL名稱所在位置首地址的RVA賦值給新增導(dǎo)入表的Name項(xiàng);⑨修改PE文件中IMAGE_DATA_DIRECTORY結(jié)構(gòu)的VirtualAddress和Size;⑩重新保存為新的PE文件.
篡改原始DLL注入即通過偽造惡意DLL文件對程序中原始的DLL文件進(jìn)行替換.當(dāng)進(jìn)程在正常執(zhí)行過程中調(diào)用相應(yīng)的DLL時,惡意DLL即可被調(diào)用執(zhí)行.其中最為典型的例子為ComRes注入.ComRes注入的原理是當(dāng)目標(biāo)進(jìn)程使用CoCreateInstance這個系統(tǒng)API時,COM服務(wù)器會加載C:Windowssystem32目錄下的ComRes.dll文件到目標(biāo)進(jìn)程中,利用這個加載過程,攻擊者可以用偽造的惡意ComRes.dll替換系統(tǒng)本身的ComRes.dll,然后利用LoadLibrary將偽造的DLL加載到目標(biāo)EXE中.
完整的實(shí)現(xiàn)過程如圖2所示.首先使用進(jìn)程PID打開進(jìn)程,獲得進(jìn)程句柄;然后利用進(jìn)程句柄申請內(nèi)存空間,將DLL路徑寫入內(nèi)存;最后創(chuàng)建遠(yuǎn)程線程,調(diào)用LoadLibrary執(zhí)行DLL.
圖2 創(chuàng)建遠(yuǎn)程線程實(shí)現(xiàn)DLL注入流程
APC即異步過程調(diào)用.異步過程調(diào)用是一種能在特定線程環(huán)境中異步執(zhí)行的系統(tǒng)機(jī)制.Windows系統(tǒng)中每個線程都會維護(hù)1個線程APC隊(duì)列,用戶可以利用系統(tǒng)API在線程APC隊(duì)列添加APC函數(shù),系統(tǒng)會產(chǎn)生1個軟中斷來執(zhí)行這些APC函數(shù).APC有2種形式,由系統(tǒng)產(chǎn)生的APC稱為內(nèi)核模式APC,由應(yīng)用程序產(chǎn)生的APC稱為用戶模式APC.同時還需要借助特定函數(shù)才能觸發(fā),如SleepEx,SignalObjectAndWait等.因此APC注入的場景應(yīng)為:1)必須是多線程環(huán)境;2)注入的進(jìn)程必須調(diào)用特定的函數(shù).APC注入的原理是利用當(dāng)線程被喚醒時,APC中的注冊函數(shù)會被執(zhí)行,并以此去執(zhí)行惡意DLL加載代碼,進(jìn)而完成DLL注入的目的.其實(shí)現(xiàn)流程為:1)當(dāng)進(jìn)程中某個線程執(zhí)行到SleepEx或者WaitForSingleObjectEx時,系統(tǒng)就會產(chǎn)生1個軟中斷;2)利用QueueUserAPC這個API可以在軟中斷時向線程的APC隊(duì)列插入1個函數(shù)指針;3)當(dāng)線程再次被喚醒時,此線程會首先執(zhí)行APC隊(duì)列中被注冊的函數(shù).如果攻擊者插入的是LoadLibrary執(zhí)行函數(shù),就能達(dá)到注入DLL的目的.
其原理是利用Windows系統(tǒng)中高權(quán)限的可信進(jìn)程通過2次注入實(shí)現(xiàn)攻擊,以圖3為例進(jìn)行說明:在第1次注入過程中,將a.dll注入到Services.exe中,再利用a.dll將b.dll注入到目標(biāo)進(jìn)程中.依賴可信進(jìn)程注入本質(zhì)上仍屬于遠(yuǎn)程線程注入的一種,區(qū)別在于它利用了系統(tǒng)可信進(jìn)程進(jìn)行遠(yuǎn)程注入,極大提高了注入的成功率,并在注入結(jié)束后將自身釋放,這種注入方式可以有效降低被查殺的可能.
圖3 依賴可信進(jìn)程的注入過程
大多數(shù)DLL注入通常需要將目標(biāo)DLL存儲在磁盤中,而文件“落地”就存在著被查殺的風(fēng)險.因此反射式DLL注入技術(shù)應(yīng)運(yùn)而生,該技術(shù)不需要在文件系統(tǒng)中存儲目標(biāo)DLL文件.減少了文件“落地”被刪的風(fēng)險.同時它并不具備傳統(tǒng)DLL注入方式的基本模式,因此更難以被殺毒軟件檢測.其核心思路是可以通過網(wǎng)絡(luò)或在本地存放1份DLL的加密版本,然后將其解密之后存儲在內(nèi)存.利用VirtualAlloc和WriteProcessMemory將DLL文件寫入目標(biāo)進(jìn)程的虛擬地址空間.DLL文件中包含1個導(dǎo)出函數(shù),該函數(shù)的功能就是裝載其自身,即實(shí)現(xiàn)了PE Loader功能.接下來只需要通過DLL的導(dǎo)出表找到該導(dǎo)出函數(shù)并調(diào)用它即可實(shí)現(xiàn)DLL注入.要實(shí)現(xiàn)反射式DLL注入需要注射器與待注入的DLL.其中,被注入的DLL除了需要導(dǎo)出1個函數(shù)來實(shí)現(xiàn)對自身的加載之外,其余部分可根據(jù)具體功能需求進(jìn)行開發(fā).而注射器只需要將待注入的DLL文件寫入目標(biāo)進(jìn)程,然后將控制權(quán)轉(zhuǎn)交給上述導(dǎo)出函數(shù)即可.
使用注冊表注入主要依賴于注冊表中的2個表項(xiàng):AppInit_DLLs和LoadAppInit_DLLs,如圖4所示.當(dāng)注冊表項(xiàng)AppInit_DLLs中存在DLL文件路徑時,會跟隨進(jìn)程的啟動加載指定的DLL文件,同時LoadAppInit_DLLs置為1.注冊表注入的原理是當(dāng)User32.dll被加載到進(jìn)程時,會讀取AppInit_DLLs表項(xiàng).若有值,將調(diào)用LoadLibrary載入這個字符串指定的每個DLL.所以注冊表注入只對加載User32.dll的進(jìn)程有效.
圖4 相關(guān)注冊表項(xiàng)
輸入法注入的原理是通過篡改IME文件實(shí)現(xiàn),目前主流的輸入法都是通過IME實(shí)現(xiàn).IME是在Windows平臺上使用的標(biāo)準(zhǔn)輸入法接口規(guī)范,其本質(zhì)是一個DLL文件,系統(tǒng)為這個DLL定義了一系列接口以滿足不同功能需求.Windows系統(tǒng)在切換輸入法時,會把這個輸入法需要的IME文件裝到當(dāng)前進(jìn)程中,利用該特性,在IME文件中使用LoadLibrary函數(shù)注入惡意DLL文件.但輸入法注入的實(shí)現(xiàn)需要對輸入法IME文件的實(shí)現(xiàn)過程有一定了解,實(shí)現(xiàn)起來相對困難.
鉤子(Hook)是一種Windows消息攔截機(jī)制[1].消息鉤子注入原理是利用SetWindowsHookEx系統(tǒng)API攔截目標(biāo)進(jìn)程的消息到指定DLL中的導(dǎo)出函數(shù),利用該特性,可以將DLL注入到指定的進(jìn)程中.操作系統(tǒng)會將消息鉤子加載到所有進(jìn)程空間,當(dāng)指定消息發(fā)生時,則優(yōu)先調(diào)用相應(yīng)的處理函數(shù),通過該過程即可實(shí)現(xiàn)DLL注入攻擊.圖5所示即為SetWindowsHookEx安裝鉤子后的消息處理流程,在進(jìn)入系統(tǒng)消息處理函數(shù)WinProc前進(jìn)入自定義的響應(yīng)消息操作流程中.
分層服務(wù)提供者(layered service provider, LSP)是一個安裝在Winsock目錄中的DLL程序.應(yīng)用程序通過Winsock2進(jìn)行網(wǎng)絡(luò)通信時,會調(diào)用ws2_32.dll的導(dǎo)出函數(shù),如connect,accept等.而后端通過LSP實(shí)現(xiàn)這些函數(shù)的底層.簡單來說,就是調(diào)用Winsock2提供的函數(shù)時會調(diào)用對應(yīng)的LSP提供的服務(wù)器提供者接口(service provider interface, SPI)函數(shù).SPI是由LSP導(dǎo)出的供ws2_32.dll調(diào)用的系列函數(shù),是Winsock2提供的一項(xiàng)新特性,通過它可以借助LSP對現(xiàn)有的傳輸服務(wù)提供者進(jìn)行擴(kuò)展.LSP注入的原理如圖6所示,可以概括為只要將自定義的LSP DLL安裝到系統(tǒng)網(wǎng)絡(luò)協(xié)議鏈中,那么所有基于Winsock實(shí)現(xiàn)的程序都會主動加載該LSP DLL.可以利用這個特點(diǎn)實(shí)現(xiàn)對網(wǎng)絡(luò)功能的進(jìn)程注入.
圖5 SetWindowsHookEx插入消息攔截后的消息處理流程
圖6 LSP劫持注入原理
DLL注入檢測技術(shù)目前主要分為2大類:靜態(tài)檢測與動態(tài)檢測.其中靜態(tài)檢測主要通過提取PE文件特征[2-4]采用比對的方式進(jìn)行判別;動態(tài)檢測以API監(jiān)控技術(shù)、回溯分析技術(shù)為主.
文獻(xiàn)[5]提出了基于Detours技術(shù)Hook[6-8]裝載DLL文件API函數(shù)的防御方法.文獻(xiàn)[9]提出對CreateRemoteThread函數(shù)進(jìn)行Hook來防止創(chuàng)建遠(yuǎn)程線程的DLL注入,該方法能夠在一定程度上避免遠(yuǎn)程線程注入,文獻(xiàn)[10]提出一種DLL搶占式注入技術(shù),通過對NtCreateThread,NtResumeThreadNt,MapViewOfSection進(jìn)行Hook實(shí)現(xiàn)一種內(nèi)核Hook引擎,同時結(jié)合用戶態(tài)檢測模塊進(jìn)行行為分析,但也都存在一定的局限性,需要Hook到每一個進(jìn)程空間中,增加進(jìn)程開銷.同時攻擊者也可以通過反Hook技術(shù)進(jìn)行繞過.
文獻(xiàn)[11]提出一種模塊比對的方法,首先在正常PE文件沒有被非法注入DLL時[12],建立合法模塊列表.當(dāng)PE文件掃描時,枚舉當(dāng)前PE文件IDT表,并與合法模塊進(jìn)行對比,將沒有在合法模塊列表中的模塊初步確定為可疑模塊,并通過進(jìn)一步判斷和分析驗(yàn)證這些可疑模塊是否為非法模塊.
文獻(xiàn)[13]提出建立線程“白名單”機(jī)制,即只允許指定進(jìn)程執(zhí)行合法線程,阻斷非法線程訪問.首先明確軟件正常運(yùn)行時的可信線程列表,線程“白名單”的建立是一個長期訓(xùn)練的過程,大部分軟件所產(chǎn)生的線程相對穩(wěn)定,但隨著運(yùn)行環(huán)境的變化,所加載的線程也可能發(fā)生一定的變化,例如引入了新功能,這些也屬于可信線程的范疇.在建立了穩(wěn)定的線程“白名單”后,便可以通過實(shí)時監(jiān)測,確保在執(zhí)行過程中只允許可信線程執(zhí)行,并攔截非法遠(yuǎn)程線程的注入執(zhí)行.
文獻(xiàn)[14]提出一種通過比對VERSIONINFO資源的方式進(jìn)行判別,該技術(shù)是基于惡意攻擊者在創(chuàng)建DLL文件時通常不關(guān)注一些額外的屬性信息,例如版本信息、公司名稱等,這將導(dǎo)致微軟構(gòu)建的合法DLL與惡意DLL在格式上存在細(xì)微差別,通過比對這些信息判斷是否存在惡意DLL文件.除此之外,還可以采取提取字符串、函數(shù)調(diào)用[15-16]或利用卷積神經(jīng)網(wǎng)絡(luò)進(jìn)行特征提取[17]等方法.這些方法均需要解析DLL獲取具體數(shù)據(jù)與正常的DLL進(jìn)行對比,判斷是否為惡意DLL.但DLL很容易被處理或加殼導(dǎo)致無法提取上述信息.
文獻(xiàn)[18]提出一種基于合法范圍的檢測技術(shù),該技術(shù)通過分析合法DLL與注入DLL區(qū)別,發(fā)現(xiàn)所有合法DLL的IAT與INT數(shù)據(jù)均按順序排列,符合一定規(guī)則.而手動注入的DLL的相關(guān)數(shù)據(jù)通常位于節(jié)空隙、節(jié)擴(kuò)展部分或新增節(jié),導(dǎo)致與合法數(shù)據(jù)存在不一致性.基于該思想檢測各模塊IAT與INT是否位于范圍內(nèi)來判斷是否存在注入行為.但如果攻擊者將數(shù)據(jù)偽造于合法范圍內(nèi),則該方法將失效.
為解決靜態(tài)比對技術(shù)存在的局限性,文獻(xiàn)[18]還提出了一種基于異?;厮莸纳疃葯z測方法,具體原理為程序輸入表中相應(yīng)的DLL均是程序自身所需的,在運(yùn)行期間需要調(diào)用其中的庫函數(shù),因此與這些DLL具有主動關(guān)聯(lián)性.而注入的DLL只是在加載時執(zhí)行DllMain中的代碼,程序自身不調(diào)用其庫函數(shù).因此程序與注入的DLL具有被動關(guān)聯(lián)性.若清除了程序所需的DLL,程序原有的調(diào)用該DLL函數(shù)的代碼就會因訪問無效的地址而導(dǎo)致進(jìn)程出現(xiàn)訪問異常;若清除了注入的DLL,由于程序沒有任何主動調(diào)用其庫函數(shù)的代碼,所以不會導(dǎo)致進(jìn)程出現(xiàn)訪問異常.以此實(shí)現(xiàn)對注入行為的檢測.但該方法需要耗費(fèi)大量時間進(jìn)行異常捕獲與進(jìn)程重啟,不適用于常規(guī)場景下的檢測.
目前,常見的DLL注入技術(shù)由于經(jīng)過長期發(fā)展,手段工具相對完善,攻擊過程逐步趨向自動化,與之相對應(yīng)的檢測技術(shù)也已十分成熟,因此面臨難以實(shí)現(xiàn)隱蔽攻擊的問題.DLL注入技術(shù)已逐漸向無文件落地及文件加密處理方式轉(zhuǎn)變[19-20],以反射式DLL注入技術(shù)為例,目前主流的團(tuán)隊(duì)滲透工具Cobalt Strike便采用該技術(shù)實(shí)現(xiàn)遠(yuǎn)程控制,通過網(wǎng)絡(luò)傳輸?shù)姆绞浇邮諓阂釪LL文件,然后對文件進(jìn)行解密,通過該方式可以躲避絕大多數(shù)防護(hù)軟件的監(jiān)測.同時,DLL注入技術(shù)的隱蔽性也取決于DLL實(shí)現(xiàn)的復(fù)雜性,例如API監(jiān)控技術(shù),DLL文件中可以通過手動實(shí)現(xiàn)各類API功能的方式進(jìn)行繞過,此類攻擊方式相對罕見,原因在于實(shí)現(xiàn)系統(tǒng)API過程十分復(fù)雜,需要對系統(tǒng)API的實(shí)現(xiàn)原理較為清晰,因此這也對攻擊者提出了更高的要求.
總體上,現(xiàn)有的動靜態(tài)檢測技術(shù)各有其優(yōu)缺點(diǎn),靜態(tài)檢測的優(yōu)勢在于低開銷,檢測效率高,但檢測方式相對簡單,容易被攻擊者繞過.而動態(tài)檢測則準(zhǔn)確率較高,但開銷較大.現(xiàn)有的檢測技術(shù)在應(yīng)對常規(guī)模式下的DLL注入均可以做到有效識別,但在應(yīng)對高級DLL注入攻擊時均存在一定的檢測障礙.在靜態(tài)檢測方面,目前的方法均通過有限特征進(jìn)行判別,下一步可以嘗試通過總結(jié)非法DLL特征,構(gòu)建1維或多維特征向量,結(jié)合深度學(xué)習(xí)技術(shù),對非法DLL進(jìn)行識別.在動態(tài)檢測方面,可以結(jié)合惡意代碼分析技術(shù),根據(jù)程序行為進(jìn)一步分析注入的DLL合法性.同時,是否可以結(jié)合動靜態(tài)檢測各自的優(yōu)勢,進(jìn)一步提升準(zhǔn)確性也是后續(xù)值得研究的重點(diǎn).從另一個角度來看,目前的檢測技術(shù)大部分關(guān)注用戶態(tài)的行為,但在用戶態(tài)下實(shí)現(xiàn)注入的方式在變換上并不復(fù)雜,攻擊者可以采取多種變形方式繞過,而對于內(nèi)核態(tài)相對較難,因此開展內(nèi)核態(tài)DLL注入檢測技術(shù)研究也將成為未來的研究方向之一.
本文主要對當(dāng)前較為常見的10種DLL注入技術(shù)的原理進(jìn)行了闡述分析,并總結(jié)了6類DLL注入檢測技術(shù).同時分析了當(dāng)前2類技術(shù)各自存在的問題,并對各自未來的研究方向與發(fā)展趨勢進(jìn)行了展望,為后續(xù)的研究提供參考借鑒.