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

C++/WinAPI x64环境下如何获取注入DLL中函数的返回值

x64架构下远程DLL注入获取64位返回值的正确实现

首先明确:GetExitCodeThread在x64环境下确实只能返回32位截断值,WinAPI没有提供直接获取64位线程返回值的原生接口,所有依赖线程退出时寄存器、栈偏移抓返回值的方案均属于未定义行为,不同Windows版本、不同补丁环境下都会出现取值错误,没有生产可用性。

稳定实现方案

核心思路是提前在目标进程申请可跨进程访问的内存块,显式作为参数传入远程线程,由注入的函数主动把返回值写入这块内存,等待线程执行完成后直接从该内存读取结果即可,全Windows版本兼容,无架构差异。

    1. 启动目标远程线程前,调用VirtualAllocEx在目标进程申请对应大小的可读可写内存:仅返回64位数值/指针时申请8字节即可,返回结构体时按结构体实际大小申请,内存权限设为PAGE_READWRITE,分配类型用MEM_COMMIT | MEM_RESERVE。
    1. 调整CreateRemoteThread调用逻辑,将申请到的目标进程内存地址作为线程入参(原代码中该位置传的是NULL),传给DLL中的导出线程函数。
    1. 修改DLL内的线程函数实现:执行完原有业务逻辑拿到返回值后,主动将64位值、结构体数据写入入参指向的内存地址,不要依赖函数默认的返回值寄存器传值。
    1. 调用WaitForSingleObject确认远程线程执行结束后,用ReadProcessMemory从之前申请的内存地址读取返回值,读取完成后调用VirtualFreeEx释放该内存块避免泄漏。

避坑提醒:不要尝试通过GetThreadContext抓取线程退出时的RAX寄存器值获取返回值。线程退出流程中内核会调用NtTerminateThread覆盖寄存器上下文,抓到的RAX值绝大多数场景是系统调用的返回状态,不是自定义函数的执行结果,稳定性极差。

参考代码修改

注入端逻辑修改

// 原有DLL加载、基址/导出偏移计算逻辑保持不变
dllBaseAddr = getDLLBaseAddr();
dllExportOffset = getDLLExportOffset(dllExportName.c_str());
LPTHREAD_START_ROUTINE lpNewThread = LPTHREAD_START_ROUTINE(dllBaseAddr + dllExportOffset);

// 新增:在目标进程申请存返回值的内存,以返回uint64_t为例,返回结构体时调整申请大小即可
SIZE_T returnBufSize = sizeof(uint64_t);
LPVOID lpReturnBuf = VirtualAllocEx(hProc, NULL, returnBufSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

// 修改:将返回值内存地址作为线程参数传入,替换原有的NULL参数
HANDLE hExportThread = CreateRemoteThread(hProc, NULL, NULL, lpNewThread, lpReturnBuf, NULL, 0);
WaitForSingleObject(hExportThread, INFINITE);

// 新增:读取返回值
uint64_t funcResult = 0;
ReadProcessMemory(hProc, lpReturnBuf, &funcResult, sizeof(uint64_t), NULL);

// 新增:释放申请的内存,关闭线程句柄
VirtualFreeEx(hProc, lpReturnBuf, 0, MEM_RELEASE);
CloseHandle(hExportThread);

DLL内导出线程函数修改

// 线程函数签名必须符合LPTHREAD_START_ROUTINE规范
DWORD WINAPI hExportThread(LPVOID lpParam)
{
    // 原有业务逻辑不变,拿到需要返回的64位结果
    uint64_t retValue = RunYourBusinessLogic();

    // 新增:将返回值写入传入的内存地址
    if (lpParam != nullptr) {
        memcpy(lpParam, &retValue, sizeof(retValue));
        // 若返回结构体,直接将结构体memcpy到lpParam即可,需保证注入端申请的内存大小匹配结构体长度
    }

    return 0;
}

如果需要返回超过内存块大小的大块数据,可在目标进程内申请堆内存存储完整数据,将堆内存的64位指针写入返回值内存块,读取到指针后再根据数据长度调用ReadProcessMemory分次读取完整数据即可。

内容的提问来源于stack exchange,提问作者krz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 13:33:22