PE文件结构详解(四)PE导入表
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呢?我们一起来看一下导入表的定义:
- typedef struct _IMAGE_IMPORT_DESCRIPTOR {
- union {
- DWORD Characteristics; // 0 for terminating null import descriptor
- DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
- } DUMMYUNIONNAME;
- DWORD TimeDateStamp; // 0 if not bound,
- // -1 if bound, and real date\time stamp
- // in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
- // O.W. date/time stamp of DLL bound to (Old BIND)
- DWORD ForwarderChain; // -1 if no forwarders
- DWORD Name;
- DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
- } IMAGE_IMPORT_DESCRIPTOR;
- 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结构:
- typedef struct _IMAGE_THUNK_DATA32 {
- union {
- DWORD ForwarderString; // PBYTE
- DWORD Function; // PDWORD
- DWORD Ordinal;
- DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
- } u1;
- } IMAGE_THUNK_DATA32;
- 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的意义。还是上个图对比一下:
上图是加载前。
上图是加载后。
最后总结一下:
- 导入表其实是一个IMAGE_IMPORT_DESCRIPTOR的数组,每个导入的DLL对应一个IMAGE_IMPORT_DESCRIPTOR。
- IMAGE_IMPORT_DESCRIPTOR包含两个IMAGE_THUNK_DATA数组,数组中的每一项对应一个导入函数。
- 加载前OriginalFirstThunk与FirstThunk的数组都指向名字信息,加载后FirstThunk数组指向实际的函数地址。
PE文件结构详解(四)PE导入表的更多相关文章
- PE文件结构详解(五)延迟导入表
PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...
- PE文件结构详解(六)重定位
前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...
- PE文件结构详解(二)可执行文件头
在PE文件结构详解(一)基本概念里,解释了一些PE文件的一些基本概念,从这篇开始,将详细讲解PE文件中的重要结构. 了解一个文件的格式,最应该首先了解的就是这个文件的文件头的含义,因为几乎所有的文件格 ...
- PE文件结构详解(三)PE导出表
上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,D ...
- PE文件结构详解(一)基本概念
PE(Portable Execute) 文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 何扩展名.那 ...
- PE文件结构详解(三)
0x01 前言 上一篇讲到了数据目录表的结构和怎找到到数据目录表(DataDirectory[16]),这篇我们我来讲讲数据目录表后面的另一个结构——区块表. 0x01 区块 区块就是PE载入器将PE ...
- PE文件结构详解
(注:最左边是文件头的偏移量.) IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic // Magic DOS signature MZ(4Dh 5Ah) DOS可执 ...
- 一起学Hive——详解四种导入数据的方式
在使用Hive的过程中,导入数据是必不可少的步骤,不同的数据导入方式效率也不一样,本文总结Hive四种不同的数据导入方式: 从本地文件系统导入数据 从HDFS中导入数据 从其他的Hive表中导入数据 ...
- 小甲鱼PE详解之输入表(导出表)详解(PE详解09)
小甲鱼PE详解之输出表(导出表)详解(PE详解09) 当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一 ...
随机推荐
- 第一个Cocos2d-JS游戏
我们的编写的第一个Cocos2d-JS程序,命名为HelloJS,从该工程开始学习其它的内容.创建工程我们创建Cocos2d-JS工程可以通过Cocos2d-x提供的命令工具cocos实现,但这种方式 ...
- 实例介绍Cocos2d-x开关菜单
开关菜单是MenuItemToggle类实现的,它是一种可以进行两种状态切换的菜单.它可以通过下面的函数创建: static MenuItemToggle*createWithCallback ( ...
- Python 字典(Dictionary) setdefault()方法
描述 Python 字典(Dictionary) setdefault() 函数和get()方法类似, 如果键不已经存在于字典中,将会添加键并将值设为默认值. 语法 setdefault()方法语法: ...
- Spring IoC容器的设计——BeanFactory应用场景2
1.BeanFactory接口设计了getBean方法,这个方法是使用IoC容器API的主要方法,通过这个方法,可以取得IoC容器中管理的Bean,Bean的取得是通过指定名字来索引的. 2.如果需要 ...
- 4月12日学习笔记——jQuery操作属性和样式
区分 DOM 属性和元素属性 <img src="images/image.1.jpg" id="hibiscus" alt="Hibiscus ...
- Tomcat找不到service.bat文件
说明:我们给客户做安装包,Tomcat我们设置了编码和端口,所以用绿色版的,同时又要注册成windows服务.但是bin下面没有service.bat文件(tomcat6.exe,tomcat6x.e ...
- ServletContext的用途
安装在一个服务器中的一个特定URL名字空间(比如,/myapplication)下的所有Servlet,JSP,JavaBean等Web部件的集合构成了一个Web的应用,每一个Web应用(同一JVM) ...
- Linux下安装Websphere MB所需的系统rpm包
很少使用到Linux,这次刚好用户有一个在linux下搭建Websphere MB/MQ的任务.试了几次都不行,经过多方打听,询问原来是少了rpm包的问题,但是,具体包名不详.. --#mount / ...
- Linux C 程序 进程间通信(20)
进程间通信 1.进程间通信的几种手段: (1).管道 数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道),如果要建立全双工通信,需要建立两个管道 只能用于 ...
- ADO.NET的五个主要对象
优秀文章链接:http://www.cnblogs.com/xianspace/archive/2009/02/21/1395307.html http://www.cnblogs.com/aito/ ...