MemLoadDll.h MemLoadDll.cpp ...
MemLoadDll.h
#if !defined(Q_OS_LINUX) #pragma once typedef BOOL (__stdcall *ProcDllMain)(HINSTANCE, DWORD, LPVOID ); class CMemLoadDll { public: CMemLoadDll(); ~CMemLoadDll(); BOOL MemLoadLibrary( void *lpFileData , int DataLength); // Dll file data buffer FARPROC MemGetProcAddress(LPCSTR lpProcName); private: BOOL isLoadOk; BOOL CheckDataValide(void *lpFileData, int DataLength); int CalcTotalImageSize(); void CopyDllDatas(void *pDest, void *pSrc); BOOL FillRavAddress(void *pBase); void DoRelocation(void *pNewBase); int GetAlignedSize(int Origin, int Alignment); private: ProcDllMain pDllMain; private: DWORD pImageBase; PIMAGE_DOS_HEADER pDosHeader; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_SECTION_HEADER pSectionHeader; }; #endif
MemLoadDll.cpp
#if !defined(Q_OS_LINUX) #include <windows.h> #include <assert.h> #include "MemLoadDll.h" #include "QDebug" CMemLoadDll::CMemLoadDll() { isLoadOk = FALSE; pImageBase = NULL; pDllMain = NULL; } CMemLoadDll::~CMemLoadDll() { if(isLoadOk) { assert(pImageBase != NULL); assert(pDllMain != NULL); //脫鉤,準備卸載dll pDllMain((HINSTANCE)pImageBase,DLL_PROCESS_DETACH,0); VirtualFree((LPVOID)pImageBase, 0, MEM_RELEASE); } } //MemLoadLibrary函數從記憶體緩衝區數據中載入一個dll到當前進程的地址空間,預設位置0x10000000 //返回值: 成功返回TRUE , 失敗返回FALSE //lpFileData: 存放dll文件數據的緩衝區 //DataLength: 緩衝區中數據的總長度 BOOL CMemLoadDll::MemLoadLibrary(void *lpFileData, int DataLength) { if (pImageBase != NULL) { return FALSE; //已經載入一個dll,還沒有釋放,不能載入新的dll } //檢查數據有效性,並初始化 if (!CheckDataValide(lpFileData, DataLength)) { return FALSE; } //計算所需的載入空間 int ImageSize = CalcTotalImageSize(); if (ImageSize == 0) { return FALSE; } // 分配虛擬記憶體 void *pMemoryAddress = VirtualAlloc((LPVOID)NULL, ImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pMemoryAddress == NULL) { return FALSE; } else { CopyDllDatas(pMemoryAddress, lpFileData); //複製dll數據,並對齊每個段 //重定位信息 if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress > 0 && pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size > 0) { DoRelocation(pMemoryAddress); } //填充引入地址表 if (!FillRavAddress(pMemoryAddress)) //修正引入地址表失敗 { VirtualFree(pMemoryAddress, 0, MEM_RELEASE); return FALSE; } //修改頁屬性。應該根據每個頁的屬性單獨設置其對應記憶體頁的屬性。這裡簡化一下。 //統一設置成一個屬性PAGE_EXECUTE_READWRITE unsigned long old; VirtualProtect(pMemoryAddress, ImageSize, PAGE_EXECUTE_READWRITE, &old); } //修正基地址 pNTHeader->OptionalHeader.ImageBase = (DWORD)pMemoryAddress; //接下來要調用一下dll的入口函數,做初始化工作。 pDllMain = (ProcDllMain)(pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD) pMemoryAddress); BOOL InitResult = pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_ATTACH, 0); if (!InitResult) //初始化失敗 { pDllMain((HINSTANCE)pMemoryAddress, DLL_PROCESS_DETACH, 0); VirtualFree(pMemoryAddress, 0, MEM_RELEASE); pDllMain = NULL; return FALSE; } isLoadOk = TRUE; pImageBase = (DWORD)pMemoryAddress; return TRUE; } //MemGetProcAddress函數從dll中獲取指定函數的地址 //返回值: 成功返回函數地址 , 失敗返回NULL //lpProcName: 要查找函數的名字或者序號 FARPROC CMemLoadDll::MemGetProcAddress(LPCSTR lpProcName) { if (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress == 0 || pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size == 0) { return NULL; } if (!isLoadOk) { return NULL; } DWORD OffsetStart = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; DWORD Size = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size; PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pImageBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); int iBase = pExport->Base; int iNumberOfFunctions = pExport->NumberOfFunctions; int iNumberOfNames = pExport->NumberOfNames; //<= iNumberOfFunctions LPDWORD pAddressOfFunctions = (LPDWORD)(pExport->AddressOfFunctions + pImageBase); LPWORD pAddressOfOrdinals = (LPWORD)(pExport->AddressOfNameOrdinals + pImageBase); LPDWORD pAddressOfNames = (LPDWORD)(pExport->AddressOfNames + pImageBase); int iOrdinal = -1; if (((DWORD)lpProcName & 0xFFFF0000) == 0) //IT IS A ORDINAL! { iOrdinal = (DWORD)lpProcName & 0x0000FFFF - iBase; } else //use name { int iFound = -1; for (int i = 0; i < iNumberOfNames; i++) { char *pName = (char * )(pAddressOfNames[i] + pImageBase); if (strcmp(pName, lpProcName) == 0) { iFound = i; break; } } if (iFound >= 0) { iOrdinal = (int)(pAddressOfOrdinals[iFound]); } } if (iOrdinal < 0 || iOrdinal >= iNumberOfFunctions ) { return NULL; } else { DWORD pFunctionOffset = pAddressOfFunctions[iOrdinal]; if (pFunctionOffset > OffsetStart && pFunctionOffset < (OffsetStart + Size)) //maybe Export Forwarding { return NULL; } else { return (FARPROC)(pFunctionOffset + pImageBase); } } } // 重定向PE用到的地址 void CMemLoadDll::DoRelocation( void *NewBase) { /* 重定位表的結構: // DWORD sectionAddress, DWORD size (包括本節需要重定位的數據) // 例如 1000節需要修正5個重定位數據的話,重定位表的數據是 // 00 10 00 00 14 00 00 00 xxxx xxxx xxxx xxxx xxxx 0000 // ----------- ----------- ---- // 給出節的偏移 總尺寸=8+6*2 需要修正的地址 用於對齊4位元組 // 重定位表是若幹個相連,如果address 和 size都是0 表示結束 // 需要修正的地址是12位的,高4位是形態字,intel cpu下是3 */ //假設NewBase是0x600000,而文件中設置的預設ImageBase是0x400000,則修正偏移量就是0x200000 DWORD Delta = (DWORD)NewBase - pNTHeader->OptionalHeader.ImageBase; //註意重定位表的位置可能和硬碟文件中的偏移地址不同,應該使用載入後的地址 PIMAGE_BASE_RELOCATION pLoc = (PIMAGE_BASE_RELOCATION)((unsigned long)NewBase + pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); while ((pLoc->VirtualAddress + pLoc->SizeOfBlock) != 0) //開始掃描重定位表 { WORD *pLocData = (WORD *)((int)pLoc + sizeof(IMAGE_BASE_RELOCATION)); //計算本節需要修正的重定位項(地址)的數目 int NumberOfReloc = (pLoc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); for ( int i = 0 ; i < NumberOfReloc; i++) { if ( (DWORD)(pLocData[i] & 0xF000) == 0x00003000) //這是一個需要修正的地址 { // 舉例: // pLoc->VirtualAddress = 0x1000; // pLocData[i] = 0x313E; 表示本節偏移地址0x13E處需要修正 // 因此 pAddress = 基地址 + 0x113E // 裡面的內容是 A1 ( 0c d4 02 10) 彙編代碼是: mov eax , [1002d40c] // 需要修正1002d40c這個地址 DWORD *pAddress = (DWORD *)((unsigned long)NewBase + pLoc->VirtualAddress + (pLocData[i] & 0x0FFF)); *pAddress += Delta; } } //轉移到下一個節進行處理 pLoc = (PIMAGE_BASE_RELOCATION)((DWORD)pLoc + pLoc->SizeOfBlock); } } //填充引入地址表 BOOL CMemLoadDll::FillRavAddress(void *pImageBase) { // 引入表實際上是一個 IMAGE_IMPORT_DESCRIPTOR 結構數組,全部是0表示結束 // 數組定義如下: // // DWORD OriginalFirstThunk; // 0表示結束,否則指向未綁定的IAT結構數組 // DWORD TimeDateStamp; // DWORD ForwarderChain; // -1 if no forwarders // DWORD Name; // 給出dll的名字 // DWORD FirstThunk; // 指向IAT結構數組的地址(綁定後,這些IAT裡面就是實際的函數地址) int i; unsigned long Offset = pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress ; if (Offset == 0) { return TRUE; //No Import Table } PIMAGE_IMPORT_DESCRIPTOR pID = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned long) pImageBase + Offset); while (pID->Characteristics != 0 ) { PIMAGE_THUNK_DATA pRealIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->FirstThunk); PIMAGE_THUNK_DATA pOriginalIAT = (PIMAGE_THUNK_DATA)((unsigned long)pImageBase + pID->OriginalFirstThunk); //獲取dll的名字 WCHAR buf[256]; //dll name; BYTE *pName = (BYTE *)((unsigned long)pImageBase + pID->Name); for (i = 0; i < 256; i++) { if (pName[i] == 0) { break; } buf[i] = pName[i]; } if (i >= 256) { return FALSE; // bad dll name } else { buf[i] = 0; } HMODULE hDll = GetModuleHandle(buf); if (hDll == NULL) { hDll = LoadLibrary(buf); } if (hDll == NULL) { return FALSE; //NOT FOUND DLL } //獲取DLL中每個導出函數的地址,填入IAT //每個IAT結構是 : // union { PBYTE ForwarderString; // PDWORD Function; // DWORD Ordinal; // PIMAGE_IMPORT_BY_NAME AddressOfData; // } u1; // 長度是一個DWORD ,正好容納一個地址。 for (i = 0; ; i++) { if (pOriginalIAT[i].u1.Function == 0) { break; } FARPROC lpFunction = NULL; if (pOriginalIAT[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) //這裡的值給出的是導出序號 { lpFunction = GetProcAddress(hDll, (LPCSTR)(pOriginalIAT[i].u1.Ordinal & 0x0000FFFF)); } else //按照名字導入 { //獲取此IAT項所描述的函數名稱 PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME) ((DWORD)pImageBase + (DWORD)(pOriginalIAT[i].u1.AddressOfData)); // if(pByName->Hint !=0) // lpFunction = GetProcAddress(hDll, (LPCSTR)pByName->Hint); // else lpFunction = GetProcAddress(hDll, (char *)pByName->Name); } if (lpFunction != NULL) //找到了! { pRealIAT[i].u1.Function = (DWORD)lpFunction;//(PDWORD) lpFunction; } else { return FALSE; } } //move to next pID = (PIMAGE_IMPORT_DESCRIPTOR)( (DWORD)pID + sizeof(IMAGE_IMPORT_DESCRIPTOR)); } return TRUE; } //CheckDataValide函數用於檢查緩衝區中的數據是否有效的dll文件 //返回值: 是一個可執行的dll則返回TRUE,否則返回FALSE。 //lpFileData: 存放dll數據的記憶體緩衝區 //DataLength: dll文件的長度 BOOL CMemLoadDll::CheckDataValide(void *lpFileData, int DataLength) { //檢查長度 if (DataLength < sizeof(IMAGE_DOS_HEADER)) { return FALSE; } pDosHeader = (PIMAGE_DOS_HEADER)lpFileData; // DOS頭 //檢查dos頭的標記 if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { return FALSE; //0x5A4D : MZ } //檢查長度 if ((DWORD)DataLength < (pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS)) ) { return FALSE; } //取得pe頭 pNTHeader = (PIMAGE_NT_HEADERS)( (unsigned long)lpFileData + pDosHeader->e_lfanew); // PE頭 //檢查pe頭的合法性 if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) { return FALSE; //0x00004550 : PE00 } if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) == 0) //0x2000 : File is a DLL { return FALSE; } if ((pNTHeader->FileHeader.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE) == 0) //0x0002 : 指出文件可以運行 { return FALSE; } if (pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(IMAGE_OPTIONAL_HEADER)) { return FALSE; } //取得節表(段表) pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS)); //驗證每個節表的空間 for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++) { if ((pSectionHeader[i].PointerToRawData + pSectionHeader[i].SizeOfRawData) > (DWORD)DataLength) { return FALSE; } } return TRUE; } //計算對齊邊界 int CMemLoadDll::GetAlignedSize(int Origin, int Alignment) { return (Origin + Alignment - 1) / Alignment * Alignment; } //計算整個dll映像文件的尺寸 int CMemLoadDll::CalcTotalImageSize() { int Size; if (pNTHeader == NULL) { return 0; } int nAlign = pNTHeader->OptionalHeader.SectionAlignment; //段對齊位元組數 // 計算所有頭的尺寸。包括dos, coff, pe頭 和 段表的大小 Size = GetAlignedSize(pNTHeader->OptionalHeader.SizeOfHeaders, nAlign); // 計算所有節的大小 for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i) { //得到該節的大小 int CodeSize = pSectionHeader[i].Misc.VirtualSize ; int LoadSize = pSectionHeader[i].SizeOfRawData; int MaxSize = (LoadSize > CodeSize) ? (LoadSize) : (CodeSize); int SectionSize = GetAlignedSize(pSectionHeader[i].VirtualAddress + MaxSize, nAlign); if (Size < SectionSize) { Size = SectionSize; //Use the Max; } } return Size; } //CopyDllDatas函數將dll數據複製到指定記憶體區域,並對齊所有節 //pSrc: 存放dll數據的原始緩衝區 //pDest:目標記憶體地址 void CMemLoadDll::CopyDllDatas(void *pDest, void *pSrc) { // 計算需要複製的PE頭+段表位元組數 int HeaderSize = pNTHeader->OptionalHeader.SizeOfHeaders; int SectionSize = pNTHeader->FileHeader.NumberOfSections * sizeof(IMAGE_SECTION_HEADER); int MoveSize = HeaderSize + SectionSize; //複製頭和段信息 memmove(pDest, pSrc, MoveSize); //複製每個節 for (int i = 0; i < pNTHeader->FileHeader.NumberOfSections; ++i) { if (pSectionHeader[i].VirtualAddress == 0 || pSectionHeader[i].SizeOfRawData == 0) { continue; } // 定位該節在記憶體中的位置 void *pSectionAddress = (void *)((unsigned long)pDest + pSectionHeader[i].VirtualAddress); // 複製段數據到虛擬記憶體 memmove((void *)pSectionAddress, (void *)((DWORD)pSrc + pSectionHeader[i].PointerToRawData), pSectionHeader[i].SizeOfRawData); } //修正指針,指向新分配的記憶體 //新的dos頭 pDosHeader = (PIMAGE_DOS_HEADER)pDest; //新的pe頭地址 pNTHeader = (PIMAGE_NT_HEADERS)((int)pDest + (pDosHeader->e_lfanew)); //新的節表地址 pSectionHeader = (PIMAGE_SECTION_HEADER)((int)pNTHeader + sizeof(IMAGE_NT_HEADERS)); return ; } #endif