PE格式,IAT目录有什么用

Yuv*_*val 7 windows portable-executable

在 PE 格式中,我们有导入表目录(由 访问IMAGE_DIRECTORY_ENTRY_IMPORT)和 IAT 目录(由 访问IMAGE_DIRECTORY_ENTRY_IAT)都是可选头数据目录的一部分。

使用导入表,加载器动态加载和解析必要的库和函数。这是通过迭代导入地址表 RVA(Thunk 表)来完成的,该表是导入表的一部分。

那么,如果我们使用导入目录进行导入解析,我们需要 IAT 目录做什么?

我一直在阅读Microsoft PE 规范,但找不到答案。此外,SO 中有一些问题,但其中大多数使用 IAT 来引用 Thunk 表而不是 IAT 目录。

谢谢

编辑

我认为导入地址表是导入表目录中的一个字段,与称为 IAT 目录的导入地址表之间存在混淆。我的问题是关于 IAT 目录。

再次感谢

Han*_*ant 6

您链接的 PE 规范第 5.4.4 章中对此进行了很好的描述。它们是相同的表:

导入地址表的结构和内容与导入查找表的结构和内容相同,直到文件被绑定。在绑定期间,导入地址表中的条目将被导入符号的 32 位(对于 PE32)或 64 位(对于 PE32+)地址覆盖。这些地址是符号的实际内存地址,尽管在技术上它们仍被称为“虚拟地址”。加载程序通常处理绑定

也许解释为什么这样做很重要。PE 文件通过直接映射到内存来加载到进程中。底层操作系统原语是内存映射文件。这提供了几个重要的优化:

  • 可执行文件使用的内存不必由分页文件支持。如果操作系统需要 RAM 用于另一个进程,则可以简单地丢弃映射到可执行文件的页面。当进程产生页面错误时,从 PE 文件再次重新加载。

  • 进程为其可执行代码使用的 RAM 可以由进程的任何实例共享。换句话说,当你开始Notepad.exe的多次再有就是只有一个在RAM中的代码的副本。每个进程共享相同的页面。这对于 DLL 尤其重要,尤其是在每个进程中使用的操作系统 DLL,如 ntdll.dll、kernel32.dll 和 user32.dll(等等)。

当加载器用导入函数的实际地址填充 IAT 时,操作系统会重新映射 IAT 的页面,并让它们由分页文件支持。所以每个进程都可以有自己的一组导入地址。包含代码和导入表的其余页面仍然共享。


Elm*_*mue 5

这里没有人回答你的问题。原因是 IMAGE_DIRECTORY_ENTRY_IAT 实际上没有记录。

我研究了 ReactOS 的代码,他们使用这个目录来了解它是如何工作的。然后我编写了自己的代码来证实我的理论。这是我的结果。

我将基于 Windows XP SP3 中的 32 位 Calc.exe 的示例进行解释。

当您列出 Calc.exe 的所有目录时,您会得到:

 0 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_EXPORT
 1 VirtAddr: 00012B80  Size: 0000008C  IMAGE_DIRECTORY_ENTRY_IMPORT
 2 VirtAddr: 00016000  Size: 00008A5C  IMAGE_DIRECTORY_ENTRY_RESOURCE
 3 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_EXCEPTION
 4 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_SECURITY
 5 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_BASERELOC
 6 VirtAddr: 00001240  Size: 0000001C  IMAGE_DIRECTORY_ENTRY_DEBUG
 7 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_ARCHITECTURE
 8 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_GLOBALPTR
 9 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_TLS
10 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG
11 VirtAddr: 00000260  Size: 00000080  IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
12 VirtAddr: 00001000  Size: 00000228  IMAGE_DIRECTORY_ENTRY_IAT
13 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
14 VirtAddr: 00000000  Size: 00000000  IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR
etc..
Run Code Online (Sandbox Code Playgroud)

可以看到有3个与import相关的目录:

IMAGE_DIRECTORY_ENTRY_IAT
IMAGE_DIRECTORY_ENTRY_IMPORT
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
Run Code Online (Sandbox Code Playgroud)

正如您在上面看到的,IAT 目录从偏移量 0x1000 开始,有 0x228 字节。它仅由指向导入的 DLL 的函数指针组成。在 32 位进程中,每个指针有 4 个字节,因此有 (0x228 = 552) / 4 = 138 个条目。我写了一个循环来列出它们:

Addr 01001000 --> function 77DA22EA
Addr 01001004 --> function 77DA23D7
Addr 01001008 --> function 77DA189A
Addr 0100100C --> function 00000000
Addr 01001010 --> function 77C41E2E
Addr 01001014 --> function 77C41D83
Addr 01001018 --> function 77C41EFF
Addr 0100101C --> function 00000000
Addr 01001020 --> function 77E59F93
Addr 01001024 --> function 77E605D8
Addr 01001028 --> function 77E5A5FD
Addr 0100102C --> function 77E7A9AD
Addr 01001030 --> function 77E536A3
Addr 01001034 --> function 77E53803
Addr 01001038 --> function 77E4E341
Addr 0100103C --> function 77E58D60
Addr 01001040 --> function 77E41BE6
Addr 01001044 --> function 77E52A2B
Addr 01001048 --> function 77E4177A
Addr 0100104C --> function 77E4C879
Addr 01001050 --> function 77E51B14
Addr 01001054 --> function 77E530C1
Addr 01001058 --> function 77E5AC37
Addr 0100105C --> function 77E54A69
etc...
Run Code Online (Sandbox Code Playgroud)

