C++/WinAPI x64环境下如何获取注入DLL中函数的返回值
x64架构下远程DLL注入获取64位返回值的正确实现
首先明确:GetExitCodeThread在x64环境下确实只能返回32位截断值,WinAPI没有提供直接获取64位线程返回值的原生接口,所有依赖线程退出时寄存器、栈偏移抓返回值的方案均属于未定义行为,不同Windows版本、不同补丁环境下都会出现取值错误,没有生产可用性。
稳定实现方案
核心思路是提前在目标进程申请可跨进程访问的内存块,显式作为参数传入远程线程,由注入的函数主动把返回值写入这块内存,等待线程执行完成后直接从该内存读取结果即可,全Windows版本兼容,无架构差异。
- 启动目标远程线程前,调用
VirtualAllocEx在目标进程申请对应大小的可读可写内存:仅返回64位数值/指针时申请8字节即可,返回结构体时按结构体实际大小申请,内存权限设为PAGE_READWRITE,分配类型用MEM_COMMIT | MEM_RESERVE。
- 启动目标远程线程前,调用
- 调整
CreateRemoteThread调用逻辑,将申请到的目标进程内存地址作为线程入参(原代码中该位置传的是NULL),传给DLL中的导出线程函数。
- 调整
- 修改DLL内的线程函数实现:执行完原有业务逻辑拿到返回值后,主动将64位值、结构体数据写入入参指向的内存地址,不要依赖函数默认的返回值寄存器传值。
- 调用
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
相关产品推荐
相关产品推荐

