Gh0st原始碼詳細剖析
宣告:
首先宣告,這篇文章完全是根據自己對gh0st原始碼的理解寫出來的,如果有理解上的偏差或者表述上的錯誤本人不負責,僅供參考!
前言:
在我剛學習gh0st原始碼的時候,覺得這東西好難,三個工程放在一起不說,還有那麼多檔案,當時就懵了。這怎麼看呢?從何下手呢?我想大部分剛學習gh0st原始碼的同胞們都有和我一樣的想法,我廢話不多說了,下面就為大家詳細講解gh0st原始碼。
思路:
首先我會講解gh0st的整體框架,包括三個工程之間的關係和每個工程的作用;然後再根據程式的執行順序,從入口函式WinMain開始逐步向下講解。
框架:
gh0st有三個工程,分別是gh0st、install和svchost。那麼這三個工程有什麼作用呢?首先gh0st這個工程,是生成控制端的一個工程,換句話說就是用MFC寫的介面。但是你千萬不要小看它。因為它絕不僅僅是一個介面那麼簡單,在這個控制端裡面還包括IOCP完成埠、網路socket和控制端與服務端之間的資料傳輸;install這個工程較其他兩個工程就簡單多了,因為它只有一個.h檔案和一個.cpp檔案。這個工程是整個程式的入口,因為WinMain函式就在這裡,它的主要功能就是生成一個exe檔案,而這個exe檔案的工程就是安裝和啟動dll裡面的服務的;接下來就是第三個工程了,前面所說的dll就是這個工程。它的主要功能就是執行服務函式,並完成與控制端的通訊。
所以用一句話來概括gh0st的功能就是:exe安裝並啟動dll裡面的服務,dll執行服務並與控制端進行通訊,從而實現各項遠端功能。
程式碼解讀:
首先看install這個工程:(紅色部分為原始碼)
int APIENTRY WinMain(HINSTANCE hInstance,
{
這個WinMain函式就是程式的入口,四個引數分別是hInstance應用程式當前的例項控制代碼、hPrevInstance應用程式先前的例項控制代碼、lpCmdLine指定傳給應用程式的命令列引數、nCmdShow指定視窗如何顯示;首先是GetInputState()這個API函式,它的功能是要讓小漏斗馬上消失,這是因為以前的cpu執行速度很慢,所以當你開啟一個程式的時候會有一個小漏斗在螢幕上顯示,直到程式開啟漏斗才消失;然後是PostThreadMessage(GetCurrentThreadId(),NULL,0,0)這個函式,大家首來先看GetCurrentThreadId這個函式,這個函式的功能是獲得當前執行緒的PID,也就是獲得當前正在執行執行緒的唯一標示符;而PostThreadMessage這個函式呢就是把當前執行緒的唯一標示符送入系統的訊息佇列中。說了半天的當前執行緒,那麼執行緒究竟是什麼呢?所謂執行緒,說白了就是計算機中正在執行的程式,那麼在gh0st裡面這個正在執行的程式是什麼呢?其實這個正在執行的程式就是在控制端生成的服務端,也就是人們所說的木馬。但是它一定是加上上線字串的,否則它是不會執行的,這個我們後面會講到。接下來定義了幾個指標變數,並賦值為NULL,下面我們會依次講解。
lpEncodeString = (char *)FindConfigString(hInstance, "AAAAAA");
下面就讓我們來看一下lpEncodeString這個變數是什麼意思,在看它之前呢要先看一下FindConfigString這個函式的作用。
LPCTSTR FindConfigString(HMODULE hModule, LPCTSTR lpString)
{
首先看這個函式的兩個引數,hModule當前程式的例項控制代碼,lpString就是"AAAAAA"這個字串。後面定義了一個大小為260的字元陣列和一個為空的字串指標還有一個雙字型的變數;然後呼叫了GetModuleFileName這個函式,這個函式就是獲取一個已裝載模板的完整路徑名稱並複製到它的第二個引數裡面,也就是把服務端的完整路徑儲存在陣列變數strFileName裡面;然後呼叫了CreateFile這個API函式用來開啟剛剛儲存路徑的檔案,返回檔案控制代碼。後面進行一個判斷,如果檔案不存在,那麼返回空並跳出。
接下來來看memfind這個函式,先看它的四個引數:lpConfigString剛剛申請的棧,裡面放有檔案末尾的10234個字元資訊、lpString六個A的字串、MAX_CONFIG_LEN1024、0。
int memfind(const char *mem, const char *str, int sizem, int sizes)
{
int da,i,j;
if (sizes == 0)
da = strlen(str);
else
da = sizes;
for (i = 0; i < sizem; i++)
{
for (j = 0; j < da; j ++)
if (mem[i+j] != str[j]) break;
if (j == da) return i;
}
return -1;
}
首先定義了三個整形變數;進行了一個判斷,sizes為零那麼da=6。之後是一個for迴圈的巢狀,外面的for迴圈次數為1024次,也就是那個棧中所有字元遍歷一遍。裡面的for迴圈次數為6次,接下來進行一個判斷,如果棧裡面的字元沒有連續六個A出現,那麼就跳出內部迴圈i++,直到找到六個A為止,返回找到六個A的為止。如果最後沒有找到那麼就返回-1
裡接下來進行了一次判斷,如果剛剛的memfind函式返回的是-1,那麼刪除棧空間,然後返回空。
}
如果返回值不是空,那麼返回找到六個A的為止。結束FindConfigString函式。相信現在你一定知道lpEncodeString這個變數是什麼意思了,沒錯!它就是在原始檔案中六個A的出現位置。
接下來回到WinMain函式
lpServiceConfig = (char *)FindConfigString(hInstance, "CCCCCC");
我想這段程式碼應該不用解釋了吧,和上面的原理完全相同!要是連這段程式碼都看不懂,那我只能說:親,別往下看了,回去學C語言吧!
char
接下來看一下這個函式strchr,它的功能是在檔案中找到第一次出現'|'的為止,並將這個位置賦值給新定義的字元指標*pos,接下來進行一個判斷,如果不存在這樣的位置,那麼返回-1,若存在,則在'|'後面加上'\0'進行字串截斷。
接下來呼叫了兩次MyDecode函式,這是作者自寫的一個加密函式,加密演算法可以查到,這個加密函式我就不講了,總之這個函式的目的就是將這兩個字串加密。其實現在你應該能看出來:'CCCCCC'後面的字串就是服務名稱,而'AAAAAA'後面的字串就是服務描述。接下來進行了一次判斷,如果服務名稱或者服務描述任何一個為空,程式都不會再繼續執行,這就是我之前說的那個服務端一定要加上上線字串的,否則它是不會執行的!
char
接下來定義了兩個字元型指標變數,第一個是服務名稱,下面會用到;第二個是更新服務端的一個標誌字串。接下來進行一個判斷,首先看一下GetCommandLine這個API函式,它的功能是獲取當前執行緒的命令列引數,那麼if (strstr(GetCommandLine(), lpUpdateArgs) == NULL)這句程式碼的意思就很明顯了,就是判斷在當先執行緒的命令列中"MyCmd Update"這個字串的出現位置,如果沒有這個字串,那麼就要建立一個指向lpEncodeString的互斥體,建立它的作用就是防止程式執行兩次或者兩次以上;接下來如果建立失敗的話就返回失敗原因,後面進行一個判斷,如果互斥體已經存在或者拒絕訪問則返回-1並跳出。接下來釋放互斥體,關閉互斥體。
else
如果是更新服務端,則等待服務端自刪除。
SetUnhandledExceptionFil
這是一個設定異常捕獲函式。當異常沒有處理的時候,系統就會呼叫SetUnhandledExceptionFil
SetAccessRights();
這是一個設定獲取系統許可權的函式,接下來進入到這個函式內部看看。
void SetAccessRights()
{
lpSysDirectory[MAX_PATH];
上面都是變數的定義,後面用到的時候再說。
接下來呼叫了幾個ZeroMemory函式,作用是將那三個記憶體塊清零。然後呼叫了GetSystemDirectory獲取系統目錄的完整路徑並儲存到lpSysDirectory這個陣列中。後面呼叫了GetUserName函式獲取當前使用者名稱,並儲存在lpUserName這個變數中。
提升使用者許可權;將使用者名稱轉換為寬字元並儲存在wUserName變數中。
接下來是一個判斷,如果上面的檢索組操作成功,那麼定義一個LPLOCALGROUP_USERS_INFO_0結構型別的指標變數pTmpBuf和雙字型的變數i;接下來又是一個判斷,先將使用者所在的組名賦值給pTmpBuf並判斷是否為空,若不為空則進入到下面的for迴圈。
如果pTmpBuf指向的字元為空則跳出,接下來呼叫WideCharToMultiByte函式將寬字元的組名轉換為多字符集。最後呼叫AddAccessRights設定改組的許可權為最高
}
最後,釋放緩衝區pBuf。
接下來請看下面的程式碼,個人認為這是最為關鍵的地方之一。
lpServiceName = InstallService(lpServiceDisplayName, lpServiceDescription, lpEncodeString);
。這句程式碼的功能就是安裝服務,它的三個引數分別為加密過的服務名稱、加密過的服務描述和未加密的六個A所在的位置。
下面看這個函式是如何實現的:
char *InstallService(LPCTSTR lpServiceDisplayName, LPCTSTR lpServiceDescription, LPCTSTR lpConfigString)
{
上面的都是定義的變數,用到的時候再說。
接下來定義了兩個變數,其中第二個變數賦值為登錄檔中Svchost的目錄;接下來呼叫了RegOpenKeyEx函式來開啟登錄檔目錄HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost。後面進行一個判斷,如果開啟失敗則跳出。
接下來又定義了兩個變數,呼叫了RegQueryValueEx函式來獲取hkRoot路徑下的netsvcs服務組的設定值,並將它儲存到緩衝區strSubKey中。接下來關閉剛剛開啟的登錄檔路徑,設定錯誤返回值rc並判斷,如果沒有取得netsvcs的設定值,那麼跳出。
接下來呼叫了OpenSCManager函式來開啟服務控制管理器並將控制代碼賦值給hscm ,判斷,如果沒有開啟則跳出。
接下來呼叫GetSystemDirectory函式獲取windows系統目錄的完整路徑名,並將其儲存在緩衝區strSysDir中。後面是兩個變數的定義,要說一下bin這個變數的值是服務的可執行檔案的路徑名,後面的netsvcs就是該服務所在的服務組。