研究一下在DllMain中如何 完成API的掛接。 首先了解一下導(dǎo)入表。 對(duì)于一個(gè)模塊來(lái)說(shuō),如果它調(diào)用 的函數(shù)的實(shí)現(xiàn)代碼在其它模塊中,那 么這個(gè)函數(shù)就叫做導(dǎo)入函數(shù)。對(duì)于一 個(gè)模塊來(lái)說(shuō),他的所有導(dǎo)入函數(shù)的函 數(shù)名和函數(shù)所駐留的DLL名等信息都 保留在該模塊的導(dǎo)入表(Import Table)中。導(dǎo)入表是一個(gè) IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu) 的數(shù)組,每一個(gè) IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu) 表述了一個(gè)導(dǎo)入模塊。 此結(jié)構(gòu)體中的兩個(gè)關(guān)鍵信息是1. 函數(shù)序號(hào)/名稱表的偏移量, 2.IAT(導(dǎo)入地址表Import Address Table)的偏移量,根據(jù)這兩個(gè)信息 可以分別找到這兩個(gè)表。 應(yīng)用程序啟動(dòng)時(shí),載入器根據(jù)PE 文件(如DLL)的導(dǎo)入表記錄的DLL 名加載相應(yīng)DLL模塊,再根據(jù)導(dǎo)入表 的hint/name表 (OriginalFirstThunk指向的數(shù)組) 記錄的函數(shù)名取得函數(shù)的地址,將這 些地址保存到導(dǎo)入表的IAT中。 應(yīng)用程序在調(diào)用導(dǎo)入函數(shù)時(shí),要 先到導(dǎo)入表的IAT中找到這個(gè)函數(shù)的 地址,然后再調(diào)用。例如,調(diào)用 User32.dll模塊中的MessageBoxA 函數(shù)的代碼最終會(huì)被匯編成如下代 碼: call dword ptr [__imp__MessageBoxA@16(00424 28c)] 函數(shù)的IAT僅僅是一個(gè)DWORD數(shù) 組,數(shù)組的每個(gè)成員記錄著一個(gè)導(dǎo)入 函數(shù)的地址。地址0042428c是導(dǎo)入 地址表中MessageBoxA函數(shù)對(duì)應(yīng)成 員的地址,這個(gè)地址處的內(nèi)容是 MessageBoxA在User32模塊的真實(shí) 地址??梢?jiàn),調(diào)用API函數(shù)時(shí),程序 先要轉(zhuǎn)向PE文件的導(dǎo)入地址表取得 API函數(shù)的真實(shí)地址,然后再轉(zhuǎn)向API 函數(shù)的執(zhí)行代碼。 于是我們可以用修改IAT表的方 法來(lái)完成HOOK API的實(shí)現(xiàn)。比如, 如果將0042428c地指出的內(nèi)容用一 個(gè)自定義函數(shù)的地址覆蓋掉,那么以 后這個(gè)模塊對(duì)MessageBoxA的調(diào)用 實(shí)際上就成了對(duì)改自定義函數(shù)的調(diào) 用,應(yīng)用程序以為自己調(diào)用的是系統(tǒng) API,但是我們將IAT表修改后,使得 在它試圖尋找系統(tǒng)API的時(shí)候,卻找 到了我們定義的用來(lái)代替該API的自 定義函數(shù)。但是,為了保持堆棧的平 衡,自定義函數(shù)使用的調(diào)用規(guī)則和參 數(shù)的個(gè)數(shù)必須與它所替代的API函數(shù) 完全相同。 這種用修改IAT表進(jìn)行HOOK API 的方法是最穩(wěn)定的一種。 下面將詳細(xì)介紹。 首先要在PE文件(如DLL)中定 位導(dǎo)入表。這主要是對(duì)PE文件結(jié)構(gòu)的 分析。 PE文件格式是任何可執(zhí)行文件或 DLL的文件格式,PE文件以64字節(jié) 的DOS頭開(kāi)始 (IMAGE_DOS_HEADER結(jié)構(gòu)),接 著是一小段DOS程序,然后是248字 節(jié)的NT文件頭 (IMAGE_NT_HEADERS結(jié)構(gòu))。NT 文件頭相對(duì)文件開(kāi)始位置的偏移量 (因?yàn)橹虚g的DOS程序長(zhǎng)短不定)可 以有IMAGE_DOS_HEADER結(jié)構(gòu)的 e_lfanew給出。 下面的代碼取得了一個(gè)指向 IMAGE_OPTIONAL_HEADER結(jié)構(gòu)的 指針。 HMODULE hMod = ::GetModuleHandle(NULL); IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hMod; IMAGE_OPTIONAL_HEADER* pOptHeader = (IMAGE_OPTIONAL_HEADER*) ((BYTE*)hMod pDosHeader->e_lfanes 24); IMAGE_OPTIONAL_HEADER結(jié) 構(gòu)體里保存著許多重要的信息,包括 我們最感興趣的數(shù)據(jù)目錄表指針(即我 們想要操作的表的地址)。PE文件保 存了16個(gè)數(shù)據(jù)目錄,最常見(jiàn)的有導(dǎo)入 表、導(dǎo)出表、資源和重定位表。我們 感興趣的是導(dǎo)入表。它是一個(gè) IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu) 的數(shù)組,每個(gè)結(jié)構(gòu)對(duì)應(yīng)著一個(gè)導(dǎo)入模 塊。下面的代碼取得導(dǎo)入表中第一個(gè) IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu) 的指針(導(dǎo)入表首地址)。 IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*) ((BYTE*)hMod pOptHeader->DataDirectory[IMAGE_DIRECTO RY_ENTRY_IMPORT].VirtualAddre ss); 除了通過(guò)分析PE文件的結(jié)構(gòu)來(lái)定 位模塊的導(dǎo)入表外,還可以使用 ImageDirectoryEntryToData函 數(shù)。這個(gè)函數(shù)知道模塊基地址后,直 接返回指定數(shù)據(jù)目錄表的首地址,用 法如下: PVOID ImageDirectoryEntryToData( PVOID Base, //模 塊及地址 BOOLEAN MappedAsImage, //如果此參數(shù)是TRUE,文件被系統(tǒng)當(dāng) 做鏡像映射,否則, //將當(dāng)做數(shù)據(jù)文件映射 USHORT DirectoryEntry, //指定要取得哪個(gè)表的表首地址 傳入 //IMAGE_DIRECTORY_ENTRY_IMPO RT,取得導(dǎo)入表 //首地址 PULONG Size //返回表象的大小 );//為了調(diào)用此API,請(qǐng)?zhí)砑哟?碼“#include IMAGE_IMPORT_DESCRIPTOR 結(jié)構(gòu)包含了hint/name(函數(shù)序號(hào)/名 稱)表和IAT表的偏移量。這兩個(gè)表的 大小相同,一個(gè)成員對(duì)應(yīng)一個(gè)導(dǎo)入函 數(shù),分別記錄了導(dǎo)入函數(shù)的名稱和地 址。下面的代碼打印出了此模塊從其 它模塊導(dǎo)入的所有函數(shù)的名稱和地 址。 此模塊從很多其它模塊(DLL) 中導(dǎo)入函數(shù),這里分別打印出它們。 每一個(gè) IMAGE_IMPORT_DESCRIPTOR結(jié)構(gòu) 描述了一個(gè)模塊(DLL)向此模塊導(dǎo) 入函數(shù)的情況,該結(jié)構(gòu)提供了很多表 的地址,如hint/name表和IAT表, 這兩個(gè)表分別記錄了了從此 IMAGE_IMPORT_DESCEIPTOR結(jié)構(gòu) 所描述的模塊導(dǎo)入的函數(shù)的名稱和地 址。 while(pImportDesc->FirstThunk) { char* pszDllName=(char*) ((BYTE*)hMod pImportDesc->Name); printf("/n 模塊名稱:%s/n",pszDllName); //一個(gè)IMAGE_THUNK_DATA就 是一個(gè)雙字,它指定了一個(gè)導(dǎo)入函數(shù) IMAGE_THUNK_DATA *pThunk = (IMAGE_THUNK_DATA*) ((BYTE*)hMod pImportDesc->OriginalFirstThunk); int n = 0; while(pThunk->u1.Function) {//取得函數(shù)名稱。hint/name表 項(xiàng)前兩個(gè)字節(jié)是函數(shù)序號(hào),后面才是 函數(shù)名稱字符串 char* pszFunName=(char*) ((BYTE*)hMod (DWORD)pThunk-> u1.AddressOfData 2); //取得函數(shù)地址。IAT表就是一個(gè) DWORD類型的數(shù)組,每個(gè)成員記錄一 個(gè)函數(shù)的地址 PDWORD lpAddr = (DWORD*) ((BYTE*)hMod pImportDesc->FirstThunk) n; //打印出函數(shù)名稱和地址 printf("從此模塊導(dǎo)入的函數(shù): %-25s, ",pszFunName); printf("函數(shù)地址:%X/n",lpAddr); n ;pThunk ; } pImportDesc ; } 由上述可知,定位導(dǎo)入表之后即 可定位導(dǎo)入地址表。為了截獲API調(diào) 用,只要用自定義函數(shù)的地址覆蓋掉 導(dǎo)入地址表中真實(shí)的API函數(shù)地址即 可。這就是用DLL遠(yuǎn)程注入和修改 IAT表的方式實(shí)現(xiàn)HOOK API的過(guò)程。 具體掛鉤過(guò)程為:在我們注入的 DLL里,因?yàn)镈LL第一次被注入時(shí)系 統(tǒng)會(huì)自動(dòng)調(diào)用DLL的DllMain函數(shù), 在DllMain里調(diào)用我們同在此DLL里 定義的SetHookApi函數(shù),在 SetHookApi函數(shù)里完成對(duì)目標(biāo)進(jìn)程 (亦即當(dāng)前進(jìn)程,因?yàn)楝F(xiàn)在調(diào)用DLL 內(nèi)函數(shù)的就是我們的目標(biāo)進(jìn)程)IAT 表的修改,具體修改方法為: 1.保存原有真實(shí)API地址,因?yàn)?此時(shí)該API已經(jīng)被映射到該進(jìn)程的空 間中,所以該API的函數(shù)名就代表了 其在進(jìn)程空間里的地址;這個(gè)應(yīng)在 SetHookApi外完成,why? 2.定位當(dāng)前進(jìn)程的導(dǎo)入表-IMAGE_IMPORT_DESCRIPTOR數(shù) 組; 3.在導(dǎo)入表中查找要替換的API 所在的DLL模塊對(duì)應(yīng)的 IMAGE_IMPORT_DESCRIPTOR結(jié) 構(gòu); 4.找到后,再定位導(dǎo)入地址表 IAT; 5.找到該DLL的IAT后,通過(guò)之 前保存的真實(shí)API地址與IAT表中的 u1.Function成員進(jìn)行匹配,找到存 放此真實(shí)API地址的內(nèi)存; 6.取得我們定義的用以替代此 API的函數(shù)的地址; 7.WriteProcessMemory進(jìn)行對(duì) IAT表的改寫(xiě)。 ============================= 最后總結(jié)一下利用遠(yuǎn)程注入 DLL、修改IAT表的方式進(jìn)行HOOK API的思路: 1.要想讓目標(biāo)進(jìn)程的API被HOOK 掉,需要讓目標(biāo)進(jìn)程執(zhí)行我們?yōu)槠涠?義的SetHookApi函數(shù),如何能讓目 標(biāo)進(jìn)程執(zhí)行我們定義的函數(shù)呢?方法 是讓目標(biāo)函數(shù)加載我們定義的DLL, SetHookApi的定義也在此DLL里, 利用DLL第一次加載時(shí)會(huì)執(zhí)行 DllMain的機(jī)會(huì),執(zhí)行SetHookApi。 2.新問(wèn)題,如何讓目標(biāo)進(jìn)程調(diào)用 LoadLibrary加載我們定義的DLL 呢?方法是為目標(biāo)進(jìn)程創(chuàng)建一個(gè)遠(yuǎn)程 線程,為其指定線程函數(shù)為 LoadLibrary,參數(shù)為"Mydll.dll"。 這樣目標(biāo)進(jìn)程的新線程創(chuàng)建后就會(huì)加 載我們定義的DLL到目標(biāo)進(jìn)程的空 間。創(chuàng)建遠(yuǎn)程線程的方法是 CreateRemoteThread函數(shù)。 3.至此,完成了DLL的加載, DLL加載后目標(biāo)進(jìn)程會(huì)自動(dòng)執(zhí)行 DllMain,DllMain中執(zhí)行 SetHookApi函數(shù),完成對(duì)API的掛 接。如何完成API的掛接?只需要修 改目標(biāo)進(jìn)程的IAT表。目標(biāo)進(jìn)程又一 個(gè)導(dǎo)入表,記錄了各個(gè)模塊導(dǎo)入到此 進(jìn)程中的導(dǎo)入函數(shù)的信息,其中又有 一個(gè)IAT表記錄導(dǎo)入函數(shù)地址的信 息,只需要修改IAT表中欲覆蓋的API 的函數(shù)地址為我們定義的函數(shù)的地址 即可完成API的掛接 |
|
來(lái)自: 滿天銀雪夢(mèng)一生 > 《計(jì)算機(jī)》