PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPORT,即导入表。

也许大家注意到过,在IMAGE_DATA_DIRECTORY中,有几项的名字都和导入表有关系,其中包括:IMAGE_DIRECTORY_ENTRY_IMPORT,IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT,IMAGE_DIRECTORY_ENTRY_IAT和IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT这几个导入都是用来干什么的,他们之间又是什么关系呢?听我慢慢道来。

  • IMAGE_DIRECTORY_ENTRY_IMPORT就是我们通常所知道的导入表,在PE文件加载时,会根据这个表里的内容加载依赖的DLL,并填充所需函数的地址。
  • IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT叫做绑定导入表,在第一种导入表导入地址的修正是在PE加载时完成,如果一个PE文件导入的DLL或者函数多那么加载起来就会略显的慢一些,所以出现了绑定导入,在加载以前就修正了导入表,这样就会快一些。
  • IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT叫做延迟导入表,一个PE文件也许提供了很多功能,也导入了很多其他DLL,但是并非每次加载都会用到它提供的所有功能,也不一定会用到它需要导入的所有DLL,因此延迟导入就出现了,只有在一个PE文件真正用到需要的DLL,这个DLL才会被加载,甚至于只有真正使用某个导入函数,这个函数地址才会被修正。
  • IMAGE_DIRECTORY_ENTRY_IAT是导入地址表,前面的三个表其实是导入函数的描述,真正的函数地址是被填充在导入地址表中的。

举个实际的例子,看一下下面这张图:

这个代码调用了一个RegOpenKeyW的导入函数,我们看到其opcode是FF 15 00 00 19 30气质FF 15表示这是一个间接调用,即call dword ptr [30190000] ;这表示要调用的地址存放在30190000这个地址中,而30190000这个地址在导入地址表的范围内,当模块加载时,PE 加载器会根据导入表中描述的信息修正30190000这个内存中的内容。

那么导入表里到底记录了那些信息,如何根据这些信息修正IAT呢?我们一起来看一下导入表的定义:

[cpp] view plain copy
  1. typedef struct _IMAGE_IMPORT_DESCRIPTOR {
  2. union {
  3. DWORD   Characteristics;            // 0 for terminating null import descriptor
  4. DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
  5. } DUMMYUNIONNAME;
  6. DWORD   TimeDateStamp;                  // 0 if not bound,
  7. // -1 if bound, and real date\time stamp
  8. //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
  9. // O.W. date/time stamp of DLL bound to (Old BIND)
  10. DWORD   ForwarderChain;                 // -1 if no forwarders
  11. DWORD   Name;
  12. DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
  13. } IMAGE_IMPORT_DESCRIPTOR;
  14. typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

使用 RtlImageDirectoryEntryToData并将索引号传1,会得到一个如上结构的指针,实际上指向一个上述结构的数组,每个导入的DLL都会成为数组中的一项,也就是说,一个这样的结构对应一个导入的DLL。

Characteristics和OriginalFirstThunk:一个联合体,如果是数组的最后一项Characteristics为0,否则OriginalFirstThunk保存一个RVA,指向一个IMAGE_THUNK_DATA的数组,这个数组中的每一项表示一个导入函数。

TimeDateStamp:映象绑定前,这个值是0,绑定后是导入模块的时间戳。

ForwarderChain:转发链,如果没有转发器,这个值是-1。

Name:一个RVA,指向导入模块的名字,所以一个IMAGE_IMPORT_DESCRIPTOR描述一个导入的DLL。

FirstThunk:也是一个RVA,也指向一个IMAGE_THUNK_DATA数组。
既然OriginalFirstThunk与FirstThunk都指向一个IMAGE_THUNK_DATA数组,而且这两个域的名字都长得很像,他俩有什么区别呢?为了解答这个问题,先来认识一下IMAGE_THUNK_DATA结构:

[cpp] view plain copy
  1. typedef struct _IMAGE_THUNK_DATA32 {
  2. union {
  3. DWORD ForwarderString;      // PBYTE
  4. DWORD Function;             // PDWORD
  5. DWORD Ordinal;
  6. DWORD AddressOfData;        // PIMAGE_IMPORT_BY_NAME
  7. } u1;
  8. } IMAGE_THUNK_DATA32;
  9. typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;

ForwarderString是转发用的,暂时不用考虑, Function表示函数地址,如果是按序号导入 Ordinal就有用了,若是按名字导入 AddressOfData便指向名字信息。 可以看出这个结构体就是一个大的union,大家都知道union虽包含多个域但是在不同时刻代表不同的意义那到底应该是名字还是序号,该如何区分呢?可以通过Ordinal判断,如果Ordinal的最高位是1,就是按序号导入的,这时候,低16位就是导入序号,如果最高位是0,则AddressOfData是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构,用来保存名字信息,由于Ordinal和AddressOfData实际上是同一个内存空间,所以AddressOfData其实只有低31位可以表示RVA,但是一个PE文件不可能超过2G,所以最高位永远为0,这样设计很合理的利用了空间。实际编写代码的时候微软提供两个宏定义处理序号导入:IMAGE_SNAP_BY_ORDINAL判断是否按序号导入,IMAGE_ORDINAL用来获取导入序号。

