宋曉斌 穆 源 朱 濤 馬陳城
(中國人民解放軍61660部隊 北京 100093)
隨著近年來網絡攻擊事件呈爆發增長趨勢,網絡安全問題已經成為業界關注和討論的熱點,正逐步引起各方重視.木馬、僵尸網絡、釣魚網站等傳統網絡安全威脅有增無減,分布式拒絕服務攻擊、高級持續性威脅(advanced persistent threat, APT)攻擊等新型網絡攻擊手段應用愈發普遍.從傳統的感染型病毒、遠控木馬,到近些年常用的社會工程、0day漏洞、勒索軟件等發起的有針對性的APT攻擊,攻擊特征更趨向于高級化、隱蔽化及持久化,并且能夠輕易繞過傳統的安全防護體系.其中DLL(dynamic link library)注入技術作為實現隱蔽滲透的一項主流技術已經給傳統安全防護體系帶來極大的挑戰.
DLL注入最初被用于改進程序、修復Bug,后來由于其隱蔽及難以檢測的特性逐步被應用于滲透攻擊.DLL注入目前作為惡意攻擊者或紅隊人員經常使用的一門技術,經過多年的發展已十分成熟.DLL注入的本質就是將DLL放進某個目標進程的地址空間里,使其成為目標進程的一部分.DLL被加載到進程后會自動運行DllMain函數,攻擊者可以把具有攻擊行為的代碼放到DllMain函數中,當通過某種手段加載該DLL時,添加的代碼就會被執行.與一般的DLL加載的區別在于目標進程在正常運行過程中并不調用該DLL中的任何函數,屬于被動加載行為.

圖1 導入表邏輯結構
在通常情況下,程序加載DLL的時機主要有以下3個:一是在進程創建階段加載輸入表中的DLL,即靜態輸入;二是通過調用系統API函數LoadLibrary主動加載,即動態加載;三是由于系統機制的要求,必須加載系統預設的一些基礎服務模塊,如網絡服務接口模塊、輸入法模塊等.因此,DLL注入也通過上述3種手段進行.
1) 干預PE加載過程注入技術.通過在完全加載PE文件之前對程序進行人工干預,為PE文件中的輸入表增加1個指向待注入的DLL項,當程序主線程在輸入表初始化階段時就會主動加載目標DLL.主要包括導入表注入、篡改原始DLL注入等.
2) 動態注入技術.通過改變程序執行流程使其主動加載目標DLL.主要包括通過創建遠程線程注入、APC注入、依賴可信任進程注入、反射式DLL注入等.
3) 利用系統機制加載注入技術.基于操作系統提供的某些系統機制需要依賴基礎服務模塊來實現,當進程主動或被動觸發這些系統機制時,就會主動加載這些模塊.因此,可以定制符合相關規范的DLL,將其注冊為系統服務模塊達到注入的目的.主要包括使用注冊表注入、輸入法注入、消息鉤取注入、LSP劫持注入等.
當程序被加載時,系統根據程序導入表信息加載需要的DLL,導入表結構如圖1所示.導入表注入的原理就是修改程序的導入表,將自定義的DLL添加到程序的導入表中,程序運行時可以將自定義的DLL加載到程序的進程空間.
具體過程為:①將需要注入DLL的程序寫入到內存中,并新增1個節;②復制原來的導入表到新節中;③在新節復制的導入表后新增1個導入表項IMAGE_IMPORT_DESCRIPTOR;④增加8 B的INT(import name table)表和8 B的IAT(import address table)表;⑤存儲需要注入DLL的名稱;⑥增加1個IMAGE_IMPORT_BY_NAME結構,并將函數名稱存入結構體第1個變量后的內存中;⑦將IMAGE_IMPORT_BY_NAME結構地址的相對虛擬地址(relative virtual address, RVA)賦值給INT表和IAT表第1項;⑧將DLL名稱所在位置首地址的RVA賦值給新增導入表的Name項;⑨修改PE文件中IMAGE_DATA_DIRECTORY結構的VirtualAddress和Size;⑩重新保存為新的PE文件.
篡改原始DLL注入即通過偽造惡意DLL文件對程序中原始的DLL文件進行替換.當進程在正常執行過程中調用相應的DLL時,惡意DLL即可被調用執行.其中最為典型的例子為ComRes注入.ComRes注入的原理是當目標進程使用CoCreateInstance這個系統API時,COM服務器會加載C:Windowssystem32目錄下的ComRes.dll文件到目標進程中,利用這個加載過程,攻擊者可以用偽造的惡意ComRes.dll替換系統本身的ComRes.dll,然后利用LoadLibrary將偽造的DLL加載到目標EXE中.
完整的實現過程如圖2所示.首先使用進程PID打開進程,獲得進程句柄;然后利用進程句柄申請內存空間,將DLL路徑寫入內存;最后創建遠程線程,調用LoadLibrary執行DLL.

