為什么在ASLR機(jī)制下DLL文件在不同進(jìn)程中的加載基址相同?
1. DLL 注入實(shí)現(xiàn)
以下是實(shí)現(xiàn) DLL注入的簡要步驟:
1.1 打開 Visual Studio,并創(chuàng)建一個(gè)新的 DLL 項(xiàng)目。
1.2 在"dllmain.cpp" 添加以下的代碼
1 // dllmain.cpp : 定義 DLL 應(yīng)用程序的入口點(diǎn)。 2 #include "pch.h" 3 4 BOOL APIENTRY DllMain( HMODULE hModule, 5 DWORD ul_reason_for_call, 6 LPVOID lpReserved 7 ) 8 { 9 switch (ul_reason_for_call) 10 { 11 case DLL_PROCESS_ATTACH: 12 MessageBoxA(NULL, "您的進(jìn)程已被注入", "注入警告", NULL); 13 break; 14 case DLL_THREAD_ATTACH: 15 MessageBoxA(NULL, "您的進(jìn)程已被注入", "注入警告", NULL); 16 break; 17 case DLL_THREAD_DETACH: 18 MessageBoxA(NULL, "您的進(jìn)程已被注入", "注入警告", NULL); 19 break; 20 case DLL_PROCESS_DETACH: 21 MessageBoxA(NULL, "您的進(jìn)程已被注入", "注入警告", NULL); 22 break; 23 } 24 return TRUE; 25 }
1.3 生成 DLL 文件,得到一個(gè)名為 "InjectDll.dll" 的 DLL文件。
1.4 運(yùn)行以下代碼,將 DLL文件注入到記事本進(jìn)程中
1 #include <Windows.h> 2 #include <stdio.h> 3 4 int main() 5 { 6 // 獲取目標(biāo)進(jìn)程的句柄 7 HWND hWnd = FindWindow(NULL, L"無標(biāo)題 - Notepad"); 8 if (hWnd == NULL) { 9 printf("未找到目標(biāo)進(jìn)程\n"); 10 return 1; 11 } 12 13 DWORD processId; 14 GetWindowThreadProcessId(hWnd, &processId); 15 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); 16 if (hProcess == NULL) { 17 printf("無法打開目標(biāo)進(jìn)程\n"); 18 return 1; 19 } 20 21 // 在目標(biāo)進(jìn)程中分配內(nèi)存 22 LPVOID pRemoteBuffer = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE); 23 if (pRemoteBuffer == NULL) { 24 printf("無法在目標(biāo)進(jìn)程中分配內(nèi)存\n"); 25 return 1; 26 } 27 28 // 將DLL路徑寫入目標(biāo)進(jìn)程 29 char dllPath[] = "E:\\Test\\InjectDll.dll"; 30 if (!WriteProcessMemory(hProcess, pRemoteBuffer, dllPath, sizeof(dllPath), NULL)) { 31 printf("無法寫入目標(biāo)進(jìn)程內(nèi)存\n"); 32 return 1; 33 } 34 35 // 獲取LoadLibrary函數(shù)的地址 36 HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll"); 37 if (hKernel32 == NULL) { 38 printf("未找到kernel32.dll\n"); 39 return 1; 40 } 41 42 FARPROC pLoadLibrary = GetProcAddress(hKernel32, "LoadLibraryA"); 43 if (pLoadLibrary == NULL) { 44 printf("未找到LoadLibrary函數(shù)\n"); 45 return 1; 46 } 47 48 // 在目標(biāo)進(jìn)程中調(diào)用LoadLibrary函數(shù)加載DLL 49 HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, pRemoteBuffer, 0, NULL); 50 if (hThread == NULL) { 51 printf("無法在目標(biāo)進(jìn)程中創(chuàng)建遠(yuǎn)程線程\n"); 52 return 1; 53 } 54 55 printf("DLL注入成功\n"); 56 57 // 等待遠(yuǎn)程線程退出 58 WaitForSingleObject(hThread, INFINITE); 59 60 // 清理資源 61 CloseHandle(hThread); 62 VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE); 63 CloseHandle(hProcess); 64 65 return 0; 66 }
總結(jié)一下 Dll 注入步驟
- 定位目標(biāo)進(jìn)程:使用Windows API函數(shù)(如FindWindow)或其他技術(shù)來獲取目標(biāo)進(jìn)程的句柄或進(jìn)程ID;
- 打開目標(biāo)進(jìn)程:使用OpenProcess函數(shù)打開目標(biāo)進(jìn)程,獲取進(jìn)程的句柄,以便后續(xù)操作;
- 在目標(biāo)進(jìn)程中分配內(nèi)存:使用VirtualAllocEx函數(shù)在目標(biāo)進(jìn)程中分配一塊內(nèi)存,用于存儲(chǔ)DLL路徑或其他數(shù)據(jù);
- 將DLL路徑寫入目標(biāo)進(jìn)程:使用WriteProcessMemory函數(shù)將DLL路徑或其他數(shù)據(jù)寫入目標(biāo)進(jìn)程的內(nèi)存空間;
- 獲取函數(shù)地址:獲取所需函數(shù)(例如LoadLibrary)在目標(biāo)進(jìn)程所加載的模塊中的地址,通常使用GetModuleHandle和GetProcAddress函數(shù);
- 在目標(biāo)進(jìn)程中創(chuàng)建遠(yuǎn)程線程:使用CreateRemoteThread函數(shù)在目標(biāo)進(jìn)程中創(chuàng)建一個(gè)遠(yuǎn)程線程,該線程執(zhí)行加載DLL的函數(shù),并將DLL路徑作為參數(shù)傳遞;
- 等待遠(yuǎn)程線程退出:使用WaitForSingleObject函數(shù)等待遠(yuǎn)程線程退出,確保注入操作完成;
- 清理資源:關(guān)閉句柄、釋放內(nèi)存等,以確保不會(huì)產(chǎn)生資源泄漏。
2. 為什么在ASLR機(jī)制下DLL文件在不同進(jìn)程中加載的基址相同
2.1. ALSR
ASLR(Address Space Layout Randomization)是一種用于增加系統(tǒng)安全性的技術(shù),它通過隨機(jī)化內(nèi)存地址的分配,使攻擊者更難以利用已知的內(nèi)存布局漏洞進(jìn)行攻擊。實(shí)際上ASLR的概念在Windows XP時(shí)代就已經(jīng)提出來了,只不過XP上面的ASLR功能很有限,只是對(duì)PEB和TEB進(jìn)行了簡單的隨機(jī)化處理,而對(duì)于模塊的加載基址沒有進(jìn)行隨機(jī)化處理,直到Windows Vista出現(xiàn)后,ASLR才真正開始發(fā)揮作用。
微軟從Visual Studio 2005 SP1開始加入了/dynamicbase鏈接選項(xiàng)使編譯好的程序支持隨機(jī)基址,只需要在編譯程序的時(shí)候啟用/dynmicbase鏈接選項(xiàng)。(Visual Studio 2022 可以在項(xiàng)目屬性中設(shè)置:配置屬性——鏈接器——高級(jí)——隨機(jī)基址)

