《Windows PE》4.2 绑定导入表

绑定导入表(Bound Import Table)是文件中的一个数据结构,用于存储已经绑定(即完成绑定导入)的外部函数的信息。

本节必须掌握的知识点:

绑定导入表数据结构

实例分析

4.2.1 绑定导入表数据结构

绑定导入是指在程序加载时,将程序所依赖的外部函数的地址与程序的IAT(Import Address Table,导入地址表)进行绑定的过程。这样,程序就可以直接通过IAT中的函数指针来调用这些外部函数,而无需再进行动态链接的过程。

绑定导入可以分为静态绑定和动态绑定两种方式:

●静态绑定:

在编译时,链接器将外部函数的地址直接嵌入到程序的IAT中。

静态绑定生成的可执行文件会包含被依赖函数的实际地址,因此在程序加载时无需进行额外的动态链接操作。

静态绑定的优点是加载速度快,无需运行时动态链接库的支持。但缺点是可执行文件的大小会增加,并且无法在运行时动态地加载新的函数或库。

●动态绑定:

在程序加载时,操作系统会根据IAT中的函数名称来查找并绑定外部函数的实际地址。

动态绑定通过操作系统的动态链接器(如Windows的LoadLibrary和GetProcAddress函数)来实现。

动态绑定的优点是可执行文件的大小较小,可以在运行时动态加载新的函数或库。但缺点是加载过程相对较慢,因为需要进行额外的符号解析和地址重定位操作。

绑定导入的具体实现方式取决于操作系统和编程语言。在Windows环境下,可以使用PE文件格式、IAT表和动态链接库(DLL)来实现绑定导入。在Linux环境下,可以使用ELF文件格式和动态链接器(ld.so)来实现绑定导入。

当可执行文件被绑定时,IAT 中的IMAGE_THUNK_DATA结构将被导入函数的实际地址覆盖。磁盘上的可执行文件具有其 IAT 中其他 DLL 中 API 的实际内存中地址。加载绑定的可执行文件时,Windows 加载程序可以绕过查找每个导入的 API 并将其写入 IAT 的步骤。正确的地址已经在那里了!

您可能对可执行绑定的安全性持怀疑态度。毕竟,如果绑定可执行文件并且它导入的 DLL 发生变化怎么办?比如kernel32.dll,在Windows 2000中其加载到进程空间的基地址为 0x77e60000,而在Windows XP SP3中其加载地址则是0x7c800000。发生这种情况时,IAT 中的所有地址都无效。在为PE加入绑定导入机制的时候,微软就已经考虑到了这个问题,所以假定PE加载前对IAT的修正都是正确的。那么PE的加载速度是加快的,即使绑定以后的EXE程序在其他的兼容系统中运行时,其地址出现错误,PE加载也有检测错误的机制。如果地址检测出错误, PE加载器会重新接管这项工作,加载时对IAT进行修正。

微软提供了一个绑定工具bind.exe程序,该程序可以把导入表中IAT表项IMAGE_ THUNK_DATA32的内容都静态替换成虚拟内存地址,然后在数据目录表的第12项指定的位 置声明这些更改。Windows在加载目标PE相关的动态链接库时,会首先检査这些地址是否正确合法,这些检查包括当前系统的DLL版本是否符合绑定导入结构中描述的版本号,如果不符合或者DLL需要被重新定位,加载器就会去遍历OriginalFirstThunk指向的数组(也就是 INT),计算新的地址。如果导入表是单桥结构,此时的遍历会失效,所以说单桥结构无法实 施静态绑定操作。

注意

绑定导入是一种静态链接的方式,因此在程序运行期间无法更改绑定的外部函数地址。如果需要在运行时动态加载新的函数或库,可以考虑使用延迟加载或手动加载的方式。

本节我们讲述PE文件中的静态绑定导入,我们将在下一小节讲述延迟加载,在第十章详细讲述动态手动加载。

●绑定导入表描述符的结构如下:

typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {

DWORD TimeDateStamp; //时间戳

WORD OffsetModuleName;// 指向DLL的名称

WORD NumberOfModuleForwarderRefs;// ModuleForwarderRef 数目

// Array of zero or more IMAGE_BOUND_FORWARDER_REF follows

} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;

TimeDateStamp,一个 DWORD,其中包含导入的 DLL 的时间/日期戳。

OffsetModuleName,一个 WORD,其中包含指向具有导入 DLL 名称的字符串的偏移量。此字段是与第一个IMAGE_BOUND_IMPORT_DESCRIPTOR的偏移量(不是 RVA)。