圖2 創建遠程線程實現DLL注入流程
APC即異步過程調用.異步過程調用是一種能在特定線程環境中異步執行的系統機制.Windows系統中每個線程都會維護1個線程APC隊列,用戶可以利用系統API在線程APC隊列添加APC函數,系統會產生1個軟中斷來執行這些APC函數.APC有2種形式,由系統產生的APC稱為內核模式APC,由應用程序產生的APC稱為用戶模式APC.同時還需要借助特定函數才能觸發,如SleepEx,SignalObjectAndWait等.因此APC注入的場景應為:1)必須是多線程環境;2)注入的進程必須調用特定的函數.APC注入的原理是利用當線程被喚醒時,APC中的注冊函數會被執行,并以此去執行惡意DLL加載代碼,進而完成DLL注入的目的.其實現流程為:1)當進程中某個線程執行到SleepEx或者WaitForSingleObjectEx時,系統就會產生1個軟中斷;2)利用QueueUserAPC這個API可以在軟中斷時向線程的APC隊列插入1個函數指針;3)當線程再次被喚醒時,此線程會首先執行APC隊列中被注冊的函數.如果攻擊者插入的是LoadLibrary執行函數,就能達到注入DLL的目的.
其原理是利用Windows系統中高權限的可信進程通過2次注入實現攻擊,以圖3為例進行說明:在第1次注入過程中,將a.dll注入到Services.exe中,再利用a.dll將b.dll注入到目標進程中.依賴可信進程注入本質上仍屬于遠程線程注入的一種,區別在于它利用了系統可信進程進行遠程注入,極大提高了注入的成功率,并在注入結束后將自身釋放,這種注入方式可以有效降低被查殺的可能.

圖3 依賴可信進程的注入過程
大多數DLL注入通常需要將目標DLL存儲在磁盤中,而文件“落地”就存在著被查殺的風險.因此反射式DLL注入技術應運而生,該技術不需要在文件系統中存儲目標DLL文件.減少了文件“落地”被刪的風險.同時它并不具備傳統DLL注入方式的基本模式,因此更難以被殺毒軟件檢測.其核心思路是可以通過網絡或在本地存放1份DLL的加密版本,然后將其解密之后存儲在內存.利用VirtualAlloc和WriteProcessMemory將DLL文件寫入目標進程的虛擬地址空間.DLL文件中包含1個導出函數,該函數的功能就是裝載其自身,即實現了PE Loader功能.接下來只需要通過DLL的導出表找到該導出函數并調用它即可實現DLL注入.要實現反射式DLL注入需要注射器與待注入的DLL.其中,被注入的DLL除了需要導出1個函數來實現對自身的加載之外,其余部分可根據具體功能需求進行開發.而注射器只需要將待注入的DLL文件寫入目標進程,然后將控制權轉交給上述導出函數即可.
使用注冊表注入主要依賴于注冊表中的2個表項:AppInit_DLLs和LoadAppInit_DLLs,如圖4所示.當注冊表項AppInit_DLLs中存在DLL文件路徑時,會跟隨進程的啟動加載指定的DLL文件,同時LoadAppInit_DLLs置為1.注冊表注入的原理是當User32.dll被加載到進程時,會讀取AppInit_DLLs表項.若有值,將調用LoadLibrary載入這個字符串指定的每個DLL.所以注冊表注入只對加載User32.dll的進程有效.

