Gh0st通信協議解析(2)

原文鏈接:http://blog.renren.com/blog/bp/Q78RzCJjOx

從被控端主動去連接主控端開始談起。世間萬事萬物有始有終,宇宙環宇的動力起點就是上帝的那一推之力。當然,主控端與被控端的交互總是從被控端主動連接到主控端開始的,讓我們從發起連接這個引爆點談起……

*******************************************************************************

首先,我需要聲明一點,我們本款遠控軟件僅僅就是一個DLL文件,爲什麼我們的木馬就是一個DLL文件,因爲要讓我們的這個木馬躲過殺軟的截殺必須想盡各種猥瑣的方法讓其啓動,這就需要我們開發第三方的程序去啓動我們的這個DLL,而如今計算機病毒的精彩技術就體現在這個第三方程序上,第三方程序的犀利程度也成了寫計算機病毒的人水平高低的一個衡量標準。我們或許在後續的文章中不會向大家展示這第三方程序的開發思路,因爲一旦將這種思路公佈,我們的這個遠控就具備了真正的殺傷力。還有一個因素,我們這套課程的主題就是分析gh0st的通信協議,因此,對其它的內容我們會因課程的需要稍微提一下而已。好,我們接下來看看我們的這款gh0st變種的一個執行過程。

 

首先,在這個DLL被加載的時候,會判斷自身的一個執行環境,如果是在rundll32.dll裏,那就開始後續的操作,否則不會有任何的動作。

接下來創建了一個工作線程,這個工作線程的線程函數爲Login,從函數名字我們也可以看出就是取連接主控端。關於這個函數的功能,我們稍後詳述,在這裏我們看一下下面這個語:CKeyboardManager::g_hInstance=(HINSTANCE)hModule; 

從這裏我們可以看出,這個值會在卸載自身的時候被用到。

接下來,我們看看這個Login線程函數,因爲這個函數比較大,我們分爲四段進行講解。

首先,是創建一個互斥量,保證單個實例運行。

HANDLE CreateMutex(                  LPSECURITY_ATTRIBUTES lpMutexAttributes,                    BOOL bInitialOwner,                     LPCTSTR lpName       );

接下來是設置工作站,關於設置工作站的作用,因爲gh0st的原作者是將這個DLL文件加載到系統服務運行的,這樣就有一個問題:服務是system運行的,有自己的窗口站,和我們默認使用的 “winsta0“不是一個窗口站,不能直接通訊、交互,因此,需要我們自己設置本進程的工作站爲winsta0。這樣這個DLL就可以與我們默認使用的這個窗口站上的程序進行交互,比如後續中的查找窗口、截獲鍵盤記錄等操作纔會有效。

設置工作站的一組API如下:

1:HWINSTA GetProcessWindowStation(VOID)The GetProcessWindowStation function returns a handle to the window station associated with the calling process.

這個函數會返回一個與調用此函數的進程相關的窗口工作站句柄。

2:HWINSTA OpenWindowStation(                           LPTSTR lpszWinSta,                         BOOL fInherit,                                  DWORDdwDesiredAccess                           );

The OpenWindowStation function returns a handle to an existing window station.

這個函數會返回一個指定的已經存在的窗口工作站的句柄。

3:BOOL SetProcessWindowStation(HWINSTA hWinSta);The SetProcessWindowStation function assigns a window station to the calling process. This enables the process to access objects in the window station such as desktops, the clipboard, and global atoms. All subsequent operations on the window station use the access rights granted to hWinSta.

這個函數會爲調用此函數的進程設置一個窗口工作站。這使得這個進程可以訪問到屬於這個窗口工作站的對象,比如桌面、剪切板、還有全局的變量。在這個工作站上的所有後續操作都將依賴於hWinSta所具有的訪問權限。

 

再接下來是設置本進程的錯誤模式,如果在本進程中發生了嚴重級別比較高的錯誤的時候,會將錯誤發送到本進程來處理,而不是不負責的彈出一個錯誤對話框,要注意我們這個DLL的坯子可不是很好。

