如何通过C++程序向已打开的应用窗口发送执行命令
C++向PsExec启动的应用发送指令的实现方案
针对两种启动模式,分别对应不同的实现路径,管道是控制台类应用场景下的最优解,具体实现如下:
一、控制台类目标应用:匿名管道重定向标准输入
不管是附着在当前CMD窗口启动,还是新开独立控制台窗口启动,只要目标应用是通过标准输入接收指令的控制台程序,都可以通过CreateProcess绑定匿名管道的方式实现后续指令下发,不需要依赖窗口消息或者模拟输入。
实现步骤
- 调用
CreatePipe创建匿名管道,设置安全属性允许子进程继承管道读端句柄,同时关闭管道写端的继承权限,避免句柄泄漏 - 填充
STARTUPINFO结构体,将子进程的标准输入句柄替换为管道读端,标准输出、标准错误可以根据需求选择继承当前窗口句柄(实现日志打印到当前窗口)或者重定向到其他管道- 若需要新开独立窗口,给
CreateProcess传入CREATE_NEW_CONSOLE创建标志,同时在STARTUPINFO里设置STARTF_USESHOWWINDOW标志指定窗口显示状态 - 若需要附着在当前CMD窗口,不需要传
CREATE_NEW_CONSOLE,直接复用当前控制台即可
- 若需要新开独立窗口,给
- 调用
CreateProcess启动PsExec及目标应用,启动成功后立刻关闭本地持有的管道读端、进程句柄、线程句柄等不需要的资源 - 后续需要下发指令时,直接调用
WriteFile向管道写端写入命令字符串即可,注意控制台应用通常按行解析输入,命令末尾需要加换行符\n
核心代码示例
#include <windows.h> #include <stdio.h> #include <tchar.h> int main() { HANDLE hPipeRead = NULL, hPipeWrite = NULL; SECURITY_ATTRIBUTES sa = {0}; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; sa.lpSecurityDescriptor = NULL; // 创建匿名管道 if (!CreatePipe(&hPipeRead, &hPipeWrite, &sa, 0)) { return GetLastError(); } // 禁止写端被子进程继承 SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, 0); STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESTDHANDLES; si.hStdInput = hPipeRead; si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); si.hStdError = GetStdHandle(STD_ERROR_HANDLE); DWORD createFlags = 0; // 若需要新开独立窗口,取消注释下两行 // createFlags |= CREATE_NEW_CONSOLE; // si.dwFlags |= STARTF_USESHOWWINDOW; // si.wShowWindow = SW_SHOW; TCHAR cmdLine[] = _T("psexec \\\\192.168.3.20 -u admin -p Passw0rd C:\\target_console_app.exe"); if (!CreateProcess(NULL, cmdLine, NULL, NULL, TRUE, createFlags, NULL, NULL, &si, &pi)) { CloseHandle(hPipeRead); CloseHandle(hPipeWrite); return GetLastError(); } // 释放不需要的句柄 CloseHandle(hPipeRead); CloseHandle(pi.hThread); CloseHandle(pi.hProcess); // 后续下发指令示例 const char* cmd1 = "start_task task_id_001\n"; DWORD written = 0; WriteFile(hPipeWrite, cmd1, strlen(cmd1), &written, NULL); // 程序退出前关闭管道写端,子进程会收到输入结束信号 // CloseHandle(hPipeWrite); return 0; }
注意:使用PsExec远程启动时不要加
-d参数,该参数会让PsExec启动目标进程后立即退出,导致管道断裂无法透传指令。
二、GUI类目标应用:窗口消息通信
如果启动的是GUI程序,不通过标准输入接收指令,管道方案不适用,直接通过Windows窗口消息通信即可:
- 由于你提到两种启动模式都会修改窗口标题,可以直接调用
FindWindow传入你设置的固定窗口标题,拿到目标主窗口的句柄;如果标题不固定,可以通过EnumWindows枚举所有顶层窗口,结合GetWindowThreadProcessId匹配你通过CreateProcess拿到的进程ID,找到对应主窗口 - 简单文本指令可以先通过
FindWindowEx找到窗口内的输入框子控件,发送WM_SETTEXT设置指令内容,再发送回车消息触发执行 - 结构化自定义指令推荐用
WM_COPYDATA消息发送,该消息原生支持跨进程数据传递,不需要额外做内存映射,兼容性最好
避坑说明
- 不要尝试用
PostMessage给控制台窗口发送WM_CHAR/WM_KEYDOWN模拟输入,控制台窗口的输入队列不响应常规窗口消息,模拟输入很容易因为窗口失焦、前台窗口切换导致指令发错位置,稳定性远不如管道重定向 - 管道写入指令时必须加换行符,绝大多数控制台程序会在收到换行符时才会提交输入缓冲区的内容做解析,缺少换行符会导致程序一直等待输入完成,看起来像指令没发成功
- 远程启动场景如果遇到管道权限不足,给PsExec加
-h参数以提升后的权限运行,同时确保远程主机默认共享(admin$)处于开启状态
内容的提问来源于stack exchange,提问作者mbaker1234d
相关产品推荐
相关产品推荐

