You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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的返回值写入预先在远程进程中分配的内存,再从本地读取该内存:

  1. 定义参数结构体,用于传递DLL路径和结果存储地址:
typedef struct _REMOTE_LOAD_PARAMS {
    LPCSTR dllPath;       // 远程进程中DLL路径的地址
    PVOID  resultStorage; // 远程进程中用于存储DLL基地址的内存地址
} REMOTE_LOAD_PARAMS, *PREMOTE_LOAD_PARAMS;
  1. 编写远程线程函数(需确保调用约定为WINAPI,且代码可被远程执行):
DWORD WINAPI RemoteLoadLibraryWrapper(PVOID param) {
    PREMOTE_LOAD_PARAMS params = (PREMOTE_LOAD_PARAMS)param;
    // 调用LoadLibraryA并将返回值写入指定内存
    *(HMODULE*)params->resultStorage = LoadLibraryA(params->dllPath);
    return 0;
}
  1. 本地进程中的实现步骤:
// 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 11:02:07