如何通过GetExitCodeThread或其他Windows API获取64位退出码?
x64环境下获取LoadLibraryA返回的64位DLL基地址问题
问题描述
通过CreateRemoteThread调用远程进程的LoadLibraryA注入DLL后,使用GetExitCodeThread获取线程退出码(即DLL基地址),在x86环境正常,但x64环境下仅能获取基地址的低32位,高32位为栈初始化的0xcccccccc,导致后续调用FreeLibrary卸载失败,DLL残留。
问题代码片段
// create remote thread to invoke LoadLibraryA HANDLE tThread = CreateRemoteThread(remoteProcess, NULL, 0, LoadLibraryAAddr, remoteAddress, 0, NULL); if (!tThread) { cout << "failed to create remote thread" << endl; return 1; } // wait for remote thread stop HMODULE exitCode; // x64下为8字节未初始化变量 WaitForSingleObject(tThread, INFINITE); cout << "successfully load library" << endl; GetExitCodeThread(tThread, (LPDWORD)&exitCode); // 仅写入低32位 // clean up, call FreeLibrary to unload dll LPTHREAD_START_ROUTINE freeLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary"); if (NULL == freeLibraryAddr) { cout << "failed to get freeLibrary address" << endl; return 1; }
原因分析
GetExitCodeThread的lpExitCode参数为LPDWORD(指向32位无符号整数的指针),在x64环境下:
LoadLibraryA返回的HMODULE是64位地址GetExitCodeThread仅会向lpExitCode指向的内存写入4字节(低32位),高4字节保持变量原有栈初始化值(0xcccccccc)- 最终得到的DLL基地址因高32位无效,无法被
FreeLibrary正确识别
解决方案
方案1:通过远程共享内存传递完整返回值
这是最可靠的跨平台(x86/x64)方案,思路是让远程线程将LoadLibraryA的返回值写入预先在远程进程中分配的内存,再从本地读取该内存:
- 定义参数结构体,用于传递DLL路径和结果存储地址:
typedef struct _REMOTE_LOAD_PARAMS { LPCSTR dllPath; // 远程进程中DLL路径的地址 PVOID resultStorage; // 远程进程中用于存储DLL基地址的内存地址 } REMOTE_LOAD_PARAMS, *PREMOTE_LOAD_PARAMS;
- 编写远程线程函数(需确保调用约定为
WINAPI,且代码可被远程执行):
DWORD WINAPI RemoteLoadLibraryWrapper(PVOID param) { PREMOTE_LOAD_PARAMS params = (PREMOTE_LOAD_PARAMS)param; // 调用LoadLibraryA并将返回值写入指定内存 *(HMODULE*)params->resultStorage = LoadLibraryA(params->dllPath); return 0; }
- 本地进程中的实现步骤:
// 1. 在远程进程中分配内存,用于存储结果和参数 HMODULE localResult = NULL; PVOID remoteResultAddr = VirtualAllocEx(remoteProcess, NULL, sizeof(HMODULE), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remoteResultAddr) { cout << "failed to allocate remote result memory" << endl; return 1; } REMOTE_LOAD_PARAMS localParams = { .dllPath = remoteAddress, // 已写入远程进程的DLL路径地址 .resultStorage = remoteResultAddr }; PVOID remoteParamsAddr = VirtualAllocEx(remoteProcess, NULL, sizeof(REMOTE_LOAD_PARAMS), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (!remoteParamsAddr) { cout << "failed to allocate remote params memory" << endl; VirtualFreeEx(remoteProcess, remoteResultAddr, 0, MEM_RELEASE); return 1; } // 2. 将参数写入远程进程 if (!WriteProcessMemory(remoteProcess, remoteParamsAddr, &localParams, sizeof(REMOTE_LOAD_PARAMS), NULL)) { cout << "failed to write remote params" << endl; VirtualFreeEx(remoteProcess, remoteParamsAddr, 0, MEM_RELEASE); VirtualFreeEx(remoteProcess, remoteResultAddr, 0, MEM_RELEASE); return 1; } // 3. 创建远程线程执行包装函数 LPTHREAD_START_ROUTINE wrapperAddr = (LPTHREAD_START_ROUTINE)RemoteLoadLibraryWrapper; // 注意:如果本地进程与远程进程位数不同,需要将包装函数代码写入远程进程,此处假设同位数 HANDLE tThread = CreateRemoteThread(remoteProcess, NULL, 0, wrapperAddr, remoteParamsAddr, 0, NULL); if (!tThread) { cout << "failed to create remote thread" << endl; VirtualFreeEx(remoteProcess, remoteParamsAddr, 0, MEM_RELEASE); VirtualFreeEx(remoteProcess, remoteResultAddr, 0, MEM_RELEASE); return 1; } // 4. 等待线程结束并读取结果 WaitForSingleObject(tThread, INFINITE); ReadProcessMemory(remoteProcess, remoteResultAddr, &localResult, sizeof(HMODULE), NULL); cout << "DLL base address: " << localResult << endl; // 5. 清理资源 CloseHandle(tThread); VirtualFreeEx(remoteProcess, remoteParamsAddr, 0, MEM_RELEASE); VirtualFreeEx(remoteProcess, remoteResultAddr, 0, MEM_RELEASE); // 6. 调用FreeLibrary卸载DLL LPTHREAD_START_ROUTINE freeLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary"); if (freeLibraryAddr) { HANDLE freeThread = CreateRemoteThread(remoteProcess, NULL, 0, freeLibraryAddr, localResult, 0, NULL); if (freeThread) { WaitForSingleObject(freeThread, INFINITE); CloseHandle(freeThread); cout << "successfully unload library" << endl; } }
方案2:针对x64环境的简化写法(同位数进程)
如果本地进程与远程进程均为x64位,可通过初始化64位变量并利用DWORD_PTR类型兼容位数:
DWORD_PTR exitCode = 0; // 初始化64位变量,避免高32位垃圾值 WaitForSingleObject(tThread, INFINITE); // 强制转换指针,让GetExitCodeThread写入完整64位值(x64环境下内部支持) GetExitCodeThread(tThread, (LPDWORD)&exitCode); HMODULE dllBase = (HMODULE)exitCode; // 后续调用FreeLibrary LPTHREAD_START_ROUTINE freeLibraryAddr = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "FreeLibrary"); if (freeLibraryAddr) { HANDLE freeThread = CreateRemoteThread(remoteProcess, NULL, 0, freeLibraryAddr, dllBase, 0, NULL); if (freeThread) { WaitForSingleObject(freeThread, INFINITE); CloseHandle(freeThread); } }
注意:此方案仅适用于x64同位数进程,跨位数场景仍需使用方案1。
内容的提问来源于stack exchange,提问作者w3nl1ng
相关产品推荐
相关产品推荐

