Gh0st通信協議解析(3) .send數據包大概是域名攔截關鍵

看到這裏大家對gh0st的iocp通訊有一定的瞭解吧


當順利的連接到主控端之後,按照程序的一個執行邏輯,被控端會將本機上的一些反映本機狀態的一個信息發送到主控端,這個過程其實涉及到了被控端與主控端間信息的交互過程。
*******************************************************************************
我們需要從sendLoginInfo這個函數講起。先看看這個函數的實現過程
講解這個函數,我們不得不先去認識一個結構體,這個結構體就是傳說中的上線包,很多殺軟現在已經攔截這個上線包,在安裝殺軟的機子上如果有這種上線包數據在往外傳輸,那麼殺軟會認爲這是由遠程協助在控制你的電腦,因此,殺軟會攔截這個上線包的傳輸。因此,如果我們想要我們的遠程協助發揮作用的話,我們必須將上線包進行一個修改,讓殺軟去匹配我們的上線包的時候無法匹配到。這樣,就會躲過殺毒軟件防黑牆的攔截。
此上線包的結構如下圖所示:
該函數的一整套操作就是填充這個數據結構裏的各個值。

首先,聲明該上線包的類型爲上線包
LoginInfo.bToken = TOKEN_LOGIN;

接着獲取操作系統的版本信息
BOOL GetVersionEx(LPOSVERSIONINFO lpVersionInformation);The GetVersionEx function obtains extended information about the version of the operating system that is currently running.調用這個函數可以獲得當前運行的操作系統的擴展版本信息。  獲取主機名 int gethostname (char FAR * name,                  int namelen );The Windows Sockets gethostname function returns the standard host name for the local machine。調用這個函數返回本機的標準主機名。 獲取連接的IP地址 int getsockname (SOCKETs,                                    struct sockaddr FAR* name,                   int FAR* namelen );The Windows Sockets getsockname function retrieves the local name for a socket.這個函數會返回用於連接的指定套接字的一個本地的IP地址。 獲取CPU的主頻 LoginInfo.CPUClockMhz = CPUClockMhz();我們接下來看看獲取CPU主頻的,CPU主頻的獲取是通過查詢註冊表裏的鍵值來取得的。
獲取CPU個數 VOID GetSystemInfo(LPSYSTEM_INFO lpSystemInfo); The GetSystemInfo function returns information about the current system. 調用這個函數會返回當前系統的一些信息。 獲取視頻信息LoginInfo.bIsWebCam = IsWebCam();  獲取視頻信息的過程是通過遍歷十個視頻驅動是否存至少存在一個來判斷。 BOOL VFWAPI capGetDriverDescription(                                 WORD wDriverIndex,                                   LPSTRlpszName,                                     INT cbName,                                        LPSTR lpszVer,                                      INTcbVer                                          ); The capGetDriverDescription function retrieves the version description of the capture driver. Index of the capture driver. The index can range from 0 through 9.調用這個函數返回視頻驅動的版本信息,如果不存在就返回FALSE。  獲取CPU的處理速度,這個參數是通過一個時間段來考量的。LoginInfo.dwSpeed = GetTickCount()_1 - GetTickCount()_2 獲取一個備註信息,備註信息是存儲在受控端的註冊表裏的 獲取一個版本信息,版本信息,版本信息是自己填寫的。 lstrcpy(LoginInfo.Ver,"0.2");對以上的過程做一個總結
組織好以上數據之後,開始向主控端發送上線包,接下來,我們去看一下這個發送數據的過程。CClientSocket::Send  這個函數就是組織待發送的數據包的過程,我們接下來詳細的看看這個組織數據的過程。 首先,待發送的數據是在參數:lpData中,而待發送的數據的大小由參數:nSize指定。   首先,將需要承載發送數據的緩衝區先清空:m_WriteBuffer.ClearBuffer();  然後,將數據進行壓縮處理。 這個地方要注意:在壓縮前,這個值的計算公式爲: unsigned long    destLen = (double)nSize * 1.001  + 12;這個是固定的。因爲,內存中壓縮數據的時候是需要一定大小的工作空間的,因此按照這個公式計算出工作需要的空間大小,然後申請這麼大小的一個內存空間作爲壓縮過的數據的存儲空間。然後使用compress進行壓縮處理:int        nRet = compress(pDest, &destLen, lpData, nSize);注意這個函數執行完成之後,在參數destLen中會返回數據被壓縮過之後的一個大小。   接下來,開始往發送緩衝區中組織欲發送的數據 LONG nBufLen = destLen + HDR_SIZE; m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));m_WriteBuffer.Write((PBYTE) &nBufLen, sizeof(nBufLen)); m_WriteBuffer.Write((PBYTE) &nSize, sizeof(nSize));m_WriteBuffer.Write(pDest, destLen); 數據包發送標記+整個數據包的大小+未壓縮前數據包的大小+被壓縮後的數據   再然後,備份要發送的數據到m_ResendWriteBuffer緩衝區中。   當然,在這個組織發送的數據的函數中還有一個小的功能,即當在接收數據的過程中,如果出現了差錯,則可以提醒主控端重新發送數據。上述描述的代碼體現如下:當受控端接收數據的時候,調用CClientSocket::OnRead這個函數,而在執行這個函數的過程中,一旦有錯誤發生則會執行下面的操作:  當出現Send(NULL,0)這種調用的時候,就會執行 m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));m_ResendWriteBuffer.ClearBuffer(); m_ResendWriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag)); 僅僅向主控端發送一個傳輸標誌的數據包,主控端在收到這種特殊的數據的時候,會有重新發送數據的邏輯處理過程。   關於這個發送的過程,我們在這裏簡單的描述一下: 首先會在一個大的循環中發送nSplitSize*N個數據包,發送完這些數據包之後,要麼還剩下少於nSplitSize大小的數據包,要麼還剩下大小爲0的數據包,然後再在下面的這個循環中完成了數據包的發送過程。 上述過程已完成,即完成了受控端向主控端發送數據的過程。 接下來,我們需要看看主控端是怎麼接收到這些這些數據,並進行處理的呢?   在主控端,我們注意IOCPServer::OnAccpet這個函數,在這個函數的最後進行了一個這樣的調用指令:PostRecv(pContext);在這裏我們再次回顧一下這個函數的實現過程: 如果稍微瞭解一點IOCP高性能服務器的話就會對這個操作覺得十分面熟。比如關聯完成端口的時候使用的句柄唯一數值——CllientContext,比如OVERLAPPEDPLUS這個結構爲什麼要這麼設計:class OVERLAPPEDPLUS {public:OVERLAPPED      m_ol;IOType          m_ioType;OVERLAPPEDPLUS(IOType ioType) {ZeroMemory(this, sizeof(OVERLAPPEDPLUS));m_ioType = ioType;}};OVERLAPPED m_ol這個變量之後的m_ioType就是傳說中的Per-IO數據,根據這個數據,我們可以知道,到底是哪種請求被投遞到了完成端口,並且可以被處理了。 這裏就是向完成端口投遞了一個讀取數據的請求,當有數據到達完成端口的時候,等待在該完成端口上,爲該完成端口服務的線程函數會被喚醒,繼續往下執行。這裏,我們就分析下,這個爲完成端口服務的線程函數,這個工作線程的個數是跟系統CPU核心數有關的。接下來,我們分析下這個工作線程的執行邏輯。
在函數的前面就是一些變量的定義,這些變量的含義我們在前面的課程中都已經講解過,因此,在這裏我們就不再重述。我們重點來看兩句指令的調用:InterlockedIncrement(&pThis->m_nCurrentThreads);InterlockedIncrement(&pThis->m_nBusyThreads);在這裏將變量:m_nCurrentThread與m_nBusyThreads以原子的方式進行加1的操作。這兩個變量的作用是什麼,我們看下面引用這兩個變量的一些地方。1:在CIOCPServer::CIOCPServer構造函數中   m_nCurrentThreads= 0;m_nBusyThreads = 0;2:在CIOCPServer::ThreadPoolFunc開始的部分,即剛進入工作線程函數的時候   InterlockedIncrement(&pThis->m_nCurrentThreads);InterlockedIncrement(&pThis->m_nBusyThreads);3:在CIOCPServer::ThreadPoolFunc結束的部分,即將要離開工作線程函數的時候   InterlockedIncrement(&pThis->m_nCurrentThreads);InterlockedIncrement(&pThis->m_nBusyThreads);因此,這兩個變量就是記錄當前進程中爲完成端口服務的工作線程的數量這麼一個值,只不過m_nCurrentThreads是用來記錄所有爲該完成端口服務的工作線程的數量,而另外的一個m_nBusyThreads則是用來記錄所有爲完成端口工作的線程數量中處於喚醒狀態,而非阻塞狀態的線程數量,這裏有一個對m_nBusyThreads的引用,可以看出它的作用。在進入GetQueuedCompletionStatus這個函數進行等待完成端口上發生請求之前,先對這個值進行了遞減的操作:InterlockedDecrement(&pThis->m_nBusyThreads);而當某個線程等待到請求發生時,即GetQueuedCompletionStatus這個函數返回的時候,會對這個變量的值進行了一個遞增的操作:InterlockedIncrement(&pThis->m_nBusyThreads)。 接下來程序進入一個無限的循環,在這個循環中就是等待並處理到來的投遞請求。
這個循環的主題就是GetQueuedCompletionStatus這個函數,這個函數就是等待在完成端口上,等待完成端口上到來的請求。該函數使用的兩個參數對理解本段代碼特別重要,一個是lpClientContext,另一個是lpOverlapped。下面我們對這兩個參數的重要性進行論述。第一:lpClientContext這個參數是在CIOCPServer::AssociateSocketWithCompletionPort這個函數裏被設定的:HANDLE h = CreateIoCompletionPort((HANDLE) socket,                                   hCompletionPort,                                   dwCompletionKey,                                   0);這其中的第三個參數,就是lpClientContext。當在這個完成端口上有了事件發生的時候,用GetQueuedCompletionStatus的第三個參數返回的數值與CreateIoCompletionPort的第三個參數的數值是相對應的,或者說他們就是指的同一個ClientContext。第二:lpOverlapped這個參數是的設定位置有三個,分別是被控端剛上線,並且將主控端與被控端交互的socket已經與完成端口相關聯之後,會發送一個IOInitialize作爲附加數據的OVERLAPPED。OVERLAPPEDPLUS   *pOverlap = new OVERLAPPEDPLUS(IOInitialize);BOOL bSuccess = PostQueuedCompletionStatus(m_hCompletionPort, 0,                                            (DWORD) pContext,                                            &pOverlap->m_ol); 當需要接受數據的時候,先要投遞一個接收數據的請求,這個時候會投遞一個IORead作爲附加數據的OVERLAPPEDOVERLAPPEDPLUS * 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);除了瞭解在這裏投遞了一個IORead類型的請求之外,在這裏還要說一點,這個WSARecv指明當有數據到來的時候,要將數據接收到ClientContext::m_wsaInBuffer這個緩衝區中。 當需要發送數據的時候,先要投遞一個發送數據的請求,這個時候會投遞一個IOWrite作爲附加數據的OVERLAPPEDOVERLAPPEDPLUS * pOverlap = new OVERLAPPEDPLUS(IOWrite)ostQueuedCompletionStatus(m_hCompletionPort,                            0,  (DWORD) pContext, &pOverlap->m_ol);附加數據的用處在哪裏呢?看看這個循環裏對附加數據的處理:pOverlapPlus = CONTAINING_RECORD(lpOverlapped, OVERLAPPEDPLUS, m_ol);這個CONTAINING_RECORD就是將指針的範圍進行一個擴大,指向整個的定義結構,這樣就能指向帶有附加數據的整個OVERLAPPEDPLUS類型,並且根據附加的數據的m_ioType域的不同,進行不同的處理:ProcessIOMessage(pOverlapPlus->m_ioType, lpClientContext, dwIoSize);接下來是對GetQueuedCompletionStatus這個函數的返回值的一個判斷,主要分爲以下的幾種情況。第一種情況:對方關閉掉了這個通信的套接字,也就是說被控端掉線了,這個時候的處理方案就是直接將被控端的數據結構摘除,並且從列表框中刪除之。第二種情況:這種情況是本程序中的設計,並非是單純的對GetQueuedCompletionStatus這個函數的返回值進行一個處理。這種情況就是根據當前CPU的一個負載能力進行工作線程的一個調整:如果當前的工作線程都處於忙碌狀態,並且當前CPU還有能力運行新的線程,那好就開啓一個新的線程爲完成端口服務。如果,當前CPU的負載能力受不了了,則適當的Kill掉自身線程函數。我以爲在這個調整自身的地方應該還有一個處理主動結束自身線程的邏輯分支,因爲當要關閉掉套接字的時候,有以下這麼些操作while (m_nWorkerCnt){         PostQueuedCompletionStatus(m_hCompletionPort, 0, (DWORD) NULL, NULL);         Sleep(100);}其實這個循環就是要求爲完成端口工作的線程主動結束自身的過程。
3:就是傳輸正確的情況下,對不同的請求進行不同的處理,這個差異化的處理函數就是ProcessIOMessage這個過程的實現我們在Gh0st通信協議分析(2)裏面有過專門的說明。在這裏我們再來回顧一下: 先前發送的投遞請求最終是由CIOCPServer:rocessIOMessage這個函數來完成的,  關於這個函數的定義,不得不去看一組宏定義: 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; } 因此,本次由被控端投遞過來的上線包的接收就應該由OnClientReadling來處理咯,我們接下來需要看看這個函數的實現過程。 在開始處,給該段代碼增加了一段臨界區,以使得該段代碼在多線程環境中可以以獨佔的方式被訪問。這段代碼應該必須被獨佔的方式訪問,因爲此函數的調用者是爲完成端口工作的線程去調用,而我們知道爲完成端口工作的線程不止一個。 接下來是爲反映傳輸速度而進行的一些編碼,在這個地方唯一需要我們注意的是靜態變量的一個用法。static DWORD nLastTick = GetTickCount();static DWORD nBytes = 0;關於靜態局部變量的說明:1:靜態局部變量在該函數被多次調用的時候只在第一次調用的時候執行定義語句。2:靜態局部變量的生存週期不會因爲該函數被執行完而結束,它在全局內存存儲。因此,上述反應傳輸速度的代碼應該這麼理解,當第一次調用OnClientReadling函數的時候,static DWORD nLastTick = GetTickCount();static DWORD nBytes = 0;這兩句會被執行,接下來通過判斷採樣時間是否超過一秒鐘,第一次調用的時候肯定不會超過一秒鐘,但是當第二次調用該函數的時候,那兩句定義靜態變量的語句不會被執行,這個時候nBytes的值會是上次傳輸的大小+本次傳輸的大小,並且nLastTick的值還是上次採樣的時間,由於本次採樣的時間也許會超過了一秒鐘,因此,反映接收速度的參數m_nRecvKbps會被重新改寫值,並且這個時候,nBytes與nLastTick的值會被重新改寫。 如果本次接收的數據大小爲0的話,說明在數據傳輸的過程中遇到了不可預知的錯誤,這個時候就將這個客戶端刪除掉去它的連接,讓它在重新連接上來,在看到這裏的時候我突然想到了,Gh0st是如何判斷受控端已經下線的問題,在這裏我們再回顧一下,因爲主控端與被控端進行交互通信的套接字都被設置了保活機制,沒過一定的時間就進行探測對方是否還存活,連續探測幾次,如果對方都無應答(這個應答是在TCP STACK裏完成的,與應用層無關),則說明對方已經下線。這個時候,如果是被控端掉線,則主控端與被控端進行連接的SOCKET並不通知主控端被控端已經下線,當下一次主控端向被控端發送數據的時候,會發生下面這種情況:int nRetVal = WSASend(pContext->m_Socket,                       &pContext->m_wsaOutBuffer,                      1,                      &pContext->m_wsaOutBuffer.len,                       ulFlags,                      &pOverlap->m_ol,                       NULL);if ( nRetVal == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING ){         RemoveStaleClient( pContext, FALSE );}這個時候,就說明被控端已經下線了,需要清除主控端與被控端的連接數據。 我們繼續看OnClientReading這個函數的其他部分:
這個if語句發生的條件是如果被控端在接收主控端發送過去的數據的時候出現了錯誤,它會要求主控端重新發送數據,而這種要求重發數據的操作正是通過被控端向主控端僅僅發送傳輸標誌。如果收到的是這樣的數據包,則主控端會將備份區的數據重新發送到被控端。主控端的數據發送過程,跟被控端的數據發送過程相差無幾,在這裏我們僅僅貼上代碼並簡要的總結幾句。
首先,如果是錯誤的調用直接返回如果主控端在接收數據的時候出現了異常,這個時候主控端需要讓被控端重新發送數據,則僅僅是向被控端準備發送標誌數據包,如此,被控端會重新向客戶端發送備份的數據。接下來是壓縮待發送的數據然後按照 標誌+整個包大小+未壓縮數據大小+壓縮的數據 這種方式組織包,並備份數據WaitForSingleObject(pContext->m_hWriteComplete, INFINITE);無用的然後用PostQueuedCompletionStatus投遞一個類型爲IOWrite類型的發送請求,注意此函數的第二個參數爲0。這個請求會由函數OnClientWriting去完成。我們看看它的實現代碼
首先,跟接收數據一樣,先更新一下代表發送速度的變量值:m_nSendKbps,這個更新的過程,跟接收的時候的一樣,在這裏只要注意一點局部靜態變量的使用方法就好了,不再贅述。接着,由於dwIOSize的值爲0,因此接下來的那個判斷語句if代碼塊永遠不會被執行。最後,在設置好WSASend函數的各個參數值之後,將數據直接就發送出去了,這個時候的發送情況基本上是一定能成功的,並且一般不會阻塞。除非被控端掉線。我們合併OnClientReading與OnClientWriting中都調用的一個函數:m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_TRANSMIT);m_pNotifyProc((LPVOID) m_pFrame, pContext, NC_RECEIVE);這個函數實際上是由CMainFrame::NotifyProc這個函數來具體執行的,我們這裏看看這兩個函數的具體的執行情況。case NC_TRANSMIT:         break;case NC_RECEIVE:         ProcessReceive(pContext);只有對NC_RECEIVE這個做了處理,我們跟進ProcessReceive去看看這個函數都做了哪些處理:
我們發現,它只對需要彈出對話框的處理過程才起作用,因此在這裏我們就不提前講解了,等後續課程中,我們再講解。 繼續回到OnClientReading中剩餘的部分。
上述過程就是解析接收到的數據包的過程,分析一下:讀出包中的標誌與大小,驗證接收的包是否合法。取出包中的標誌、整個包的大小、未壓縮數據的大小。讀取出壓縮數據,並進行解壓處理。將解壓後的數據存儲到解壓緩衝區中。如果上述過程有任意差池則調用Send(pContext, NULL, 0);要求受控端從新發送數據。在這裏我們假設,當主控端在接收數據的時候發生了錯誤,則會僅僅向被控端發送一個含有傳輸標誌的數據包,我們跟蹤一下這個數據包的傳輸過程。這裏,我們不得不回到被控端去分析被控端的這個通信DLL。上次,我們已經分析到向主控端發送了上線包的過程,在連接到了主控端之後,新開啓了一個工作線程在函數CClientSocket::Connect中m_hWorkerThread = (HANDLE)MyCreateThread(NULL,                                         0,                      (LPTHREAD_START_ROUTINE)WorkThread,                      (LPVOID)this, 0, NULL,true);接下來去看看這個工作線程的實現過程:
本工作線程中使用了Windows套接字模型中的選擇模型,對選擇模型進行一個簡單的闡述。Select模型是指:將需要探測的套接字句柄組織到一個套接字集合中,然後在一個循環中調用Select函數,如此當這些需要探測的套接字集合中的任意一個發生網絡事件,則該函數會返回發生網絡事件的套接字數量,並且將集合中沒有發生網絡事件的套接字句柄剔除掉,如果在規定的事件內沒有發生網絡事件,則直接就返回。在本工作線程中的應用是:將用於與主控端進行通信的套接字句柄放到fdSocket中,每次調用Select函數來探測此套接字上是否有數據到來,而等待的時間則設置爲無限長的時間,也就是說,如果這個函數返回並且沒有出現錯誤的話,則一定是在這個套接字上有數據到來,因此在本次分析中,要求重新發送數據的數據包到達被控端之後,數據會被接收到buffer中,然後調用OnRead函數來具體的解析這個包中的內容。我們取看看OnRead這個函數的實現。這裏的CClientSocket::OnRead函數與主控端的CIOCPServer::OnClientReading比較像。
其實在本次分析中指分析這一小塊就夠了,因爲本次發送過來的是要求被控端重新發送上線包的數據包,因爲在這裏dwIOSize == FLAG_SIZE 並且包中只有傳輸標誌,因此在這裏只是將備份的數據重新發送了一份就ok了,但是爲了我們描述的完整性,我們需要分析下這段代碼的功能:將接收到得數據轉儲到壓縮緩衝區中,取出傳輸標誌進行校驗。
逐一從緩衝區中取出數據,分別爲:傳輸標誌、包的總大小、爲壓縮前數據的大小、壓縮數據。然後對壓縮數據進行解壓縮處理。
然後將解壓縮的數據往另外一個鑑別指令類型的函數中派發,這個函數我們在這裏就不細看了,等後續課程中會詳細分析每一個指令的用途。當然,如果在上述過程中有任何的差錯的話,也會要求主控端重新發送控制命令。void CKernelManager::OnReceive(LPBYTE lpBuffer, UINT nSize){         switch (lpBuffer[0])         {         case COMMAND_ACTIVED:              //激活命令                 break;         case COMMAND_LIST_DRIVE:           // 文件管理                 break;         case COMMAND_SCREEN_SPY:           // 屏幕查看                  break;         case COMMAND_WEBCAM:               // 攝像頭                  break;         case COMMAND_AUDIO:                // 攝像頭                 break;         case COMMAND_SHELL:                // 遠程sehll                 break;         case COMMAND_KEYBOARD:             //鍵盤記錄                 break;         case COMMAND_SYSTEM:               //系統管理                 break;         case COMMAND_DOWN_EXEC:            // 下載者                 break;         case COMMAND_OPEN_URL_SHOW:        // 顯示打開網頁                 break;         case COMMAND_OPEN_URL_HIDE:        // 隱藏打開網頁                 break;         case COMMAND_REMOVE:               // 卸載,                 break;         case COMMAND_CLEAN_EVENT:                   // 清除日誌                  break;         case COMMAND_SESSION:              //信息交互                 break;         case COMMAND_RENAME_REMARK:        // 改備註                 break;         case COMMAND_UPDATE_SERVER:        // 更新服務端                 break;         case COMMAND_OPEN_PROXY:           // 開啓代理                 break;         case COMMAND_OPEN_3389:            // 開啓3389                 break;         case COMMAND_NET_USER:             // 無NET加用戶                 break;         case COMMAND_HIT_HARD:             // 硬盤鎖                 break;         case COMMAND_REPLAY_HEARTBEAT:     // 回覆心跳包                 break;         }        }上述整個過程就完成了被控端向主控端發起連接、並且發送上線包、主控端接收上線包、並假設接收出錯並要求被控端重新發送上線包,這整個過程,我們稍後會討論主控端正確的接收到數據之後,是對數據如何進行處理的,就是將上線包中的各種信息給咱排列到List控件中。在這裏我們先把被控端連接到主控端的這整個過程剩餘的部分進行一下講解,然後我們再去主控端看數據的組織過程。
實例化了一個CKernelManager實例。
這個實例化的過程,我們在前面也講述過,這裏只提幾個變量的作用:m_strKillEvent:主控端要求結束自身的時候,會創建一個名爲m_strKillEvent的事件對象,如此連接到主控端的大循環會推出。m_strMasterHost/m_nMasterPort記錄主控端的IP地址與Portm_nThread:記錄由自己創建的所有的線程。m_bIsActive:記錄是否已經激活,對這個變量我們會在後續詳細分析。在這裏需要注意,CKernelManager繼承自CManager這個類,因此在CKernelManager這個類被初始化的同時,CManager這個類也同時被初始化了,在這裏我們看看這個初始化的過程。
這裏就是將這兩個變量記錄在成員變量中。CManager::m_pClient  記錄 CClientSocket類的實例化對象。CClientSocket::m_pManager記錄CManager實例化的對象,接下來還要修改之。爲什麼要在CManager::m_pClient中記錄CClientSocket。這是因爲,在後續文件管理、進程管理、桌面管理、視頻管理、遠程CMD等功能執行的時候,都會執行類似於文件管理的操作:
創建一個只用於文件傳輸的socket,注意當這個socket連接到主控端的時候,因爲它不是上線包,因此記錄被控端數據結構的ClientContext裏面的m_bIsMainSocket就是爲false。而不像正常的被控端登陸的時候,首先會發送一個上線包,以此才認爲這個socket是主代表被控端的主socket。創建了這個用於傳輸文件的socket之後,還要創建一個用於管理本次文件傳輸會話的調配中心。調配中心的一個核心任務就是重載了虛函數OnReceive,這樣當主控端使用這個連接的套接字進行發送命令的時候,被控端用於文件傳輸的這個socket在接收到文件傳輸類的相關命令的時候會將命令發送到文件管理的調配中心。這個過程的實現就是靠虛函數來實現的:在ClientSocket::OnRead中,有如下調用m_pManager->OnReceive(m_DeCompressionBuffer.                     GetBuffer(0), m_DeCompressionBuffer.GetBufferLen());這個m_pManager在這裏就是指代文件管理的調配中心,因此在CClientSocket::m_pManager中需要記錄CManager實例化的對象。當文件管理模塊需要將信息傳輸給主控端的時候,此調用:Send(lpPacket, nPacketSize);這個send函數實際上是CManager的,而在CManager的成員函數中,Send的實現又是靠CClientSocket的send函數:int CManager::Send(LPBYTE lpData, UINT nSize){         int      nRet = 0;         try         {                 nRet = m_pClient->Send((LPBYTE)lpData, nSize);         }catch(...){};         return nRet;}因此,需要在CManager中保存CClientSocket實例化的對象,保存該對象的變量的任務就落在了m_pClient的身上。 關於具體的功能分析,我們會在後續文章中詳細的指出,在這裏我們僅僅是提一點他們通信的一個運行機理,免得我們後面看到這部分內容的時候會喫力。
然後創建了一個初始狀態爲未授信、人工重置的事件對象,關於這個事件對象的使用方法,我們在後面使用的時候在講解。 接下來在連接到主控端的這個大循環中,繼續調用了socketClient.setManagerCallBack(&manager);這是重新將m_pManager設置爲manager,指向沒變,只是改變了虛函數表中的虛函數。關於這部分內容,不熟悉的C++不過關啊。以基類之指針指向派生類之對象的特性需要自行回顧。 我們需要回過頭來看看,主控端收到上線包之後的操作流程,假設收到的包是正確的。CMainFrame::NotifyProc這個函數會被執行,並且因爲我們完了所有的數據,因此會執行Case NC_RECEIVE_COMPLETE:         ProcessReceiveComplete(pContext);而在CMainFrame:rocessReceiveComplete這個函數中,因爲我們接受的是上線包,因此會執行 看代碼,我們知道如果是上線包上線的話,在這個邏輯段裏只執行了三個方面的內容:第一:將到來的客戶端添加到ListView控件第二:將該ClientContext的m_bIsMainSocket設置爲TRUE第三:向被控端發送激活指令下面我們來一條一條的看這幾個功能首先,我們看看向ListView添加一條記錄的方式,本例中是通過發送一條自定義的消息——WM_ADDTOLIST,然後由CGh0stView::OnAddToList函數去響應這條消息。
   以上所有的操作都很簡單,就是逐一解析數據包的內容,然後將各個項插入到ListView控件中。 
解決重複上線的問題,如果前三個項目相同,則視爲重複上線,刪除之,並且更新當前連接的數量:
繼續往下看:
準備ToolTips的文本。然後根據是否有無純真數據庫,從數據庫中提取地理位置,然後將地理位置增添到ListView的一列中再接下來指定唯一標示,這個唯一標示就是用ListView的一條記錄來保存與該條相關的唯一數值,這個值非常有用。然後根據配置信息m_bIsDisablePopTips判斷是否應該彈出Tips。關於托盤圖標的編程,我們會在後續的Gh0st界面編程中詳細講解。 然後我們看看將m_bIsMainSocket設置爲TRUE的作用,因爲對任意一個被控端它連接上主控端之後,它與主控端之間並非就一個連接,因爲每一種大的功能還需要開啓一對新的連接進行數據交互,這樣連接到主控端的每一個被控端都有好幾個ClientContext,而這多個代表每一個連接的數據結構中只有一個的socket是主socket,其它的都是因爲某種功能的需要而臨時開啓的。而ClientContext種類的劃分就是根據這次上線是否發送上線包來判斷本次的ClientContext.Socket是否爲主socket。 最後,我們來看看主控端向被控端發送激活指令的過程,當然發送的這個過程是相當的複雜的,但是我們已經將這整個的發送過程分析的清晰易懂,大體總結下就是,在Send函數中進行數據包的組織,然後向完成端口提交一個發送數據的請求,然後完成端口調用發送數據的函數將這部分數據發送出去,而這個時候的被控端的行爲呢,在ClientSocket的工作線程中,因爲有數據的到來,所以Select函數返回,這個時候讀取數據,然後對數據進行解析,再然後調用CKernelManager::OnReceive對不同的命令包進行不同的處理,這個時候會執行:switch (lpBuffer[0]){case COMMAND_ACTIVED:         InterlockedExchange((LONG *)&m_bIsActived, true);         break;將m_bIsActive設置爲TRUE。 然後我們繼續看連接到主控端的這個大循環:
轉載地址 http://www.xiaoqi7.com
因爲,m_bIsActive被設置爲了TRUE。因此,for循環會退出,但是這個時候不能讓我們的程序退出啊,必須用一種方法down住這個連接函數的繼續執行,因爲主線程都退出了,還通信個蛋。所以,後面的這個do——while循環就是爲此目的而存在的。這樣主線程沒有退出,而工作線程不斷的接收主控端傳輸過來的命令並處理,就完美的完成了遠程協助的執行功能。
原文地址 ccnyou博客 http://blog.csdn.net/ccnyou/article/details/7645950
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章