我正在尝试为Windows编写一个exe包装器。到目前为止,我已经掌握了一些基础知识。我要做的部分是阅读“BOUND IMPORT目录表”(或.idata部分?),基本上是PE文件的一部分,其中包含加载程序需要导入的DLL列表。
我想知道最好的方法是什么:
[A]找出IAT的位置(因为针对几个不同的.exe运行PEView似乎表明这个列表可以包含在多个不同的地方),然后阅读列表
OR
[B]找到一种直接读取exe需要导入的DLL列表的方法。
有没有办法做到这一点?有没有进一步阅读的人可以推荐IAT应该在哪里以及如何阅读它?
答案 0 :(得分:8)
此外,这里有一个pdf可以帮助您了解结构的命名和组织方式。一些有用的程序: CFF Explorer ,以及一个好的十六进制编辑器。
我的回答与上述不同,它描述了一种在磁盘上的可执行文件中手动执行上述操作的方法。
要获取IAT的相对虚拟地址(运行时的地址,即RVA):
IMAGE_DOS_HEADER
结构。 e_lfanew
字段转到IMAGE_NT_HEADERS
结构。
从偏移0开始,向下跳转0x3c并取消引用以获取IMAGE_NT_HEADERS的开始。 OptionalHeader
(由IMAGE_NT_HEADERS包含,因此也是如此)。要进入IMAGE_OPTIONAL_HEADER
结构(尽管它的名称,它不再是可选的),请知道它是IMAGE_NT_HEADER中的第三个结构。
要访问OptionalHeader,请将0x18添加到之前取消引用的值 如果你想遍历DLL的列表及其功能地址,有一些背景知识:
在磁盘上指定的RVA将不允许您遍历该文件,因为RVA表示二进制文件加载到内存时的地址;要遍历磁盘上的二进制文件,您需要将RVA值转换为正确的形式。公式很简单; HMODULE + RVA = PE元素的线性地址。 HMODULE也称为基地址。但是获取基地址实际上需要一个有点冗长的算法,并且取决于你所讨论的RVA的实际价值。要获取给定RVA的基址的值,以便计算磁盘上PE元素的线性地址:
获取sectionHeader;为此,请浏览部分列表(例如.data,.text,ect),直到找到问题的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应该> =到该部分的虚拟地址,并且<部分的虚拟地址和虚拟大小的总和。通过将12添加到该部分的地址,可以找到该部分的虚拟地址。并且该部分的虚拟大小可以通过8到该部分的地址找到。总结一下:传递if - (section.virtualAddress + section.virtualSize)> RVA> = section.virtualAddress。 *要迭代到下一节,节描述的长度为0x28;您只需将0x28添加到当前节指针即可转到下一个指针。最后一部分是一个空字节来表示结束。
从获得的部分标题中,执行以下操作:(baseAddress + RVA) - (sectionHeader.virtualAddress - sectionhHeader.PointerToRawData)。 * sectionHeader的virtualAddress与sectionHeader本身相距12字节,如上所述。 PointerToRawData距离节标题20。
那是满口的。如果你想回顾一下,你应该阅读 Rootkit Arsenal 中第5章(挂钩呼叫表)的第257-60页,但是为了更容易理解的图形,请查看openrce.org pdf链接我给了顶部附近。
要做到这一点,不过从......开始:
使用上述算法,将导入目录的RVA转换为可用指针,并使用以下算法迭代导入目录数组。
4.1)对于importDescriptor,将名称字段RVA转换为指针以获取名称。它可能是null
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
答案 1 :(得分:4)
是的,您可以通过浏览可执行文件的标题找到IAT。在winnt.h
中查找标题声明。
有关如何在标题中查找信息的详细信息,请参阅 MSDN Magazine 中的Matt Pietrek系列,“深入了解Win32可移植可执行文件格式”,部分{{3 }和I。
您还可以从II获取实际的Microsoft PE规范。
TL; DR:基本上,查找顺序如下:
IMAGE_DOS_HEADER
结构。e_lfanew
字段转到IMAGE_NT_HEADERS
结构。OptionalHeader
进入IMAGE_OPTIONAL_HEADER
结构(尽管它的名称,它不再是可选的)。DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
到IMAGE_IMPORT_DESCRIPTOR
结构数组。每个导入的DLL都有一个条目。此数组中的最后一个条目将被清零。Name
字段是指向DLL名称的RVA。 FirstThunk
字段是指向该DLL的IAT的RVA,该IAT是IMAGE_THUNK_DATA
结构的数组。