前言
在上一篇文章中介紹了以模擬調(diào)試器的方式來對特定的API地址進(jìn)行鉤取。而這次本篇文章介紹的是修改IAT來同樣達(dá)到鉤取API的目的,這也是一種“劫持”API的方法。
本文為個人學(xué)習(xí)《逆向工程核心原理》書籍的學(xué)習(xí)筆記,如果想深入學(xué)習(xí)逆向工程,推薦去觀摩一下該本書籍。因為它實在是十分的易懂。
原理
當(dāng)程序調(diào)用函數(shù)的時候,它會使用函數(shù)所在模塊名稱進(jìn)行一個查表操作。這個表就是俗稱的IID(IMAGE_IMPORT_DESCRIPTOR)。表上記載著程序調(diào)用目標(biāo)函數(shù)的地址又稱為IAT。待查詢到目標(biāo)函數(shù)地址后,程序才會真正的通過這個函數(shù)地址來進(jìn)行函數(shù)調(diào)用:

而我們只要修改IID中記錄的對應(yīng)的函數(shù)地址IAT,使它指向我們自己定義的另一個MessageBoxA,這樣函數(shù)查表返回的地址就不是原來程序中定義的地址,而是我們修改后指向自己定義函數(shù)的地址了,這就完成了一次IAT“劫持”。

實現(xiàn)思路
整體思路:將修改函數(shù)地址的實現(xiàn),以及新函數(shù)的實現(xiàn)寫在一個DLL里,再編寫一個注入器,將該DLL注入到目標(biāo)程序中。DLL注入成功后便會修改IAT函數(shù)地址,達(dá)到IAT鉤取的目的。
細(xì)節(jié)思路:主要是如何找到該程序的IID里對應(yīng)的IAT。我們可以先使用getModuleHandle(NULL)函數(shù)找到當(dāng)前被注入程序的基址pfile,該基址即是指向當(dāng)前PE文件DOS頭的起始地址。通過DOS頭結(jié)構(gòu)體的e_lfanew屬性,獲取到NT頭的虛擬內(nèi)存空間的偏移量RVA,將該值加上基址pfile即可得到NT頭的虛擬內(nèi)存地址。而最后通過相關(guān)的屬性操作,就可以從NT頭中獲取對應(yīng)的IAT地址。
實踐
獲取IID:
void findIID() {
// 找到IID所在的內(nèi)存地址
//獲取當(dāng)前被注入DLL的程序模塊基址
pfile = (PBYTE)GetModuleHandle(NULL);
//轉(zhuǎn)換成DOS頭
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)pfile;
//通過DOS頭的e_lfanew找到NT頭
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)(pfile + dosHeader->e_lfanew);
//NT頭中的可選NT頭里的DataDirectory數(shù)組,記載著IID的虛擬內(nèi)存偏移地址
IMAGE_DATA_DIRECTORY IATSection = (IMAGE_DATA_DIRECTORY)(ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]);
//加上基址,獲取到IID虛擬內(nèi)存地址
currentIID = PIMAGE_IMPORT_DESCRIPTOR(pfile + IATSection.VirtualAddress);
}
修改IAT
LPCSTR dialogDLL = "user32.dll";
void hookIAT() {
// 修改IAT
while (currentIID)
{
//去IID中的Name字段,其為IAT對應(yīng)的模塊名稱虛擬內(nèi)存地址偏移量,如MessageBoxA是在系統(tǒng)函數(shù)user32.dll中
DWORD RVAName = currentIID->Name;
char* VAName = (char*)(pfile + RVAName);
//對比是否為user32.dll
if (strcmp(VAName, dialogDLL))
{
// 符合條件,取出IAT列表
DWORD RVAIAT = currentIID->FirstThunk;
PIMAGE_THUNK_DATA pthunk = (PIMAGE_THUNK_DATA)(pfile + RVAIAT);
//循環(huán)IAT函數(shù)地址列表,找到MessageBoxA函數(shù)地址
while (pthunk->u1.Function)
{
DWORD funAddress = pthunk->u1.Function;
if (funAddress == (DWORD)oriAddress)
{
//修改IAT
DWORD oldProtect = NULL;
VirtualProtect((LPVOID)&pthunk->u1.Function, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
pthunk->u1.Function = (DWORD)hookMessageBox;
VirtualProtect((LPVOID)&pthunk->u1.Function, 4, oldProtect, &oldProtect);
break;
}
pthunk++;
}
return;
}
currentIID++;
}
}
結(jié)果
例中使用的依舊是上篇文章中編寫的可憐的彈窗程序:


結(jié)論
IAT鉤取具有一定的局限性,我們只能鉤取IAT中存在的函數(shù),而不能無中生有。而我們定義的函數(shù)簽名也必須符合鉤取的原函數(shù)前的簽名;如參數(shù)類型,函數(shù)返回等須相同。
總的來說IAT鉤取還是比較簡單快速的,其難點是尋找IAT的過程。倘若我們對PE文件結(jié)構(gòu)毫無所知,不能理解DOS頭,NT頭等重要結(jié)構(gòu),想必就要多費(fèi)一些心思了吧。