导入表是为实现代码重用而设置的。通过分析导入表数据, 可以知道程序调用的函数名单,和其所在的动态链接库(DLL)。Windows 加载器在运行PE 时会将导入表中声明的动态链接库一并加载到进程的地址空间, 并修正指令代码中调用的函数地址。在IMAGE_OPTIONAL_HEADER32数据目录中一共有四种类型的数据与导入表数据有关。这四种数据依次为:

  • 导入表:import_directory,位于DataDirectory[1]
  • 导入函数地址表:IAT Directory,位于DataDirectory[12]
  • 绑定导入表:bound_import Directory,位于DataDirectory[11]
  • 延迟加载导入表:delay_import Directory,位于DataDirectory[13]

1 导入表双桥结构

以 Kernel32.dll 为例,其在pe文件中结构如下

1.1 导入表——DataDirectory[1]

DataDirectory结构如下:

typedef struct _IMAGE_DATA_DIRECTORY {
 DWORD VirtualAddress;    //数据起始RAV(内存中的地址)
 DWORD Size;            //数据块长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

导入表目录项[1]中,DataDirectory[1].VirtualAddress指向若干个结构体导入表描述符IMAGE_IMPORT_DESCRIPTOR。每个描述符结构体大小为20字节,代表一个导入的DLL,最后一组为全0结构, 表示导入表描述已经结束。其实, Windows在查找导入表的时候并不一定要求最后一组的20个字节都为0, 只要其中的字段Name是0就已经满足结束条件了

1.2 导入表描述符——IMAGE_IMPORT_DESCRIPTOR

IMAGE_IMPORT_DESCRIPTOR结构如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk;//指向输入名称表的表(INT)的RVA,桥1
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;//指向导入映像文件(DLL)的名称的RVA
DWORD FirstThunk;//指向输入地址表的表(IAT)的RVA,桥2
   
} IMAGE_IMPORT_DESCRIPTOR;
  • OriginalFirstThunk,指向若干个结构体导入名称表(INT) IMAGE_THUNK_DATA32,每个结构体4字节,该结构体保存了被导入的函数信息,简称桥1。
  • Name,指向DLL名称字符串的RVA
  • FirstThunk,与OriginalFirstThunk相同, 它指向的链表定义了针对Name这个动态链接库引入的所有导入函数, 简称桥2。

1.3 导入地址表(IAT)和导入名称表(INT)——IMAGE_THUNK_DATA32

IMAGE_THUNK_DATA32结构如下:

typedef struct  _IMAGE_IMPORT_BY_NAME {
union u1{
DWORD ForwarderString; //指向函数名字符串的RVA
DWORD Function; //函数的内存地址RVA?
DWORD Ordinal; //函数在DLL内的序号hint
PIMAGE_IMPORT_BY_NAME AddressOfData; //指向PIMAGE_IMPORT_BY_NAME结构体
};
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

IMAGE_THUNK_DATA32,每个结构体4字节,每个结构体保存了一个函数的相关信息,最后一个结构体为全0时表示该表(IAT或者INT)结束。其实每个结构体都保存了一个RVA,该RVA指向另一个结构体,IMAGE_IMPORT_BY_NAME;这个结构大小不确定, 是桥1的最终目的地。但在PE文件中尽管通过桥2和桥1指向的数值相同, 但其存储的位置却是不同的。当PE被加载进虚拟地址空间以后, IAT 的内容会被操作系统更改为函数的VA。这个修改最终会导致桥2发生断裂。

1.4 函数名称表——IMAGE_IMPORT_BY_NAME

IMAGE_IMPORT_BY_NAME结构如下:

typedef struct _IMAGE_IMPORT_BY_NAME {
   WORD    Hint;
   BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
  • hint,表示在DLL中该函数的编号
  • Name,大小不确定,指向函数名称字符串的首地址

2 调导入表机制总览