當前位置:
首頁 > 新聞 > 注入系列——DLL注入

注入系列——DLL注入

目的

我最近看到一個帖子,大家也可以看看,說的是最新發布的win10 Build1709有個新版的Windows Defender,標記了Invoke-shellcode模塊(這不僅是Empire才有的問題,Metasploit和其他流行框架也都被標記了,它們開始變得越來越過時了)。這意味著從紅隊攻擊的角度來看,我們需要新的shellcode注入技術,需要在繞過AV方面更具創造性。所以我想開始搞這個系列文章,關於不同的注入技巧,希望最終能形成一個完整的操作指南。

我想從DLL注入和在內存中映射和執行DLL的不同技術開始,因為我經常聽到這個技術,而且非常有興趣,不過一直沒機會深入探索。

我們首先從定義DLL並理解它們的組件開始。

概覽

DLL的意思就是動態鏈接庫(Dynamic-Link Libraries),它是包含很多函數和數據的一種模塊,可以被其他模塊調用(應用或DLL)。完整的DLL文檔,可以在微軟的網站上找到,鏈接在這裡。

DLL可以定義兩類函數,導出函數和內部函數。導出函數可以被其他模塊調用,也可以在定義它們的DLL中調用,而內部函數只能在定義它們的DLL中調用。

DLL和內存管理

載入DLL的每個進程都將其映射到其虛擬地址空間。在進程將DLL載入到其虛擬地址後,它可以調用導出的DLL函數。

系統維護著每個DLL的進程引用計數。當線程載入DLL時,引用計數加1。當進程終止時,或者引用計數變為零時(僅運行時動態鏈接),DLL將從進程的虛擬地址空間中卸載。

調用入口函數

只要出現以下任何一個事件,系統就會調用入口函數:

1.進程載入DLL。對於使用載入時動態鏈接的進程,DLL在進程初始化期間載入。對於使用運行時鏈接的進程,DLL在LoadLibrary或LoadLibraryEx返回之前載入。

2.進程卸載DLL。當進程終止或調用FreeLibrary函數並且引用計數變為零時,將卸載DLL。

3.在已載入DLL的進程中創建新線程。

4.已載入DLL的進程的線程正常終止,而不是使用TerminateThread或TerminateProcess來終止。

進程或線程引起函數被調用時,系統便會調用入口函數。這使得DLL可以使用入口函數在調用進程的虛擬地址空間中分配內存或打開進程可訪問的句柄。

根據Microsoft的文檔,有兩種方法可以調用DLL中的函數:

·載入時動態鏈接:對導出的DLL函數進行顯式調用,就好像它們是本地函數一樣。這要求您將模塊與包含這些函數的DLL的導入庫鏈接。導入庫為系統提供載入DLL所需的信息,並在載入應用程序時找到導出的DLL函數。

·運行時動態鏈接:使用LoadLibrary或LoadLibraryEx函數在運行時載入DLL。載入DLL後,模塊調用GetProcAddress函數來獲取導出的DLL函數的地址。該模塊使用GetProcAddress返回的函數指針調用導出的DLL函數。這樣就不需要導入庫了。

運行時動態鏈接

當應用程序調用LoadLibrary或LoadLibraryEx函數時,系統會嘗試查找DLL。如果搜索成功,系統將DLL模塊映射到進程的虛擬地址空間並增加引用計數。如果對LoadLibrary或LoadLibraryEx的調用指定了一個DLL,其代碼已經映射到調用進程的虛擬地址空間,則該函數只返回DLL的句柄並遞增DLL引用計數。

當線程調用LoadLibrary或LoadLibraryEx時,系統就會調用入口函數。如果系統找不到DLL或者入口函數返回FALSE,那麼LoadLibrary或LoadLibraryEx返回NULL。如果LoadLibrary或LoadLibraryEx成功,它將返回DLL模塊的句柄。該進程可以使用此句柄來識別調用GetProcAddress,FreeLibrary或FreeLibraryAndExitThread的函數中的DLL。

GetModuleHandle函數返回GetProcAddress,FreeLibrary或FreeLibraryAndExitThread中使用的句柄。該進程可以使用GetProcAddress來獲取DLL中導出函數的地址,這個DLL使用LoadLibrary,LoadLibraryEx或GetModuleHandle返回的DLL模塊句柄。

DLLMain入口函數

動態鏈接庫的可選入口,由BOOL WINAPI DllMain()函數定義。當系統啟動或終止進程或線程時,它使用進程的第一個線程為每個載入的DLL調用入口函數。當使用LoadLibrary和FreeLibrary函數載入或卸載DLL時,系統也會調用DLL的入口函數。

