張 鐘
(重慶理工大學(xué),重慶 400054)
在Windows系統(tǒng)的編程應(yīng)用中,有時(shí)需要對(duì)某些API函數(shù)進(jìn)行攔截,在攔截函數(shù)里執(zhí)行自己的代碼功能后,再返回原函數(shù)執(zhí)行或者改變?cè)瘮?shù)操作行為等。攔截簡(jiǎn)稱為鉤子,可分為內(nèi)核級(jí)和用戶級(jí)鉤子,內(nèi)核級(jí)鉤子有SYSENTER鉤子、SSDT鉤子、內(nèi)聯(lián)鉤子、IRP鉤子、LADDR 鉤子、IDT 鉤子、IOAPIC 鉤子[1]等,實(shí)現(xiàn)起來(lái)較難,須具有0環(huán)權(quán)限;用戶級(jí)鉤子3環(huán)權(quán)限就可實(shí)現(xiàn)。用戶級(jí)攔截API函數(shù)常用的方法有2種:一種是內(nèi)聯(lián)鉤子,一種是導(dǎo)入地址表(Import Address Table,IAT)鉤子。內(nèi)聯(lián)鉤子的特點(diǎn)是將被攔截函數(shù)的首部的5個(gè)字節(jié)先保存起來(lái),然后修改為JMP+鉤子函數(shù)的偏移地址,待鉤子函數(shù)代碼執(zhí)行完后,再將保存起來(lái)的5個(gè)字節(jié)寫回原函數(shù)的首部,然后再執(zhí)行原函數(shù)。這種方法的缺點(diǎn)是:(1)對(duì)硬件CPU有依賴性,不同的CPU其JMP指令是不同的;(2)在搶占式、多線程環(huán)境下根本不能工作。因?yàn)橐粋€(gè)線程覆蓋另一個(gè)函數(shù)起始位置的代碼是需要時(shí)間的,在這個(gè)過(guò)程中,另一個(gè)線程可能調(diào)用同一函數(shù),其結(jié)果將是災(zāi)難性的[2]。而導(dǎo)入地址表鉤子不存在上述2個(gè)問(wèn)題,方法簡(jiǎn)單,容易實(shí)現(xiàn),而且程序也相當(dāng)健壯。此外,在軟件的加密、解密和反病毒應(yīng)用中也往往涉及PE文件的IAT。當(dāng)Windows裝載器把PE文件裝入內(nèi)存時(shí),會(huì)根據(jù)PE文件頭中的導(dǎo)入表所記錄的DLL名稱,將DLL裝入內(nèi)存,以供PE文件使用DLL中的函數(shù)代碼來(lái)實(shí)現(xiàn)程序的各種功能。當(dāng)程序調(diào)用API函數(shù)時(shí)會(huì)首先在導(dǎo)入表中的IAT中查找該函數(shù)入口地址,找到后調(diào)用該函數(shù)。PE文件的導(dǎo)入表可分為2種結(jié)構(gòu)形式:(1)導(dǎo)入表磁盤映像,可供查看導(dǎo)入表所記錄的DLL名稱和導(dǎo)入函數(shù)名稱或序號(hào);(2)導(dǎo)入表的內(nèi)存映像,適用于鉤掛導(dǎo)入地址表IAT。本文在分析闡明導(dǎo)入表的內(nèi)存數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)上,重點(diǎn)闡明導(dǎo)入地址表(IAT)與鉤子函數(shù)關(guān)聯(lián)起來(lái)的鉤子原理:直接鉤掛和間接鉤掛,據(jù)此原理編程實(shí)現(xiàn)鉤掛IAT的鉤子模塊。
圖1 PE文件裝入內(nèi)存中的導(dǎo)入表結(jié)構(gòu)(部分)
要鉤掛IAT首先要掌握導(dǎo)入表的內(nèi)存映像結(jié)構(gòu),圖1是PE文件裝入內(nèi)存中的導(dǎo)入表的部分結(jié)構(gòu)。PE文件的導(dǎo)入表是由一系列的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)組成的數(shù)組,每一個(gè)IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)對(duì)應(yīng)一個(gè)DLL,導(dǎo)入表的最后由一個(gè)內(nèi)容全為0的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)結(jié)束。該結(jié)構(gòu)的定義如下:
字段OriginalFirstThunk所指的導(dǎo)入名稱表(Import Name Table,INT)由若干個(gè) IMAGE_THUNK_DATA結(jié)構(gòu)組成的數(shù)組,每一個(gè)IMAGE_THUNK_DATA結(jié)構(gòu)對(duì)應(yīng)一個(gè)API導(dǎo)入函數(shù),數(shù)組的最后由一個(gè)內(nèi)容全為0的IMAGE_THUNK_DATA結(jié)構(gòu)結(jié)束。有的鏈接器產(chǎn)生的 PE文件沒有 INT,如 Borland公司的TLINK,因此由Borland生成的PE文件是不能綁定的[3],但這不影響鉤掛IAT。該結(jié)構(gòu)的定義如下:
從這個(gè)結(jié)構(gòu)的定義可看到,該結(jié)構(gòu)是一個(gè)共用體,實(shí)際上就是一個(gè)雙字。當(dāng)雙字的最高位是1時(shí),表示函數(shù)是以序號(hào)導(dǎo)入的,低31位就是函數(shù)的序號(hào)值;當(dāng)最高位是0時(shí),表示函數(shù)是以函數(shù)名稱(ANSI字符串,以0結(jié)尾)導(dǎo)入的,雙字表示是一個(gè)RVA,此時(shí)指向一個(gè)IMAGE_IMPORT_BY_NAME結(jié)構(gòu)。IMAGE_IMPORT_BY_NAME結(jié)構(gòu)定義如下:
(1)直接法鉤掛。在圖1中的IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)中,字段FirstThunk指向?qū)氲刂繁?IAT),導(dǎo)入地址表中的每一項(xiàng)都對(duì)應(yīng)一個(gè)導(dǎo)入函數(shù)的入口地址,用鉤子函數(shù)的地址取代IAT中導(dǎo)入函數(shù)的入口地址即可實(shí)現(xiàn)鉤掛。
(2)間接法鉤掛。在圖1中,字段FirstThunk同時(shí)也指向IMAGE_THUNK_DATA結(jié)構(gòu)數(shù)組,在內(nèi)存中IMAGE_THUNK_DATA結(jié)構(gòu)數(shù)組就是導(dǎo)入地址表IAT,該結(jié)構(gòu)中的共用體字段成員變量u1.Function的值是與IAT共用的同一個(gè)導(dǎo)入函數(shù)的入口地址,用鉤子函數(shù)的地址取代成員變量u1.Function的值即可實(shí)現(xiàn)鉤掛,并自動(dòng)同步取代IAT中對(duì)應(yīng)導(dǎo)入函數(shù)的入口地址。
在查看內(nèi)存中的導(dǎo)入地址表時(shí),看到的只是一個(gè)IAT。但用編程的方法去鉤掛IAT時(shí),則可選擇字段FirstThunk指向的2條路徑之一去鉤掛IAT。
要鉤掛IAT,首先要定位內(nèi)存中導(dǎo)入表的位置,然后由IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)中的字段FirstThunk找到IAT,再置換導(dǎo)入函數(shù)在IAT中對(duì)應(yīng)的入口地址(直接法)或者置換成員變量u1.Function的值(間接法)即可。定位IAT位置的方法有3種。
(1)從PE基地址開始將其定位到PE頭的IMAGE_NT_HEADERS結(jié)構(gòu)體,由結(jié)構(gòu)中的字段名OptionalHeader再定位到 IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)體,由該結(jié)構(gòu)中的字段名DataDirectory所指的第2個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)體,由該結(jié)構(gòu)中的字段名VirtualAddress所指即是導(dǎo)入表IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)體數(shù)組,通過(guò)該結(jié)構(gòu)的字段名FirstThunk即指向IAT的RVA。
(2)通過(guò) IMAGE_OPTIONAL_HEADER32結(jié)構(gòu)體中的字段名DataDirectory所指的第13個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)體,由該結(jié)構(gòu)中的字段名VirtualAddress直接指向IAT的RVA。
(3)使用imagehlp.dll動(dòng)態(tài)鏈接庫(kù)中的函數(shù)ImageDirectoryEntryToData,該函數(shù)的返回值即是指向?qū)氡鞩MAGE_IMPORT_DESCRIPTOR結(jié)構(gòu)體數(shù)組的VA,通過(guò)該結(jié)構(gòu)的字段名FirstThunk即指向IAT的RVA。
本文采用方法(1)并結(jié)合圖1的導(dǎo)入表的內(nèi)存結(jié)構(gòu),用Win32匯編語(yǔ)言實(shí)現(xiàn)直接法鉤子模塊Hook-PEIAT用于鉤掛IAT,可用于鉤掛導(dǎo)入表中指定的DLL中的指定API導(dǎo)入函數(shù);此外,為測(cè)試直接法和間接法實(shí)現(xiàn)的鉤子模塊的鉤掛效果,所要注入遠(yuǎn)程進(jìn)程中的DLL模塊必須包括鉤子模塊和鉤子函數(shù)模塊,因?yàn)殂^掛IAT的目的就是為了實(shí)現(xiàn)鉤子函數(shù)的代碼功能。
按圖1所示的字段FirstThunk指向的間接法去鉤掛IAT,可在鉤子模塊HookPEIAT中用下面程序代碼(Ⅱ)置換代碼(Ⅰ)即可實(shí)現(xiàn)。
現(xiàn)在用C或VC++編寫的鉤掛IAT的程序代碼都是采用間接法實(shí)現(xiàn)導(dǎo)入地址表鉤子。這是因?yàn)镃和VC++操作匯編指令比較麻煩,而操作變量和取變量地址卻很容易。
在 Windows 7 旗艦版,CPU:Core i3,2.2 GHz和Windows XP SP2,CPU:Celeron,1.4 GHz 等環(huán)境下,采用遠(yuǎn)程線程注入DLL的方法:(1)用GetProcAddress取得LoadLibraryA的實(shí)際地址;(2)用VirtualAllocEx在遠(yuǎn)程進(jìn)程中分配一塊內(nèi)存(用于存放DLL的全路徑名);(3)將DLL的全路徑名復(fù)制到(2)所分配的內(nèi)存中;(4)用CreateRemoteThread在遠(yuǎn)程進(jìn)程中創(chuàng)建一個(gè)線程,線程函數(shù)的地址就是LoadLibraryA的實(shí)際地址,參數(shù)就是(2)中分配的內(nèi)存地址,這樣就把DLL注入到遠(yuǎn)程進(jìn)程地址空間中。對(duì)記事本進(jìn)程注入DLL進(jìn)行了測(cè)試,直接法和間接法均可靠鉤掛IAT中的CreateFileW函數(shù)入口地址,并實(shí)現(xiàn)了測(cè)試鉤子函數(shù)的簡(jiǎn)單功能:(1)允許打開所選擇的文件;(2)放棄打開所選擇的文件。本文除對(duì)Create-FileW函數(shù)進(jìn)行了鉤掛測(cè)試,還對(duì)最常用的Message-BoxA、MessageBoxW和ExitProcess等導(dǎo)入函數(shù)用鉤子模塊HookPEIAT進(jìn)行了鉤掛測(cè)試,測(cè)試結(jié)果表明,直接法和間接法都可靠鉤掛了IAT。
(1)替代原導(dǎo)入函數(shù)的鉤子函數(shù)所有的參數(shù)類型應(yīng)相同,參數(shù)個(gè)數(shù)要相同;返回值應(yīng)相同;調(diào)用約定也應(yīng)相同[2]。如果不一致可能會(huì)引發(fā)異常或不能實(shí)現(xiàn)鉤子函數(shù)預(yù)期的目的。另外,將Win32匯編編寫的鉤掛MessageBoxA或MessageBoxW的DLL分別注入用C或VC++編寫的程序進(jìn)程中進(jìn)行測(cè)試,均能可靠地鉤掛IAT中的導(dǎo)入函數(shù)地址,測(cè)試用的鉤子函數(shù)顯示正常,這說(shuō)明了Win32匯編寫的鉤子函數(shù)在調(diào)用約定,參數(shù)個(gè)數(shù)等與C或VC++是一致的。
(2)在進(jìn)行鉤掛前應(yīng)先檢查該P(yáng)E文件的導(dǎo)入表中有無(wú)要鉤掛的導(dǎo)入函數(shù),如沒有要鉤掛的導(dǎo)入函數(shù),則注入DLL后將不會(huì)有任何反映。如果鉤掛前經(jīng)檢查PE文件的導(dǎo)入表中有要鉤掛的函數(shù),但注入DLL后沒有反映,有一種情況是該導(dǎo)入函數(shù)已在DLL注入前就已執(zhí)行過(guò)了,雖已鉤掛但該導(dǎo)入函數(shù)此后沒有再調(diào)用了,像這種情況可先將線程掛起后,再注入DLL,然后啟動(dòng)線程即可。
(3)對(duì)于用 LoadLibrary和 GetProcAddress顯式裝入的函數(shù),由于PE文件的導(dǎo)入表中沒有此函數(shù),所以是無(wú)法通過(guò)鉤掛IAT的方法鉤住此函數(shù)。但可以鉤掛LoadLibrary和GetProcAddress這2個(gè)函數(shù)來(lái)攔截所要鉤掛的函數(shù),實(shí)際上通過(guò)鉤掛GetProcAddress就可實(shí)現(xiàn)攔截顯式裝入的函數(shù),因?yàn)橐〉煤瘮?shù)的地址必須調(diào)用GetProcAddress。本文僅對(duì)MessageBoxA進(jìn)行了簡(jiǎn)單的顯式裝入攔截測(cè)試。思路是:通過(guò)鉤掛GetProcAddress導(dǎo)入函數(shù),在鉤子函數(shù)內(nèi)部對(duì)每次調(diào)用GetProcAddress的每一個(gè)函數(shù)名參數(shù)都與字符串“MessageBoxA”,0進(jìn)行比對(duì),如果找到了,表明是要鉤掛的函數(shù)MessageBoxA,顯示攔截成功的信息,再返回GetProcAddress的值,繼續(xù)調(diào)用顯式裝入函數(shù)MessageBoxA來(lái)顯示原來(lái)的信息;如果不是字符串“MessageBoxA”,0,就直接返回 GetProcAddress的值。
(4)鉤掛IAT除可采用遠(yuǎn)程注入DLL的方法外,還可以采用遠(yuǎn)程注入代碼的方式;但后者需要對(duì)注入代碼中的全局變量和函數(shù)進(jìn)行重定位,并需要自己動(dòng)態(tài)搜索API函數(shù),而前者則沒有這方面的問(wèn)題。
(5)要解除對(duì)PE或DLL的IAT的鉤掛,只要在鉤子模塊HookPEIAT中,將參數(shù)lpoldfunc(原函數(shù)入口地址)與lphookfunc(鉤子函數(shù)入口地址)值進(jìn)行對(duì)換,然后調(diào)用HookPEIAT,即可解除對(duì)IAT的鉤掛。
(6)在實(shí)際的鉤掛IAT的程序時(shí),為了防止因考慮不周而引發(fā)異常暴露鉤子函數(shù)的行為,建議編程時(shí)加入異常處理程序,以便出現(xiàn)小錯(cuò)誤時(shí)異常處理程序內(nèi)部自己就處理了,以保護(hù)自己的程序能隱蔽執(zhí)行。
(7)對(duì)于使用了延遲加載DLL技術(shù)的PE文件,由于API函數(shù)在使用前無(wú)法在IAT中找到入口地址,這樣會(huì)使直接鉤掛IAT失?。?],但仍可以通過(guò)鉤掛LoadLibrary和GetProcAddress來(lái)實(shí)施攔截。本文就利用鉤子模塊HookPEIAT,對(duì)PE文件的延遲加載的user32.dll中的MessageBoxW函數(shù),通過(guò)鉤掛GetProcAddress,成功攔截了MessageBoxW函數(shù)。
(8)對(duì)于綁定的導(dǎo)入表,建議在內(nèi)存中鉤掛導(dǎo)入地址表為好;因?yàn)閃indows裝載器在加載PE文件時(shí)會(huì)先檢查所裝入的DLL地址的有效性,如不滿足要求,將會(huì)重新生成一個(gè)新的IAT[2],從而使靜態(tài)鉤掛的IAT完全失效。
Windows鉤子技術(shù)博大精深,在防火墻、進(jìn)程監(jiān)控、進(jìn)程隱藏、進(jìn)程自我防護(hù)、實(shí)時(shí)數(shù)據(jù)采集、即時(shí)翻譯、內(nèi)存信息隱藏、注冊(cè)表項(xiàng)隱藏、網(wǎng)絡(luò)攻擊與防御、反病毒、加密解密等方面得到廣泛的應(yīng)用,鉤子技術(shù)還擴(kuò)展了原函數(shù)的功能。本文所介紹的用戶級(jí)IAT鉤子,由于簡(jiǎn)單、可靠和適用,也得到了廣泛的使用。IAT鉤子在使用中有幾點(diǎn)要注意:(1)任何阻止DLL或代碼注入到進(jìn)程中的方法,都會(huì)阻止鉤掛IAT;還有將其注入的DLL釋放掉,也會(huì)使IAT鉤子失效,這可以采用注入代碼的方法來(lái)應(yīng)對(duì)。(2)任何對(duì)導(dǎo)入表或IAT進(jìn)行加密處理,也會(huì)阻止鉤掛IAT。對(duì)于加密的程序,只有當(dāng)外殼程序把執(zhí)行權(quán)交給被加密程序時(shí),PE文件的導(dǎo)入表和IAT才恢復(fù)原狀,這時(shí)才能正確鉤掛IAT,但時(shí)間點(diǎn)的確定是以后鉤掛IAT研究的方向。(3)反鉤掛IAT。Rootkit反鉤掛是通過(guò)比較IAT中的地址與DLL中導(dǎo)出函數(shù)的地址,如發(fā)現(xiàn)二者之間有任何差異[4],表明可能帶有 IAT鉤子,可用DLL中導(dǎo)出函數(shù)的地址覆蓋掉IAT中鉤子函數(shù)地址,使IAT鉤子失效。
:
[1]任曉琿.黑客免殺攻防[M].北京:機(jī)械工業(yè)出版社,2013:375-376,390-413.
[2][美]杰夫瑞,[法]克里斯托夫.Windows核心編程(第5版)[M].葛子昂,周靖,廖敏譯.北京:清華大學(xué)出版社,2008:600-601.
[3]段鋼.加密與解密(第3版)[M].北京:電子工業(yè)出版社,2008:285-286.
[4][美]戴維斯,等.黑客惡意軟件和RootKit安全大曝光[M].姚軍,等譯.北京:機(jī)械工業(yè)出版社,2011:218-219.
[5]蘇雪麗,袁丁.Windows下兩種API鉤掛技術(shù)的研究與實(shí)現(xiàn)[J].計(jì)算機(jī)工程與設(shè)計(jì),2011,32(7):2548-2552.
[6]陳云超,馬兆豐.基于API函數(shù)攔截技術(shù)的跨進(jìn)程攻擊防護(hù)研究[C]//2011年通信與信息技術(shù)新進(jìn)展——第八屆中國(guó)通信學(xué)會(huì)學(xué)術(shù)年會(huì)論文集.2011.
[7]舒敬榮,朱安國(guó),齊善明.HOOK API時(shí)代碼注入方法和函數(shù)重定向技術(shù)研究[J].計(jì)算機(jī)應(yīng)用與軟件,2009,26(5):107-110.
[8]黃頂源,李陶深,嚴(yán)毅,等.HOOK API在內(nèi)網(wǎng)安全監(jiān)管系統(tǒng)中的應(yīng)用[J].廣西物理,2006,27(4):38-41.
[9]陶廷頁(yè),孫樂(lè)昌,汪永益.利用API HOOK技術(shù)實(shí)現(xiàn)計(jì)算機(jī)保密通信[J].安徽電子信息職業(yè)技術(shù)學(xué)院學(xué)報(bào),2004,3(5-6):107-108.
[10]鄧樂(lè),李曉勇.基于IAT表的木馬自啟動(dòng)技術(shù)[J].信息安全與通信保密,2007(2):151-153.
[11]王泰格,邵玉如,楊翌.全局IAT Hook技術(shù)原理及實(shí)現(xiàn)[J].信息與電腦:理論版,2012(7):110-111.
[12]程彥,楊建召.Win32中API攔截技術(shù)及其應(yīng)用[J].長(zhǎng)春工業(yè)大學(xué)學(xué)報(bào):自然科學(xué)版,2006,27(4):369-371.