临近年关,春节前的一周时间都没什么事,每天上班光明正大的摸鱼。但一周都没事做也不免有些无聊。为了打发无聊的时间,翻出我珍藏多年的移动硬盘,在硬盘的某个快被遗忘的角落,翻出了一个我多年以前刚开始学C语言时在网上淘到的一个双管道后门程序。这个后门程序当时我是看不懂的,随手丢在了角落吃灰,这么多年过去了,我觉得我应该能看得懂了,就翻出来研究一下。
其大致原理是这样的:先在本地起一个监听socket,然后开启两个管道,运行cmd程序,并将管道1的读端绑定到cmd的标准输入,管道2的写端绑定到cmd的标准输出和错误输出。然后远程想要用后门了,需要连接这个socket,连接成功后,发送一条命令,比如是dir。后门程序通过socket读到了这条命令,然后把socket读到的命令写入到管道1的写端。还记得管道1的读端在哪不?就是绑定在cmd的标准输入。cmd进程发现管道有数据了,就会读取这条数据,然后执行相应的命令,并将结果写入到管道2的写端。然后后门程序发现管道2的写端有数据了,就读出来。最后通过socket将cmd的返回结果发送给远程。
看到这里,突然联想到,QProcess读取进程的输出是不是也用管道实现的呢?写段代码跟踪一下
QProcess proc;
proc.start("C:/WINDOWS/system32/cmd.exe", QStringList("/c") << "dir\r\n");
if (proc.state() == QProcess::NotRunning)
{
LOG_ERROR("failed to start %s: %s", exe.toLocal8Bit().data(), QMetaEnum::fromType<QProcess::ProcessError>().valueToKey(proc.error()));
return false;
}
proc.waitForFinished(-1);
QByteArray message = proc.readAllStandardOutput().trimmed();
这段代码很简单,是QProcess的一个常见用法,先执行某个进程,然后等待进程结束,最后读取该进程的输出内容。
接下来要做的是跟进去每个函数,看这些函数都做了什么。
启动进程
首先是构造函数,构造函数没什么可说的,就是一些变量的初始化。
start函数进去后,跳过那些不重要的代码,直接来到QProcessPrivate::startProcess函数内,这才是启动进程的关键位置。QProcessPrivate是不对用户开放的类,但却是QProcess的真正核心,QProcess的很多功能都在QProcessPrivate中实现的。先来看一下QProcessPrivate::startProcess的实现,下面代码删除了该函数内不太重要的内容,只保留真正核心功能,后面的所有Qt的代码段都是这么处理的,完整实现请自行参阅Qt源码
void QProcessPrivate::startProcess()
{
pid = new PROCESS_INFORMATION;
memset(pid, 0, sizeof(PROCESS_INFORMATION));
if (!openChannel(stdinChannel) ||
!openChannel(stdoutChannel) ||
!openChannel(stderrChannel))
return;
DWORD dwCreationFlags = (GetConsoleWindow() ? 0 : CREATE_NO_WINDOW);
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
STARTUPINFOW startupInfo = {
sizeof( STARTUPINFO ), 0, 0, 0,
(ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
(ulong)CW_USEDEFAULT, (ulong)CW_USEDEFAULT,
0, 0, 0,
STARTF_USESTDHANDLES,
0, 0, 0,
stdinChannel.pipe[0], stdoutChannel.