最近听别人讲的我晕晕乎乎的,于是上网上百度下,感觉这篇还不错。  链接:http://www.blogfshare.com/pe-export.html

一、导入表简介

在编程中常常用到“导入函数”(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等。

于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后,Windows 加载器才将相关DLL 装入,并将调用输入函数的指令和函数实际所处的地址联系起来。这就是“动态链接”的概念。动态链接是通过PE 文件中定义的“导入表”来完成的,导入表中保存的正是函数名和其驻留的DLL 名等。

1.调用导入函数的指令

程序被执行的时候是怎样使用导入函数的呢?我们来对一个简单的弹出一个MessageBox的程序反汇编一把,看看调用导入函数的指令都是什么样子的.

灰常简单的一个小程序,如图双击程序只显示一个对话窗口,然后就结束~试验用小程序,我们尽量的将内部的结构删减,调试起来才方便些。我们这次体验的目的就是想靠所学的知识,试图来找到MessageBox 在内存中的地址。
注:MessageBox 是来自于USER32.DLL 动态链接库里的一个函数,我们通过对PE 文件的静态反编译分析来观察hello.exe 这个试验品是如何定位和调用MessageBox 这个在USER32.dll的函数的。
(MessageBox 有两个版本,一个是MessageBoxA 还有一个是MessageBoxW 分别代表ASCII码形式和UNICODE~)

需要反汇编的两句源码如下:

invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

invoke  ExitProcess,NULL

我们直接对其反汇编:

反汇编后,对MessageBox和ExitProcess函数的调用变成了对0040101A和00401020地址的调用,但是这两个地址显然是位于程序自身模块而不是DLL模块中,实际上,这是由编译器在程序所有代码的后面自动加上的Jmp dword ptr[xxxxxxxx]类型的指令,这个指令时一个间接寻址的跳转指令,xxxxxxxx地址中存放的才是真正的导入函数的地址。在这个例子中,00402000地址处存放的就是ExitProcess函数的地址。

那在没有装载到内存前,PE文件中的00402000地址处的内容又是什么呢?我们来分析一下它的节表。

由于建议装入地址是00400000h,所以00402000地址实际上处于RVA为2000h的地方,再看看各个节的虚拟地址,可以发现2000h开始的地方位于.rdata节内,而这个节的Raw_偏移为600h,也就是00402000h的内容实际上对应于PE文件中偏移600h处的数据。

我们再来看看文件0600h处的内容是什么:

查看的结果是0002076h,这显然不是内存中的ExitProcess函数的地址。不过,我们将它作为RVA看会怎么样?RVA地址00002076h也处于.rdata节内,减去节的其实地址00002000h后得到这个RVA相对于节首的偏移是76h,也就是对应文件0676h开始的地方,接下来会惊奇地发现,0676h再过去两个字节的内容正是函数名字符串“ExitProcess”!

是不是感觉有点不对?

如果我告诉你,当PE文件被装载的时候,Windows装载器会根据xxxxxxxx处的RVA得到函数名,再根据函数名在内存中找到函数地址,并且用函数地址将xxxxxxx处的内容替换成真正的函数地址,那么所有的疑惑就迎刃而解了。接下来看看如何获取导入表的位置。

2.获取导入表的位置

导入表的位置和大小可以从PE文件中IMAGE_OPTIONAL_HEADER32结构的数据目录字段中获取。从IMAGE_OPTIONAL_DIRECTORY结构的VirtualAddress字段得到的是导入表的RVA值,如果在内存中查找导入表,那么将RVA值加上PE文件装入的基址就是实际的地址,如果在PE文件中查找导入表,那么需要使用上一篇中讲的将RVA转换成文件偏移的方法进行转换。

二、导入表的结构

1.PE文件中的导入表

导入表由一系列的IMAGE_IMPORT_DESCRIPTOR结构组成,结构的数量取决于程序要使用的DLL文件的数量,每一个结构对应一个DLL文件,在所有这些结构的最后,由一个内容全为0的IMAGE_IMPORT_DESCRIPTOR结构作为结束。

IMAGE_IMPORT_DESCRIPTOR结构的定义:

IMAGE_IMPORT_DESCRIPTOR STRUCT 
union 
    Characteristics              DWORD   ? 
    OriginalFirstThunk        DWORD   ? 
ends 
TimeDateStamp                     DWORD   ? 
ForwarderChain                     DWORD   ? 
Name1                                    DWORD   ? 
FirstThunk                            DWORD   ?
IMAGE_IMPORT_DESCRIPTOR ENDS

①Name1

它表示DLL 名称的相对虚地址(译注:相对一个用null作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称,如:KERNEL32.DLL)。

②OriginalFirstThunk和FirstThunk

现在可以看成是相同的(现在),它们都指向一个包含一系列IMAGE_THUNK_DATA结构的数组,数组中的每个IMAGE_THUNK_DATA结构定义了一个导入函数的信息,数组最后以一个内容为0的IMAGE_THUNK_DATA结构作为结束。

一个IMAGE_THUNK_DATA结构实际上就是一个双字,之所以把它定义成结构,是因为它在不同时刻有不同的含义:

IMAGE_THUNK_DATA STRUC
union u1
ForwarderString       DWORD  ?        ; 指向一个转向者字符串的RVA
Function                      DWORD  ?        ; 被输入的函数的内存地址
Ordinal                       DWORD  ?        ; 被输入的API 的序数值
AddressOfData         DWORD  ?        ; 指向 IMAGE_IMPORT_BY_NAME
ends
IMAGE_THUNK_DATA ENDS

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。(读者可以用预定义值IMAGE_ORDINAL_FLAG32或80000000h来对最高位进行测试)
当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一IMAGE_IMPORT_BY_NAME 结构。

IMAGE_IMPORT_BY_NAME STRUCT
Hint        WORD    ? 
Name1      BYTE      ?
IMAGE_IMPORT_BY_NAME ENDS

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,Name1字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

我们来看一个例子:

2.内存中的导入表

为什么需要两个一样的IMAGE_THUNK_DATA 数组呢?

当PE文件被装入内存的时候,其中一个数组的值将被改作他用,正如上面分析的,Windows装载器会将指令Jmp dword ptr[xxxxxxxx]指定的xxxxxxxx处的RVA替换成真正的函数地址,其实xxxxxxx地址正是FirstThunk字段指向的那个数组的一员。

实际上,当PE文件被装入内存后,内存中的映象就被Windows装载器修正成了下图的样子,其中由FirstThunk字段指向的那个数组中的每个双字都被替换成了真正的函数入口地址,之所以在PE文件中使用两份IMAGE_THUNK_DATA 数组的拷贝并修改其中的一份,是为了最后还可以留下一份拷贝用来反过来查询地址所对应的导入函数名。

3.导入地址表(IAT)

暂把上面FirstThunk指向的真正导入函数地址数组称为导入地址数组。在PE文件中,所有DLL对应的导入地址数组是被排列在一起的,全部这些数组的组合也被称为导入地址表(Import Address Table),导入表中第一个IMAGE_IMPORT_DESCRIPTOR结构的FirstThunk字段指向的就是IAT的起始地址。也可以通过数据目录表的第13项找到IAT数据块的位置和大小。

《初识PE》导入表的更多相关文章

  1. PE文件结构详解(四)PE导入表

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

  2. 逆向-PE导入表

    导入表 动态链接库需要导入表 结构 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; // 0 for ...

  3. PE导入表分析

    A.dll 导入 B.dll 导出函数 A.dll 表内容 这个结构指向的B导出函数的地址 Hook这个位置 等同于 Hook B.dll导出函数

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

    PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...

  5. 初识PE文件结构

    前言 目前网络上有关PE文件结构说明的文章太多了,自己的这篇文章只是单纯的记录自己对PE文件结构的学习.理解和总结. 基础概念 PE(Portable Executable:可移植的执行体)是Win3 ...

  6. 手动添加导入表修改EXE功能

    目标: 改动PE导入表,手工给HelloWorld增加一个功能,就是启动的时候写入一条开机启动项,C:\cmd0000000000000000000000000000.exe 实现方法: 直接在注册相 ...

  7. 利用模块加载回调函数修改PE导入表实现注入

    最近整理PE文件相关代码的时候,想到如果能在PE刚刚读进内存的时候再去修改内存PE镜像,那不是比直接对PE文件进行操作隐秘多了么? PE文件在运行时会根据导入表来进行dll库的"动态链接&q ...

  8. 小甲鱼PE详解之输入表(导入表)详解(PE详解07)

    捷径并不是把弯路改直了,而是帮你把岔道堵上! 走得弯路跟成长的速度是成正比的!不要害怕走上弯路,弯路会让你懂得更多,最终还是会在终点交汇! 岔路会将你引入万劫不复的深渊,并越走越深…… 在开始讲解输入 ...

  9. 利用PE数据目录的导入表获取函数名及其地址

    PE文件是以64字节的DOS文件头开始的(IMAGE_DOS_HEADER),接着是一段小DOS程序,然后是248字节的 NT文件头(IMAGE_NT_HEADERS),NT的文件头位置由IMAGE_ ...

随机推荐

  1. 使用vs2010打开VS2013的工程文件

    在开发团队,会出现vs工具使用版本的不一样的情况.我的电脑使用的是VS2010,可是其他人员使用的是vs2013. 要打开其他人员上传的工程文件,可以通过三种方式: 1.下载一个vs2013版本. 2 ...

  2. HDU 1079 Calendar Game 博弈

    题目大意:从1900年1月1日 - 2001年11月4日间选择一天为起点,两个人依次进行两种操作中的任意一种,当某人操作后为2001年11月4日时,该人获胜.问先手是否获胜 操作1:向后移一天 操作2 ...

  3. JavaScript 执行环境(执行上下文) 变量对象 作用域链 上下文 块级作用域 私有变量和特权方法

    总结自<高程三>第四章  理解Javascript_12_执行模型浅析   JS的执行环境与作用域  javascript高级程序第三版学习笔记[执行环境.作用域] 在javascript ...

  4. shell中$(( )) 与 $( ) 还有${ }的区别

    http://blog.chinaunix.net/uid-14351756-id-2820651.html $( ) 与 ` ` (反引号)在 bash shell 中,$( ) 与 ` ` (反引 ...

  5. 通用mapper的使用

    通用mapper的使用 导入依赖 <dependency> <groupId>com.github.abel533</groupId> <artifactId ...

  6. 辽宁OI2016夏令营模拟T1-dis

    数值距离(dis.pas/c/cpp)题目大意我们可以对一个数 x 进行两种操作:1. 选择一个质数 y,将 x 变为 x*y2. 选择一个 x 的质因数 y,将 x 变为 x/y定义两个数 a,b ...

  7. (转)提高代码质量---one

    1. 摘要 这是烂代码系列的第二篇,在文章中我会跟大家讨论一下如何尽可能高效和客观的评价代码的优劣. 在发布了关于烂代码的那些事(上)之后,发现这篇文章竟然意外的很受欢迎,很多人也描(tu)述(cao ...

  8. 【Python@Thread】Semaphore&糖果机

    信号量适用与多线程竞争有限资源的情况. from atexit import register from time import ctime, sleep from threading import ...

  9. java 读取excel 将数据插入到数据库

    import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.sql.Con ...

  10. map 理解

    键值对 map会将同名的值覆盖掉 public static void main(String[] args) { Map<String,String> maptest=new HashM ...