前言
PE文件中的输入表含有三个重要结构IID,INT,IAT。PE文件为需要加载的DLL文件创建一个IID结构,一个DLL与一个IID对应。INT是输入名称表,IAT输入地址表,在没有绑定输入的情况下磁盘中的文件INT与IAT相同。如果有绑定输入的话因为绑定输入的函数其磁盘文件中的IAT项就已经是对应函数的地址了,所以INT与IAT就不同了。
函数隐式调用
我们一般在调用函数的时候都是直接用函数的名称,例如MessageBox()这种其在底层汇编指令上并不是直接调用MessageBox()函数的地址而是调用IAT中对应函数的地址(也就是函数的地址实际存放在IAT中)。一般在PE文件加载进入内存时,windows加载器会根据INT(如果INT为空就根据IAT)的函数信息在对应的DLL中查询实际函数的地址,然后重写到IAT中。也就是在加载到内存前IAT与INT存储的都是输入函数的信息,并不包含函数的实际地址。只有当windows加载器将PE文件加载到内存后IAT中才会存放函数的实际地址,而我们实际调用函数的指令会指向这对应的IAT中函数的位置,完成函数的调用。这种方式称为函数隐式调用。
函数显式调用
如果我们调用函数的时候不直接使用函数名称,而是使用LoadLibrary()和GetProcAddress()函数在运行时动态获取函数的地址,这种函数调用根本不依赖于IAT,称为显示链接。
注意当我们自己的代码直接使用LoadLibrary()和GetProcAddress()函数时其汇编代码就是调用对应的函数地址。
函数绑定输入
对于函数隐式调用而言,因为需要在PE文件加载到内存时通过INT中的函数信息从DLL中获取函数的地址然后写到IAT中,这效率很低。如果我们能让PE文件在没加载到内存时就确定对应IAT中函数的地址的话就可以大大提高效率,这种就称为绑定输入函数。为了防止绑定输入的函数对应的dll发生改变造成函数调用出错,PE文件有对应的绑定输入表可以通过数据目录表访问,在PE文件加载到内存时windows加载器会依据绑定输入表中的信息与加载的dll的信息进行对比,如果发现不一致则会重新改写IAT。
延迟加载
延迟加载dll需要在链接器进行设置,这样会在数据目录表中的延迟加载表中形成对应的数据结构包含对应的IAT和INT,此IAT和INT与正常的IAT和INT一样只不过此IAT不是windows加载器写入的。这样在第一次调用延迟加载的其中一个函数时运行库就会调用LoadLibrary()和GetProcAddress()写入此IAT,然后以后调用此函数都会来到此IAT处。
隐式,显式,绑定输入和延迟加载在HOOK中的使用
因为显式调用不依赖与IAT所以IAT_HOOK对其进行无效,而对于延迟加载的dll需要在其对应的延迟加载dll的IAT处HOOK。对于EAT_HOOK来说其只对后续加载的模块的HOOK有效果,因为如果是隐式调用,其在pe加载到内存时会根据EAT修改IAT,但是自此以后EAT对其就没用了。而后续加载的模块,例如显式调用和延迟加载其会利用EAT获取函数的地址,所以EAT_hook对二者有用.
区分动态/静态/运行时链接和显示/隐式调用
注意动态链接是指将对应Dll中的信息保存到PE文件的IID中,然后调用的函数信息存在IAT和INT中。当PE文件加载到内存中时将IID对应的DLL加入到内存中进一步完成函数的调用。而静态链接是将函数对应的dll中的代码全部整合到PE文件中,这样函数的调用就不依赖于DLL也不会形成对应的IID。运行时链接是与函数的显示调用一起的,通过LoadLibrary()链接DLL,在通过GetProcAddress()获得API的地址显示调用函数。