再贴一个资料,基本上,这个问题已经解决了.
Win32 EXE 在调用一个外部 DLL 中的函数时,首先要调用 LoadLibary 函数来载入此 DLL 到程序的进程地址空间。 如果 LoadLibary 载入此 DLL 成功,将返回一个该 DLL 的句柄。 这个句柄实际上就是该 DLL 在内存中的起始地址。 在载入 DLL 成功后,还必须调用 GetProcAddress 函数来获取要调用的函数的地址。然后再根据该地址来调用这个函数。
根据上述原理,我们可以把一个 DLL 作为资源文件放到 EXE 文件中,在程序运行时,分配一块内存,然后将此资源复制到该分配的内存中,并根据该内存地址计算得到相关的导出函数地址,然后,当我们需要调用某一函数时,可以用该函数在内存中的地址来调用它。
程序实现。
首先,把要合并的 DLL 作为资源加入到项目的资源文件中,然后在程序运行时,从资源中载入该资源,以得到该 DLL 在内存中的位置:
LPVOID sRawDll; // 资源文件在内存中的地址 HRSRC hRes; HMODULE hLibrary; HGLOBAL hResourceLoaded; char lib_name[MAX_PATH]; GetModuleFileName(hInstance, lib_name, MAX_PATH ); // 得到运行程序的名字 hLibrary = LoadLibrary(lib_name); // 就象载入一个 DLL 一样载入运行程序到内存中 if (NULL != hLibrary) { // 得到指定的资源文件在内存中的位置 hRes = FindResource(hLibrary, MAKEINTRESOURCE(IDR_DATA1), RT_RCDATA); if (NULL != hRes) { // 将资源文件载入内存 hResourceLoaded = LoadResource(hLibrary, hRes); if (NULL != hResourceLoaded) { // 得到资源文件大小 SizeofResource(hLibrary, hRes); // 锁定资源以得到它在内存中的地址 sRawDll = (LPVOID)LockResource(hResourceLoaded); } } else return 1; FreeLibrary(hLibrary);}else return 1;
然后,从资源中载入 DLL 到内存函数 LoadPbDllFromMemory 将载入 DLL 到内存中, 该函数有两个参数,第一个参数是指向 DLL 资源在内存中的地址的指针,也就是前面代码中的 LockResource 函数的返回值。第二个参数是一个空的指针,如果函数 LoadPbDllFromMemory 运行成功,该指针将指向重新组合后的内存中的 DLL 的起始地址。该函数还有一个功能就是如果运行成功,它将手动地用 DLL_PROCESS_ATTACH 参数调用 DLL 的入口函数 DllMain 来初始化该 DLL。除此之外,它还会手动地载入合并的 DLL 的入口表中导入的 DLL 并调整它们在内存中的相对地址。以下是该函数代码:
DWORD LoadPbDllFromMemory(LPVOID lpRawDll, LPVOID lpImageDll) { SYSTEM_INFO sSysInfo; PIMAGE_DOS_HEADER dosHeader; PIMAGE_NT_HEADERS pNTHeader; PIMAGE_SECTION_HEADER section; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_IMPORT_BY_NAME pOrdinalName; PIMAGE_BASE_RELOCATION baseReloc; PDWORD lpLink; unsigned char Protection[4096]; HINSTANCE hDll; WORD i; DWORD ImagePages,fOldProtect,j,MaxLen,HdrLen,Addr1,Addr2,Pg,Pg1,Pg2; char * sDllName; if(NULL == lpRawDll) return 1 ; dosHeader = (PIMAGE_DOS_HEADER)lpRawDll; // Is this the MZ header? if ((TRUE == IsBadReadPtr(dosHeader,sizeof (IMAGE_DOS_HEADER))) || (IMAGE_DOS_SIGNATURE != dosHeader->e_magic)) return 2; // Get the PE header. pNTHeader = MakePtr(PIMAGE_NT_HEADERS,dosHeader,dosHeader->e_lfanew); // Is this a real PE image? if((TRUE == IsBadReadPtr(pNTHeader,sizeof ( IMAGE_NT_HEADERS))) || ( IMAGE_NT_SIGNATURE != pNTHeader->Signature)) return 3 ; if(( pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(pNTHeader->OptionalHeader)) || (pNTHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)) return 4; if (pNTHeader->FileHeader.NumberOfSections < 1) return 5; section = IMAGE_FIRST_SECTION( pNTHeader ); int HeaderSize = sizeof(IMAGE_SECTION_HEADER); // 节头长度 HdrLen = (DWORD)section - (DWORD)dosHeader + HeaderSize * pNTHeader->FileHeader.NumberOfSections; // 找出最大的节的长度,此节一般是代码所在的节(.text 节) MaxLen = HdrLen; int ii=0; for (i = 0;i<(DWORD)pNTHeader->FileHeader.NumberOfSections;i++)// find MaxLen { if(MaxLen < section
.VirtualAddress + section.SizeOfRawData) { MaxLen = section.VirtualAddress + section.SizeOfRawData; } if(strcmp((const char *)section.Name,".rsrc" == 0) ii=i; } GetSystemInfo(&sSysInfo); ImagePages = MaxLen / sSysInfo.dwPageSize; if (MaxLen % sSysInfo.dwPageSize) ImagePages++; // 分配所需的内存 DWORD NeededMemory = ImagePages * sSysInfo.dwPageSize; lpImageDll = VirtualAlloc(NULL, NeededMemory, MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (lpImageDll == NULL) return 6; // 分配内存失败 MoveMemory( lpImageDll, lpRawDll, HdrLen ); // 复制节头 DWORD OrgAddr = 0; DWORD NewAddr = 0; DWORD Size = 0; // 复制 .text 节数据 for (i = 0;i<pNTHeader->FileHeader.NumberOfSections;i++) { OrgAddr = (DWORD)lpImageDll + (DWORD)section.VirtualAddress; NewAddr = (DWORD)lpRawDll + (DWORD)section.PointerToRawData; Size = (DWORD)section.SizeOfRawData; MoveMemory((void *)OrgAddr, (void *)NewAddr, Size ); } // 把指针指向新的 DLL 映像 dosHeader = (PIMAGE_DOS_HEADER) lpImageDll; // Switch to new image pNTHeader = (PIMAGE_NT_HEADERS) ((DWORD)dosHeader + dosHeader->e_lfanew); section = (PIMAGE_SECTION_HEADER) ((DWORD)pNTHeader + sizeof(IMAGE_NT_HEADERS)); pImageBase = (PBYTE)dosHeader; if((ii!=0) && (IsNT()==TRUE)) { section[ii].VirtualAddress = section[ii].VirtualAddress + (DWORD)lpRawDll; section[ii].PointerToRawData = section[ii].PointerToRawData + (DWORD)lpRawDll; } DWORD importsStartRVA; // Look up where the imports section is (normally in the .idata section) // but not necessarily so. Therefore, grab the RVA from the data dir. importsStartRVA = GetImgDirEntryRVA(pNTHeader,IMAGE_DIRECTORY_ENTRY_IMPORT); if ( !importsStartRVA ) { VirtualFree(dosHeader,0, MEM_RELEASE); return 7; } pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) pNTHeader-> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; if(pImportDesc!= 0) pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ((DWORD)pImportDesc + (DWORD)dosHeader); else { VirtualFree(dosHeader,0, MEM_RELEASE); return 8; } while (1) // 处理各入口表中的 DLL { // 检查是否遇到了空的 IMAGE_IMPORT_DESCRIPTOR if ((pImportDesc->TimeDateStamp==0 ) && (pImportDesc->Name==0)) break; // 从磁盘载入必须的 Dll, // 注意,载入的 DLL 是合并的 DLL 的入口表中的 DLL, // 不是合并到 EXE 中的 DLL sDllName = (char *) (pImportDesc->Name + (DWORD)pImageBase); hDll = GetModuleHandle(sDllName); if (hDll == 0 ) hDll = LoadLibrary(sDllName); if (hDll == 0 ) { MessageBox(NULL, "Can''t find required Dll", "Error in LoadPbDllFromMemory()",0); VirtualFree(dosHeader,0, MEM_RELEASE); return 9; } DWORD *lpFuncNameRef = (DWORD *) (pImportDesc->OriginalFirstThunk + (DWORD)dosHeader); DWORD *lpFuncAddr = (DWORD *) (pImportDesc->FirstThunk + (DWORD)dosHeader); while( *lpFuncNameRef != 0) { pOrdinalName = (PIMAGE_IMPORT_BY_NAME) (*lpFuncNameRef + (DWORD)dosHeader); DWORD pIMAGE_ORDINAL_FLAG = 0x80000000; if (*lpFuncNameRef & pIMAGE_ORDINAL_FLAG) *lpFuncAddr = (DWORD) GetProcAddress(hDll, (const char *)(*lpFuncNameRef & 0xFFFF)); else *lpFuncAddr = (DWORD) GetProcAddress(hDll, (const char *)pOrdinalName->Name); if (lpFuncAddr == 0) { VirtualFree(dosHeader,0, MEM_RELEASE); return 10;// Can''t GetProcAddress } lpFuncAddr++; lpFuncNameRef++; } pImportDesC++; } DWORD TpOffset; baseReloc = (PIMAGE_BASE_RELOCATION)((DWORD)pNTHeader-> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); if (baseReloc !=0) { baseReloc = (PIMAGE_BASE_RELOCATION) ((DWORD)baseReloc + (DWORD)dosHeader); while(baseReloc->VirtualAddress != 0) { PWORD lpTypeOffset = (PWORD) ((DWORD)baseReloc + sizeof(IMAGE_BASE_RELOCATION)); while (lpTypeOffset < (PWORD)((DWORD)baseReloc + (DWORD)baseReloc->SizeOfBlock)) { TpOffset = *lpTypeOffset & 0xF000; if(TpOffset == 0x3000) { lpLink = (PDWORD) ((DWORD)dosHeader + baseReloc->VirtualAddress + (*lpTypeOffset & 0xFFF)); *lpLink = (DWORD)dosHeader + (*lpLink) - pNTHeader->OptionalHeader.ImageBase; } else { if (TpOffset != 0) { VirtualFree(dosHeader,0, MEM_RELEASE); return 10; } } lpTypeOffset++; } baseReloc = (PIMAGE_BASE_RELOCATION)((DWORD)baseReloc + (DWORD)baseReloc->SizeOfBlock); } } // 取得原始的内存状态 memset(Protection,0,4096); for (i = 0;i<=pNTHeader->FileHeader.NumberOfSections;i++) { if (i == pNTHeader->FileHeader.NumberOfSections) { Addr1 = 0; Addr2 = HdrLen; j = 0x60000000; } else { Addr1 = section.VirtualAddress; Addr2 = section.SizeOfRawData; j = section.Characteristics; } Addr2 += Addr1 - 1; Pg1 = Addr1 / sSysInfo.dwPageSize; Pg2 = Addr2 / sSysInfo.dwPageSize; for(Pg = Pg1 ;Pg<=Pg2;Pg++) { if (j & 0x20000000) Protection[Pg] |= 1; // Execute if (j & 0x40000000) Protection[Pg] |= 2; // Read if (j & 0x80000000) Protection[Pg] |= 4; // Write } } // 恢复原始的内存状态 Addr1 = (DWORD)dosHeader; for (Pg = 0 ;Pg<= ImagePages;Pg++) { switch(Protection[Pg]) { case 2: fOldProtect = PAGE_READONLY; break; case 3: fOldProtect = PAGE_EXECUTE_READ; break; case 6: fOldProtect = PAGE_READWRITE; break; default: // Ignore strange combinations fOldProtect = PAGE_EXECUTE_READWRITE; break; } if (fOldProtect !=PAGE_EXECUTE_READWRITE) { if (VirtualProtect((void *)Addr1, sSysInfo.dwPageSize, fOldProtect, &fOldProtect) == 0) { VirtualFree(dosHeader,0, MEM_RELEASE); return 11; } } Addr1 += sSysInfo.dwPageSize; } EntryPoint = (LPENTRYPOINT) ((DWORD)pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD)dosHeader); LPVOID lpReserved = 0; EntryPoint((HINSTANCE)dosHeader, DLL_PROCESS_ATTACH, lpReserved); lpImageDll2=lpImageDll; return 0; }
一但 DLL 被正确地载入到内存中,我们就可以通过自定义函数 GetProcAddressDirectly 来获取某函数在内存中的地址,并根据该地址来调用该函数,该函数也有两个参数,第一个参数是指向载入到内存中的 DLL 的起始地址的指针,第二个是要调用的函数的函数名。以下是 GetProcAddressDirectly 函数代码:
DWORD GetProcAddressDirectly(PIMAGE_DOS_HEADER dosHeader, char * FuncName) { PIMAGE_NT_HEADERS pNTHeader; PIMAGE_EXPORT_DIRECTORY pExportDir; PWORD lpNameOrdinals; LPDWORD lpFunctions; DWORD * lpName; char * lpExpFuncName; DWORD i; DWORD j; char * lpFuncName; if(dosHeader->e_magic != IMAGE_DOS_SIGNATURE) return 0; pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + dosHeader->e_lfanew); if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) return 0; if ((pNTHeader->FileHeader.SizeOfOptionalHeader != sizeof(pNTHeader->OptionalHeader)) || (pNTHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC)) return 0; DWORD exportsStartRVA, exportsEndRVA; pImageBase = (PBYTE)dosHeader; // Make pointers to 32 and 64 bit versions of the header. pNTHeader = MakePtr( PIMAGE_NT_HEADERS, dosHeader,dosHeader->e_lfanew ); exportsStartRVA = GetImgDirEntryRVA(pNTHeader,IMAGE_DIRECTORY_ENTRY_EXPORT); exportsEndRVA = exportsStartRVA + GetImgDirEntrySize(pNTHeader, IMAGE_DIRECTORY_ENTRY_EXPORT); // Get the IMAGE_SECTION_HEADER that contains the exports. This is // usually the .edata section, but doesn''t have to be. PIMAGE_SECTION_HEADER header; header = GetEnclosingSectionHeader( exportsStartRVA, pNTHeader ); if ( !header ) return 0; INT delta; delta = (INT)(header->VirtualAddress - header->PointerToRawData); pExportDir = (PIMAGE_EXPORT_DIRECTORY)GetPtrFromRVA(exportsStartRVA, pNTHeader, pImageBase); pExportDir =(PIMAGE_EXPORT_DIRECTORY) (pNTHeader-> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); if (pExportDir == 0) { MessageBox(NULL,"Error in GetProcAddressDirectly()",0,0); return 0; } pExportDir =(PIMAGE_EXPORT_DIRECTORY) ((DWORD)pExportDir + (DWORD)dosHeader); lpNameOrdinals =(PWORD)((DWORD)pExportDir->AddressOfNameOrdinals + (DWORD)dosHeader); lpName =(LPDWORD) (pExportDir->AddressOfNames + (DWORD)dosHeader); lpFunctions =(LPDWORD) (pExportDir->AddressOfFunctions + (DWORD)dosHeader); lpFuncName = FuncName; if(HIWORD(lpFuncName)!=0 ) { for( i = 0;i<=pExportDir->NumberOfFunctions - 1;i++) { DWORD entryPointRVA = *lpFunctions; // Skip over gaps in exported function if ( entryPointRVA == 0 ) continue; for( j = 0;j<=pExportDir->NumberOfNames-1;j++) { if( lpNameOrdinals[j] == i) { lpExpFuncName = (char *) (lpName[j] + (DWORD)dosHeader); if(strcmp((char *)lpExpFuncName,(char *)FuncName)==0) return (DWORD) (lpFunctions + (DWORD)dosHeader); } } } } else { for (i = 0 ;i<=pExportDir->NumberOfFunctions - 1;i++) { if (lpFuncName == (char *)(pExportDir->Base + i)) { if (lpFunctions) return (unsigned long) (lpFunctions + dosHeader); } } } return 0; }
在调用完函数后,不要忘记用 UnloadPbDllFromMemory 来从内存中移去 DLL 以释放分配的内存,该函数还会用 DLL_PROCESS_DETACH 参数调用 DLL 的入口函数 DllMain 来从调用进程的地址空间卸载该 DLL。 以下是 UnloadPbDllFromMemory 函数代码:
DWORD UnloadPbDllFromMemory(PIMAGE_DOS_HEADER dosHeader) { PIMAGE_NT_HEADERS pNTHeader; pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)dosHeader + (DWORD)dosHeader->e_lfanew); EntryPoint = (LPENTRYPOINT)(pNTHeader->OptionalHeader.AddressOfEntryPoint + (DWORD)dosHeader); EntryPoint((HINSTANCE)dosHeader, DLL_PROCESS_DETACH, 0); return VirtualFree(dosHeader, 0, MEM_RELEASE); }