x64 DLL注入器如何获取x86进程所需的WinAPI函数地址?
在x64程序中获取x86函数地址的解决方案
你遇到的问题本质是:x64进程中默认加载的系统DLL都是64位版本,所以GetProcAddress返回的自然是64位函数地址。要拿到32位函数的地址,你不能依赖系统的加载器,得手动读取32位DLL的文件镜像,解析它的导出表来获取函数的相对虚拟地址(RVA),再结合目标x86进程中该DLL的实际加载基址计算出最终的32位函数地址。
下面是具体的实现步骤和要点:
核心思路
x64进程无法通过LoadLibrary加载32位DLL(会直接报错),所以我们需要:
- 直接读取32位DLL的磁盘文件到内存
- 解析32位PE结构,定位导出表
- 根据函数名或序号找到目标函数的RVA
- 在注入x86进程时,用目标进程中该32位DLL的加载基址 + RVA,得到目标进程中的实际函数地址
具体实现步骤
1. 读取32位DLL文件到内存
用文件操作API打开32位DLL(比如C:\Windows\SysWOW64\kernel32.dll,这是32位版本的kernel32),然后映射到内存:
HANDLE hFile = CreateFileW(L"C:\\Windows\\SysWOW64\\kernel32.dll", GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile == INVALID_HANDLE_VALUE) { /* 处理错误 */ } HANDLE hMap = CreateFileMapping(hFile, nullptr, PAGE_READONLY, 0, 0, nullptr); if (!hMap) { /* 处理错误 */ } void* pDllBase = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (!pDllBase) { /* 处理错误 */ }
2. 解析32位PE结构
要使用32位的PE结构体(因为我们处理的是32位DLL):
IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)pDllBase; IMAGE_NT_HEADERS32* pNtHeaders = (IMAGE_NT_HEADERS32*)((uintptr_t)pDllBase + pDosHeader->e_lfanew); // 验证是否是32位PE文件 if (pNtHeaders->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) { // 不是32位DLL,处理错误 }
3. 定位并解析导出表
找到导出表的位置,然后遍历导出表查找目标函数:
IMAGE_DATA_DIRECTORY* pExportDir = &pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; IMAGE_EXPORT_DIRECTORY* pExportTable = (IMAGE_EXPORT_DIRECTORY*)((uintptr_t)pDllBase + pExportDir->VirtualAddress); // 获取导出表的关键数组 DWORD* pNames = (DWORD*)((uintptr_t)pDllBase + pExportTable->AddressOfNames); WORD* pNameOrdinals = (WORD*)((uintptr_t)pDllBase + pExportTable->AddressOfNameOrdinals); DWORD* pFunctions = (DWORD*)((uintptr_t)pDllBase + pExportTable->AddressOfFunctions); // 遍历查找目标函数(比如"GetProcAddress") for (DWORD i = 0; i < pExportTable->NumberOfNames; i++) { const char* pFuncName = (const char*)((uintptr_t)pDllBase + pNames[i]); if (strcmp(pFuncName, "GetProcAddress") == 0) { WORD ordinal = pNameOrdinals[i]; DWORD funcRva = pFunctions[ordinal]; // 这里的funcRva就是32位函数在DLL中的相对虚拟地址 break; } }
4. 计算目标进程中的实际函数地址
在注入x86进程时,你需要先获取目标进程中该32位DLL的加载基址(比如通过EnumProcessModulesEx在x86进程中查询,或者在手动映射DLL时自己指定基址),然后:
// 假设targetBase是目标x86进程中32位kernel32.dll的加载基址 uintptr_t targetFuncAddr = (uintptr_t)targetBase + funcRva;
额外注意事项
- 处理转发函数:有些函数的导出是转发到其他DLL的(比如
CreateFile可能转发到kernelbase.dll),此时funcRva会是一个指向转发字符串的RVA,你需要读取这个字符串(格式为DLLName.FuncName),然后递归解析对应的32位DLL的导出表。 - 基址重定位:如果目标进程中32位DLL的加载基址和它的默认基址(在PE文件中指定的
ImageBase)不同,系统会自动处理重定位,但如果你是手动映射DLL到目标进程,需要自己处理基址重定位表。 - SysWOW64路径:32位系统DLL都在
C:\Windows\SysWOW64目录下,x64版本在C:\Windows\System32,不要搞混路径。
内容的提问来源于stack exchange,提问作者lotsch
相关产品推荐
相关产品推荐

