调用ResumeThread恢复挂起进程崩溃,Process Explorer恢复则正常
修改进程启动参数后ResumeThread导致崩溃的问题排查
问题描述
我正在学习C语言,开发一个修改进程启动命令行参数的项目。遇到以下问题:
- 通过代码调用
ResumeThread恢复挂起的进程时,进程直接崩溃退出 - 但通过Process Explorer手动恢复该进程时,进程能正常启动并运行
我的测试程序print_my_arguments.exe用于循环打印命令行参数:若注释代码中的ResumeThread(pi.hThread);语句,通过Process Explorer恢复进程后,程序会打开新窗口并正常打印参数;保留该语句时,进程直接退出。不清楚问题根源,也不知道如何自动收集进程退出的相关信息。
实现代码
#include <Windows.h> #include <winternl.h> #include <stdio.h> // 新增用于打印退出码 int main(int argc, char* argv[]) { STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof(si); // 注意:路径中的反斜杠需要转义为双反斜杠 BOOL bRet = CreateProcessW(NULL, L"C:\\Users\\John\\Desktop\\print_my_arguments.exe firstarg secondarg thirdarg", NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); if (!bRet) { printf("CreateProcess failed: %lu\n", GetLastError()); return 1; } WCHAR szNewCommandLine[] = L"This is the new arguments that will show up."; SIZE_T sLen = 0; SIZE_T nNewCmdLineLen = wcslen(szNewCommandLine); PROCESS_BASIC_INFORMATION pinfo; NtQueryInformationProcess(pi.hProcess, ProcessBasicInformation, &pinfo, sizeof(pinfo), (DWORD*)&sLen); // Read the PEB. PEB peb; if (!ReadProcessMemory(pi.hProcess, pinfo.PebBaseAddress, &peb, sizeof(peb), &sLen)) { printf("Read PEB failed: %lu\n", GetLastError()); return 1; } // Read the length of the Process Parameters buffer. DWORD nOriginalProcessParamsLen; if (!ReadProcessMemory(pi.hProcess, &((DWORD*)peb.ProcessParameters)[1], &nOriginalProcessParamsLen, sizeof(nOriginalProcessParamsLen), &sLen)) { printf("Read ProcessParams length failed: %lu\n", GetLastError()); return 1; } // Allocate a local buffer to store the contents of our Process Parameters buffer. DWORD nNewProcessParamsLen = nOriginalProcessParamsLen + ((nNewCmdLineLen + 1) * 2); LPBYTE pNewParamsBuffer = (LPBYTE)malloc(nNewProcessParamsLen); if (!pNewParamsBuffer) { printf("Malloc failed\n"); return 1; } RTL_USER_PROCESS_PARAMETERS* pNewParams = (RTL_USER_PROCESS_PARAMETERS*)pNewParamsBuffer; // Read the contents of the original Process Parameters buffer, into our new local buffer if (!ReadProcessMemory(pi.hProcess, (LPVOID)peb.ProcessParameters, pNewParamsBuffer, nOriginalProcessParamsLen, &sLen)) { printf("Read original ProcessParams failed: %lu\n", GetLastError()); free(pNewParamsBuffer); return 1; } // Allocate a new buffer in the remote process. BYTE* pNewParamsAddr = (BYTE*)VirtualAllocEx(pi.hProcess, NULL, nNewProcessParamsLen, MEM_COMMIT, PAGE_READWRITE); if (!pNewParamsAddr) { printf("VirtualAllocEx failed: %lu\n", GetLastError()); free(pNewParamsBuffer); return 1; } // Copy the new command line to the end of the new Process Parameters buffer wcscpy_s((LPWSTR)&pNewParamsBuffer[nOriginalProcessParamsLen], nNewCmdLineLen + 1, szNewCommandLine); // Adjust the buffer size at the beginning of the Process Parameters structure (first two DWORDs) ((DWORD*)pNewParamsBuffer)[0] = (DWORD)nNewProcessParamsLen; ((DWORD*)pNewParamsBuffer)[1] = (DWORD)nNewProcessParamsLen; // Set the command line pointer to the address at the end of the buffer. pNewParams->CommandLine.Buffer = pNewParamsAddr + nOriginalProcessParamsLen; pNewParams->CommandLine.Length = (USHORT)(nNewCmdLineLen * 2); pNewParams->CommandLine.MaximumLength = (USHORT)((nNewCmdLineLen * 2) + 2); // 修正:正确处理指针偏移,避免64位系统下的指针截断 for (DWORD offset = 0; offset < nOriginalProcessParamsLen; offset += sizeof(PVOID)) { PVOID* valuePtr = (PVOID*)&pNewParamsBuffer[offset]; PVOID value = *valuePtr; if (value >= peb.ProcessParameters && value < (PVOID)((BYTE*)peb.ProcessParameters + nOriginalProcessParamsLen)) { *valuePtr = (PVOID)((BYTE*)value - (BYTE*)peb.ProcessParameters + (BYTE*)pNewParamsAddr); } } // Write the new buffer to the remote process if (!WriteProcessMemory(pi.hProcess, (LPVOID)pNewParamsAddr, pNewParamsBuffer, nNewProcessParamsLen, &sLen)) { printf("Write new ProcessParams failed: %lu\n", GetLastError()); VirtualFreeEx(pi.hProcess, pNewParamsAddr, 0, MEM_RELEASE); free(pNewParamsBuffer); return 1; } // 修正:正确计算远程PEB中ProcessParameters的地址 LPVOID pebProcessParamsAddr = (LPVOID)((BYTE*)pinfo.PebBaseAddress + offsetof(PEB, ProcessParameters)); if (!WriteProcessMemory(pi.hProcess, pebProcessParamsAddr, &pNewParamsAddr, sizeof(PVOID), &sLen)) { printf("Write PEB ProcessParams failed: %lu\n", GetLastError()); VirtualFreeEx(pi.hProcess, pNewParamsAddr, 0, MEM_RELEASE); free(pNewParamsBuffer); return 1; } // 可选:将远程内存权限改为只读,贴近系统默认属性 DWORD oldProtect; VirtualProtectEx(pi.hProcess, pNewParamsAddr, nNewProcessParamsLen, PAGE_READONLY, &oldProtect); free(pNewParamsBuffer); ResumeThread(pi.hThread); // 新增:等待进程结束并获取退出码 WaitForSingleObject(pi.hProcess, INFINITE); DWORD exitCode; GetExitCodeProcess(pi.hProcess, &exitCode); printf("Process exited with code: %lu\n", exitCode); // 关闭句柄 CloseHandle(pi.hThread); CloseHandle(pi.hProcess); Sleep(5000); return 0; }
问题根源与修复要点
1. 核心问题:PEB指针写入地址错误
原代码中修改PEB的ProcessParameters指针时,错误地使用了本地进程的结构体地址:
// 错误写法 WriteProcessMemory(pi.hProcess, (LPVOID)(&pinfo.PebBaseAddress->ProcessParameters), &pNewParamsAddr, sizeof(VOID*), &sLen);
&pinfo.PebBaseAddress->ProcessParameters是本地栈中pinfo结构体的成员地址,而非远程进程PEB中ProcessParameters的实际地址。正确做法是用offsetof宏计算PEB结构体中ProcessParameters的偏移量,再加上远程PEB的基地址:
// 正确写法 LPVOID pebProcessParamsAddr = (LPVOID)((BYTE*)pinfo.PebBaseAddress + offsetof(PEB, ProcessParameters)); WriteProcessMemory(pi.hProcess, pebProcessParamsAddr, &pNewParamsAddr, sizeof(PVOID), &sLen);
这是导致进程崩溃的直接原因——错误的内存写入导致PEB指向非法地址,进程恢复后无法正常读取参数。
2. 次要问题:64位指针截断
原代码中处理指针偏移时,错误地将PVOID类型转换为BYTE*,导致64位系统下指针被截断为1字节:
// 错误写法 BYTE* value = *((BYTE*)(&pNewParamsBuffer[offset]));
修正后用PVOID*类型处理指针,确保32/64位系统都能正确解析指针值:
// 正确写法 PVOID* valuePtr = (PVOID*)&pNewParamsBuffer[offset]; PVOID value = *valuePtr;
3. 进程退出信息收集
要自动获取进程退出原因,可在ResumeThread后调用WaitForSingleObject等待进程结束,再用GetExitCodeProcess获取退出码:
- 若退出码为
0xC0000005(STATUS_ACCESS_VIOLATION),说明存在内存访问错误 - 若为
0则表示正常退出 - 其他错误码可对照Windows系统错误码表定位问题
验证方法
修复上述问题后,重新编译运行代码:
- 进程恢复后应正常启动
print_my_arguments.exe并打印新的命令行参数 - 控制台会输出进程退出码,若为
0则表示正常结束
内容的提问来源于stack exchange,提问作者nahjoh44
相关产品推荐
相关产品推荐

