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 目录。
再次感谢
您链接的 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 的页面,并让它们由分页文件支持。所以每个进程都可以有自己的一组导入地址。包含代码和导入表的其余页面仍然共享。
这里没有人回答你的问题。原因是 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() 所带来的微小速度优化可以忽略不计。
以下文章及其第一部分是有关 PE 可执行文件的信息的良好来源:
摘自 MSDN 杂志 2002 年 3 月号:Inside Windows
归档时间: |
|
查看次数: |
4653 次 |
最近记录: |