【问题标题】:PE Format - IAT QuestionsPE 格式 - IAT 问题
【发布时间】:2011-10-06 11:43:19
【问题描述】:

我正在尝试为 Windows 编写一个 exe 打包程序。到目前为止,我已经完成了一些基础知识。我要做的部分是读取“绑定导入目录表”(或 .idata 部分?),基本上是 PE 文件的部分,其中包含加载程序需要导入的 DLL 列表。

我想知道最好的方法是什么:

[A] 找出 IAT 在哪里(因为对几个不同的 .exe 运行 PEView 似乎表明该列表可以包含在多个不同的位置),然后读取该列表

或

[B]只要想办法直接读取exe需要导入的DLL列表即可。

有没有办法做到这一点?关于 IAT 应该在哪里以及如何阅读它,人们是否可以推荐任何进一步的阅读材料?

【问题讨论】:

    标签: c format portable-executable


    【解决方案1】:

    另外,这里有一个pdf,可以帮助您了解结构的命名和组织方式。一些有用的程序:CFF Explorer,和一个很好的十六进制编辑器。

    我的回答与上述不同,因为它描述了一种在仍在磁盘上的可执行文件中手动执行上述操作的方法。

    要获取 IAT 的相对虚拟地址(运行时地址,也称为 RVA):

    1. 从二进制的基地址开始。那是一个IMAGE_DOS_HEADER 结构。
    2. 按照e_lfanew 字段进入IMAGE_NT_HEADERS 结构。 从偏移量 0 开始,向下跳转 0x3c 并取消引用以获取 IMAGE_NT_HEADERS 的开头。
    3. 遵循OptionalHeader(包含在IMAGE_NT_HEADERS 中,因此与IMAGE_NT_HEADERS 相邻)。要访问IMAGE_OPTIONAL_HEADER 结构(尽管它的名称,它不再是可选的),要知道它是 IMAGE_NT_HEADER 中的第三个结构。 要访问 OptionalHeader,请将 0x18 添加到您之前取消引用的值
    4. 从 OptionalHeader 中取消引用到 DataDirectory。 DataDirectory 是 OptionalHeader 中的一个数组,它位于 IMAGE_NT_HEADERS 中。按照 DataDirectory 数组中的第 24 个(如果 0 是第一个,如 0、1、2...)条目到 IMAGE_DIRECTORY_ENTRY_IAT。 将 0xc0 添加到您当前所在的地址以获得导入地址表目录

    如果要遍历 DLL 及其函数地址的列表,有一些背景知识:

    • IMAGE_IMPORT_DESCRIPTOR 结构中的 3 个重要字段:OriginalFirstThunk(导入查找表的 RVA)、Name( 以 null 结尾的 ASCII Dll 名称)和 FirstThunk(IAT/数组的 RVA 由加载器构建的线性地址)。
    • 需要两个数组 因为一个是导入例程 (ILT) 的名称数组,而 其他是导入例程地址 (IAT) 的数组。例程 从 DLL 导入的可以按名称或序号导入 数字。要判断例程是否为序号导入,请检查 ILT 数组中 IMAGE_THUNK_DATA 结构的序号字段中设置的标志。
    • 模块在加载时导入的每个函数都将由一个 IMAGE_THUNK_DATA 结构表示。 Original First Thunk 和 First Thunk 都指向 IMAGE_THUNK_DATA 数组。然而,IMAGE_THUNK_DATA 结构是一个联合,它包含另一个结构 IMAGE_IMPORT_BY_NAME。了解这一点很重要,因为 Original First Thunk 使用 IMAGE_IMPORT_BY_NAME 结构,而 First Thunk 使用 IMAGE_THUNK_DATA 结构内部联合中的 Function 字段。
    • 在磁盘上指定的 RVA 将不允许您遍历文件,因为 RVA 表示二进制文件加载到内存时的地址;要遍历磁盘上的二进制文件,您需要将 RVA 值转换为正确的形式。公式很简单; HMODULE + RVA = PE 元素的线性地址。 HMODULE 也称为基地址。但是获取基地址实际上需要一个有点冗长的算法,并且取决于您所讨论的 RVA 的值实际上是什么。获取给定 RVA 的基地址值,以计算磁盘上 PE 元素的线性地址:

      1. 获取sectionHeader;为此,请遍历部分列表(例如 .data、.text 等),直到找到问题的 RVA 在 currentSection.VirtualAddress 和 currentSection.VirtualAddress + currentSection.size 内的部分。

        1.1) 首先,在 NT_HEADERS 结构中找到 FileHeader 中的节数。它是 FileHeader 中 2 字节机器号之后的 2 字节。 *手动执行此操作:将 0x6 添加到从 e_lfanew 取消引用的值;所以从偏移量 0 跳转 0x3c,取消引用该值,然后 0x6 到那个。然后读取两个字节并解释为整数。

        1.2) 找到第一节的位置;它与 OptionalHeader 连续。请记住,在 OptionalHeader 中是 DataDirectories 数组。 OptionalHeader 是 216 字节长,加上末尾的 2 个字,表示它的结尾;所以取 224 十六进制 (0xe0) 并将其添加到从一开始就在 0x3c 处取消引用的值中,以获得第一部分的位置。

        1.3) 要找到 RVA 所在的节标题,请针对您所在的当前节持续执行此测试。如果测试失败,请转到下一部分。如果您遍历所有部分并发现您到达结尾的 NULL 单词,那么该文件应该已损坏或您犯了一个错误。测试如下:将要转换的RVA比较为指向该节的虚拟地址的可用指针; RVA 应该 >= 到节的虚拟地址,并且 RVA >= section.virtualAddress。 *迭代到下一段,一段描述的长度为0x28;您只需将 0x28 添加到当前节指针即可到达下一个节。最后一段是一个空字节,表示结束。

      2. 从获得的节头中,执行以下操作:(baseAddress+RVA) - (sectionHeader.virtualAddress - sectionhHeader.PointerToRawData)。 *sectionHeader 的 virtualAddress 与上面计算的 sectionHeader 本身相距 12 个字节。 PointerToRawData 距离节标题 20。

      3. 获得的值表示指向 RVA 所需/表示的数据的实际指针。您可以使用它来查找所需数据的实际文件位置。

    那是一口。如果您想回顾一下,您应该阅读 The Rootkit Arsenal 中第 5 章(Hooking Call Tables)的第 257-60 页,但要获得更易于理解的图形,请查看 openrce.org pdf 链接我给了接近顶部。

    要做到这一点,但要从...开始:

    1. 如上所述获取 OptionalHeader。 OptionalHeader 包含一个 (DataDirectory) IMAGE_DATA_DIRECTORY 元素数组,因为它是最后一个元素。该数组中的第二个元素是 IMAGE_DIRECTORY_ENTRY_IMPORT,它定位 IAT。因此,澄清一下,IMAGE_NT_HEADER 包含 OptionalHeader 数组,其中包含 DataDirectory 数组。此数组中的最后一项将被清零。
    2. 从 OptionalHeader 取消引用到 IMAGE_DIRECTORY_ENTRY_IMPORT。下一个词是导入目录的大小。 从文件中的偏移量,向下跳转 0x68。
    3. 此值是导入目录的 RVA,它是一个 IMAGE_IMPORT_DESCRIPTOR 类型的结构数组(一个用于模块导入的每个 DLL),其中最后一个的字段设置为零。 IMAGE_IMPORT_DESCRIPTOR 中的第三个词包含一个指向 IMAGE_THUNK_DATA 的 FirstThunk 指针。
    4. 使用上面介绍的算法,将Import Directory的RVA转换为可用指针,并使用下面的算法遍历import directory数组。

      4.1) 对于 importDescriptor,将名称字段 RVA 转换为指针以获取名称。可能为空

      4.2) 要获取每个导入例程的名称和地址,请获取导入描述符的 OriginalFirstThunk 和 FirstThunk RVA 条目。每个 OFT 和 FT 都可能为空,这表明它是空的,所以检查一下。

      4.3) 将 OFT RVA 转换为指针; OFT 对应于 ILT,FT 对应于 IAT。 ILT 和 IAT 可能为空,表示它们为空。

      4.4) 从ILT指针获取函数名,从IAT指针获取函数地址。要移动到下一个导入函数,请记住 ILT 和 IAT 是数组;下一个元素距离恒定。

      4.5) 检查获得的新ILT&IAT指针值是否不为零;如果它们不为零,请重复。如果其中一个为零,则您已到达为该 dll 导入的函数列表的末尾;导入描述符也是一个重复数组,因此下一个导入的 dll 的偏移量是恒定的。本质上,您正在迭代 dll,并且对于每个 dll,您都在迭代以这种方式导入的函数。 19

    【讨论】:

    • 我对您引用 Aaron Klotz 的回答的次数不满意,但没有明确说明引用的内容和添加的内容。您应该引用他的答案,然后说出您要添加/更改的内容,而不仅仅是将他的答案复制并粘贴到您自己的答案中。
    • 如果您认为某些内容比添加为新答案有用,请不要进行这样的编辑,我已经修复了它,但以后不会
    • @CareyGregory 是的,在处理了一整天之后,我有点不想删除它,只是因为另一个人已经在我的答案中了。我觉得把他写的东西放在里面比较有内涵(这里也是很有价值的信息),所以我会把他在我的回复中写的内容标记为他的答案。
    • 我想到了你说的,我想我提供的答案完全不同;就此而言,它应该是它自己的答案。一开始的澄清最终变成了一场全面的冒险,我很高兴你回想起来指出了这一点。我以后会小心的。
    • 经过深思熟虑,彻底的第二个答案通常是使 SO 真正有用的原因。好多了!
    【解决方案2】:

    是的,您可以通过浏览可执行文件的标头找到 IAT。在 winnt.h 中查找标头声明。

    有关如何在标头中查找信息的详细说明,请参阅 Matt Pietrek 在 MSDN 杂志 中的系列文章,“深入了解 Win32 可移植可执行文件格式”,部分 I和II。

    您还可以从here 获取实际的 Microsoft PE 规范。

    TL;DR:基本上查找的顺序如下:

    1. 从二进制的基地址开始。那是一个IMAGE_DOS_HEADER 结构。
    2. 按照e_lfanew 字段进入IMAGE_NT_HEADERS 结构。
    3. 按照OptionalHeader 到达IMAGE_OPTIONAL_HEADER 结构(尽管它的名称,它不再是可选的)。
    4. 跟随DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] 到IMAGE_IMPORT_DESCRIPTOR 结构的数组。每个导入的 DLL 有一个条目。此数组中的最后一项将被清零。
    5. 每个条目中的Name 字段是一个指向DLL 名称的RVA。 FirstThunk 字段是指向该 DLL 的 IAT 的 RVA,它是 IMAGE_THUNK_DATA 结构的数组。

    【讨论】:

    • 这个答案无法解释 RVA 是什么,如何从中制定有用的指针,以及如何遍历 IAT 本身中的实际指针表;它只解释了如何到达 IAT。用户的问题包括需要阅读列表。后一个答案是冗长的,并且不太可能被任何利用 Visual Studio 或其他任何通过偏移量手动遍历它和类似逆向工程的计算的人阅读。在这种情况下,简要说明如何通过一些 C++ 代码对其进行迭代将是一个有用的补充。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多