王 琚,張 偉
(1. 國家計算機病毒應(yīng)急處理中心 天津 300457;2. 天津市公安局公共信息網(wǎng)絡(luò)安全監(jiān)察總隊案件支隊 天津 300020)
Hook(鉤子)實際上是 1個消息處理的程序段,它通過系統(tǒng)調(diào)用實現(xiàn)掛入系統(tǒng)。鉤子程序總能先于目的程序率先截獲原本發(fā)往該地的消息,從而率先得到控制權(quán)。隨后鉤子函數(shù)可以對消息進行修改,或放行接著傳遞,或直接結(jié)束該消息。
Hook程序與消息緊密相關(guān),其主要功能是對消息進行監(jiān)聽。Hook根據(jù)要截獲的消息可以分為若干類。但無論何種消息,Hook對“對象的搭接”不是硬性的,它是一種柔性連接,一經(jīng)接觸,Hook的搭鉤立刻能“化”進監(jiān)聽的線路中,即Hook的代碼能變成對象進程的一部分。原本用戶進程間彼此互相獨立,互不聯(lián)系,而 Hook機制提供了操縱特定程序行為的方法。
Hook程序想發(fā)揮更大的作用,要找到一種系統(tǒng)內(nèi)置函數(shù),最佳的目標為 API。API函數(shù)是用戶應(yīng)用程序需求的接收者和工作的完成者。API Hook的基本思想是由 Hook“套”到API的入口點,把它的地址指向自定義函數(shù)地址。
談Hook獲得API入口點,要從內(nèi)存空間中的PE可執(zhí)行文件說起。
PE文件頭的格式為:
其中“IMAGE_FILE_HEADER”與“MAGE_OPTIONAL_HEADER32”這 2個結(jié)構(gòu),分別對 PE文件的屬性進行定義,兩者共同構(gòu)造了 PE文件的結(jié)構(gòu)。在后者的結(jié)構(gòu)中擁有不下30個字段的描述,最后一條是:
IMAGE_DATA_DIRECTORY,這個 DataDirectory是一個結(jié)構(gòu)數(shù)組,有16個成員,它們的結(jié)構(gòu)是相同的,均為:
根據(jù) winnt.H中的定義,將 DataDirectory結(jié)構(gòu)的數(shù)據(jù)目錄定義列表(見表1)。
表1 Data Directory結(jié)構(gòu)的數(shù)據(jù)目錄定義子表Tab.1 Subtable of Data directory definitions
在 PE文件中查找想要的數(shù)據(jù)需先從表 1中搜尋對應(yīng)的信息,如需要找DLL文件中有哪些API函數(shù)(導(dǎo)入函數(shù))在運行,則需要查 IMAGE_IMPORT_DESCRIPTOR(導(dǎo)入表),它是1個結(jié)構(gòu)數(shù)組,其中的每個元素代表1個動態(tài)鏈接庫,而各成員變量則包括了動態(tài)鏈接庫導(dǎo)入了哪些函數(shù)及這些函數(shù)地址的信息。想找到導(dǎo)入表,需要知道它的地址。從表 1中得知導(dǎo)入表對應(yīng)第 2個目錄 IMAGE_DIRECTORY_EN-TRY_IMPORT,即從IMAGE_DATA_DIRECTORY結(jié)構(gòu)數(shù)組中查第2個元素的Virtual Address和Size值,從中找到導(dǎo)入表的地址和大小,這里數(shù)據(jù)的起始地址稱為 RVA(RVA是相對虛地址,Relative Virtual Addresses。它是相對于基址 Base address而言。一個 PE文件加載進內(nèi)存后,就以當前內(nèi)存地址作為基址,文件中各個模塊都以 RVA表示,只要參照基址就能得出各自實際地址)。
成員變量OriginalFirstThunk包含1個IMAGE_THUNK_DATA結(jié)構(gòu)數(shù)組的地址,該數(shù)組存儲著函數(shù)序號和名稱。成員變量 Name記錄的是引入的動態(tài)鏈接庫名稱,這提醒我們,1個IMAGE_IMPORT_DESCRIPTOR直接對應(yīng)1個動態(tài)鏈接庫。而成員變量FirstThunk包含1個IMAGE_THUNK_DATA結(jié)構(gòu)數(shù)組的地址。可以看出,這是兩個同名函數(shù)。IMAGE_THUNK_DATA數(shù)組結(jié)構(gòu)為:
在導(dǎo)入表加載之前,F(xiàn)irstThunk所指向的數(shù)組與OriginalFirstThunk指向的數(shù)組有相同的內(nèi)容,都包含了要導(dǎo)入的函數(shù)的名稱和序號。而在導(dǎo)入表加載之后,F(xiàn)irstThunk所指向的數(shù)組所包含的內(nèi)容就變成了要導(dǎo)入函數(shù)的實際地址。
這樣,通過 PE文件頭,一路找尋,最終找到了 API函數(shù)的入口點,從而可以為Hook操作所用。
利用進程Hook操作可以實現(xiàn)對特定進程的監(jiān)控,對目標實現(xiàn)限時、關(guān)閉、暫停的操作。監(jiān)控可以通過定期獲得系統(tǒng)當前進程的快照隊列完成,而每次將隊列中進程名逐個與監(jiān)控目標進程名作比較,不一致就調(diào)用隊列的下一個進程;一致則對其進行相應(yīng)控制。對系統(tǒng)當前進程的掌握可以借助ToolHelp API的幾個函數(shù)完成。首先是 CreateToolhelp32 Snapshot函數(shù),它的結(jié)構(gòu)是:
參數(shù)dwFlags:DWORD根據(jù)用戶針對的目標類型不同而采取不同設(shè)置,對進程、線程、進程的堆列表、進程的模塊列表進行信息捕獲時分別對應(yīng)的設(shè)置值為:TH32CS_SNAPPROCESS、TH32CS_SNAPTHREAD、TH32CS_SNAPHEAPLIS、TH32CS_SNAPMODULE。
利用以上 2個函數(shù)可以實現(xiàn)對系統(tǒng)當前進程隊列的遍歷,Process32First捕獲隊列中首個進程的信息,Process32Next則對下一個進程操作。其中參數(shù) hSnapshot返回CreateToolhelp32Snapshot函數(shù)建立的當前進程快照的句柄;有了快照,還要設(shè) 1個存放它們的所在,于是又有了 1個PROCESSENTRY32結(jié)構(gòu)體來承擔這項工作,而參數(shù)LPPROCESSENTRY32就充當指向結(jié)構(gòu) PROCESSENTRY32的指針。
對進程的控制用到OpenProcess函數(shù)和TerminateProcess函數(shù)。它們的結(jié)構(gòu)分別為:
HANDLE OpenProcess(DWORD dwDesiredAccess,BOOL bInheritHandle,DWORD dwProcessId);
BOOL TerminateProcess(HANDLE hProcess;UINT uExit-Code);
OpenProcess函數(shù)用來獲得要訪問進程的句柄,參數(shù)dwDesiredAccess存放目標進程的訪問權(quán)限,參數(shù)bInheritHandle表示所得到的目標進程的句柄能否被繼承,參數(shù)dwProcessId表示目標進程的PID值。
TerminateProcess函數(shù)用來無延時地終止特定進程,參數(shù)HANDLE hProcess的值即目標進程的句柄,這個值要從Open Process函數(shù)獲得;參數(shù)uExitCode負責接收代碼退出值。
在進行進程操作時,先要調(diào)用 OpenProcess函數(shù),接著再調(diào)用 TerminateProcess函數(shù),一般使用完進程句柄后,應(yīng)調(diào)用CloseHandle函數(shù)把它關(guān)閉,否則會造成句柄泄漏,降低系統(tǒng)效率。
實際上,如果對 OpenProcess函數(shù)進行 Hook操作,能達到進程免殺的效果。在進程的眾多屬性中,只有 ProcessId值是唯一的,只有它能準確標識進程。當對 OpenProcess函數(shù)實施Hook,一旦OpenProcess函數(shù)被調(diào)用,我們就可以截獲它的調(diào)用信息,判斷被調(diào)用的 ProcessId值與設(shè)置的進程 ID是否一致,只要一致,就為調(diào)用程序返回 1個錯誤值,這樣系統(tǒng)始終無法獲得我們的進程句柄,也就無法停止它。這在實現(xiàn)進程監(jiān)控和植入木馬的應(yīng)用上都是有效方法。
在進程加載 DLL庫的過程中,要涉及到 LoadLibrary(lpFileName)函數(shù),它返回DLL的句柄。而與之經(jīng)常配合使用的是 GetProcAddress(Hinstance,lpname)函數(shù),其中參數(shù)Hinstance獲得 LoadLibrary函數(shù)返回的句柄,參數(shù) Ipname為文件名,最終的返回值為動態(tài)鏈接庫的地址。這2個函數(shù)均為動態(tài)調(diào)用,即只有當需要調(diào)用它們時,才將函數(shù)實例引入。LoadLibrary函數(shù)可以對應(yīng)不同類型的 DLL 庫,而GetProcAddress函數(shù)對應(yīng)不同的實例。一旦調(diào)用鏈接庫失敗,不影響程序的運行。
但是,當外部進程要調(diào)用 LoadLibrary函數(shù)以實現(xiàn)加載DLL目的時,因為沒有訪問權(quán)限不能直接使用。這時可以使用CreateRemoteThread函數(shù)來建立遠程線程,它能夠在其他進程地址空間中創(chuàng)建線程。CreateRemoteThread函數(shù)不僅實現(xiàn)跨進程地址空間訪問的目的,而且它的參數(shù) IpParameter是指向線程操作函數(shù)的指針,這與LoadLibrary函數(shù)的IpFileName參數(shù)有異曲同工的效果,這樣在完成遠程線程的建立后又可以用LoadLibrary函數(shù)實現(xiàn)線程操作?!?/p>
[1] Evil. eagle PE文件結(jié)構(gòu)詳解(四)PE導(dǎo)入表[EB/OL].http://blog.csdn.net/evileagle/article/details/12357155.2013.
[2] Qxiniu. Windows下 Hook API技術(shù)[EB/OL]. http://wenku.baidu.com/link?url=hl3FowyNyM8kIfKfg4ur_vj0rcligYxccbEtCQNpY3dADCH528tYu2n2Ut-9BC9LXTYrqQT7wuVFAznpO3GGXcT2bAQWJnQheUhFUh ZRxK. 2010.
[3] 天堂水. 2009 Windows下Hook API技術(shù)小結(jié)[EB/OL].http://www.cnblogs.com/heavenwater/articles/1527446.html. 2009.