使用CreateRemoteThread注入线程导致目标进程崩溃的问题排查
CreateRemoteThread无DLL注入崩溃问题分析与解决
1. 是否可以实现“无DLL的DLL注入”?
可以,这种技术叫做代码注入,无需依赖DLL文件,直接将待执行的机器码写入目标进程的内存空间,再通过CreateRemoteThread执行这段代码。核心是把本地的函数逻辑转换成可在目标进程中运行的机器码,而非直接引用本地函数地址。
2. 崩溃的根本原因
你的代码存在两个致命错误:
- 线程函数地址无效:
CreateRemoteThread传入的HackThread是本地进程(injector.exe)中的函数地址,目标进程(game.exe)的虚拟地址空间与本地完全独立,这个地址在目标进程中要么是未分配的内存,要么是无关数据,执行时直接触发内存访问错误导致崩溃。 - 逻辑混淆:数据与代码的位置错误:你向目标进程分配的内存中写入的是
player变量的地址(0x00791000),而非要执行的线程函数代码。目标进程执行这段数据时,会把它当作机器码解析,必然引发崩溃。
修复方案
步骤1:提取线程函数的机器码
要让目标进程执行HackThread的逻辑,需要先将该函数的机器码提取出来。可以通过__declspec(naked)定义线程函数,避免编译器插入额外的序言/尾声代码,便于精准提取机器码。
步骤2:正确注入机器码与参数
- 向目标进程分配可执行内存,写入提取的机器码
- 传递正确的线程参数(目标进程中
player变量的真实地址,需从game.exe的输出中获取或通过内存扫描得到)
修改后的injector.cpp示例
#include <windows.h> #include <iostream> #include <cstring> const WCHAR* TARGET_PROCESS_NAME = L"game.exe"; const DWORD MEMORY_ALLOCATION_SIZE = 1 << 12; // 裸函数,便于提取机器码,手动处理栈帧 __declspec(naked) DWORD WINAPI HackThread() { __asm { mov eax, [esp+4] // 获取线程参数(player的地址) mov dword ptr [eax], 0x64 // 设置*player = 100 xor eax, eax // 返回0 ret 4 // 清理参数栈 } } // 补充getProcessId函数实现 DWORD getProcessId(const WCHAR* processName) { PROCESSENTRY32 pe32; pe32.dwSize = sizeof(PROCESSENTRY32); HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) return 0; if (!Process32First(hSnapshot, &pe32)) { CloseHandle(hSnapshot); return 0; } do { if (!wcscmp(pe32.szExeFile, processName)) { CloseHandle(hSnapshot); return pe32.th32ProcessID; } } while (Process32Next(hSnapshot, &pe32)); CloseHandle(hSnapshot); return 0; } int main() { auto pid = getProcessId(TARGET_PROCESS_NAME); if (pid == 0) { std::cerr << "Error: Unable to find process " << TARGET_PROCESS_NAME << std::endl; return 1; } HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (hProcess == NULL) { std::cerr << "Error: Unable to open process. Error code: " << GetLastError() << std::endl; return 1; } // 分配可执行内存用于存放注入的机器码 LPVOID remoteCode = VirtualAllocEx(hProcess, nullptr, MEMORY_ALLOCATION_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (remoteCode == nullptr) { std::cerr << "Error: Unable to allocate code memory in remote process. Error code: " << GetLastError() << std::endl; CloseHandle(hProcess); return 1; } // 提取HackThread的机器码(实际需通过反汇编确定准确长度) const unsigned char* codeStart = reinterpret_cast<const unsigned char*>(&HackThread); size_t codeLength = 10; // 示例长度,需根据反汇编结果调整 // 将机器码写入目标进程 if (!WriteProcessMemory(hProcess, remoteCode, codeStart, codeLength, NULL)) { std::cerr << "Error: Unable to write code to remote process. Error code: " << GetLastError() << std::endl; VirtualFreeEx(hProcess, remoteCode, MEMORY_ALLOCATION_SIZE, MEM_RELEASE); CloseHandle(hProcess); return 1; } // 替换为game.exe输出的player真实地址 LPVOID playerAddr = reinterpret_cast<LPVOID>(0x00791000); // 创建远程线程执行注入的代码 HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, (LPTHREAD_START_ROUTINE)remoteCode, playerAddr, NULL, nullptr); if (hThread == NULL) { std::cerr << "Error: Unable to create remote thread. Error code: " << GetLastError() << std::endl; VirtualFreeEx(hProcess, remoteCode, MEMORY_ALLOCATION_SIZE, MEM_RELEASE); CloseHandle(hProcess); return 1; } WaitForSingleObject(hThread, INFINITE); // 清理资源 VirtualFreeEx(hProcess, remoteCode, MEMORY_ALLOCATION_SIZE, MEM_RELEASE); CloseHandle(hThread); CloseHandle(hProcess); return 0; }
关键注意事项
- 机器码长度:实际使用时需通过反汇编工具(如x64dbg、IDA)查看
HackThread的机器码长度,避免写入不完整或多余字节。 - 地址随机性:game.exe中的
player地址可能因ASLR机制每次运行变化,建议通过内存扫描动态获取,而非硬编码地址。 - 权限要求:确保injector.exe以管理员权限运行,避免因权限不足导致操作失败。
内容的提问来源于stack exchange,提问作者Tu Le Anh
相关产品推荐
相关产品推荐