PE文件中IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE值為1,則說明該 PE文件支持ASLR,如下圖所示。

ALSR 會(huì)隨機(jī)化的地址包括:
- 堆地址
- 棧地址
- PE文件加載基址
- PEB和TEB地址
ALSR 機(jī)制能保證在每次系統(tǒng)重啟后,系統(tǒng)DLL文件在進(jìn)程中的加載基址不會(huì)是默認(rèn)的地址0x10000000(EXE文件的默認(rèn)加載基址是0x00400000),而是一個(gè)隨機(jī)的地址。系統(tǒng)重啟后這個(gè)加載基址會(huì)再次變化,ASLR的出現(xiàn)使得shellcode中的關(guān)鍵跳轉(zhuǎn)只能在系統(tǒng)重啟前,甚至只有程序的本次運(yùn)行時(shí)才能執(zhí)行,這使得exploit的難度大大增加。在ALSR開啟的狀態(tài)下,DLL 注入依然能實(shí)現(xiàn)是因?yàn)镈LL文件在不同進(jìn)程中加載的基址雖然經(jīng)過了隨機(jī)化的處理,但系統(tǒng)DLL文件(如system32目錄下的DLL)在各個(gè)進(jìn)程中通常加載地址仍然是相同的,以保證不同進(jìn)程能互相調(diào)用這些系統(tǒng)DLL提供的 API。
2.2. Copy-On-Write
其中更深層次的原因是操作系統(tǒng)需要支持寫時(shí)復(fù)制機(jī)制(copy-on-write)。寫時(shí)復(fù)制是現(xiàn)代操作系統(tǒng)的一個(gè)重要特性。操作系統(tǒng)使用頁表(Page Table)來將進(jìn)程的虛擬地址映射到物理地址。頁表是一種數(shù)據(jù)結(jié)構(gòu),它存儲(chǔ)了虛擬地址和物理地址之間的映射關(guān)系。
A 進(jìn)程和 B 進(jìn)程共享同一個(gè) DLL 時(shí),它們的虛擬地址空間中的虛擬地址會(huì)指向相同的物理內(nèi)存頁。這意味著它們共享同一份物理內(nèi)存。當(dāng) A 進(jìn)程嘗試對(duì) DLL 內(nèi)存頁進(jìn)行寫操作時(shí),操作系統(tǒng)會(huì)觸發(fā)寫時(shí)復(fù)制,操作系統(tǒng)會(huì)將共享的物理內(nèi)存頁復(fù)制一份,創(chuàng)建一個(gè)新的物理頁供 A 進(jìn)程使用。A 進(jìn)程會(huì)擁有自己的獨(dú)立副本,而進(jìn)程B仍然使用原始的物理內(nèi)存頁。
Copy-On-Write機(jī)制觸發(fā)并不會(huì)影響虛擬地址空間的映射關(guān)系。因此,在Copy-On-Write機(jī)制中,虛擬地址空間中DLL的加載基址不會(huì)發(fā)生變化。進(jìn)程A仍然可以通過原始的加載基址訪問和調(diào)用DLL中的代碼和數(shù)據(jù)。當(dāng)多個(gè)進(jìn)程加載同一個(gè) DLL 文件并且它們的加載基址保持相同時(shí),可以更好地利用 Copy-On-Write 機(jī)制。這樣可以實(shí)現(xiàn)代碼和只讀數(shù)據(jù)的共享,延遲數(shù)據(jù)的復(fù)制,并提高內(nèi)存利用率和性能。如果 DLL 加載地址不一致,Copy-On-Write 無法共享內(nèi)存頁,每個(gè)進(jìn)程都需要單獨(dú)復(fù)制 DLL 的只讀內(nèi)存,失去了內(nèi)存優(yōu)化的效果。
2.3. PE文件的加載機(jī)制
在 PE 文件中有一個(gè)加載基址(Image Base)的字段,它指定了 DLL 文件在內(nèi)存中的起始地址。操作系統(tǒng)在加載DLL文件時(shí),首先會(huì)檢查文件頭中的標(biāo)志,確定是否啟用了ASLR或者是否存在重定位表。如果存在重定位表,操作系統(tǒng)會(huì)遍歷重定位表中的每個(gè)條目。重定位表中的每個(gè)條目包含了兩個(gè)關(guān)鍵信息:
- 重定位類型(Relocation Type): 指定了需要進(jìn)行的重定位操作,例如相對(duì)地址的基址絕對(duì)化。
- 偏移量(Offset): 指定了在文件中的位置,即需要進(jìn)行重定位的虛擬地址相對(duì)于模塊基址的偏移量。
操作系統(tǒng)使用以下公式計(jì)算PE 文件中需要進(jìn)行重定位的虛擬地址:VirtualAddress = ImageBase + Offset(偏移量)
在相同的操作系統(tǒng)和相同的加載條件下,相同的DLL文件在不同進(jìn)程中的重定位計(jì)算是一致的。因此,無論 ASLR 是否啟用,PE 文件的加載機(jī)制決定了 DLL 文件在各個(gè)進(jìn)程中的加載基址是相同的。
綜合這三個(gè)角度,雖然ASLR在操作系統(tǒng)的進(jìn)程管理中引入了加載基址的隨機(jī)化,但由于Copy-On-Write和PE文件加載機(jī)制的作用,同一DLL文件在不同進(jìn)程中的加載基址仍然是相同的。這有助于確保不同進(jìn)程中的DLL文件內(nèi)部結(jié)構(gòu)保持一致,同時(shí)確保進(jìn)程間的數(shù)據(jù)隔離,提高系統(tǒng)的整體安全性和資源使用效率。
posted on 2023-11-18 23:08 ZyOrca 閱讀(251) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)