圖4 相關注冊表項
輸入法注入的原理是通過篡改IME文件實現,目前主流的輸入法都是通過IME實現.IME是在Windows平臺上使用的標準輸入法接口規范,其本質是一個DLL文件,系統為這個DLL定義了一系列接口以滿足不同功能需求.Windows系統在切換輸入法時,會把這個輸入法需要的IME文件裝到當前進程中,利用該特性,在IME文件中使用LoadLibrary函數注入惡意DLL文件.但輸入法注入的實現需要對輸入法IME文件的實現過程有一定了解,實現起來相對困難.
鉤子(Hook)是一種Windows消息攔截機制[1].消息鉤子注入原理是利用SetWindowsHookEx系統API攔截目標進程的消息到指定DLL中的導出函數,利用該特性,可以將DLL注入到指定的進程中.操作系統會將消息鉤子加載到所有進程空間,當指定消息發生時,則優先調用相應的處理函數,通過該過程即可實現DLL注入攻擊.圖5所示即為SetWindowsHookEx安裝鉤子后的消息處理流程,在進入系統消息處理函數WinProc前進入自定義的響應消息操作流程中.
分層服務提供者(layered service provider, LSP)是一個安裝在Winsock目錄中的DLL程序.應用程序通過Winsock2進行網絡通信時,會調用ws2_32.dll的導出函數,如connect,accept等.而后端通過LSP實現這些函數的底層.簡單來說,就是調用Winsock2提供的函數時會調用對應的LSP提供的服務器提供者接口(service provider interface, SPI)函數.SPI是由LSP導出的供ws2_32.dll調用的系列函數,是Winsock2提供的一項新特性,通過它可以借助LSP對現有的傳輸服務提供者進行擴展.LSP注入的原理如圖6所示,可以概括為只要將自定義的LSP DLL安裝到系統網絡協議鏈中,那么所有基于Winsock實現的程序都會主動加載該LSP DLL.可以利用這個特點實現對網絡功能的進程注入.

圖5 SetWindowsHookEx插入消息攔截后的消息處理流程