UINT SetErrorMode(UINT uMode);The SetErrorMode function controls whether the system will handle the specified types of serious errors, or whether the process will handle them.

這個函數可以設置是否由系統來處理一些制定類型的嚴重錯誤,還是由程序來處理他們。

對幾個變量的作用進行解析。

1:lpszHost:將要連上的主控端的IP地址或者域名地址

2:dwPort:將要連接上的主控端的監聽端口

3:hEvent:這個變量是作爲主線程退出的一個哨兵監視點,看看這個變量被利用的幾個位置。

A:在向主控端進行連接的時候的這個無限循環的開始處,有如下的調用

for (int i = 0; i < 500; i++)

{

hEvent = OpenEvent(EVENT_ALL_ACCESS, false, "BITS");

   if (hEvent != NULL)

   {

socketClient.Disconnect();

         CloseHandle(hEvent);

         break;

}

        Sleep(60);

}

B:在主控端要求結束進行對被控端的控制的時候,對此變量有這樣的操作

void CKernelManager::UnInstallService()

{

  char MyPath[MAX_PATH];

  GetModuleFileName(CKeyboardManager::g_hInstance,MyPath,MAX_PATH);

  DeleteFile("C:\\FW.FW");

  MoveFile(MyPath,"C:\\FW.FW");

  CreateEvent(NULL, true, false, m_strKillEvent);

C:在向主控端進行連接的時候的這個無限循環的結束處,有如下的調用

do

{

      hEvent = OpenEvent(EVENT_ALL_ACCESS, false, "BITS");

        dwIOCPEvent = WaitForSingleObject(socketClient.m_hEvent, 100);

        Sleep(500);

} while(hEvent == NULL && dwIOCPEvent != WAIT_OBJECT_0);

    對以上三處的調用我們做一個說明:第一處調用是在判斷當前沒有連接、並分析出當前沒有連接的原因不是NOT_CONNECT,這個時候會在一個循環中等待第二處調用的地方將這個Event創建出來,即所有操作完成後,通知主線程可以退出。第三處的調用跟第一處調用類似,知識多了一個IOCPEvent的判斷。

4:bBreakError就是一個記錄斷開連接的原因的一個變量。

 

接下來,我們根據程序的執行流程走一遍,先看CClientSocket socketClient;

看看CClientSocket這個類的構造函數中都進行了哪些操作。

初始化了Socket庫,創建了一個人工置信、初始狀態未受信、未命名的一個事件對象。關於這個事件對象的作用,我們看以下幾個地方。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

1:在CClientSocket::CClientSocket()中

   m_hEvent = CreateEvent(NULL, true, false, NULL);創建了這個事件對象

2:在CClientSocket::~CClientSocket()中

   CloseHandle(m_hEvent);關閉了事件對象句柄

3:在CClientSocket::Connect中,連接主控端之前

   ResetEvent(m_hEvent);重置了該事件對象的受信狀態爲未受信。

4:在CClientSocket::Disconnect(),關閉到主控端的連接中

   SetEvent(m_hEvent);將該事件對象設置爲受信狀態

5:剛剛在連接循環中看到的dwIOCPEvent = WaitForSingleObject(socketClient.m_hEvent, 100);從以上幾個調用的地方,我們可以得知,這個事件對象的作用就是監視被控端與主控端的一個連接狀態的哨兵。

接下來是填充了一個通信數據包中使用的簽名數據。

 

我們繼續往下看這個連接主控端的無限循環。接下來,如果主控端主動卸載被控端的時候,將會使得上述討論的hEvent=OpenEvent(EVENT_ALL_ACCESS, false, "BITS");返回非NULL的值,也就會使得socketClient.Disconnect();會被執行。我們看看這個函數的定義。

在前面一節課Gh0st通信協議解析(1)中,我們已經分析過關閉套接字的用法,在這裏我們就回顧一下以前的分析:

設置 l_onoff爲非0,l_linger爲0,則套接口關閉時TCP夭折連接,TCP將丟棄保留在套接口發送緩衝區中的任何數據併發送一個RST給對方,而不是通常的四分組終止序列,這避免了TIME_WAIT狀態。這樣直接丟棄了所有在這個套接字上的所有的數據,不論是待發送的還是待接收的,都被丟棄。解決掉了套接字上殘留的數據之後,接下來開始進行撤銷在此套接字上懸而未決的操作,接着關閉掉這個套接字句柄,並且將這個套接字句柄的值設置爲:INVALID_SOCKET。

 

CancelIo:這個函數的講解。

BOOL CancelIo(   HANDLE hFile  // file handle for which to cancel I/O );The CancelIofunction cancels all pending input and output (I/O) operations that were issued by the calling thread for the specified file handle. The function does not cancel I/O operations issued for the file handle by other threads.

這個函數可以取消掉調用此函數的線程中的個句柄上阻塞的輸入、輸出操作。但是這個函數無法取消掉在其它的線程中的某個句柄上的輸入、輸出操作。

 

InterlockedExchange:這個函數我們以前沒有詳細講解過。

LONG InterlockedExchange(                       LPLONG Target,                       LONGValue );

 

The InterlockedExchange function atomically exchanges a pair of 32-bit values. The function prevents more than one thread from using the same variable simultaneously.

這個函數的執行是原子性的操作,也就是說它的執行是不可中斷的。它執行的操作就是交換兩個數據的值。這個函數阻止多個線程同時對這個數據進行引用。

 

接下是一個SetEvent(m_hEvent)操作,使得在CClientSocket的構造函數中創建的這個事件對象的處於受信狀態,如此便可使得當初連接到主控端的那個無限循環中等待連接結束的小循環中的這一句調用dwIOCPEvent = WaitForSingleObject(socketClient.m_hEvent, 100);返回一個WAIT_OBJECT_0。

 

我們繼續看,連接到主控端的這個無限循環中部分代碼。

在開始分析下面的代碼前,我們需要明確一點gh0st的一個很優秀的功能就是,被控端可以不直接連接到主控端上,而是可以連接到代理服務器上,而在我們將要分析與製作的這款gh0st修改版上,我們不打算支持這個功能。因此,我們對代理這一塊確實做了簡約化處理。

 

我們簡單的談一下gh0st原版的一個查找主控端信息以及代理信息的一個過程,因爲在我們的這個修改版本中並沒有這個過程。

1:首先在執行文件的本模塊中找到經過加密處理的上線字符串

2:然後對這個上線字符串進行一個解密

3:對解密後的字符串進行一個判斷,或者是用域名上線,或者是得從網上獲取上線的信息。

4:然後對獲取到得上線信息進行一個信息提取,解析出上線主機IP/端口、代理IP/端口。

 

好了,我們繼續看這個連接的過程。

首先調用的是一句:socketClient.setGlobalProxyOption();這個函數是有默認的參數的,如下:

void setGlobalProxyOption(int nProxyType = PROXY_NONE, LPCTSTR lpszProxyHost = NULL, UINT nProxyPort = 1080, LPCTSTR lpszUserName = NULL, LPCSTR lpszPassWord = NULL);

也就是說,如果按照我們的這種調用方是,那麼我們默認是不使用代理服務器的。

我們看看這個函數的一個實現方式:

我們僅僅是大體看下這個函數的實現方式,其中的變量我們不去深究,因爲在我們的程序中這些變量的存在意義不大。

 

接下來就是被控端向主控端進行連接的地方,主要是調用了CClientSocket::Connect這個函數:

連接之前,首先要執行CClientSocket::Disconnect這個函數,目的就是清除一下socket資源。這裏面有個險中取勝的一個地方,在Disconnect函數中有一個對m_hEvent進行置信的操作,要知道在連接主控端的這個大循環中是不斷的循環測試這個值的,如果這個值受信了則就退出這個連接循環,那客戶端豈不是就掉線了?而問題的解決方案就在這裏,在Connect這個函數中調用了Disconnect之後緊接着調用了ResetEvent這個函數,馬上將m_hEvent設置爲未受信的狀態。

因爲我們忽略了代理服務器,因此在這裏所有對代理服務器的操作我們都可以忽略到,除了這些我們會發現,上面一段代碼就是創建了一個用於連接的套接字,然後連接主控端。

連接到主控端之後,設置了該套接字的一個保活特性,關於這部分的內容我們在Gh0st通信協議分析(1)裏有也有講過,在這裏我們再回顧一下這種使用方法:

設置了SIO_KEEPALIVE_VALS後,激活包由TCP STACK來負責。當網絡連接斷開後,TCP STACK並不主動告訴上層的應用程序,但是當下一次RECV或者SEND操作進行後,馬上就會返回錯誤告訴上層這個連接已經斷開了如果檢測到斷開的時候,在這個連接上有正在PENDING的IO操作,則馬上會失敗返回。

上面這句代碼的含義是:每隔m_nKeepLiveTime的時間,開始向受控端發送激活包,重複發送五次,每次發送的時間間隔是10秒鐘,如果在十秒鐘之內都沒能得到回覆,則判定主控端已經掉線。對掉線後的處理,在這裏我必須要說說:由於TCP STACK並不主動告訴上層的應用程序,只有當下一次發送數據,或者接收數據的時候纔會被發現。

 

接下來就創建了一個無限循環的工作線程,這個工作線程的主要任務就是監聽來自客戶端的命令請求,關於這個線程的分析,我們稍後再表。

 

讓我們話分兩路,去看看當有被控端主動去連接到主控端的時候,主控端會有怎樣的操作。

 

當有被控端連接到主控端的時候,在監聽套接字上會有網絡事件發生,因此阻塞在m_hEvent這個事件對象上的線程會被喚醒,接下來會詳細判斷出發生在監聽套接字上的這個網絡事件具體是否爲FD_ACCEPT,因爲我們在監聽套接字上,只對這個網絡事件感興趣。如果確實爲FD_ACCEPT這個網絡事件的發生的話,那麼就要調用CIOCPSserver::OnAccept這個函數,對到來的連接進行處理。

我們先來看看這一段接收所用到得API函數的功能進行一個簡單的說明。

1:reinterpret_cast:CIOCPServer* pThis = reinterpret_cast<CIOCPServer*>(lParam);

The reinterpret_cast operator allows any pointer to be converted into any other pointer type, and it allows any integral type to be converted into any pointer type and vice versa. Misuse of the reinterpret_cast operator can easily be unsafe. Unless the desired conversion is inherently low-level, you should use one of the other cast operators.

這個操作符允許你將任意類型的指針轉化成其它類型的指針,並且允許你將整形轉換成任意類型的指針,反之亦然。錯誤的使用這個操作符可以輕易的使你的程序處於不安全的狀態。2:WaitForSingleObject:

DWORD WaitForSingleObject(                         HANDLE hHandle,                         WORD dwMilliseconds );The WaitForSingleObject function returns when one of the following occurs: The specified object is in the signaled state. The time-out interval elapses. 當以下兩種情況發生的時候,這個函數會返回:指定的對象處於受信的狀態。等待超時。3:WSAWaitForMultipleEvents DWORD WSAWaitForMultipleEvents(                            WORD cEvents,   const WSAEVENT FAR*lphEvents,     BOOL fWaitAll,                                                 DWORDdwTimeOUT,                                              BOOL fAlertable                );The WSAWaitForMultipleEvents function returns either when any one or when all of the specified objects are in the signaled state, or when the time-out interval elapses. 當所有指定的對象受信的時候或者只有一個對象受信的時候,又或者等待的時間超時的時候,這個函數纔會返回。 4:WSAEnumNetworkEvents int WSAEnumNetworkEvents (                        SOCKET s,                                                    WSAEVENT hEventObject,                                       LPWSANETWORKEVENTS lpNetworkEvents  );The Windows SocketsWSAEnumNetworkEventsfunction discovers occurrences of network events for the indicated socket, clear internal network event records, and reset event objects (optional).這個函數會識別指定的socket上發生的網絡事件,並且會清除內部的網絡事件記錄,還會重置事件對象。 接下來,我們去CIOCPServer::OnAccept裏去看看這個函數的實現原理。在這個函數裏有個接收後續數據的引爆點——PostRecv。

對以上代碼進行說明: 首先,創建了一個與被控端進行通信的clientSocket,這個clientSocket是主控端與被控端進行信息交互的傳輸媒介。接下來是爲了與被控端進行信息交互而創建了一個保存客戶端數據的變量ClientContext* pContext = AllocateContext();我們看以下這個函數的實現:  首先是用臨界區CLock cs(CIOCPServer::m_cs, "AllocateContext")鎖住了這塊代碼,以使得這個線程獨佔的訪問該代碼。 接着判斷m_listFreePool這個鏈表裏面是否還有元素存在,注意這個連表裏的每一個元素都是一個指針,指向一個ClientContext結構。有的話直接從這個連表裏摘取一個下來,否則的話需要從新申請一個ClientContext結構。我們對這個結構的成員變量進行一番說明。  m_Socket:主控端用來記錄與每個被控端進行通信的Socket m_WriteBuffer:這個變量的類型是CBuffer類,關於這個類型的定義如下所示,在這裏我們也囉嗦一下,全面講講這個類的各個成員函數。  首先呢,看看CBuffer這個類的三個成員變量 m_pBase:始終指向Buffer的一個起始位置。 m_pPtr:始終指向Buffer的一個結束位置。 m_nSize:始終反映當前這個緩衝區的大小。 接下來看看這幾個成員函數: 構造函數  析構函數  重新調整緩衝區的大小的函數(往大了去調整) 重新調整緩衝區的大小的函數(往小了去調整)   返回CBuffer對象一些參數信息。比如緩衝區的大小m_nSize,緩衝區中有效數據的擦長度。  在這裏我們要注意,緩衝區的大小與緩衝區中存儲的信息不是一個概念,我們看返回這倆個數據的函數。  返回緩衝區的大小  返回有效數據的長度  往緩衝區中寫數據  從緩衝區中讀取數據  往緩衝區首部插入數據  從緩衝區首部中刪除數據  從指定的位置開始搜索字符串  返回緩衝區中指定的位置處得字符串  清空緩衝區中的數據  實際上並沒有清空,這個緩衝區裏還有1024個字節的空間。   下面是幾種往緩衝區中增加數據的方式,包括以CString的方式,CBuffer的方式,File的方式。     至此,這個CBuffer這個類的成員變量以及成員函數我們就看到這裏。我們繼續回到這個類——ClientContext。 CBuffer  m_WriteBuffer;            // 將要發送的數據 CBuffer  m_CompressionBuffer;      // 接收到的壓縮的數據CBuffer  m_DeCompressionBuffer;    // 解壓後的數據 CBuffer  m_ResendWriteBuffer;      // 上次發送的數據包,接收失敗時重發時用 int      m_Dialog[2];              // 第一個int是類型,第二個是CDialog的地址int      m_nTransferProgress;      // 記錄傳輸的速度 // Input Elements for Winsock WSABUF   m_wsaInBuffer; BYTE     m_byInBuffer[8192]; 以上兩個值是給非阻塞函數WSARecv函數作爲參數用的,具體的用法,看下面: ******************************************************************************* pContext->m_wsaInBuffer.buf = (char*)pContext->m_byInBuffer; pContext->m_wsaInBuffer.len = sizeof(pContext->m_byInBuffer); UINT nRetVal = WSARecv(pContext->m_Socket,                        &pContext->m_wsaInBuffer,                        1,                        &dwNumberOfBytesRecvd,                       &ulFlags,                        &pOverlap->m_ol,                        NULL); 首先,將m_wsaInBuffer 這個變量的兩個成員變量賦值爲ClientContext裏的成員變量m_byInBuffer。然後再WSARecv這個函數裏會用到m_wsaInBuffer。在這裏我們要第一次簡單的初探主控端與被控端的交互過程:我打算從兩個不同角度去簡要的敘述一下主控端與被控端之間的交互過程。 第一:數據的發送過程。 1:在CIOCPServer::Send函數中準備好待發送的數據。就是將需要發送的數據先存儲在ClientContext::m_WriteBuffer這個緩衝區中,主控端主動向被控端發送的數據基本上都是一些命令數據,因此,沒有將命令數據進行壓縮傳輸。但是,在傳輸的過程中可能會引起數據丟失,需要備份將要發送的數據,因此,在ClientContext::m_ResendWriteBuffer中備份了這些命令數據。 2:準備好將要發送的數據之後,使用 OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite); PostQueuedCompletionStatus(m_hCompletionPort,                           0,                           (DWORD) pContext,                          &pOverlap->m_ol); 向完成端口投遞一個發送數據的請求,這個時候的數據並沒有送出到網卡的數據緩衝區,當然也就沒有被髮送出去,這個時候的數據甚至都可能沒有發送至TCP/IP協議棧的緩衝區中。 3:守候在完成端口上的工作線程會因爲這裏投遞了一個發送數據的請求而被喚醒,這個時候BOOL bIORet = GetQueuedCompletionStatus(hCompletionPort,                                           &dwIoSize,                                           LPDWORD) &lpClientContext,                                            &lpOverlapped, INFINITE); 等待在此函數上的線程會被喚醒,這個函數會返回,並且在lpClientContext,會返回由PostQueuedCompletionStatus的參數pContext指向的內容地址。在lpOverlapped中會返回pOverlap這個變量的值。 PostQueuedCompletionStatus GetQueuedCompletionStatus 這兩個函數的參數是一一對應的。 4:先前發送的投遞請求最終是由CIOCPServer::ProcessIOMessage這個函數來完成的,關於這個函數的定義,不得不去看一組宏定義: enum IOType { IOInitialize, IORead, IOWrite, IOIdle }; #define BEGIN_IO_MSG_MAP() \ public: \ Bool ProcessIOMessage(IOType clientIO, ClientContext* pContext, DWORD dwSize = 0)\ { \ bool bRet = false; #define IO_MESSAGE_HANDLER(msg, func) \ if (msg == clientIO) \        bRet = func(pContext, dwSize);   #define END_IO_MSG_MAP() \ return bRet; \ } 接下來,我們需要看看使用這個宏的地方的定義: BEGIN_IO_MSG_MAP()          IO_MESSAGE_HANDLER(IORead, OnClientReading)         IO_MESSAGE_HANDLER(IOWrite, OnClientWriting)          IO_MESSAGE_HANDLER(IOInitialize, OnClientInitializing) END_IO_MSG_MAP() 對這組宏調用進行宏展開,展開之後的情形爲: public: Bool ProcessIOMessage(IOType clientIO, ClientContext* pContext, DWORD dwSize = 0)\ bool bRet = false; if (IORead == clientIO) \         bRet = OnClientReading(pContext, dwSize); if (IOWrite == clientIO) \        bRet = OnClientWriting(pContext, dwSize); if (IOInitialize == clientIO) \          bRet = OnClientInitializing(pContext, dwSize);      return bRet; } 5:這樣的話,我們所投遞的發送數據的請求,就由OnClientWriting這個函數來處理了,這個函數的處理方式也比較簡單。 pContext->m_wsaOutBuffer.buf = (char*) pContext->m_WriteBuffer.GetBuffer(); pContext->m_wsaOutBuffer.len = pContext->m_WriteBuffer.GetBufferLen(); int nRetVal = WSASend(pContext->m_Socket,                       &pContext->m_wsaOutBuffer,                       1,                       &pContext->m_wsaOutBuffer.len,                       ulFlags,                       &pOverlap->m_ol,                      NULL); 將含有待發送數據的緩衝區地址賦給我們使用WSASend函數的參數,然後將數據發送出去,這樣就完成了整個數據的發送過程。而且這整個過程也都是由動作驅動的,有數據發送,則主動投遞發送請求。 第二:數據的接收過程 首先說明一點,數據的接收的過程是由程序自身驅動的,我們必須自己先調用WSARecv函數,通知完成端口一旦在該套接字上有數據到達即調用爲完成端口服務的線程中分發函數進行處理到來的數據。這一整個過程可以描述如下。 1:當有客戶連接到來的時候,即調用 PostRecv(pContext); OVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IORead); ULONG                 ulFlags = MSG_PARTIAL;DWORD                 dwNumberOfBytesRecvd; UINT nRetVal = WSARecv(pContext->m_Socket,                      &pContext->m_wsaInBuffer,                       1,                       &dwNumberOfBytesRecvd,                       &ulFlags,                       &pOverlap->m_ol,                       NULL); 在這個函數中,調用WSARecv函數,並不是要接收數據,而是使得當在pContext->m_Socket這個Socket上有數據到來的時候,可以像完成端口投遞一個IORead類型的讀數據請求,當然這個IORead數據的讀請求理所當然的由OnClientReadling這個函數來完成。 2:在OnclientReadling這個函數裏完成,對數據的提取以及解壓縮,在這裏我們要注意一點,在函數WSARecv中要求數據到來的時候,填充到pContext->m_wsaInBuffer,這個緩衝區中,而這個緩衝區實際上是 pContext->m_wsaInBuffer.buf = (char*)pContext->m_byInBuffer; pContext->m_wsaInBuffer.len = sizeof(pContext->m_byInBuffer); 這個緩衝區pContext->m_byInBuffer中會承載接收到得數據。 然後對這個緩衝區中的數據進行一個解析,將數據先拷貝到m_CompressionBuffer這個緩衝區中,然後由這個緩衝區解壓縮到m_DeCompressionBuffer這個緩衝區中,這樣玩徹骨了一次數據的讀取過程,接下來再次調用PostRecv這個函數,保證這個接收數據的操作始終是處於蓄勢待發的狀態,有數據到來,立馬處理之。 // Output elements for Winsock WSABUF   m_wsaOutBuffer; 這個成員變量就是用來給WSASend作爲函數參數來使用的,它的使用方式我們在上面也已經說過,在這裏就不再贅述。 HANDLE   m_hWriteComplete; 這個變量在這裏,我先臨時定爲無意義的一個變量,因爲我確實沒看到這個變量有被初始化。// Message counts... purely for example purposes LONG     m_nMsgIn; LONG     m_nMsgOut; 以上兩個變量記錄發送出去,或者接收到得數據包的個數。

BOOL     m_bIsMainSocket; // 是不是主socket

這兩個變量並沒有被啓用。 ClientContext*            m_pWriteContext; ClientContext*            m_pReadContext;   接下來,讓我們回到CIOCPServer::AllocateContext這個函數,繼續往下看這個函數裏的實現:if (pContext != NULL) { ZeroMemory(pContext, sizeof(ClientContext)); pContext->m_bIsMainSocket = false; memset(pContext->m_Dialog, 0, sizeof(pContext->m_Dialog)); } 對申請到得緩衝區進行一個清零,並且初始化幾個成員變量的值。   繼續回溯,回到CIOCPServer::OnAccept()這個函數,有剩餘的代碼需要分析pContext->m_Socket = clientSocket; pContext->m_wsaInBuffer.buf = (char*)pContext->m_byInBuffer;pContext->m_wsaInBuffer.len = sizeof(pContext->m_byInBuffer); 以上就是對新申請到得這個緩衝區的必要成員變量進行一個賦值操作,各個成員變量的含義我們在前面已經闡述過,這裏不再贅述。 if (!AssociateSocketWithCompletionPort(clientSocket,                                        m_hCompletionPort,                                      (DWORD) pContext)) {    delete pContext;pContext = NULL; closesocket( clientSocket );    closesocket( m_socListen );    return; } 接下來,我們重點看看CIOCPServer::AssociateSocketWithCompletionPort這個函數的實現過程,這個函數的作用就是將主控端與被控端進行交互的套接字與完成端口關聯起來,如此當在這些套接字上發生網絡事件的時候,爲完成端口工作的工作線程可以及時處理這些事件。  關於CreateIoCompletionPort這個函數的具體使用方法,在前面的我們有提到,在這裏我們再回顧一下:

The CreateIoCompletionPort function can associate an instance of an opened file with a newly created or an existing input/output (I/O) completion port; or it can create an I/O completion port without associating it with a file。

 

也就是說這個函數既可以將某個打開的“文件”句柄與新創建的或者已經存在的輸入輸出端口相關聯起來,也可以僅僅創建一個完成端口而不與某個“文件”相關聯。上圖所示即是將主控端與被控端端進行通信的套接字句柄與先前創建的那個完成端口相關聯。

 

我們繼續看CIOCPServer::OnAccept()這個函數未解讀的部分

設置了該套接字的一個保活特性,關於這部分的內容我們在Gh0st通信協議分析(1)裏有也有講過,在這裏我們再回顧一下這種使用方法:

設置了SIO_KEEPALIVE_VALS後,激活包由TCP STACK來負責。當網絡連接斷開後,TCP STACK並不主動告訴上層的應用程序,但是當下一次RECV或者SEND操作進行後,馬上就會返回錯誤告訴上層這個連接已經斷開了如果檢測到斷開的時候,在這個連接上有正在PENDING的IO操作,則馬上會失敗返回。

上面這句代碼的含義是:每隔m_nKeepLiveTime的時間,開始向受控端發送激活包,重複發送五次,每次發送的時間間隔是10秒鐘,如果在十秒鐘之內都沒能得到回覆,則判定受控端已經掉線。對掉線後的處理,在這裏我必須要說說:由於TCP STACK並不主動告訴上層的應用程序,只有當下一次發送數據,或者接收數據的時候纔會被發現。

 

繼續分析CIOCPServer::OnAccept()這個函數未解讀的部分

保存這個會話數據結構到m_listContexts這個變量中,接下來向完成端口投遞一個名稱爲IOInitialize的請求,請求完成端口能處理這個請求,而完成端口對這個請求的處理,我們根據前面的分析,應該由CIOCPServer::OnClientInitializing:這個函數來處理,我們看看這個函數的實現方式:  並沒有做什麼特別的處理。   接着調用了與界面進行交互的那個通知函數,我們這次再次進入這個函數裏追蹤一下上線這個過程。通過追蹤NC_CLIENT_CONNECT這個變量我們發現,程序中並沒有對這個通知做任何特殊的處理。 接下來是調用了PostRecv(pContext)這個函數,讓我們看看這個函數的實現過程  這個函數就是向完成端口投遞一個接收數據的請求,以待後續有數據傳輸過來的時候,會有完成端口的工作線程負責調用相應的函數去處理。   至此,主控端就順利的完成了一次被上線主機的一個接收過程,接下來的過程,是受控端主動主控端發送上線包的這麼一個過程,以及,被控端對各種控制命令的一個響應的過程,關於這部分的內容,我們在Gh0st通信協議解析(3)裏在討論。  

                                                               

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章