前言:
將DLL文件作為資源插入到自己程序中的方法,前面已經說過了。附上鏈接:MFC —— 資源文件釋放(為了程序更簡潔) 程序需要動態調用DLL文件,內存加載運行技術可以把這些DLL作為資源插入到自己的程序中。此時直接在內存中加載運行即可,不需要再將DLL釋放到本地。
實現原理:
將資源加載到內存,然后把DLL文件按照映像對齊大小映射到內存中,切不可直接將DLL文件數據存儲到內存中。因為根據PE結構的基礎知識可知,PE文件有兩個對齊字段,一個是映像對齊大小SectionAlignment,另一個是文件對齊大小FileAlignment。其中,映像對齊大小是PE文件加載到內存中所用的對齊大小,而文件對齊大小是PE文件存儲在本地磁盤所用的對齊大小。一般文件對齊大小會比映像對齊大小要小,這樣文件會變小,以此節省磁盤空間。然而,成功映射內存數據之后,在DLL程序中會存在硬編碼數據,硬編碼都是以默認的加載基址作為基址來計算的。由於DLL可以任意加載到其他進程空間中,所以DLL的加載基址並非固定不變。當改變加載基址的時候,硬編碼也要隨之改變,這樣DLL程序才會計算正確。但是,如何才能知道需要修改哪些硬編碼呢?換句話說,如何知道硬編碼的位置?答案就藏在PE結構的重定位表中,重定位表記錄的就是程序中所有需要修改的硬編碼的相對偏移位置。根據重定位表修改硬編碼數據后,這只是完成了一半的工作。DLL作為一個程序,自然也會調用其他庫函數,例如MessageBox。那么DLL如何知道MessageBox函數的地址呢?它只有獲取正確的調用函數地址后,方可正確調用函數。PE結構使用導入表來記錄PE程序中所有引用的函數及其函數地址。在DLL映射到內存之后,需要根據導入表中的導入模塊和函數名稱來獲取調用函數的地址。若想從導入模塊中獲取導出函數的地址,最簡單的方式是通過GetProcAddress函數來獲取(此次采用的方法)。但是為了避免調用敏感的WIN32 API函數而被殺軟攔截檢測,采用直接遍歷PE結構導出表的方式來獲取導出函數地址。
實現流程:
(1).將資源形式的dll文件加載到內存
(2).根據PE結構獲取其文件映像大小
(3).根據文件映像大小再申請一塊可讀、可寫、可執行的內存
(4).將內存DLL數據按映像對齊大小(SectionAlignment)映射到剛剛申請的內存中
(5).根據PE結構的重定位表,對需要重定位的數據進行修正
(6).根據PE結構的導入表,加載所需的DLL,獲取函數地址並寫入導入地址表
(7).修改DLL的加載基址為第(3)步申請的空間的首地址
(8).獲取Dll的入口地址並構造DllMain函數,然后調用DllMain函數
實現代碼:
//************************************ // 函數名: CStartDlg::LoadMyResource // 返回類型: LPVOID // 功能: 加載資源到內存 // 參數1: UINT uiResourceName 資源名 // 參數1: char * lpszResourceType 資源類型 //************************************ LPVOID CStartDlg::LoadMyResource(UINT uiResourceName, char* lpszResourceType) { //獲取指定模塊里的資源 HRSRC hRsrc = FindResource(GetModuleHandle(NULL), MAKEINTRESOURCE(uiResourceName), (LPCWSTR)lpszResourceType); if (NULL == hRsrc) { MessageBox(L"獲取資源失敗!"); return FALSE; } //獲取資源大小 DWORD dwSize = SizeofResource(NULL, hRsrc); if (dwSize <= 0) { MessageBox(L"獲取資源大小失敗!"); return FALSE; } //將資源加載到內存里 HGLOBAL hGlobal = LoadResource(NULL, hRsrc); if (NULL == hGlobal) { MessageBox(L"資源加載到內存失敗!"); return FALSE; } //鎖定資源 LPVOID lpVoid = LockResource(hGlobal); if (NULL == lpVoid) { MessageBox(L"鎖定資源失敗!"); return FALSE; } return lpVoid; } //************************************ // 函數名: CStartDlg::MmLoadLibrary // 返回類型: LPVOID // 功能: 模擬LoadLibrary加載內存文件到進程中 // 參數1: LPVOID lpData 文件基址 // 參數2: BOOL IsExe 文件屬性標志,TRUE表示exe文件,FALSE表示dll文件 //************************************ LPVOID CStartDlg::MmLoadLibrary(LPVOID lpData,BOOL IsExe) { LPVOID lpBaseAddress = NULL; // 獲取映像大小 DWORD dwSizeOfImage = GetSizeOfImage(lpData); // 在進程中申請一個可讀、可寫、可執行的內存塊 lpBaseAddress = VirtualAlloc(NULL, dwSizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (NULL == lpBaseAddress) { MessageBox(L"申請空間失敗!"); return NULL; } // 將申請的空間的數據全部置0 RtlZeroMemory(lpBaseAddress, dwSizeOfImage); // 將內存DLL數據按映像對齊大小(SectionAlignment)映射到剛剛申請的內存中 if (FALSE == MmMapFile(lpData, lpBaseAddress)) { MessageBox(L"區段映射到內存失敗!"); return NULL; } // 修改PE文件的重定位表信息 if (FALSE == DoRelocationTable(lpBaseAddress)) { MessageBox(L"修復重定位失敗!"); return NULL; } // 填寫PE文件的導入表信息 if (FALSE == DoImportTable(lpBaseAddress)) { MessageBox(L"導入表填寫失敗!"); return NULL; } // 修改PE文件的加載基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase if (FALSE == SetImageBase(lpBaseAddress)) { MessageBox(L"修改加載機制失敗!"); return NULL; } // 調用DLL的入口函數DllMain,函數地址即為PE文件的入口點AddressOfEntryPoint if (FALSE == CallDllMain(lpBaseAddress,IsExe)) { MessageBox(L"調用入口函數失敗!"); return NULL; } return lpBaseAddress; } //************************************ // 函數名: CStartDlg::GetSizeOfImage // 返回類型: DWORD // 功能: 獲取文件映像大小 // 參數1: LPVOID lpData 文件基址 //************************************ DWORD CStartDlg::GetSizeOfImage(LPVOID lpData) { //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData; //判斷是否是有效的PE文件 0x5A4D if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { MessageBox(L"這不是一個PE文件!"); return 0; } //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader+pDosHeader->e_lfanew); //判斷是否是有效的PE文件 0x00004550 if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) { MessageBox(L"這不是一個PE文件!"); return 0; } //獲取文件映像大小 return pNtHeader->OptionalHeader.SizeOfImage; } //************************************ // 函數名: CStartDlg::MmMapFile // 返回類型: BOOL // 功能: 將內存DLL數據按映像對齊大小(SectionAlignment)映射到剛剛申請的內存中 // 參數1: LPVOID lpData 文件基址 // 參數2: LPVOID lpBaseAddress 申請的內存的首地址 //************************************ BOOL CStartDlg::MmMapFile(LPVOID lpData, LPVOID lpBaseAddress) { //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpData; //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); //獲取所有頭部+區段表的大小 DWORD dwSizeOfHeaders = pNtHeader->OptionalHeader.SizeOfHeaders; //獲取區段數量 WORD wNumberOfSections = pNtHeader->FileHeader.NumberOfSections; //獲取區段表數組的首元素 PIMAGE_SECTION_HEADER pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader); //將頭部(包括區段表)拷貝到內存 RtlCopyMemory(lpBaseAddress, lpData, dwSizeOfHeaders); LPVOID lpSrcMem = NULL; LPVOID lpDestMem = NULL; DWORD dwSizeOfRawData = 0; //循環加載所有區段 for (WORD i = 0; i < wNumberOfSections; i++) { //過濾掉無效區段 if (0 == pSectionHeader->VirtualAddress || 0 == pSectionHeader->SizeOfRawData) { pSectionHeader++; continue; } //獲取區段在文件中的位置 lpSrcMem = (LPVOID)((DWORD)lpData + pSectionHeader->PointerToRawData); //獲取區段映射到內存中的位置 lpDestMem = (LPVOID)((DWORD)lpBaseAddress + pSectionHeader->VirtualAddress); //獲取區段在文件中的大小 dwSizeOfRawData = pSectionHeader->SizeOfRawData; //將區段數據拷貝到內存中 RtlCopyMemory(lpDestMem, lpSrcMem, dwSizeOfRawData); //獲取下一個區段頭(屬性) pSectionHeader++; } return TRUE; } //************************************ // 函數名: CStartDlg::DoRelocationTable // 返回類型: BOOL // 功能: 修改PE文件的重定位表信息 // 參數1: LPVOID lpBaseAddress 映像對齊后的文件基址 //************************************ BOOL CStartDlg::DoRelocationTable(LPVOID lpBaseAddress) { //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); //獲取重定位表的地址 PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pDosHeader + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); //注意重定位表的位置可能和硬盤文件中的偏移地址不同,應該使用加載后的地址 //判斷是否有重定位表 if ((PVOID)pReloc == (PVOID)pDosHeader) { //沒有重定位表 return TRUE; } int nNumberOfReloc = 0; WORD* pRelocData = NULL; DWORD* pAddress = NULL; //開始修復重定位 while (pReloc->VirtualAddress != 0 && pReloc->SizeOfBlock != 0) { //計算本區域(每一個描述了4KB大小的區域的重定位信息)需要修正的重定位項的數量 nNumberOfReloc = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for (int i = 0; i < nNumberOfReloc; i++) { //獲取IMAGE_BASE_RELOCATION結構后面的數據的地址 pRelocData = (WORD*)((DWORD)pReloc + sizeof(IMAGE_BASE_RELOCATION)); //每個WORD由兩部分組成,高4位指出了重定位的類型,WINNT.H中的一系列IMAGE_REL_BASED_xxx定義了重定位類型的取值。 //大部分重定位屬性值都是0x3 //低12位是相對於IMAGE_BASE_RELOCATION中第一個元素VirtualAddress描述位置的偏移 //找出需要修正的地址 if ((WORD)(pRelocData[i] & 0xF000) == 0x3000) { //獲取需要修正數據的地址, 按位與計算優先級比加減乘除低 pAddress = (DWORD*)((DWORD)pDosHeader + pReloc->VirtualAddress + (pRelocData[i] & 0x0FFF)); //進行修改 *pAddress += (DWORD)pDosHeader - pNtHeader->OptionalHeader.ImageBase; } } //下一個重定位塊 pReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pReloc + pReloc->SizeOfBlock); } return TRUE; } //************************************ // 函數名: CStartDlg::DoImportTable // 返回類型: BOOL // 功能: 填寫PE文件的導入表信息 // 參數1: LPVOID lpBaseAddress 映像對齊后的文件基址 //************************************ BOOL CStartDlg::DoImportTable(LPVOID lpBaseAddress) { //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); //獲取導入表地址 PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDosHeader + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); char* pDllName = nullptr; HMODULE hDll = NULL; PIMAGE_THUNK_DATA pIat = NULL; FARPROC pFuncAddress = NULL; PIMAGE_IMPORT_BY_NAME pImportByName = NULL; //循環遍歷導入表 while (pImport->Name) { //獲取導入表中的Dll名稱 pDllName = (char*)((DWORD)pDosHeader + pImport->Name); //檢索Dll模塊獲取模塊句柄 hDll = GetModuleHandleA(pDllName); //獲取失敗 if (NULL == hDll) { //加載Dll模塊獲取模塊句柄 hDll = LoadLibraryA(pDllName); //加載失敗 if (NULL == hDll) { pImport++; continue; } } //獲取IAT pIat = (PIMAGE_THUNK_DATA)((DWORD)pDosHeader + pImport->FirstThunk); //遍歷IAT中函數 while (pIat->u1.Ordinal) { //判斷導入的函數是名稱導入還是序號導入 //判斷最高位是否為1,如果是1那么是序號導入 if (pIat->u1.Ordinal & 0x80000000) { //獲取函數地址 pFuncAddress = GetProcAddress(hDll, (LPCSTR)(pIat->u1.Ordinal & 0x7FFFFFFF)); } //名稱導入 else { //獲取IMAGE_IMPORT_BY_NAME結構 pImportByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)pDosHeader + pIat->u1.AddressOfData); //獲取函數地址 pFuncAddress = GetProcAddress(hDll, pImportByName->Name); } //將函數地址填入到IAT中 pIat->u1.Function = (DWORD)pFuncAddress; pIat++; } pImport++; } return TRUE; } //************************************ // 函數名: CStartDlg::SetImageBase // 返回類型: BOOL // 功能: 修改PE文件的加載基址IMAGE_NT_HEADERS.OptionalHeader.ImageBase // 參數1: LPVOID lpBaseAddress 映像對齊后的文件基址 //************************************ BOOL CStartDlg::SetImageBase(LPVOID lpBaseAddress) { //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); //修改默認加載基址 pNtHeader->OptionalHeader.ImageBase = (DWORD)lpBaseAddress; return TRUE; } //************************************ // 函數名: CStartDlg::CallDllMain // 返回類型: BOOL // 功能: 調用PE文件的入口函數 // 參數1: LPVOID lpBaseAddress 映像對齊后的文件基址 // 參數2: BOOL IsExe 文件屬性標志,TRUE表示exe文件,FALSE表示dll文件 //************************************ BOOL CStartDlg::CallDllMain(LPVOID lpBaseAddress,BOOL IsExe) { //定義函數指針變量 typedef_DllMain DllMain = NULL; typedef_wWinMain MyWinMain = NULL; //獲取Dos頭 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; //獲取NT頭 PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); BOOL bRet = TRUE; //如果是exe文件 if (IsExe) { MessageBox(_T("有問題,待解決")); //MyWinMain = (typedef_wWinMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint+0xF8D); //bRet = MyWinMain((HINSTANCE)lpBaseAddress, NULL, NULL, SW_SHOWNORMAL); } //dll 文件 else { DllMain = (typedef_DllMain)((DWORD)pDosHeader + pNtHeader->OptionalHeader.AddressOfEntryPoint); //調用入口函數,附加進程DLL_PROCESS_ATTACH bRet = DllMain((HINSTANCE)lpBaseAddress, DLL_PROCESS_ATTACH, NULL); } return bRet; }