圖6 LSP劫持注入原理
DLL注入檢測技術目前主要分為2大類:靜態檢測與動態檢測.其中靜態檢測主要通過提取PE文件特征[2-4]采用比對的方式進行判別;動態檢測以API監控技術、回溯分析技術為主.
文獻[5]提出了基于Detours技術Hook[6-8]裝載DLL文件API函數的防御方法.文獻[9]提出對CreateRemoteThread函數進行Hook來防止創建遠程線程的DLL注入,該方法能夠在一定程度上避免遠程線程注入,文獻[10]提出一種DLL搶占式注入技術,通過對NtCreateThread,NtResumeThreadNt,MapViewOfSection進行Hook實現一種內核Hook引擎,同時結合用戶態檢測模塊進行行為分析,但也都存在一定的局限性,需要Hook到每一個進程空間中,增加進程開銷.同時攻擊者也可以通過反Hook技術進行繞過.
文獻[11]提出一種模塊比對的方法,首先在正常PE文件沒有被非法注入DLL時[12],建立合法模塊列表.當PE文件掃描時,枚舉當前PE文件IDT表,并與合法模塊進行對比,將沒有在合法模塊列表中的模塊初步確定為可疑模塊,并通過進一步判斷和分析驗證這些可疑模塊是否為非法模塊.
文獻[13]提出建立線程“白名單”機制,即只允許指定進程執行合法線程,阻斷非法線程訪問.首先明確軟件正常運行時的可信線程列表,線程“白名單”的建立是一個長期訓練的過程,大部分軟件所產生的線程相對穩定,但隨著運行環境的變化,所加載的線程也可能發生一定的變化,例如引入了新功能,這些也屬于可信線程的范疇.在建立了穩定的線程“白名單”后,便可以通過實時監測,確保在執行過程中只允許可信線程執行,并攔截非法遠程線程的注入執行.
文獻[14]提出一種通過比對VERSIONINFO資源的方式進行判別,該技術是基于惡意攻擊者在創建DLL文件時通常不關注一些額外的屬性信息,例如版本信息、公司名稱等,這將導致微軟構建的合法DLL與惡意DLL在格式上存在細微差別,通過比對這些信息判斷是否存在惡意DLL文件.除此之外,還可以采取提取字符串、函數調用[15-16]或利用卷積神經網絡進行特征提取[17]等方法.這些方法均需要解析DLL獲取具體數據與正常的DLL進行對比,判斷是否為惡意DLL.但DLL很容易被處理或加殼導致無法提取上述信息.
文獻[18]提出一種基于合法范圍的檢測技術,該技術通過分析合法DLL與注入DLL區別,發現所有合法DLL的IAT與INT數據均按順序排列,符合一定規則.而手動注入的DLL的相關數據通常位于節空隙、節擴展部分或新增節,導致與合法數據存在不一致性.基于該思想檢測各模塊IAT與INT是否位于范圍內來判斷是否存在注入行為.但如果攻擊者將數據偽造于合法范圍內,則該方法將失效.
為解決靜態比對技術存在的局限性,文獻[18]還提出了一種基于異常回溯的深度檢測方法,具體原理為程序輸入表中相應的DLL均是程序自身所需的,在運行期間需要調用其中的庫函數,因此與這些DLL具有主動關聯性.而注入的DLL只是在加載時執行DllMain中的代碼,程序自身不調用其庫函數.因此程序與注入的DLL具有被動關聯性.若清除了程序所需的DLL,程序原有的調用該DLL函數的代碼就會因訪問無效的地址而導致進程出現訪問異常;若清除了注入的DLL,由于程序沒有任何主動調用其庫函數的代碼,所以不會導致進程出現訪問異常.以此實現對注入行為的檢測.但該方法需要耗費大量時間進行異常捕獲與進程重啟,不適用于常規場景下的檢測.
目前,常見的DLL注入技術由于經過長期發展,手段工具相對完善,攻擊過程逐步趨向自動化,與之相對應的檢測技術也已十分成熟,因此面臨難以實現隱蔽攻擊的問題.DLL注入技術已逐漸向無文件落地及文件加密處理方式轉變[19-20],以反射式DLL注入技術為例,目前主流的團隊滲透工具Cobalt Strike便采用該技術實現遠程控制,通過網絡傳輸的方式接收惡意DLL文件,然后對文件進行解密,通過該方式可以躲避絕大多數防護軟件的監測.同時,DLL注入技術的隱蔽性也取決于DLL實現的復雜性,例如API監控技術,DLL文件中可以通過手動實現各類API功能的方式進行繞過,此類攻擊方式相對罕見,原因在于實現系統API過程十分復雜,需要對系統API的實現原理較為清晰,因此這也對攻擊者提出了更高的要求.
總體上,現有的動靜態檢測技術各有其優缺點,靜態檢測的優勢在于低開銷,檢測效率高,但檢測方式相對簡單,容易被攻擊者繞過.而動態檢測則準確率較高,但開銷較大.現有的檢測技術在應對常規模式下的DLL注入均可以做到有效識別,但在應對高級DLL注入攻擊時均存在一定的檢測障礙.在靜態檢測方面,目前的方法均通過有限特征進行判別,下一步可以嘗試通過總結非法DLL特征,構建1維或多維特征向量,結合深度學習技術,對非法DLL進行識別.在動態檢測方面,可以結合惡意代碼分析技術,根據程序行為進一步分析注入的DLL合法性.同時,是否可以結合動靜態檢測各自的優勢,進一步提升準確性也是后續值得研究的重點.從另一個角度來看,目前的檢測技術大部分關注用戶態的行為,但在用戶態下實現注入的方式在變換上并不復雜,攻擊者可以采取多種變形方式繞過,而對于內核態相對較難,因此開展內核態DLL注入檢測技術研究也將成為未來的研究方向之一.
本文主要對當前較為常見的10種DLL注入技術的原理進行了闡述分析,并總結了6類DLL注入檢測技術.同時分析了當前2類技術各自存在的問題,并對各自未來的研究方向與發展趨勢進行了展望,為后續的研究提供參考借鑒.