NumberOfModuleForwarderRefs,一个 WORD,其中包含紧跟此结构的IMAGE_BOUND_FORWARDER_REF结构数。这些结构与IMAGE_BOUND_IMPORT_DESCRIPTOR相同,只是保留了最后一个 WORD(NumberOfModuleForwarderRefs)。

在简单的情况下,每个导入的DLL的IMAGE_BOUND_IMPORT_DESCRIPTORs将是一个简单的数组。但是,当绑定到转发到另一个DLL的API时,还必须检查转发的DLL的有效性。因此,IMAGE_BOUND_FORWARDER_REF结构与IMAGE_BOUND_IMPORT_DESCRIPTORs结构交织在一起。

假设您链接到HeapAlloc,它被转发到NTDLL中的RtlAllocateHeap。然后在可执行文件上运行BIND。在你的EXE中,你会有一个IMAGE_BOUND_IMPORT_DESCRIPTOR for KERNEL32.DLL,然后是一个IMAGE_BOUND_FORWARDER_REF for NTDLL.DLL。紧随其后的可能是您导入和绑定的其他dll的附加IMAGE_ BOUND_IMPORT_DESCRIPTORs。

绑定导入表中的每个元素都是一个被绑定的外部函数的地址。这些地址在程序加载时由操作系统或动态链接库进行填充,以便程序可以直接调用这些函数。

注意

1.绑定导入表是可选的,不是所有的PE文件都会包含绑定导入表。只有在编译时进行了绑定导入操作,并且保留了绑定导入表的信息,才会在PE文件中存在绑定导入表。

2.绑定导入表的目的是为了提高程序的运行效率,避免了每次运行时都进行动态链接的开销。通过在程序加载时将外部函数的地址直接嵌入到绑定导入表中,可以加快程序的启动速度。

3.同一操作系统的不同版本导入函数地址可能存在差异,这就意味着绑定导入函数的地址可能存在兼容问题的风险。如果绑定导入函数地址错误,操作系统PE加载器会启用导入表实现IAT表的重新绑定。

4.绑定导入表通常会存在于32位PE文件中,64位PE文件并不包含绑定导入表。

4.2.2 实例分析

实验二十九:绑定导入表的定位及解析

绑定导入表的RVA地址及大小信息位于数据目录项的第11项。我们以32位记事本程序为例:

●数据目录项:第11项。

将notepad32.exe拖入WinHex,并找到数据目录项,如下所示:

00000150 00 00 00 00 10 00 00 00 00 00 00 00 00 00 00 00 ................

00000160 04 76 00 00 C8 00 00 00 00 B0 00 00 20 7F 00 00 .v..?...?. ...

00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

00000180 00 00 00 00 00 00 00 00 50 13 00 00 1C 00 00 00 ........P.......

00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

000001A0 00 00 00 00 00 00 00 00 A8 18 00 00 40 00 00 00 ........?..@...

000001B0 50 02 00 00 D0 00 00 00 00 10 00 00 48 03 00 00 P...?......H...

000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

000001D0 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00 .........text...

数据目录项第11项中记录绑定导入表的RVA地址为:00000250H,大小为D0H。

●绑定导入表:位于文件偏移地址250H地址处。

00000250 A2 BD 02 48 58 00 00 00 B6 BD 02 48 65 00 00 00 ⒔.HX...督.He...

00000260 CA BD 02 48 71 00 00 00 6C BD 02 48 7E 00 00 00 式.Hq...l?H~...

00000270 6C BD 02 48 8B 00 00 00 89 BD 02 48 96 00 00 00 l?H?..壗.H?..

00000280 C6 BD 02 48 A3 00 01 00 C5 BD 02 48 B0 00 00 00 平.H?..沤.H?..

00000290 81 BD 02 48 BA 00 00 00 BD BD 02 48 C4 00 00 00 .?H?..浇.H?..

000002A0 00 00 00 00 00 00 00 00 63 6F 6D 64 6C 67 33 32 ........comdlg32

000002B0 2E 64 6C 6C 00 53 48 45 4C 4C 33 32 2E 64 6C 6C .dll.SHELL32.dll

000002C0 00 57 49 4E 53 50 4F 4F 4C 2E 44 52 56 00 43 4F .WINSPOOL.DRV.CO

000002D0 4D 43 54 4C 33 32 2E 64 6C 6C 00 6D 73 76 63 72 MCTL32.dll.msvcr

000002E0 74 2E 64 6C 6C 00 41 44 56 41 50 49 33 32 2E 64 t.dll.ADVAPI32.d

000002F0 6C 6C 00 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C 00 ll.KERNEL32.dll.

