王 琚,張 偉
(1. 國家計算機病毒應急處理中心 天津 300457;2. 天津市公安局公共信息網絡安全監察總隊案件支隊 天津 300020)
Hook(鉤子)實際上是 1個消息處理的程序段,它通過系統調用實現掛入系統。鉤子程序總能先于目的程序率先截獲原本發往該地的消息,從而率先得到控制權。隨后鉤子函數可以對消息進行修改,或放行接著傳遞,或直接結束該消息。
Hook程序與消息緊密相關,其主要功能是對消息進行監聽。Hook根據要截獲的消息可以分為若干類。但無論何種消息,Hook對“對象的搭接”不是硬性的,它是一種柔性連接,一經接觸,Hook的搭鉤立刻能“化”進監聽的線路中,即Hook的代碼能變成對象進程的一部分。原本用戶進程間彼此互相獨立,互不聯系,而 Hook機制提供了操縱特定程序行為的方法。
Hook程序想發揮更大的作用,要找到一種系統內置函數,最佳的目標為 API。API函數是用戶應用程序需求的接收者和工作的完成者。API Hook的基本思想是由 Hook“套”到API的入口點,把它的地址指向自定義函數地址。
談Hook獲得API入口點,要從內存空間中的PE可執行文件說起。
PE文件頭的格式為:


其中“IMAGE_FILE_HEADER”與“MAGE_OPTIONAL_HEADER32”這 2個結構,分別對 PE文件的屬性進行定義,兩者共同構造了 PE文件的結構。在后者的結構中擁有不下30個字段的描述,最后一條是:
IMAGE_DATA_DIRECTORY,這個 DataDirectory是一個結構數組,有16個成員,它們的結構是相同的,均為:

根據 winnt.H中的定義,將 DataDirectory結構的數據目錄定義列表(見表1)。

表1 Data Directory結構的數據目錄定義子表Tab.1 Subtable of Data directory definitions
在 PE文件中查找想要的數據需先從表 1中搜尋對應的信息,如需要找DLL文件中有哪些API函數(導入函數)在運行,則需要查 IMAGE_IMPORT_DESCRIPTOR(導入表),它是1個結構數組,其中的每個元素代表1個動態鏈接庫,而各成員變量則包括了動態鏈接庫導入了哪些函數及這些函數地址的信息。想找到導入表,需要知道它的地址。從表 1中得知導入表對應第 2個目錄 IMAGE_DIRECTORY_EN-TRY_IMPORT,即從IMAGE_DATA_DIRECTORY結構數組中查第2個元素的Virtual Address和Size值,從中找到導入表的地址和大小,這里數據的起始地址稱為 RVA(RVA是相對虛地址,Relative Virtual Addresses。它是相對于基址 Base address而言。一個 PE文件加載進內存后,就以當前內存地址作為基址,文件中各個模塊都以 RVA表示,只要參照基址就能得出各自實際地址)。

成員變量OriginalFirstThunk包含1個IMAGE_THUNK_DATA結構數組的地址,該數組存儲著函數序號和名稱。成員變量 Name記錄的是引入的動態鏈接庫名稱,這提醒我們,1個IMAGE_IMPORT_DESCRIPTOR直接對應1個動態鏈接庫。而成員變量FirstThunk包含1個IMAGE_THUNK_DATA結構數組的地址。可以看出,這是兩個同名函數。IMAGE_THUNK_DATA數組結構為:

在導入表加載之前,FirstThunk所指向的數組與OriginalFirstThunk指向的數組有相同的內容,都包含了要導入的函數的名稱和序號。而在導入表加載之后,FirstThunk所指向的數組所包含的內容就變成了要導入函數的實際地址。
這樣,通過 PE文件頭,一路找尋,最終找到了 API函數的入口點,從而可以為Hook操作所用。
利用進程Hook操作可以實現對特定進程的監控,對目標實現限時、關閉、暫停的操作。監控可以通過定期獲得系統當前進程的快照隊列完成,而每次將隊列中進程名逐個與監控目標進程名作比較,不一致就調用隊列的下一個進程;一致則對其進行相應控制。對系統當前進程的掌握可以借助ToolHelp API的幾個函數完成。首先是 CreateToolhelp32 Snapshot函數,它的結構是:

