自己实现LoadLibrary操作
LoadLibrary函数大家经常会用到,这里写下如何自己实现这个函数。
LoadLibrary是将动态库从文件中加载到内存中,其实很容易实现,主要的难点就是从文件对齐到内存对齐涉及到数据的偏移和对一些值的修正。
这里对PE文件的就够就不再介绍了,如果不是很熟悉可以参照书本学习。这套代码加上我写的另一篇帖子《在PE文件中插入一个新节》里的代码全都搞懂就一定对PE文件结构信息非常了解了。在帖子的最后我都有附上项目链接。
进入正题,首先自己创建了个函数MyLoadLibrary,它同样传入动态库路径作为参数,但是返回值不再是模块句柄而是BOOL。
1 BOOL MyLoadLibrary(char * szDllPath) 2 { 3 VOID* ulBaseAddress = NULL; 4 DWORD dwFileSize = 0; 5 6 //将文件数据映射到内存中 7 MapDllFile(szDllPath, (ULONG_PTR*)&ulBaseAddress,&dwFileSize); 8 9 //检查PE文件的有效性 10 CheckDataValidity(ulBaseAddress, dwFileSize); 11 12 //计算所需要的加载空间 13 int iImageLength = CalcTotalImageSize(); 14 15 //分配虚拟内存 该函数会自动将申请到的内存初始化成0 16 VOID* ImageData = VirtualAlloc(NULL, iImageLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 17 18 if (ImageData == NULL) 19 { 20 return FALSE; 21 } 22 else 23 { 24 CopyDllDatas(ImageData, ulBaseAddress); //复制DLL数据,并对其每个段 25 26 if (m_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress > 0 27 && m_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size > 0) 28 { 29 DoRelocation(ImageData); //修复重定向表 30 } 31 32 if (!FixImportAddressTable(ImageData)) //修正导入表 33 { 34 VirtualFree(ImageData, 0, MEM_RELEASE); 35 return FALSE; 36 } 37 38 ULONG ulOld; 39 VirtualProtect(ImageData, iImageLength, PAGE_EXECUTE_READWRITE, &ulOld); 40 } 41 42 //修正基地址 43 #ifdef WIN32 44 m_NtHeader->OptionalHeader.ImageBase = (UINT32)ImageData; 45 #else 46 m_NtHeader->OptionalHeader.ImageBase = (UINT64)ImageData; 47 #endif 48 m_DllMain = (ProcDllMain)(m_NtHeader->OptionalHeader.AddressOfEntryPoint + (PBYTE)ImageData); 49 50 BOOL bInitResult = m_DllMain((HINSTANCE)ImageData, DLL_PROCESS_ATTACH, 0); 51 52 if (!bInitResult) //初始化失败 53 { 54 m_DllMain((HINSTANCE)ImageData, DLL_PROCESS_DETACH, 0); 55 VirtualFree(ImageData, 0, MEM_RELEASE); 56 m_DllMain = NULL; 57 return FALSE; 58 } 59 60 m_bIsLoadAlready = TRUE; 61 m_ImageBase = ImageData; 62 return TRUE; 63 }
其中,检查PE文件有效性很关键。不光要检查标志位,还要检查大小是否合理。
计算所需的加载空间和复制DLL数据这两个函数都需要进行文件对齐到内存对齐的转换。一定要理解对齐粒度。这个函数用来计算一定大小的数据按照一定的对齐粒度对齐后的大小:
1 int GetAlignedSize(int iOriginalData, int iAlignment) 2 { 3 return (iOriginalData + iAlignment - 1) / iAlignment * iAlignment; 4 }
其实很好理解,比如9个字节的数据,按照4字节对齐,两个4字节容不下,当然需要三个4字节也就是12字节。这样对齐后占用空间就是12字节。
之后就是修改导入表和重定向表。其中导入表较为复杂,因为要将其导入的所有函数地址进行赋值。
1 BOOL FixImportAddressTable(VOID* ImageData) 2 { 3 ULONG ulOffset = m_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; 4 5 if (ulOffset == 0) 6 { 7 return TRUE; //没有导入表 8 } 9 10 PIMAGE_IMPORT_DESCRIPTOR ImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((PBYTE)ImageData + ulOffset); 11 12 while (ImageImportDescriptor->Characteristics != 0) 13 { 14 PIMAGE_THUNK_DATA FirstThunkData = (PIMAGE_THUNK_DATA)((PBYTE)ImageData + ImageImportDescriptor->FirstThunk); 15 PIMAGE_THUNK_DATA OriginalThunkData = (PIMAGE_THUNK_DATA)((PBYTE)ImageData + ImageImportDescriptor->OriginalFirstThunk); 16 17 //获取Dll的名字 18 char szDllName[256] = { 0 }; 19 BYTE* bName = (BYTE*)((PBYTE)ImageData + ImageImportDescriptor->Name); 20 int i = 0; 21 for (i = 0; i<256; i++) 22 { 23 if (bName[i] == 0) 24 { 25 break; 26 } 27 szDllName[i] = bName[i]; 28 } 29 if (i > 256) 30 { 31 return FALSE; 32 } 33 else 34 { 35 szDllName[i] = 0; 36 } 37 38 HMODULE hDll = GetModuleHandleA(szDllName); 39 40 if (hDll == NULL) 41 { 42 return FALSE; 43 } 44 45 for (i = 0;; i++) 46 { 47 if (OriginalThunkData[i].u1.Function == 0) 48 { 49 break; 50 } 51 52 FARPROC FunctionAddress = NULL; 53 54 if (OriginalThunkData[i].u1.Ordinal & IMAGE_ORDINAL_FLAG) //这里的值给出的是导出序号 55 { 56 FunctionAddress = GetProcAddress(hDll, (LPCSTR)(OriginalThunkData[i].u1.Ordinal & ~IMAGE_ORDINAL_FLAG)); 57 } 58 else //按名字导出 59 { 60 //获取此IAT项所藐视的函数名称 61 PIMAGE_IMPORT_BY_NAME ImageImportByName = (PIMAGE_IMPORT_BY_NAME)((PBYTE)ImageData + (OriginalThunkData[i].u1.AddressOfData)); 62 FunctionAddress = GetProcAddress(hDll, (char*)ImageImportByName->Name); 63 } 64 65 if (FunctionAddress != NULL) //找到了 66 { 67 #ifdef _WIN64 68 FirstThunkData[i].u1.Function = (ULONGLONG)FunctionAddress; 69 #else 70 FirstThunkData[i].u1.Function = (DWORD)FunctionAddress; 71 #endif 72 } 73 else 74 { 75 return FALSE; 76 } 77 } 78 79 //移动到下一个导入模块 80 ImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((PBYTE)ImageImportDescriptor + sizeof(IMAGE_IMPORT_DESCRIPTOR)); 81 } 82 return TRUE; 83 }
细心的人会发现这里有个缺点:因为我就是在自己实现LoadLibrary函数,所以不会去调用它,这就导致我查找到的被导入DLL中的模块只能通过GetModuleHandle函数获得,这样如果DLL中加载了这个程序没有加载的模块就会出现问题。
项目地址:https://github.com/HollyDi/MyLoadLibrary