DllMain是庫定義的函數名的佔位符。您必須指定生成DLL時使用的實際名稱。在初始進程啟動期間或調用LoadLibrary之後,系統會掃描載入的DLL列表以查找進程。對於尚未使用DLL_PROCESS_ATTACH值調用的每個DLL,系統調用DLL的入口函數。此調用是在線程引起進程地址空間發生改變時進行的,例如進程的主線程或調用LoadLibrary的線程。

因為在調用入口函數時Kernel32.dll肯定會被載入到進程地址空間中,所以在Kernel32.dll中調用函數不會導致在執行初始化代碼之前使用DLL。因此,入口函數可以調用Kernel32.dll中不載入其他DLL的函數。

現在我們已經定義了一些我研究時發現有用的結構,讓我們深入研究。

發現目標進程

對於本系列中的大多數技術,我們需要做的第一件事就是找到我們想要注入的目標進程。遍歷和列舉進程的方法有幾種,但我發現最有用的是Process Walking。它使用CreateToolhelp32Snapshot函數拍攝快照,快照中包含進程列表,並包含每個當前正在執行的進程的信息。您可以使用Process32First函數獲取列表中第一個進程的信息。獲取列表中的第一個進程後,可以使用Process32Next函數遍歷進程列表來查找後面的進程,直到找到你的目標進程(例如putty.exe)或者只是將該進程添加到向量中並列印出所有的進程。找到目標進程後,我們使用OpenProcess函數來獲取進程的句柄(示例如下)。

您可以在此處找到完整源代碼(作為獨立工具)。我們還將它作為輔助函數合併到我們的DLL注入工具中。

DLL注入

DLL注入簡單地定義為將DLL插入另一個進程的空間然後執行其代碼的過程。以下是OpenSecurityResearch博客文章中對此過程進行簡化的可視化視圖:

可以在以下代碼片段中看到傳統注入技術的示例:

簡要分析一下在這個LoadLibrary示例中我們所做的事情(稍後我們將深入探討並對其中的某些部分進行擴展):

我們的第一個參數是HANDLE hProcess,它可以由OpenProcess函數定義(見下文),第二個參數是LPCSTR dllpath,它定義了完整的dll路徑,例子如下:

HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID);

一旦我們有了進程句柄,我們就可以使用VirtualAllocEx在目標進程的虛擬內存空間中分配一個內存區域。

LPVOID lpBaseAddress = VirtualAllocEx(hProcess, NULL, strlen(dllpath), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

然後,我們使用WriteProcessMemory函數通過DLL payload的完整路徑將數據寫入進程。

WriteProcessMemory(hProcess, lpBaseAddress, dllpath, strlen(dllpath), NULL);

然後,我們找到LoadLibrary的地址,並使用GetProcAddress和GetModuleHandle函數存儲該內存地址。

LPVOID lpLibraryAddress = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");

最後,我們在目標進程中執行LoadLibrary函數,使用CreateRemoteThread函數將DLL payload作為庫進行強制載入。我們也可以使用NtCreateThreadEx或RtlCreateUserThread,這些都是未記錄的函數,但思想是一樣的。由於CreateRemoteThread執行LoadLibrary函數,目標進程將立即使用DLL_PROCESS_ATTACH函數執行DLL的DllMain入口函數。

CreateRemoteThread(hProcess, NULL, (LPTHREAD_START_ROUTINE)lpLibraryAddress, lpBaseAddress, NULL, NULL);

使用這種方法,我們必須定義DLLMain入口函數,也很簡單,示例如下:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {

? ? ? ? ::MessageBox(NULL, L"Hello World!", L"Test DLL", MB_OK);

? ? ? ? return TRUE;

}

注意:這只是基本的POC,不過如果你看一下動態鏈接庫最佳實踐,你會看到DllMain函數就是用來在編譯時初始化靜態數據結構和成員的,創建和初始化同步對象,分配內存並初始化動態數據結構。不過,有些函數應該避免使用,例如User32.dll和Gdi32.dll。因為,在User32.dll中實現的MessageBox函數可能會導致問題(崩潰或死鎖)。

第一個例子比較基礎,而且動靜很大,因為CreateRemoteThread(大多數安全產品都會定位)和LoadLibrary函數調用更新了該進程中的數據結構,所以,任何監視進程的人(使用ProcMon)都可以看到每個進程中載入的每個DLL的名稱。

替代方案就是在遠程進程中為DLL的實際內容分配足夠的空間,但這很棘手,因為我們必須手動解析並將二進位映射到內存中。我們將在下一節探討這種替代方案 - 反射DLL注入(敬請期待)。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

又有人打著WannaCry的幌子來嚇唬人了
Threat Hunting之橫向移動攻擊

TAG:嘶吼RoarTalk |