PE格式,IAT目录有什么用

时间:2013-12-18 16:35:58

标签: windows portable-executable

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

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

因此,如果我们使用导入目录进行导入解析,我们需要IAT目录吗?

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

由于

修改

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

再次感谢

5 个答案:

答案 0 :(得分:4)

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

答案 1 :(得分:2)

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

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

答案 2 :(得分:1)

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

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

答案 3 :(得分:1)

没人在这里回答您的问题。原因是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个与导入相关的目录:

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中的地址。但是我不知道原始基址存储在映像中的什么位置?当Microsoft在2004年推出ASLR(地址空间布局随机化)时,它成为所有DLL都加载到随机基址的规则。

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

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上忽略。

答案 4 :(得分:0)

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

自2002年3月的MSDN杂志:内部Windows

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