Windows API管道双向通信:写入cmd后无法读取输出
问题:通过管道与cmd.exe交互时ReadFile挂起,无法获取输入后的输出
我尝试编写一个程序,通过两组管道向cmd.exe提供输入并获取其输出。但通过PARENT_WRITE写入输入后,无法从PARENT_READ管道获取输出,ReadFile()出现挂起情况。
原代码
#include <stdio.h> #include <windows.h> #define BUFFER_LENGTH 1024 void handle_cleanup(STARTUPINFOW, PROCESS_INFORMATION); DWORD WINAPI ReadPipe(LPVOID); HANDLE CMD_WRITE, PARENT_READ, CMD_READ, PARENT_WRITE; int CreateProcessCMD(void){ DWORD ThreadID; DWORD bytes_written; HANDLE hProcessCMD; STARTUPINFOW PSTARTUPINFO; PROCESS_INFORMATION PPROCESSINFO; SECURITY_ATTRIBUTES SECURITYATTR; SECURITYATTR.nLength = sizeof(SECURITY_ATTRIBUTES); SECURITYATTR.bInheritHandle = TRUE; SECURITYATTR.lpSecurityDescriptor = NULL; if(!CreatePipe(&PARENT_READ,&CMD_WRITE, &SECURITYATTR,0)){ printf("Could not create pipe"); return EXIT_FAILURE; } printf("CreatePipe(PARENT_READ, CMD_WRITE) - Success!\n"); if(!CreatePipe(&CMD_READ,&PARENT_WRITE, &SECURITYATTR,0)){ printf("Could not create pipe"); return EXIT_FAILURE; } printf("CreatePipe(CMD_READ, PARENT_WRITE) - Success!\n"); ZeroMemory(&PSTARTUPINFO, sizeof(STARTUPINFO)); printf("ZeroMemory(&PSTARTUPINFO, sizeof(STARTUPINFO) - Zeroing STARTUPINFO structure success!\n"); PSTARTUPINFO.cb = sizeof(STARTUPINFO); PSTARTUPINFO.hStdOutput = CMD_WRITE; printf("CMD_WRITE ---> %x\n", CMD_WRITE); printf("STARTINFO.hStsOutput ---> %x\n", PSTARTUPINFO.hStdOutput); PSTARTUPINFO.hStdInput = CMD_READ; printf("CMD_READ ---> %x\n", CMD_READ); printf("STARTINFO.hStdsInput ---> %x\n", PSTARTUPINFO.hStdInput); PSTARTUPINFO.dwFlags |= STARTF_USESTDHANDLES; PSTARTUPINFO.dwFlags |= STARTF_USESHOWWINDOW; PSTARTUPINFO.wShowWindow = SW_SHOWNORMAL; BOOL success = CreateProcessW(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, NULL, NULL, &PSTARTUPINFO, &PPROCESSINFO); if(!success){ printf("CreateProcessW() --> Could not create process!\n"); printf("[-] Closing program"); return EXIT_FAILURE; } printf("C:\\Windows\\System32\\cmd.exe started with PID ---> %i\n", PPROCESSINFO.dwProcessId); HANDLE hThread = CreateThread(NULL, 0, ReadPipe, NULL, 0, &ThreadID); if(hThread == NULL){ printf("CreateThread() --> Failed, Returned %i\n", GetLastError()); return EXIT_FAILURE; } printf("CreateThread() --> New thread created with TID --> %i\n", ThreadID); Sleep(2000); while(TRUE){ char cmd[256]; printf("Prompt> "); gets(cmd); WriteFile(PARENT_WRITE, cmd, strlen(cmd), &bytes_written, NULL); printf("WriteFile() --> Wrote %i bytes\n", bytes_written); } hProcessCMD = PPROCESSINFO.hProcess; switch (WaitForSingleObject(hProcessCMD, INFINITE)) { case WAIT_ABANDONED : printf("WaitForSingleObject(hProcess, Timeout) ---> Returned WAIT_ABANDONED\n"); break; case WAIT_OBJECT_0: printf("WaitForSingleObject(hProcess, Timeout) ---> The state of the specified object is signaled (Process has terminated)\n"); break; case WAIT_TIMEOUT: printf("WaitForSingleObject(hProcess, Timeout) ---> Returned WAIT_TIMEOUT\n"); break; case WAIT_FAILED: printf("WaitForSingleObject(hProcess, Timeout) ---> Failed, Returned &i\n", GetLastError()); break; default: break; } handle_cleanup(PSTARTUPINFO, PPROCESSINFO); return EXIT_SUCCESS; } int main(void){ printf("Parent PID ------> %u\n", GetCurrentProcessId()); if(CreateProcessCMD()){ printf("CreateProcessCMD() - Function returned EXIT_FAILURE\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } void handle_cleanup(STARTUPINFOW startupinfo, PROCESS_INFORMATION processinfo ){ if(!CloseHandle(startupinfo.hStdInput)) printf("[-] Could not close stdin handle\n"); if(!CloseHandle(startupinfo.hStdOutput)) printf("[-] Could not close stdout handle\n"); if(!CloseHandle(startupinfo.hStdError)) printf("[-] Could not close stderr handle\n"); if(!CloseHandle(processinfo.hProcess)) printf("[-] Could not close process handle\n"); if(!CloseHandle(processinfo.hThread)) printf("[-] Could not close thread handle\n"); } DWORD WINAPI ReadPipe(LPVOID lpThreadParameter){ char buffer[BUFFER_LENGTH]; DWORD bytes_read_from_pipe; while(TRUE) { int ret = ReadFile(PARENT_READ, buffer, BUFFER_LENGTH, &bytes_read_from_pipe, NULL); printf("ReadFile() --> Read %i\n", bytes_read_from_pipe); if(bytes_read_from_pipe>0){ printf("%s\n", buffer); ZeroMemory(buffer, BUFFER_LENGTH); bytes_read_from_pipe = 0; continue; } break; } }
原运行输出
Parent PID ------> 12064 CreatePipe(PARENT_READ, CMD_WRITE) - Success! CreatePipe(CMD_READ, PARENT_WRITE) - Success! ZeroMemory(&PSTARTUPINFO, sizeof(STARTUPINFO) - Zeroing STARTUPINFO structure success! CMD_WRITE ---> 100 STARTINFO.hStsOutput ---> 100 CMD_READ ---> 104 STARTINFO.hStdsInput ---> 104 C:\Windows\System32\cmd.exe started with PID ---> 8700 CreateThread() --> New thread created with TID --> 16680 ReadFile() --> Read 43 Microsoft Windows [Version ...] ReadFile() --> Read 89 (c) Microsoft Corporation. All rights reserved. C:\Users\<USER>\Sandbox\> Prompt> where calc.exe WriteFile() --> Wrote 14 bytes Prompt> WriteFile() --> Wrote 0 bytes
问题分析与修复方案
1. 命令缺少换行符,cmd未执行
cmd.exe需要接收到\r\n换行符才会执行输入的命令。当前代码仅写入命令文本,cmd处于等待输入完成的状态,不会输出结果。
修复:写入命令时追加\r\n,并处理输入的换行符:
char cmd[256]; printf("Prompt> "); fgets(cmd, sizeof(cmd), stdin); size_t cmd_len = strlen(cmd); if (cmd[cmd_len-1] == '\n') { cmd[cmd_len-1] = '\0'; // 去掉fgets读取的换行 } strcat(cmd, "\r\n"); // 追加cmd需要的换行 WriteFile(PARENT_WRITE, cmd, strlen(cmd), &bytes_written, NULL);
2. 未重定向标准错误输出
cmd的错误输出默认未进入管道,部分场景下会导致阻塞。需要将hStdError也指向CMD_WRITE管道:
PSTARTUPINFO.hStdError = CMD_WRITE;
3. 管道句柄继承问题
创建子进程时开启了句柄继承,但父进程的PARENT_READ和PARENT_WRITE不需要被子进程继承,否则子进程会持有这些句柄,导致管道不会被正确关闭,ReadFile会一直等待。
修复:创建管道后,关闭这两个句柄的继承属性:
// 创建PARENT_READ/CMD_WRITE后 SetHandleInformation(PARENT_READ, HANDLE_FLAG_INHERIT, 0); // 创建CMD_READ/PARENT_WRITE后 SetHandleInformation(PARENT_WRITE, HANDLE_FLAG_INHERIT, 0);
4. ReadPipe线程错误处理完善
当ReadFile返回FALSE时,需要检查错误码,比如ERROR_BROKEN_PIPE表示管道已关闭,此时应该退出线程:
DWORD WINAPI ReadPipe(LPVOID lpThreadParameter){ char buffer[BUFFER_LENGTH]; DWORD bytes_read_from_pipe; while(TRUE) { BOOL ret = ReadFile(PARENT_READ, buffer, BUFFER_LENGTH-1, &bytes_read_from_pipe, NULL); if (!ret) { DWORD err = GetLastError(); if (err == ERROR_BROKEN_PIPE) { printf("ReadPipe: Pipe broken, exiting thread\n"); break; } else { printf("ReadFile failed, error: %lu\n", err); break; } } if(bytes_read_from_pipe>0){ buffer[bytes_read_from_pipe] = '\0'; // 确保字符串终止 printf("ReadFile() --> Read %lu bytes\n", bytes_read_from_pipe); printf("%s", buffer); // 输出内容,避免重复换行 ZeroMemory(buffer, BUFFER_LENGTH); } } return 0; }
5. 替换不安全的gets函数
gets存在缓冲区溢出风险,改用fgets读取输入,同时处理换行符。
修复后的完整代码
#include <stdio.h> #include <windows.h> #include <string.h> #define BUFFER_LENGTH 1024 void handle_cleanup(STARTUPINFOW, PROCESS_INFORMATION); DWORD WINAPI ReadPipe(LPVOID); HANDLE CMD_WRITE, PARENT_READ, CMD_READ, PARENT_WRITE; int CreateProcessCMD(void){ DWORD ThreadID; DWORD bytes_written; HANDLE hProcessCMD; STARTUPINFOW PSTARTUPINFO; PROCESS_INFORMATION PPROCESSINFO; SECURITY_ATTRIBUTES SECURITYATTR; SECURITYATTR.nLength = sizeof(SECURITY_ATTRIBUTES); SECURITYATTR.bInheritHandle = TRUE; SECURITYATTR.lpSecurityDescriptor = NULL; if(!CreatePipe(&PARENT_READ,&CMD_WRITE, &SECURITYATTR,0)){ printf("Could not create pipe"); return EXIT_FAILURE; } printf("CreatePipe(PARENT_READ, CMD_WRITE) - Success!\n"); SetHandleInformation(PARENT_READ, HANDLE_FLAG_INHERIT, 0); if(!CreatePipe(&CMD_READ,&PARENT_WRITE, &SECURITYATTR,0)){ printf("Could not create pipe"); return EXIT_FAILURE; } printf("CreatePipe(CMD_READ, PARENT_WRITE) - Success!\n"); SetHandleInformation(PARENT_WRITE, HANDLE_FLAG_INHERIT, 0); ZeroMemory(&PSTARTUPINFO, sizeof(STARTUPINFO)); printf("ZeroMemory(&PSTARTUPINFO, sizeof(STARTUPINFO) - Zeroing STARTUPINFO structure success!\n"); PSTARTUPINFO.cb = sizeof(STARTUPINFO); PSTARTUPINFO.hStdOutput = CMD_WRITE; PSTARTUPINFO.hStdInput = CMD_READ; PSTARTUPINFO.hStdError = CMD_WRITE; printf("CMD_WRITE ---> %p\n", CMD_WRITE); printf("STARTINFO.hStdOutput ---> %p\n", PSTARTUPINFO.hStdOutput); printf("CMD_READ ---> %p\n", CMD_READ); printf("STARTINFO.hStdInput ---> %p\n", PSTARTUPINFO.hStdInput); PSTARTUPINFO.dwFlags |= STARTF_USESTDHANDLES; PSTARTUPINFO.dwFlags |= STARTF_USESHOWWINDOW; PSTARTUPINFO.wShowWindow = SW_HIDE; BOOL success = CreateProcessW(L"C:\\Windows\\System32\\cmd.exe", NULL, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &PSTARTUPINFO, &PPROCESSINFO); if(!success){ printf("CreateProcessW() --> Could not create process! Error: %lu\n", GetLastError()); printf("[-] Closing program"); return EXIT_FAILURE; } printf("C:\\Windows\\System32\\cmd.exe started with PID ---> %lu\n", PPROCESSINFO.dwProcessId); CloseHandle(CMD_WRITE); CloseHandle(CMD_READ); HANDLE hThread = CreateThread(NULL, 0, ReadPipe, NULL, 0, &ThreadID); if(hThread == NULL){ printf("CreateThread() --> Failed, Returned %lu\n", GetLastError()); return EXIT_FAILURE; } printf("CreateThread() --> New thread created with TID --> %lu\n", ThreadID); Sleep(500); while(TRUE){ char cmd[256]; printf("Prompt> "); if(fgets(cmd, sizeof(cmd), stdin) == NULL){ break; } size_t cmd_len = strlen(cmd); if (cmd[cmd_len-1] == '\n') { cmd[cmd_len-1] = '\0'; } strcat(cmd, "\r\n"); if(!WriteFile(PARENT_WRITE, cmd, strlen(cmd), &bytes_written, NULL)){ printf("WriteFile failed, error: %lu\n", GetLastError()); break; } printf("WriteFile() --> Wrote %lu bytes\n", bytes_written); } CloseHandle(PARENT_WRITE); hProcessCMD = PPROCESSINFO.hProcess; switch (WaitForSingleObject(hProcessCMD, INFINITE)) { case WAIT_ABANDONED : printf("WaitForSingleObject(hProcess, Timeout) ---> Returned WAIT_ABANDONED\n"); break; case WAIT_OBJECT_0: printf("WaitForSingleObject(hProcess, Timeout) ---> The state of the specified object is signaled (Process has terminated)\n"); break; case WAIT_TIMEOUT: printf("WaitForSingleObject(hProcess, Timeout) ---> Returned WAIT_TIMEOUT\n"); break; case WAIT_FAILED: printf("WaitForSingleObject(hProcess, Timeout) ---> Failed, Returned %lu\n", GetLastError()); break; default: break; } WaitForSingleObject(hThread, INFINITE); CloseHandle(hThread); handle_cleanup(PSTARTUPINFO, PPROCESSINFO); CloseHandle(PARENT_READ); return EXIT_SUCCESS; } int main(void){ printf("Parent PID ------> %lu\n", (DWORD)GetCurrentProcessId()); if(CreateProcessCMD() != EXIT_SUCCESS){ printf("CreateProcessCMD() - Function returned EXIT_FAILURE\n"); return EXIT_FAILURE; } return EXIT_SUCCESS; } void handle_cleanup(STARTUPINFOW startupinfo, PROCESS_INFORMATION processinfo ){ if(!CloseHandle(processinfo.hProcess)) printf("[-] Could not close process handle\n"); if(!CloseHandle(processinfo.hThread)) printf("[-] Could not close thread handle\n"); } DWORD WINAPI ReadPipe(LPVOID lpThreadParameter){ char buffer[BUFFER_LENGTH]; DWORD bytes_read_from_pipe; while(TRUE) { BOOL ret = ReadFile(PARENT_READ, buffer, BUFFER_LENGTH-1, &bytes_read_from_pipe, NULL); if (!ret) { DWORD err = GetLastError(); if (err == ERROR_BROKEN_PIPE) { printf("\nReadPipe: Pipe closed by cmd, exiting thread\n"); break; } else { printf("\nReadFile failed, error: %lu\n", err); break; } } if(bytes_read_from_pipe>0){ buffer[bytes_read_from_pipe] = '\0'; printf("\nReadFile() --> Read %lu bytes:\n%s", bytes_read_from_pipe, buffer); ZeroMemory(buffer, BUFFER_LENGTH); } } return 0; }
内容的提问来源于stack exchange,提问作者Yxd
相关产品推荐
相关产品推荐

