深入理解PE结构中的IAT
先写一个简单的程序:
#include "stdafx.h"
#include <Windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
MessageBox(0,0,0,0);
ExitProcess(0);
return 0;
}
生成release版本的exe,然后使用od进行调试,找到调用这2个函数的地方
调试之前,先把ALSR关闭或者生成之后使用二进制编辑器修改为00 81,防止下次加载的时候,加载地址变化

我们在od中找到调用 MessageBox和ExitProcess的地方,发现
call MessageBox =======> call [0x4020A8] =====>call 760200EF
call ExitProcess =======> call [0x402000] =====>call 74E87A28
OD加载exe,相当于把程序运行起来了。

我们静态分析一下该程序中 [0x4020A8] 和 [0x402000] 这个2个地址在磁盘文件中存储的是什么值,是不是就是程序中的这2个值。
call MessageBox =======> call [0x4020A8] =====>call 760200EF
call ExitProcess =======> call [0x402000] =====>call 74E87A28

当前文件的ImageBase地址是0x400000,那么 0x4020A8 - 0x400000 = 0x20A8(RVA) <====>0xEA8(FOA),0x402000 - 0x400000 = 0x2000(RVA)<====>0xE00(FOA)

从图中可以看到 [0xEA8]--->0x00002320, 这个值与760200EF看起来没什么关系,
[0xE00]--->0x00002304, 这个值与74E87A28看起来没什么关系
这个时候把 0x00002320当做一个RVA,转换为FOA是0x1120,继续找[0x00001120],
[0x1120]---> 0x0215
MessageBox 我们要调用的MessageBox函数的名称

把 0x00002304当做一个RVA,转换为FOA是0x1104,继续找[0x1104],
[0x1104]---> 0x0119
ExitProcess 我们要调用的ExitProcess函数的名称

现在就出现1个问题,exe运行前和运行后里面的值不一样。
程序运行的时候,和PE文件拉伸到内存中的一样的。先在4GB的虚拟内存中把exe贴进去,然后贴要用到的各个dll。

我们在exe中引用动态链接库的时候,分为隐式链接和显示链接,隐式链接是什么时候用,什么时候把dll加载进去,同时也可以随时卸载dll。
显示链接是在程序开始的时候都把dll加载进去,程序结束,卸载dll。
如果编译、链接之后exe把用到dll里面函数的位置,都填为绝对地址,那么如果发生:1、dll更新版本了,2、dll没有占住ImageBase这个位置,加载到别的位置。

那么这个时候调用的绝对地址就会发生问题,因为dll更新版本,函数的地址就会发生变化;或者dll没有占住ImageBase这个位置,加载到别的位置,那么里面的函数地址也变了(原来是760200EF,那么现在肯定是0x85XXXXXX),如果还用原来的绝对760200EF地址,肯定会出问题。
这个时候就引入了IAT表(导入地址表),导入地址表与导入表关系密切,下一章详细分析导入地址表的结构体。
导入地址表:
就是在程序中先存着指向要执行的函数的名称或者序号,当使用到dll中的函数的时候,使用GetProcessAddress(hInstance,函数名或者序号)找到函数的地址FunctionAddress,然后把这个FunctionAddress填到IAT表中。
而GetProcessAddress(hInstance,函数名或者序号)查找函数地址的原理,在导出表中我们已经实现过了,hInstance是句柄,句柄是什么,句柄本质就是ImageBase。
因为不管dll加载到什么位置,加载到ImageBase,加载不到也好,我们都可以使用句柄我们先找到要用的dll。
函数名或者序号:在导入表中有3张表,通过这3张表找到函数名或序号找到的函数地址FunctionAddress。
然后把找到的函数地址FunctionAddress填到IAT中,那么再调用USER32,dll中的MessageBox就不会出问题。
IAT就是这样设计的。
加载前:

加载后:

查找IAT表的2种方式:
第一种:通过导入表 (下一章详细介绍)
第二种:数据目录项中 DataDirectory[0xC].RVA

0x2000 <===>0xE00(FOA) IAT表

这里找到第一个元素0x00002304 < ====>0x1104(FOA)

打印IAT表的代码
void PrintfIATDirectory(void* FileBuffer)// FileBuffer为磁盘文件读到内存中的首地址
{
unsigned int* NTOffset = (unsigned int*)((char*)FileBuffer + 0x3c);
PE_ntHeader* lpNtHeader=(PE_ntHeader*)((unsigned char*)FileBuffer + *NTOffset);
if(lpNtHeader->SizeofOptionalHeader == 0xF0)
{
//lpOptionHeade
PE_OptionalHeader64* lpOptionHeader1= (PE_OptionalHeader64*)((char*)FileBuffer + *NTOffset + 24);
unsigned int ImportTableOffset = RvaToFoa(FileBuffer,lpOptionHeader1->DataDirectory[0xC].RVA);
int IATSizeof = lpOptionHeader1->DataDirectory[0xC].SIZE;
if(ImportTableOffset == 0)
{
printf("使用数据目录项查找的IAT表不存在!\n");
}else{
unsigned long long* TATTable= (unsigned long long*)((unsigned char*)FileBuffer + ImportTableOffset);
while(IATSizeof >=0)
{
printf("%016llx\n",*TATTable);
TATTable++;
IATSizeof = IATSizeof-8;
}
}
}else if(lpNtHeader->SizeofOptionalHeader == 0xE0){
PE_OptionalHeader32* lpOptionHeader1= (PE_OptionalHeader32*)((char*)FileBuffer + *NTOffset + 24);
unsigned int ImportTableOffset = RvaToFoa(FileBuffer,lpOptionHeader1->DataDirectory[0xC].RVA);
int IATSizeof = lpOptionHeader1->DataDirectory[0xC].SIZE;
if(ImportTableOffset == 0)
{
printf("使用数据目录项查找的IAT表不存在!\n");
}else{
unsigned int* TATTable= (unsigned int*)((unsigned char*)FileBuffer + ImportTableOffset);
while(IATSizeof >0)
{
printf("%08x\n",*TATTable);
TATTable++;
IATSizeof = IATSizeof-4;
}
}
}
}

浙公网安备 33010602011771号