导入表

结构

1
2
3
4
5
6
7
8
9
10
11
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; //RVA 指向IMAGE_THUNK_DATA结构数组(INT表 Image Name Table)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; //时间戳
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //RVA 指向dll名字,以0结尾
DWORD FirstThunk; // RVA 指向IMAGE_THUNK_DATA结构数组(IAT表 Image Address Table)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

单个导入表占20个字节,导入表数量不确定,以20个字节0结束,以此判断导入表的数量。


IAT表

OriginalFirstThunk和FirstThunk分别指向了INT表和IAT表,由IMAGE_THUNK_DATA数组组成,以4个字节0为结束,以此判断IMAGE_THUNK_DATA的数量,此数量代表依赖于模块中函数的数量。

INT表和IAT表在PE加载前表中所存的内容都是一样的

如下图,程序在调用dll中的函数时在运行时才会替换地址,运行前储存的是一个字符串的地址,只有调用自身的函数时地址才是固定的,因为程序运行前并没有确定dll的基址,dll的IMAGEBASE可能会变动(重定位)


获取函数名称

IMAGE_THUNK_DATA结构体 占4个字节,判断二进制最高位是否为1,如果是,则除去最高位的值就是函数的导出序号,如果不是,那么这个值是一个RVA 指向IMAGE_IMPORT_BY_NAME,结构如下

1
2
3
4
5
6
7
8
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
1
2
3
4
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //可能为空,编译器决定,如果不为空 是函数在导出表中的索引(不是导出序号)
CHAR Name[1]; //函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME

获取函数地址

在程序中只要调用了其他dll的函数,编译器生成的汇编都是间接call,间接calI的地址指向IAT表,在PE加载到内存后,IAT表不在储存IMAGE_THUNK_DATA结构体,而是储存函数的地址,如下