绑定导入表

绑定导入的基本概念

在程序加载前,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; // 额外引用的 DLL 数量
} 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_FORWARDER_REF 的作用
    描述 DLL 的转发依赖关系。

  • 绑定导入表的结构图

image-20250103222659439

加载时的注意事项

  • 如果 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;
}