1.三種DLL加載時機:
- 進程創建加載輸入表中的DLL(靜態輸入)
- 通過調用LoadLibrary主動加載(動態加載)
- 系統預設加載
通過干預輸入表處理過程加載目標dll
1.靜態修改PE輸入表法(測試程序 Notepad.exe)
- 准備工作:自行編寫一個MsgDLL,到處一個函數Msg();
#include "stdafx.h" BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if (ul_reason_for_call == DLL_PROCESS_ATTACH) { CreateThread(NULL, 0, ThreadShow, NULL, 0, NULL); } return TRUE; } DWORD WINAPI ThreadShow(LPVOID lpParameter) { char szPath[MAX_PATH] = { 0 }; char szBuf[1024] = { 0 }; //獲取宿主進程的路徑 GetModuleFileName(NULL, (LPWSTR)szPath, MAX_PATH); sprintf(szBuf, "DLL已經注入到進程:%s\t[pid =%d]\n", szPath, GetCurrentProcessId()); //以三種方式顯示自己的存在 //1. MessageBox(NULL, (LPWSTR)szBuf, "DLL存在", MB_OK); //2. printf("%s\n", szBuf); //3. OutputDebugString((LPWSTR)szBuf); return 0; }
參數意義:
①hModule參數:指向DLL本身的實例句柄;
②ul_reason_for_call參數:指明了DLL被調用的原因,可以有以下4個取值:
1. DLL_PROCESS_ATTACH:
當DLL被進程 <<第一次>> 調用時,導致DllMain函數被調用,
同時ul_reason_for_call的值為DLL_PROCESS_ATTACH,
如果同一個進程后來再次調用此DLL時,操作系統只會增加DLL的使用次數,
不會再用DLL_PROCESS_ATTACH調用DLL的DllMain函數。
2.DLL_PROCESS_DETACH:
當DLL被從進程的地址空間解除映射時,系統調用了它的DllMain,傳遞的ul_reason_for_call值是DLL_PROCESS_DETACH。
★如果進程的終結是因為調用了TerminateProcess,系統就不會用DLL_PROCESS_DETACH來調用DLL的DllMain函數。這就意味着DLL在進程結束前沒有機會執行任何清理工作。
3.DLL_THREAD_ATTACH:
當進程創建一線程時,系統查看當前映射到進程地址空間中的所有DLL文件映像,
並用值DLL_THREAD_ATTACH調用DLL的DllMain函數。
新創建的線程負責執行這次的DLL的DllMain函數,
只有當所有的DLL都處理完這一通知后,系統才允許線程開始執行它的線程函數。
4.DLL_THREAD_DETACH:
如果線程調用了ExitThread來結束線程(線程函數返回時,系統也會自動調用ExitThread),
系統查看當前映射到進程空間中的所有DLL文件映像,
並用DLL_THREAD_DETACH來調用DllMain函數,
通知所有的DLL去執行線程級的清理工作。
★注意:如果線程的結束是因為系統中的一個線程調用了TerminateThread,
系統就不會用值DLL_THREAD_DETACH來調用所有DLL的DllMain函數。
③lpReserved參數:保留
- 第二步:判斷是否還有足夠的空間存儲我們的導出函數 -----》(PE格式知識)
- )如果空間不足,那么我們則需要采取擴大節或者新增節來進行位置存儲;
- Notepad無法存儲我們的導出函數,那么我這里采取擴大最后一個節的方法
根據PE格式可以看到再數據目錄項中的導入表RVA為00007604
那么我們這里講RVA轉成FOA文件偏移 :0x7604 -0x1000(Virtual Address) + 0x400 (Raw Address) =0x6a04 ---》PE知識,看不懂重學PE
然后用16進制軟件打開notepad,我這里使用的是010 editor:
首先我們可以看到一個導入表的結構為20字節也就是16h,輸入表中,每20個字節(一個Image_Import_Directory)對應一個動態鏈接庫Dll的調用數據:
並且導入表是連續的,直到它以一組0x14大小的全0的結束標記來結束
定位到0x6a04,如圖所示,后面則是0x14大小的結束標記
接下來,我們將最后一個節區進行擴充:
將原有導入表搬入新地址(直接復制粘貼到新地址):
粘貼好后,在原有導入表區域構建新的OriginalFirstThunk、name和FirstThunk結構(注意我們粘貼后把原有導入表區域清零,騰出空間,做我們自己的結構)
首先清零
然后構建我們的OriginalFirstThunk、name和FirstThunk結構
在PE文件被加載前 ,OriginalFirstThunk和FirstThunk都是指向IMPORT_BY_NAME
根據結構我們可以知道:
DLLName RawOffset =0x6A14 RVA= 0x6A14 -0x400(Raw Address)+0x1000(Virtual Address) =0x7614
IMPORT_BY_NAME RawOffset =0x6A20 RVA= 0x6A20 -0x400(Raw Address)+0x1000(Virtual Address) =0x7620
在手動填寫數據的時候一定要注意字節順序問題:
然后根據剛填充的兩個結構和Name的偏移,填寫新的導入表結構
OriginalFirstThunk :RawOffset =0x6a04 RVA =0x6a04 -0x400(Raw Address)+0x1000(Virtual Address) =0x7604
FirstThunk:RawOffset =0x6a0c RVA = 0x6a0c -0x400(Raw Address)+0x1000(Virtual Address) =0x7620
DLLName RawOffset =0x6A14 RVA= 0x6A14 -0x400(Raw Address)+0x1000(Virtual Address) =0x7614
修改完成后,我們修正PE文件頭信息:
- 輸入表目錄指向位置
- FirstThunk -》可寫屬性
首先修正輸入表目錄指向位置:
在010中定位到導入表的RVA :
將它原本指向的值修改成我們新替換的位置也就是最后一節的位置:
內存偏移 = 0XB000 +0X8000 =0X13000
文件偏移 = 0x8400+0x8000=0x10400
由於使用了原來導入表數組的位置存放FirstThunk,而它原來的位置的RVA是0X7604,根據各節的起始位置和偏移量,可以確定該節屬於text節,而該節原來的屬性是0X60000020,寫屬性定義如下
# define IMAGE_SCN_MEM_WRITE 0x800000000 //節是可寫的
0x 60000020+0x80000000 =0xE0000020
然后把新節屬性就是原屬性加上這個值也就是0xE0000020
至此我們的修改工作全部完成,保存修改結果
接下來運行修改后的NotePad,結果MessageBox沒有彈出來!
這里是因為IMAGE_IMPORT_DESCRIPTOR中定義的TimeDateStamp為0xFFFFFFFF也就是-1,表示改輸入項是原來預先Bound的,如果系統檢測發現預綁定是有效的,那么就不會再去處理輸入表加載了,所以我們只需把0x1B0到0x1B8 內容清零再次保存即可
可以看到確實加載了我們的MsgDll.dll