IAT表的详解(转)

IAT表详解

IAT的全称是Import Address Table。

IAT表是执行程序或者dll为了实现动态加载和重定位函数地址,用到的一个导入函数地址表。这里面记录了每个导入函数的名字和所在的dll名称,在pe加载的时候系统会加载这些dll到用户的地址空间然后把函数地址覆盖这个表里的函数地址,然后重构所有用到这个表的代码,让其调用直接指向实际函数地址(PE是否覆盖不确定,驱动会这么做),PE的IAT表会留在内存,驱动的就丢弃了。

 

对于每一个引入的可执行文件(例如dll),有一个镜像引入描述符(IMAGE_IMPORT_DESCRIPTOR)。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
     union {
          DWORD Characteristics;         // 0 for terminating null import descriptor
          DWORD OriginalFirstThunk;   // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
     };
     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;                             // RVA,指向字符串,是这个可执行文件的名字。例如"ACE.dll"
      DWORD FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;

dll的forward不好讲,因为很多都是undocumented的.

我们着重关心两个指针,OriginalFirstThunk和FirstThunk。
Characteristics一词出于历史原因,故在此给它了一个别名,OriginalFirstThunk。
OriginalFirstThunk和FirstThunk是两个DWORD值,存贮着两个RVA数值,其实它们就是两个指针。
OriginalFirstThunk和FirstThunk实际上都是指向同一个数组。
前者,我们称之为INT,而后者,我们称之为IAT.

 

IAT是一个IMAGE_THUNK_DATA类型的数组。有多少个函数被导入,这个数组就有多少个成员。该数组以0结尾。
typedef struct _IMAGE_THUNK_DATA32 {
     union {
           DWORD ForwarderString;          // 一个RVA地址,指向forwarder string 
           DWORD Function;                       // PDWORD,被导入的函数的入口地址
           DWORD Ordinal;                         // 该函数的序数
           DWORD AddressOfData;           // 一个RVA地址,指向IMAGE_IMPORT_BY_NAME
      } u1;
} IMAGE_THUNK_DATA32;

IMAGE_THUNK_DATA64与IMAGE_THUNK_DATA32的区别,仅仅是把DWORD换成了64位整数。

PIMAGE_IMPORT_BY_NAME是一个非常简单的结构,就两个成员。
typedef struct _IMAGE_IMPORT_BY_NAME {
     WORD Hint;            // 该函数的导出序数
     BYTE Name[1];      // 该函数的名字
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

而IMAGE_THUNK_DATA32就是一个非常魔术般的东西了。
struct IMAGE_THUNK_DATA的大小,恰好等于一个指针的大小。(32bit机器下是32bit,64bit机器下是64bit)

每一个IMAGE_THUNK_DATA对应着一个被导入的函数。
对于可执行文件而言,IAT中的IMAGE_THUNK_DATA中存储的要么是Ordinal,要么是AddressOfData。
怎么判断IMAGE_THUNK_DATA中存储的是Ordinal 还是 AddressOfData 呢?
众所周知,在32bit的机器上,地址空间是00000000-FFFFFFFF,
一般而言,其中00000000-7FFFFFFF是用户空间,其它是系统空间。
于是,看IMAGE_THUNK_DATA的最高位,如果是1,就是Ordinal,否则就是AddressOfData。

但是这里还存在一个2GB的问题。因为2GB的用户地址空间对于很多程序不够用,(主要是数据库系统),于是微软就想了一些变通的办法。例如win 2000的/3GB选项。
在启动文件,boot.ini中加上这个选项后,用户空间变成3GB,系统空间减少到1GB。
然后呢?
然后在链接该可执行文件的时候必须加上特殊的选项,这样在PE头就会有一个特殊的设置。
如果开了3GB选项,如果PE头不加这个设置,那么用户空间是2GB,系统空间是1GB.
如果开了3GB选项,且PE头加了这个设置,那么用户空间是3GB,系统空间是1GB.


而INT和IAT中存储的本来应该是同样的数据。
然后说绑定(binding).
当一个可执行文件被绑定的时候,IAT中的IMAGE_THUNK_DATA被改写为(被导入的)该函数的实际地址。
这一步也许是交给链接器在链接的时候执行,也许是在该可执行文件载入的时候执行。
但是,如果,该可执行文件已经和dll绑定。但是这个dll后来又被更改了,这些被导入的函数依然在该dll中存在,但是实际地址已经改变了。还有,我们保留过一个IAT的副本,它就是INT.(这就是为什么我们称之为Original FirstThunk).根据INT中的内容,我们可以重建IAT表。

 

综上所述,将exe文件绑定到dll的最佳时机,是在安装可执行文件的时候。这就是安装程序,Windows installer,所要做的事情之一。

 

下面说,怎么判断IAT中的信息是否已经过期。
首先,绑定分两种类型,新式的和老式的。
前面已经说过IMAGE_IMPORT_DESCRIPTOR中的TimeDateStamp有三种可能性。
1.TimeDateStamp等于0 =〉 尚未绑定
2.TimeDateStamp等于-1 => 新式绑定
3.其它 => 老式绑定,这里存储的就是上次绑定是在什么时间。

然后我详细介绍下新式绑定
DataDirectory[ IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ]指向一个重要的数据结构。
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
      DWORD TimeDateStamp;      // a DWORD that contains the time/date stamp of the imported DLL.
      WORD OffsetModuleName;    // a WORD that contains an offset to a string with the name of the imported DLL. 
                                                            // This field is an offset (not an RVA) from the first IMAGE_BOUND_IMPORT_DESCRIPTOR.
       WORD NumberOfModuleForwarderRefs;  // 这个结构体后面还有多少个IMAGE_BOUND_FORWARDER_REF 结构体
                                                                                   // Array of zero or more IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;

来看看IMAGE_BOUND_FORWARDER_REF是一个什么样的结构体。
typedef struct _IMAGE_BOUND_FORWARDER_REF {
          DWORD TimeDateStamp;
          WORD OffsetModuleName;
         WORD Reserved;
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
和IMAGE_BOUND_IMPORT_DESCRIPTOR完全相同,除了最后一个字节,它是被保留的。

然后说这两个结构体的作用。

IMAGE_BOUND_IMPORT_DESCRIPTOR的作用很显然。根据TimeDateStamp和OffsetModuleName字段的值我们就可以判断IAT表中的信息是否已经过期。

但是存在这样一种情况。一个dll导到另一个dll中。例如USER32.DLL和KERNEL32.DLL。

假如USER32.DLL未更改,但是KERNEL32.DLL更改过了。此时需要重建USER32.DLL的IAT。但是我们的程序只是直接用到了 USER32.DLL,于是导入表中就没有KERNEL32.DLL的IAT,也没有KERNEL32.DLL的TimeDateStamp和 OffsetModuleName。
所以也就有, 如果一个dll forward了另一个dll,那么在这个dll的IMAGE_BOUND_IMPORT_DESCRIPTOR结构体后面需要再插入被forward的 dll的IMAGE_BOUND_FORWARDER_REF结构体。之后才是下一个dll的 IMAGE_BOUND_IMPORT_DESCRIPTOR。

IMAGE_BOUND_IMPORT_DESCRIPTOR.NumberOfModuleForwarderRefs的意义就不言而喻了。

每个dll一个IAT表,一般而言,这些IAT表都是统一存储在一起的。由于每个IAT表是以0结尾。所以很容易分离开来

 

posted @ 2017-09-23 14:36  gd_沐辰  阅读(6728)  评论(0编辑  收藏  举报