00000300 4E 54 44 4C 4C 2E 44 4C 4C 00 47 44 49 33 32 2E NTDLL.DLL.GDI32.

00000310 64 6C 6C 00 55 53 45 52 33 32 2E 64 6C 6C 00 00 dll.USER32.dll..

总结

Notepad32.exe中共有10个_IMAGE_BOUND_IMPORT_DESCRIPTOR绑定导入表描述符结构,对应10个DLL模块名。

每个绑定导入表描述符的前4个字节为时间戳。

OffsetModuleName字段指向DLL模块名。以第一个绑定导入表描述符为例,RVA地址为0058H,FOA地址=0058H+250H=2A8H,即comdlg32.dll。

NumberOfModuleForwarderRefs字段为ModuleForwarderRef (模块转发器)数目。以284H地址处的A3 00 01 00为例,A3指向2F3H地址处的KERNEL32.dll,模块转发器数量为1,即紧随其后的B0 00 00 00,指向300H地址处的NTDLL.DLL。

接下来我们再观察一下IAT导入函数地址表。IAT表的RVA地址为数据目录项的第12项,RVA值为00001000H,大小为348H。位于.text节区的起始位置,即文件偏移地址00000400H地址处。

●节表:绑定导入表位于.text节区400H文件偏移地址处。

000001D0 00 00 00 00 00 00 00 00 2E 74 65 78 74 00 00 00 .........text...

000001E0 48 77 00 00 00 10 00 00 00 78 00 00 00 04 00 00 Hw.......x......

000001F0 00 00 00 00 00 00 00 00 00 00 00 00 20 00 00 60 ............ ..`

00000200 2E 64 61 74 61 00 00 00 A8 1B 00 00 00 90 00 00 .data...?......

00000210 00 08 00 00 00 7C 00 00 00 00 00 00 00 00 00 00 .....|..........

00000220 00 00 00 00 40 00 00 C0 2E 72 73 72 63 00 00 00 ....@..?rsrc...

00000230 20 7F 00 00 00 B0 00 00 00 80 00 00 00 84 00 00 ....?..€...?.

00000240 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@

●IAT导入函数地址表:大小384H,以被填充导入函数的实际VA地址。

00000400 EF 6F DA 77 17 6C DA 77 25 BA DC 77 05 BD DC 77 飋趙.l趙%很w.杰w

00000410 AB 7A DA 77 42 78 DA 77 57 D7 DA 77 00 00 00 00 珃趙Bx趙W宗w....

00000420 70 D2 18 77 00 00 00 00 19 DC F0 77 05 4A F2 77 p?w.....莛w.J騱

00000430 A9 DE F0 77 5F 6E EF 77 56 F4 F0 77 9D 7F EF 77 ┺饂_n飛V麴w..飛

00000440 28 BE EF 77 25 4B F2 77 BB A5 EF 77 AC 7E EF 77 (撅w%K騱互飛瑍飛

00000450 95 56 F2 77 22 FB F1 77 C1 61 EF 77 B3 83 EF 77 昖騱"w羇飛硟飛

00000460 69 5A EF 77 8F 93 EF 77 FA 6B EF 77 B9 7D EF 77 iZ飛.擄w鷎飛箎飛

00000470 DB 5E EF 77 B7 D4 EF 77 05 3A F0 77 AE 3A F0 77 踍飛吩飛.:饂?饂

00000480 10 94 EF 77 70 5B EF 77 00 00 00 00 B8 97 80 7C .旓wp[飛....笚€|

...

相关推荐
冬风诉14 分钟前
汇编内存寻址
汇编
Eternal-Student19 分钟前
预处理、编译、汇编、链接
linux·汇编·windows
学无止境\n1 小时前
[C语言]指针和数组
c语言·数据结构·算法
sp_wxf1 小时前
Stream流
linux·服务器·windows
jyan_敬言2 小时前
【Linux】Linux命令与操作详解(一)文件管理(文件命令)、用户与用户组管理(创建、删除用户/组)
linux·运维·服务器·c语言·开发语言·汇编·c++
芯的一天3 小时前
windows下DockerDesktop命令行方式指定目录安装
windows·docker
jmlinux3 小时前
环形缓冲区(Ring Buffer)在STM32 HAL库中的应用:防止按键丢失
c语言·stm32·单片机·嵌入式硬件
localbob4 小时前
uniapp超全user-agent判断 包括微信开发工具 hbuilder mac windows 安卓ios端及本地识别
windows·macos·uni-app·user-agent
科研小达人4 小时前
ChatGPT进行文本分类
windows·chatgpt