天天看點

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

前言

       如果說我們的計算機中安裝有防毒軟體,那麼當我們有意或無意地下載下傳了一個惡意程式後,殺軟一般都會彈出一個對話框提示我們,下載下傳的程式很可能是惡意程式,建議删除之類的,或者殺軟就不提示,直接删除了;或者當我們運作了某一個程式,包含有可疑操作,比如建立開機啟動項,那麼殺軟一般也會對此進行提醒;或者當我們在計算機中插入U盤,殺軟往往也會第一時間對U盤進行掃描,确認沒有問題後,再打開U盤……上述這些,其實都屬于殺軟的“主動防禦”功能。

“主動防禦”簡介

       防毒軟體通常內建監控識别、病毒掃描和清除、自動更新病毒庫、主動防禦等功能,有的防毒軟體還帶有資料恢複等功能,是計算機防禦系統的重要組成部分。關于其中的“病毒掃描和清除”,我們之前在專殺工具的編寫以及特征碼清除的課程中,已經講解過基本的原理。而防毒軟體發展至今,各大安全廠商也越來越将目光放在“主動防禦”技術的研發上。之是以會如此重視這項技術,是因為優秀的“主動防禦”系統即便不更新病毒庫,依舊可以預防各種未知木馬和新病毒。因為任何惡意程式,它如果想要實作各種各樣的功能,最終都是需要調用各種API函數的。那麼我們就可以對敏感API函數進行監控,在API函數執行之前,先解析API函數各個參數的内容,進而判定目标程式究竟是想實作什麼樣的功能,如果是惡意操作,就進行攔截,讓該API函數無法執行。如果是正常操作,則直接放行。

       比如惡意程式都喜歡将自己添加進系統的啟動項中,那麼我們就可以開啟對系統資料庫的相關函數的監控,如果檢測到目标程式會添加系統資料庫啟動項,那麼在添加之前,先進行攔截,詢問使用者是否同意該程式加入啟動項,如果使用者同意,則取消攔截,将程式加入啟動項。如果使用者不同意,或者“主動防禦”系統就認定目标程式是惡意程式,那麼就會阻止惡意程式的這一功能,以保證我們計算機的安全。通過我們之前的課程對于病毒的分析可以知道,病毒程式往往會有多種行為,那麼也就需要我們對于計算機系統的方方面面進行監控,甚至還需要一定的算法來分析各個API函數調用的互相關系,進而對目标程式進行判定。正是因為這樣,“主動防禦”系統才不是那麼倚重病毒庫的支援。是以很多高手的計算機中往往不安裝任何防毒軟體,但是卻一定要安裝“主動防禦”系統。

       成熟的防毒軟體或者專門的“主動防禦”系統,它們的程式設計實作是基于 Ring0層的,因為也隻有在核心級别,才能夠實作良好的監控效果。而我們的課程為了簡單起見,也便于大家了解,讨論的是最簡單的基于Ring3層的“主動防禦”系統的實作。雖說是R3級的,但是也涉及了不少的知識,希望大家一定要先将這次所講的理論弄清楚,這樣在接下來的程式設計實作中,才會遊刃有餘。

       我們需要的知識有以下幾點:

       1、Inline Hook的基本原理

       2、DLL注入的基本方法

       3、對系統進行全局監控

Inline Hook的基本原理

       API函數都儲存在作業系統提供的DLL檔案中。當我們在程式中調用某個API函數并運作程式後,程式會隐式地将API函數所在的DLL檔案加載到程序中,這樣,程式就會像調用自己的函數一樣調用API函數。我們可以看一下這個實驗程式:

#include<stdio.h>
#include<windows.h>

int main()
{
    char szCommandLine[] = "notepad.exe";  // 所要啟動的程式
    STARTUPINFO si = {sizeof(si)};
    PROCESS_INFORMATION pi;
    si.dwFlags = STARTF_USESHOWWINDOW; // 指定wShowWindow成員有效
    si.wShowWindow = TRUE;                // 此成員設為TRUE的話則顯示建立程序的主視窗
    BOOL bRet = CreateProcess(
                        NULL,              // 不在此指定可執行檔案的檔案名
                        szCommandLine,   // 指令行參數
                        NULL,              // 預設程序安全性
                        NULL,              // 預設程序安全性
                        FALSE,             // 指定目前程序内句柄不可以被子程序繼承
                        CREATE_NEW_CONSOLE,// 為新程序建立一個新的控制台視窗
                        NULL,              // 使用本程序的環境變量
                        NULL,              // 使用本程序的驅動器和目錄
                        &si,
                        &pi );
    if(bRet)
	{
        // 關掉不使用的句柄
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
	}
	getchar();
    return 0;
}
           

       上述程式運作後,會打開“記事本”程式。因為我們整個“主動防禦”程式的設計就是圍繞着CreateProcess()這個函數展開的,是以我們的例子也是以這個函數來講解的。我們可以使用OD載入這個程式來看一下函數調用位置處的語句:

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

圖1

       可以看到,當一系列的push讓參數入棧後,程式調用了call語句來調用kernel32.dll中的CreateProcess()函數。其實call語句要實作兩個功能,首先是向棧中壓入目前指令在記憶體中的位置,即利用push語句儲存傳回位址;之後是利用jmp語句跳轉到所調用函數的入口處。可以先單步執行到上圖中的call語句處,然後按下F7步入這個call:

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

圖2

       此時程式跳到了0x7C80236B的位置,也就是CreateProcess()真正實作的代碼位置。通過觀察目前記憶體中的加載狀态,可知該位址位于kernel32.dll中:

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

