1. 程式人生 > >gh0st通訊協議解析(2)

gh0st通訊協議解析(2)

Gh0st通訊協議解析(2)

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

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

首先,我需要宣告一點,我們本款遠控軟體僅僅就是一個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,                                  DWORD dwDesiredAccess                           );

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,                       LONG Value );

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,                                                DWORD dwTimeOUT,                                             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 Sockets WSAEnumNetworkEventsfunction 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