这就是所谓的导入地址表,模块中的代码在其中查找对外部函数的调用。如果您的代码调用 GetLastError(),它会在此处查找该函数在 Kernel32.dll 中的位置。

此列表中的 00000000 标记有另一个 DLL 正在跟随。仅此列表是没有用的,因为您不知道这些地址的含义是什么。

重要提示:每个可执行文件都有一个 IAT。但并非每个可执行文件都使用 IMAGE_DIRECTORY_ENTRY_IAT 公开它。

如果可执行文件另外还有一个 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 目录,则 IAT 会附带预加载的值。绑定目录由 IMAGE_BOUND_IMPORT_DESCRIPTOR 和 IMAGE_BOUND_FORWARDER_REF 条目链组成。这是内容:

Bound DLL: SHELL32.dll   Timestamp: 3B842039
Bound DLL: msvcrt.dll    Timestamp: 3B842039
Bound DLL: ADVAPI32.dll  Timestamp: 3B842038
Bound DLL: KERNEL32.dll  Timestamp: 3B842038
Bound DLL: GDI32.dll     Timestamp: 3B842039
Bound DLL: USER32.dll    Timestamp: 3B842038
Run Code Online (Sandbox Code Playgroud)

Windows 加载程序会检查编译 Calc.exe 时此目录中的时间戳是否与磁盘上 DLL 的时间戳相同。在这种特定情况下,不需要调用 GetProcAddress() 来解析导入,并且可以使用 IAT 中预加载的入口点。这是为了速度优化。

某些 DLL 将调用转发到另一个 DLL。例如,Kernel32.dll 有一些直接进入 NtDll.dll 的调用。在这种情况下,有一个转发器条目 (IMAGE_BOUND_FORWARDER_REF),它还允许检查转发的 DLL 的时间戳。

如果 DLL 已加载到另一个基地址,则必须将增量添加到 IAT 中的地址。但我不知道图像中原始基地址存储在哪里?当微软在 2004 年推出 ASLR(地址空间布局随机化)时,所有 DLL 都加载到随机基地址就成了规则。

就我而言,预加载的 IAT 完全没用,因为 XP 上的 Calc.exe 是 2004 年的,而导入的 DLL 是 2008 年的。因此,所有条目都必须重新解析。一旦您安装了更新系统上某些 DLL 的 Windows 更新,绑定导入就不再起作用。

IMAGE_DIRECTORY_ENTRY_IMPORT 指向 IAT 中完全相同的地址(通过 IMAGE_IMPORT_DESCRIPTOR->FirstThunk)。我打印了预加载的 IAT 和用 GetProcAddress() 的结果覆盖它们的值:

LoadLibrary(ADVAPI32.dll)       --> HMODULE 77DA0000, Timestamp 4802BE8C
IAT 01001000 'RegOpenKeyExA'    --> Value 77DA22EA updated 77DA7842
IAT 01001004 'RegQueryValueExA' --> Value 77DA23D7 updated 77DA7AAB
IAT 01001008 'RegCloseKey'      --> Value 77DA189A updated 77DA6C17
LoadLibrary(GDI32.dll)          --> HMODULE 77EF0000, Timestamp 4802BE8A
IAT 01001010 'SetBkColor'       --> Value 77C41E2E updated 77EF5E29
IAT 01001014 'SetTextColor'     --> Value 77C41D83 updated 77EF5D77
IAT 01001018 'SetBkMode'        --> Value 77C41EFF updated 77EF5EDB
etc...
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,绑定导入在所有这些情况下都是毫无用处的。新值(右侧)不仅仅是预加载值的恒定偏移。它们指向不同的 DLL。由于 Advapi32.dll 与编译 Calc.exe 时不再相同,因此必须重新解析它们。

在 Windows 7 的 Calc.exe 中我发现了相同的方案。但在 Windows 10 上情况有所不同。他们似乎将偏移量存储到 DLL 中,而不是入口点地址?

摘要:如果您编写自己的 DLL 加载程序,则可以完全忽略 IMAGE_DIRECTORY_ENTRY_IAT 和 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT。在当今快速的 CPU 上,不调用 GetProcAddress() 所带来的微小速度优化可以忽略不计。


Bri*_*ian 2

以下文章及其第一部分是有关 PE 可执行文件的信息的良好来源:

摘自 MSDN 杂志 2002 年 3 月号:Inside Windows

深入研究 Win32 可移植可执行文件格式,第 2 部分