【问题标题】:PE format, what is the use for IAT DirectoryPE格式,IAT目录有什么用
【发布时间】:2013-12-18 16:35:57
【问题描述】:

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

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

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

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

谢谢

编辑

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

再次感谢

【问题讨论】:

    标签: windows portable-executable


    【解决方案1】:

    在您链接的 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 的页面,并由分页文件支持它们。所以每个进程都可以有自己的一组导入地址。包含代码和导入表的其余页面仍然是共享的。

    【讨论】:

    • 导入地址表 RVA 和导入查找表 RVA 都是导入表目录的一部分。 IAT目录总是指向不同的地址,并不相同。
    • 不知道这是什么意思。当然地址是不一样的。它们是单独的表。
    • 据我了解,IAT 目录与导入目录不同,使用导入目录就足以解析导入库(同时使用 IAT RVA 和 ILT RVA)。那么 IAT 目录是干什么用的呢?
    • 这就是我的全部答案的全部内容,我无法猜测其中不清楚的部分。再次强调,表格首先包含相同的数据。但是当程序开始运行时,IAT 会更新为实际地址。
    • IAT 和 ILT 包含相同的数据。但 IAT 目录 (DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT]) 没有。
    【解决方案2】:

    根据 PE 的文档,IAT / IMAGE_DIRECTORY_ENTRY_IAT 似乎用于延迟加载 DLL

    https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#delay-import-address-table

    【讨论】:

    • 在我看来这是文件中的错误。对于任何延迟,都有 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 并且此字段(在文档中)指向 _IMAGE_DELAY_IMPORT_DESCRIPTOR。我无法理解 IMAGE_DIRECTORY_ENTRY_IAT 的真正含义。 ((
    【解决方案3】:

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

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

    An In-Depth Look into the Win32 Portable Executable File Format, Part 2

    【讨论】:

    • 确实提到了。以图 6 右侧的表格为例。
    • 是的。仅作为图 6 的一部分,并且没有说明差异。当我检查 IAT RVA(它是导入目录的一部分)和 IAT 目录时,它们具有不同的地址和不同的值。
    • “绑定”一章解释了 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 的工作原理。但是 IMAGE_DIRECTORY_ENTRY_IAT 并没有用一个词提到。我仍然不知道它的内容如何。这似乎是微软的秘密。
    【解决方案4】:

    IMAGE_DIRECTORY_ENTRY_IMPORT 最终会导致多个 IAT thunk,它们存储在一个内存区域中,该区域从 [IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress 开始,大小为 [IMAGE_DIRECTORY_ENTRY_IAT].Size

    我想当所有部分默认加载为只读时它很有用,您可以使用IMAGE_DIRECTORY_ENTRY_IAT 使 IAT(而不是 ILT)thunk 可写。

    【讨论】:

    • 这听起来很合理。但这意味着不公开 IMAGE_DIRECTORY_ENTRY_IAT 的 DLL 必须在 writebale 页面中具有 IAT。
    【解决方案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..
    

    你看到有3个目录与import相关:

    IMAGE_DIRECTORY_ENTRY_IAT
    IMAGE_DIRECTORY_ENTRY_IMPORT
    IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT
    

    正如您在上面看到的,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...
    

    这是所谓的导入地址表,模块中的代码在其中查找对外部函数的调用。如果您的代码调用 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
    

    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...
    

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

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

    总结:如果您编写自己的 DLL 加载程序,您可以完全忽略 IMAGE_DIRECTORY_ENTRY_IAT 和 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT。通过不调用 GetProcAddress() 进行的微小速度优化在当今快速 CPU 上可以忽略。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多