參數dwFlags:DWORD根據用戶針對的目標類型不同而采取不同設置,對進程、線程、進程的堆列表、進程的模塊列表進行信息捕獲時分別對應的設置值為:TH32CS_SNAPPROCESS、TH32CS_SNAPTHREAD、TH32CS_SNAPHEAPLIS、TH32CS_SNAPMODULE。

利用以上 2個函數可以實現對系統當前進程隊列的遍歷,Process32First捕獲隊列中首個進程的信息,Process32Next則對下一個進程操作。其中參數 hSnapshot返回CreateToolhelp32Snapshot函數建立的當前進程快照的句柄;有了快照,還要設 1個存放它們的所在,于是又有了 1個PROCESSENTRY32結構體來承擔這項工作,而參數LPPROCESSENTRY32就充當指向結構 PROCESSENTRY32的指針。
對進程的控制用到OpenProcess函數和TerminateProcess函數。它們的結構分別為:
HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId);
BOOL TerminateProcess(HANDLE hProcess;UINT uExit-Code);
OpenProcess函數用來獲得要訪問進程的句柄,參數dwDesiredAccess存放目標進程的訪問權限,參數bInheritHandle表示所得到的目標進程的句柄能否被繼承,參數dwProcessId表示目標進程的PID值。
TerminateProcess函數用來無延時地終止特定進程,參數HANDLE hProcess的值即目標進程的句柄,這個值要從Open Process函數獲得;參數uExitCode負責接收代碼退出值。
在進行進程操作時,先要調用 OpenProcess函數,接著再調用 TerminateProcess函數,一般使用完進程句柄后,應調用CloseHandle函數把它關閉,否則會造成句柄泄漏,降低系統效率。
實際上,如果對 OpenProcess函數進行 Hook操作,能達到進程免殺的效果。在進程的眾多屬性中,只有 ProcessId值是唯一的,只有它能準確標識進程。當對 OpenProcess函數實施Hook,一旦OpenProcess函數被調用,我們就可以截獲它的調用信息,判斷被調用的 ProcessId值與設置的進程 ID是否一致,只要一致,就為調用程序返回 1個錯誤值,這樣系統始終無法獲得我們的進程句柄,也就無法停止它。這在實現進程監控和植入木馬的應用上都是有效方法。
在進程加載 DLL庫的過程中,要涉及到 LoadLibrary(lpFileName)函數,它返回DLL的句柄。而與之經常配合使用的是 GetProcAddress(Hinstance,lpname)函數,其中參數Hinstance獲得 LoadLibrary函數返回的句柄,參數 Ipname為文件名,最終的返回值為動態鏈接庫的地址。這2個函數均為動態調用,即只有當需要調用它們時,才將函數實例引入。LoadLibrary函數可以對應不同類型的 DLL 庫,而GetProcAddress函數對應不同的實例。一旦調用鏈接庫失敗,不影響程序的運行。
但是,當外部進程要調用 LoadLibrary函數以實現加載DLL目的時,因為沒有訪問權限不能直接使用。這時可以使用CreateRemoteThread函數來建立遠程線程,它能夠在其他進程地址空間中創建線程。CreateRemoteThread函數不僅實現跨進程地址空間訪問的目的,而且它的參數 IpParameter是指向線程操作函數的指針,這與LoadLibrary函數的IpFileName參數有異曲同工的效果,這樣在完成遠程線程的建立后又可以用LoadLibrary函數實現線程操作。■
[1] Evil. eagle PE文件結構詳解(四)PE導入表[EB/OL].http://blog.csdn.net/evileagle/article/details/12357155.2013.
[2] Qxiniu. Windows下 Hook API技術[EB/OL]. http://wenku.baidu.com/link?url=hl3FowyNyM8kIfKfg4ur_vj0rcligYxccbEtCQNpY3dADCH528tYu2n2Ut-9BC9LXTYrqQT7wuVFAznpO3GGXcT2bAQWJnQheUhFUh ZRxK. 2010.
[3] 天堂水. 2009 Windows下Hook API技術小結[EB/OL].http://www.cnblogs.com/heavenwater/articles/1527446.html. 2009.