这时我们可以回头看看OriginalFirstThunk与FirstThunk,OriginalFirstThunk指向的IMAGE_THUNK_DATA数组包含导入信息,在这个数组中只有Ordinal和AddressOfData是有用的,因此可以通过OriginalFirstThunk查找到函数的地址。FirstThunk则略有不同,在PE文件加载以前或者说在导入表未处理以前,他所指向的数组与OriginalFirstThunk中的数组虽不是同一个,但是内容却是相同的,都包含了导入信息,而在加载之后,FirstThunk中的Function开始生效,他指向实际的函数地址,因为FirstThunk实际上指向IAT中的一个位置,IAT就充当了IMAGE_THUNK_DATA数组,加载完成后,这些IAT项就变成了实际的函数地址,即Function的意义。还是上个图对比一下:

上图是加载前。

上图是加载后。

最后总结一下:

  1. 导入表其实是一个IMAGE_IMPORT_DESCRIPTOR的数组,每个导入的DLL对应一个IMAGE_IMPORT_DESCRIPTOR。
  2. IMAGE_IMPORT_DESCRIPTOR包含两个IMAGE_THUNK_DATA数组,数组中的每一项对应一个导入函数。
  3. 加载前OriginalFirstThunk与FirstThunk的数组都指向名字信息,加载后FirstThunk数组指向实际的函数地址。

by evil.eagle 转载请注明出处。

http://blog.csdn.net/evileagle/article/details/12357155

PE文件结构详解(四)PE导入表相关推荐

  1. PE文件结构详解 --(完整版)

    From:https://blog.csdn.net/adam001521/article/details/84658708 PE结构详解:https://www.cnblogs.com/zheh/p ...

  2. PE文件结构详解(五)延迟导入表

    by evil.eagle 转载请注明出处. http://blog.csdn.net/evileagle/article/details/12718845 PE文件结构详解(四)PE导入表讲了一般的 ...

  3. PE文件结构详解(六)重定位

    前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...

  4. 【转】PE文件结构详解--(完整版)

    (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 ...

  5. PE文件结构详解(一)基本概念

    (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 ...

  6. PE文件结构详解(二)可执行文件头

    by evil.eagle 转载请注明出处. http://blog.csdn.net/evileagle/article/details/11903197 在PE文件结构详解(一)基本概念里,解释了 ...

  7. PE文件结构详解(三)PE导出表

    上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,D ...

  8. PE文件结构详解(三)

    0x01 前言 上一篇讲到了数据目录表的结构和怎找到到数据目录表(DataDirectory[16]),这篇我们我来讲讲数据目录表后面的另一个结构--区块表. 0x01 区块 区块就是PE载入器将PE ...

  9. Windows PE 第四章 导入表

    第四章 导入表 导入表是PE数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的.通过分析导入表数据,可以获得诸如OE文件的指令中调用了多少外来函数,以及这些外来函数都存在于哪些动态链接库里等 ...

最新文章

  1. 手机测试用例-设置测试用例
  2. 自定义的无数据提示界面
  3. 帮朋友招一个IM开发人员
  4. 美团字节滴滴重启支付大战,王兴张一鸣不甘心
  5. hdu 2544 最短路 Dijkstra算法
  6. IE与Mozila FireFox 中的 JS
  7. 复地邮箱服务器地址,打印服务器设置方法
  8. Spring Boot笔记-get请求发送json数据(方便前端vue解析)
  9. Extjs嵌入html
  10. 如何使用ES6在JavaScript中有条件地构建对象
  11. Python Tricks(二)—— 牛顿法求解平方根(最大整数)
  12. python连接linux后一步一步的操作_Python使用技巧
  13. oracle 简单job
  14. window安全中心关闭防火墙后仍然会将xx文件删除的解决办法
  15. 微信小程序 - 数据转excel下载并复制链接
  16. 利用微软Text-To-Speech朗读文本
  17. Nginx - 静态网站;负载均衡;静态代理;动静分离;虚拟主机
  18. Mac下查找支持的字体
  19. 和利时scada系统服务器参数,MACS-SCADA综合监控系统
  20. 落克王国经验计算机,洛克王国宠物经验计算器

热门文章

  1. Bean标签基本配置
  2. Spring系列之BeanPostProcessor分析
  3. 单例设计模式-反射攻击解决方案及原理分析
  4. webservice的css哪里添加,jQuery_XML+XSLT+CSS+JQuery+WebService组建Asp.Net网(2), 3.       更 - phpStudy...
  5. 【代码块】代码块使用注意事项和细节讨论
  6. Asp.net MVC Filter监控页面性能和运行时间
  7. 从构建分布式秒杀系统聊聊WebSocket推送通知
  8. 【208天】黑马程序员27天视频学习笔记【Day21-中】
  9. centos Crontab
  10. iOS_11_tableViewCell使用alertView变更数据