圖3

       可以總結一下,當我們所編寫的exe檔案需要調用CreateProcess()函數的時候,會将kernel32.dll載入記憶體(其實不管運作什麼程式,kernel32.dll一般都會自動載入記憶體),然後調用該動态連結庫中的CreateProcess()函數(其實真正調用的是CreateProcessA或CreateProcessW),因為真正的CreateProcess()函數的實作在kernel32.dll子產品中。這裡所說的調用,其實可以了解為直接跳到該函數的位址去執行。基于這些知識,我們完全可以修改API函數在記憶體中的映像,進而實作對API函數的鈎取。具體來說就是直接使用彙編指令jmp來改變代碼的執行流程,先不讓它跳到0x7C80236B的位置,而是跳到我們自己編寫的代碼處執行,在執行完我們自己的代碼後,再決定是否讓它再跳到0x7C80236B繼續執行。

       那麼現在的問題是應該如何構造jmp語句。其實關于這個問題,我在《緩沖區溢出分析》系列的課程中,曾經講過一個“jmp back”的方法,這裡的方式與它是一樣的。不妨先來看一下,我們的程式中的jmp語句的特點。進入main函數後,正好就有一個jmp:

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

圖4

       程式通過這個jmp,就來到的main函數的真正位置。這裡分析一下位于0x00401005處的反彙編代碼,是“E9 06000000”。其中的“E9”就是jmp對應的機器碼,後面的“06000000”其實是一個跳轉偏移,偏移的計算公式如下:

jmp後的偏移值 = 目的位址 – 目前位址 - 5

       公式中減去5,是因為jmp指令進行跳轉,需要五個位元組實作。那麼針對于我們目前的這個程式,結合上圖,目的位址為0x00401010,目前位址為0x00401005,即:

jmp後的偏移值 = 0x00401010 - 0x00401005 – 5 = 6

       也就是“E9”後面的“06”的由來。經過上述分析可知,我們隻要在欲鈎取的函數位置處,修改前五個位元組為我們的jmp語句,使其跳向我們自己的函數位置就可以了。

由于這種方法是在程式的流程中直接進行嵌入jmp指令來改變流程的,是以就把它叫做Inline Hook。

DLL注入的基本方法

       我們希望被鈎取的函數所在的程式,在每次調用CreateProcess()的時候,都能夠主動執行我們的jmp語句,這就需要我們對目标程式的功能進行擴充,這可以通過讓目标程式加載我們編寫的DLL來完成。那麼為了讓目标程式能夠加載DLL,就需要利用DLL注入的方法來實作。

       具體到我們所要編寫的主動防禦程式,其實函數鈎取,也就是Hook CreateProcess()功能,是通過一個DLL程式來實作的,而我們的主函數的作用,就是将DLL注入到相應的程序中,當停止監控時,再将DLL解除安裝。DLL的注入與解除安裝是一系列嚴格的流程,注入的流程如下:

       1、OpenProcess獲得要注入程序的句柄

       2、VirtualAllocEx在遠端程序中開辟出一段記憶體,長度為strlen(dllname)+1;

       3、WriteProcessMemory将Dll的名字寫入第二步開辟出的記憶體中。

       4、CreateRemoteThread将LoadLibraryA作為線程函數,參數為Dll的名稱,建立新線程

       5、CloseHandle關閉線程句柄

       解除安裝的流程如下:

       1、CreateRemoteThread将GetModuleHandle注入到遠端程序中,參數為被注入的Dll名

       2、GetExitCodeThread将線程退出的退出碼作為Dll子產品的句柄值。

       3、CloseHandle關閉線程句柄

       3、CreateRemoteThread将FreeLibraryA注入到遠端程序中,參數為第二步獲得的句柄值。

       4、WaitForSingleObject等待對象句柄傳回

       5、CloseHandle關閉線程及程序句柄。

       其實上述流程的道理并不難,而我們下次課程中的程式的編寫,就依據上述流程來進行。

對系統進行全局監控

       在Ring3層,我們常用的對系統進行全局監控的方式是使用Windows的全局鈎子。在作業系統中安裝全局鈎子以後,隻要目标程序符合我們所設定的條件,全局鈎子的DLL檔案會被作業系統自動或強行地加載到該程序中,而DLL檔案存放的正是鈎子函數的代碼,也即我們想要鈎取實作的功能。可見,這種鈎子需要使用DLL注入的方式來實作。如果使用這種方式,我們需要使用SetWindowsHookEx()函數來進行鈎子的設定,并将該函數的第一個參數設定為WH_GETMESSAGE,即監視被投遞到消息隊列中的消息。那麼關于這種方式的具體實作方法,由于并不是我們讨論的重點,是以大家可以參考相應的資料。我們這次所采用的是一種比較簡單的辦法,并不需要進行全局監控,而是監控explorer.exe程序。

       其實絕大多數的程序都是由explorer.exe程序建立的,比如我們打開ProcessExplorer,然後運作一下我們上面所編寫的程式:

病毒木馬清除實戰第020篇:Ring3層主動防禦之基本原理前言“主動防禦”簡介Inline Hook的基本原理DLL注入的基本方法對系統進行全局監控小結

圖5

       可見在explorer.exe程序下有非常多的子程序,而我們的CreateProcessTest.exe正是其子程序的一員,包括由該程式所啟動的notepad.exe程式。是以隻要我們對該程序進行監控,鈎取其CreateProcess()函數,就能夠達到我們想要實作的監控目的。當然,使用這種方式并不見得能夠起到絕對的監控效果,畢竟方法還是太簡單了,但是基本能夠達到我們希望的效果。

小結

       這次我們讨論了基于Ring3層的主動防禦技術的基本原理,我們讨論的都是最簡單的方式,希望大家能夠弄清楚這次我們所講解的每一個知識,這樣在下次的程式設計實作中,才不會有困惑。

繼續閱讀