IAT Hook 导入表
每一个模块都会有导出表,导入表。IAT HOOK就是修改导入表内的函数,使得模块中的 CALL [XXXXX] call到自己的函数里面去。执行自己的功能
模块开始是 IMAGE_DOS_HEADER,这个结构内有一个e_flew成员,这是个偏移,h_module+e_flew可以得到 IMAGE_NT_HEADER,Nt头内包含了很多信息,导入表也在这里面。
PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)h_module;
PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)(LONG(h_module)+pDosHeader->e_lfanew);
pNtHeader->OptionHeader->DataDirectory[] ,这个DataDirectory包含的IMAGE_DATA_DIRECTORY结构共有16个。其中都有定义,每个结构分别负责什么,IMAGE_DIRECTORY_ENTRY_IMPORT(定义为1)负责导入表,就是第二个IMAGE_DATA_DIRECTORY结构.这个结构内包含了size以及VirtualAddress,也就是大小,以及导入表结构的偏移,
因此.模块基址+结构内的偏移就可以得到指向导入表结构的指针了。
PIMAGE_IMPORT_DESCRIPTOR pId=(PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)h_module+pNtHeader->OptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
IMAGE_IMPORT_DESCRIPTOR 结构为
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // (PIMAGE_THUNK_DATA)结构,也就是hint保存的地址 以及函数名字的地址
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //导入模块的名称的偏移,比如"kernel32.dll"
DWORD FirstThunk; // 导入函数的地址的偏移
} IMAGE_IMPORT_DESCRIPTOR;
导入了多少个模块的函数就有多少个 IMAGE_IMPORT_DESCRIPTOR结构,因此获取的时候可以通过一个 PIMAGE_IMPORT_DESCRIPTOR指针循环枚举
知道某个结构的FirstThunk值为0 就停止。
FirstThunk 还有OriginalFirstThunk 偏移指向的都是IMAGE_THUNK_DATA结构数组(其实就是一个DWORD数组)。这个union结构在不同的时候有不同的意思,由于IAT HOOK肯定是程序运行了。因此我说的是这个模块被加载到内存以后的状态。
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
当用FirstThunk的时候 要用的就是Function,此时FUNCTION里面的内容已经不再是偏移了,如下中p->u1.Function就已经是某一个导入函数的地址。而 p->u1.Function的地址就是我们需要的东西了,我们修改地址内的值就可以让CALL 到我们的函数里去。
p->u1.Function=我们的函数地址;
PIMAGE_THUNK_DATA p=(PIMAGE_THUNK_DATA)((BYTE*)h_module+pId->FirstThunk);
然后可以用p++循环得到所有导入函数的实际地址,注意是导入函数的地址,不是保存导入函数的地址。
例如 是 p->u1.Function=0x77f14840就是函数地址(假设为 GetModuleHandleA)。若要取得保存它的地址,只要& p->u1.Function 就行了,例如得到& p->u1.Function=0x42204 (假设);
所有 该模块中调用ACLL GetModuleHandleA的地方会编译为 CALL [42204],我们修改42202里面的值就可以CALL到我们的函数了。
用OriginalFirstThunk的时候需要用的是AddressOfData ,AddressOfData还是个偏移( PIMAGE_IMPORT_BY_NAME),这个偏移指向了一个PIMAGE_IMPORT_BY_NAME,然后这指针这里的这个结构有两部分,前一部分的WORD是函数的序号(hint),后一部分就是函数的字符串ASCII
PIMAGE_THUNK_DATA p2=(PIMAGE_THUNK_DATA)((BYTE*)h_module+pId->OriginalFirstThunk);
(BYTE*)h_module+p2->u1.AddressOfData就是 HINT
(char*)((BYTE*)h_module+p2->u1.AddressOfData+2);这就是函数名;
然后p2++;循环
这里的 p p2的数据是一一对应的,
所以同时p++;p2++
就可以通过函数名字获得函数的地址以及保存它的地址了。
传入模块句柄,需要IAT HOOK的函数名 ,这个函数所在的模块名。成功返回保存函数地址的地址。否则返回0;
DWORD Addr=ReadIATTable(GetModuleHandle(NULL),"GetCurrentThreadId","Kernel32.dll");
printf("\n\n0x%08x\n\n",Addr);
最后输出 0XD82008
而0XD82008里面保存的是 7712F1B2 也就是GetCurrentThreadId的地址,修改0XD82008里面的值就可以达到IAT hook的效果
注意,如果函数不是用名字导入的。而是用序号导入的,那么p2->u1.AddressOfData 以及p->u1.Function里面的DWORD数据的最高位就是1 后一个WORD就是导入序号
比如p2->u1.AddressOfData ==0X80000010==p->u1.Function这就表示该函数是用序号导入的
DWORD ReadIATTable(HMODULE h,char * funcname,char *modulename)
{
if(h==NULL)
return 0;
PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)\
(LONG(h)+pDosHeader->e_lfanew);
PIMAGE_OPTIONAL_HEADER pOpt=&pNtHeader->OptionalHeader;
PIMAGE_IMPORT_DESCRIPTOR pId=(PIMAGE_IMPORT_DESCRIPTOR)((BYTE*)h+pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while(pId->FirstThunk)
{
PIMAGE_THUNK_DATA p=(PIMAGE_THUNK_DATA)((BYTE*)h+pId->FirstThunk);
PIMAGE_THUNK_DATA p2=(PIMAGE_THUNK_DATA)((BYTE*)h+pId->OriginalFirstThunk);
if(strcmpi(modulename,(char*)((BYTE*)h+pId->Name))==0)
{
while(p->u1.Function)
{
if(strcmpi(funcname,\
(char*)((BYTE*)h+p2->u1.AddressOfData+2))==0)
{
return (DWORD)&p->u1.Function;
}
p2++;
p++;
}
}
pId++;
}
return 0;
}