绑定导入表
绑定导入的基本概念
在程序加载前,IAT(Import Address Table)和INT(Import Name Table)中的内容通常相同,表示程序引用的 DLL 中函数的名称或序号。加载完成后,IAT 中的内容会被替换为函数的实际地址。
- 绑定导入的意义
- 如果 DLL 文件更新,绑定导入表的时间戳和 DLL 文件的时间戳将不同,此时需要修正函数序号、函数名称和函数地址表。
- 加载前直接写绝对地址:在 IAT 表中保存绝对地址,可以加快程序启动速度,因为省去了修复 IAT 表的步骤。
- 优点
- 加快程序启动速度:在启动程序时需要申请4gb内存空间、贴exe、贴dll、将IAT表修复为地址等等,如果直接用绝对地址,则省去了修复IAT表的操作;
- 缺点
- DLL 重定位时,如果 DLL 没有占据其自身的 ImageBase 地址,需要修复绝对地址。
- DLL 修改时,IAT 表中的函数地址可能不正确,需要重新修复。
Windows 提供的一些程序(如记事本)使用了这种绑定导入方式。
判断绑定导入的方法
在导入表的结构中有一个属性:TimeDateStamp
,其值可以反映是否使用了绑定导入:
- TimeDateStamp = 0
表示当前的 DLL 函数未绑定,程序加载时会通过系统函数获取函数地址。
- TimeDateStamp = -1
表示当前的 DLL 函数已经绑定,绑定时间存储在绑定导入表中。
绑定导入表
PE 文件在加载 EXE 时,通过 IMAGE_IMPORT_DESCRIPTOR
结构中的 TimeDateStamp
判断是否需要重新计算 IAT 表中的地址。
- 绑定导入表的位置
数据目录的第 12 项。
- 绑定导入表的结构
1 2 3 4 5
| typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR { DWORD TimeDateStamp; WORD OffsetModuleName; WORD NumberOfModuleForwarderRefs; } IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
|
- 字段说明
- TimeDateStamp
表示绑定时 DLL 的时间戳,与 DLL 文件 PE 头的时间戳对比,用于判断是否为同一版本。
- OffsetModuleName
表示 DLL 的名称,通过 第一个 DESCRIPTOR 的值 + OffsetModuleName
计算得出。
- NumberOfModuleForwarderRefs
表示当前 DLL 额外依赖的 DLL 数量。
当 NumberOfModuleForwarderRefs > 0
时,会包含额外的 IMAGE_BOUND_FORWARDER_REF
结构:
1 2 3 4 5
| typedef struct _IMAGE_BOUND_FORWARDER_REF { DWORD TimeDateStamp; WORD OffsetModuleName; WORD Reserved; } IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
|
加载时的注意事项
- 如果
IMAGE_BOUND_IMPORT_DESCRIPTOR
中的 TimeDateStamp
与 DLL 文件 PE 头中的 TimeDateStamp
不符,或者 DLL 需要重新定位,则需要重新计算 IAT 中的值。
- 绑定导入可以显著提升启动速度,但需要对 DLL 的完整性和一致性进行严格检查。
这种方式通过绑定时间和依赖关系的优化,提升了程序加载效率,同时也对程序的动态库依赖管理提出了更高要求。
Work
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include "PE.h"
bool StructIsNull( IN LPVOID obj, IN size_t size ) { for (DWORD i = 0; i < size; i++) { if (*((PCHAR)obj + i) != 0) { return false; } } return true; }
void PrintBoundImportTable( IN LPVOID pe ) { PIMAGE_DOS_HEADER pDosHeader = nullptr; PIMAGE_FILE_HEADER pFileHeader = nullptr; PIMAGE_OPTIONAL_HEADER32 pOptionalHeader = nullptr; PIMAGE_SECTION_HEADER* pSectionHeaderArr = nullptr; AnlyzePE(pe, pDosHeader, pFileHeader, pOptionalHeader, pSectionHeaderArr);
PIMAGE_BOUND_IMPORT_DESCRIPTOR pBoundImportTable = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)((PCHAR)pe + RvaToFoa(pe, pOptionalHeader->DataDirectory[12].VirtualAddress)); PIMAGE_BOUND_IMPORT_DESCRIPTOR pFirstBoundImportTable = pBoundImportTable; while (!StructIsNull(pBoundImportTable, sizeof(*pBoundImportTable))) { printf(">>>>>>>>>> 主DLL <<<<<<<<<<\n"); printf("主DLL绑定时间戳 = %d\n", pBoundImportTable->TimeDateStamp); printf("主DLL名称 = %s\n", (PCHAR)pFirstBoundImportTable + pBoundImportTable->OffsetModuleName); printf("主DLL依赖数量 = %d\n", pBoundImportTable->NumberOfModuleForwarderRefs); printf(">>>>> 副DLL <<<<<\n"); for (DWORD i = 0; i < pBoundImportTable->NumberOfModuleForwarderRefs; i++) { PIMAGE_BOUND_FORWARDER_REF rely = (PIMAGE_BOUND_FORWARDER_REF)((PCHAR)pBoundImportTable + (sizeof(IMAGE_BOUND_FORWARDER_REF) * i)); printf("依赖DLL绑定时间戳 = %d\n", rely->TimeDateStamp); printf("依赖DLL名称 = %s\n", (PCHAR)pFirstBoundImportTable + rely->OffsetModuleName); } pBoundImportTable = (PIMAGE_BOUND_IMPORT_DESCRIPTOR)((PCHAR)pBoundImportTable + (sizeof(IMAGE_BOUND_FORWARDER_REF) * pBoundImportTable->NumberOfModuleForwarderRefs)); } free(pSectionHeaderArr); }
int main() { LPVOID pe = ReadPE(R"(D:\source\repos\dllmain\Debug\MyDll.dll)"); if (pe) { PrintBoundImportTable(pe); free(pe); } return 0; }
|