1.导入表
1.0 什么是导入表?
告诉系统你需要用到哪些dll,用哪些函数。
1.1 导入表定位
1.可选PE头中_IMAGE_DATA_DIRECTORY DataDirectory[16]
的第二个目录项指向导入表开始的地址。
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //RVA 指向导入表结构 DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
2.通过上述地址中的RVA计算出FOA,找到真正的导入表开头,调用多个dll就会有如下多个_IMAGE_IMPORT_DESCRIPTOR
结构。
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; ...... typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; sizeOf(IMAGE_IMPORT_DESCRIPTOR) 个 0 代表导入表结束
3.每一个上述结构体中的OriginalFirstThunk
和FirstThunk
分别指向其对应的INT表
和IAT表
。
1.2 导入表的基本结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; //RVA,指向IMAGE_THUNK_DATA结构数组(INT表,导入名称表) }; DWORD TimeDateStamp; //时间戳 DWORD ForwarderChain; DWORD Name; //RVA,指向dll名字,该名字已0结尾 DWORD FirstThunk; //RVA,指向IMAGE_THUNK_DATA结构数组(IAT导入地址表) } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
1.2.1 PE文件加载前的导入表
在PE文件加载之前,其实INT表和IAT表当中存储的值是完全相同的(未导入绑定表的情况下),存储的内容都是RVA或者是一个序号。
1.2.2 PE文件加载后的导入表
加载之后IAT表当中存储函数的地址。
1.2.3 加载过程中IAT表修正过程
遍历INT表,判断INT表结构中IMAGE_THUNK_DATA32(DWORD)中的首位是否为1,首位为1则表示是一个序号,首位不是1表示一个用来保存函数名称的结构的RVA地址,该结构如下:
typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint;//两个字节,是一个没用的序号,可能被编译器全改成0。 BYTE Name[1];//由于名称长度不一定,所以声明只写了1个字节,其实读到0才是一个Name的结束。 } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
至此,便可以根据dll的名称和函数的名称或序号去修复对应导入表中的IAT表中的内容。
1.3 绑定导入表
1.3.0 什么是绑定导入表?
在程序中提前将导入表中IAT表的值写死,那么导入表绑定的时间存储在绑定导入表中。
1.3.1 提前绑定导入表利弊
-
利:经常需要调用的某些exe,如系统自带的
notepad.exe
程序,为了保证启动速度更快,运行时不需要一个个dll去查询函数地址因此对函数地址进行提前绑定。 -
弊:如果所调用dll没能占住所确定地址则IAT表中固定的地址无效需要重新修正。
1.3.2 系统如何判断是否绑定了导入表?
导入表中TimeDateStamp
字段如果为0xFFFFFFFF
则进行了绑定导入表,如果这个值为0
则没有提前绑定导入表,此时真正的绑定时间存储在IMAGE_BOUND_IMPORT_DESCRIPTOR
的TimeDateStamp
中。
1.3.3 绑定导入表定位
- 1.数据目录的第12项存储着绑定导入表的RVA。
- 2.RVA转FOA获得绑定导入表的真实位置,指向如下struct开头地址。
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR { DWORD TimeDateStamp;//所绑定dll的时间戳,用来和所绑定的dll对比看dll是否更新了。 WORD OffsetModuleName;//dll的名称,第一个_IMAGE_BOUND_IMPORT_DESCRIPTOR的值+OffsetModuleName才是真正的dll名称的RVA。 WORD NumberOfModuleForwarderRefs;//该dll所依赖的dll数量 } IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
- 3.如果所依赖的dll数量为n,则紧接着这个结构会跟着n个如下结构用于说明所依赖的dll:
typedef struct _IMAGE_BOUND_FORWARDER_REF { DWORD TimeDateStamp;//该dll的时间戳,用来和所绑定的dll对比看dll是否更新了。 WORD OffsetModuleName;//dll的名称 WORD Reserved;//保留字段 } IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
- 4.紧接着为一个新的
IMAGE_BOUND_IMPORT_DESCRIPTOR
的内容或者是一个全0的IMAGE_BOUND_IMPORT_DESCRIPTOR
表示绑定导入表结束。
示意图如下图所示:
1.3.4 重新修改IAT表的情况
- 1.绑定导入表中的
TimeDateStamp
和对应dll的TimeDateStamp
不一致。 - 2 .没有成功占住预定地址,dll需要重定位。