这篇文章转载自小甲鱼的PE文件详解系列原文传送门

之前简单提了一下节表和数据目录表,那么他们有什么区别?

其实这些东西都是人为规定的,一个数据在文件中或者在内存中的位置基本是固定的,通过数据目录表进行索引和通过节表进行索引都是可以找到的,也可以这么说,同一个数据在节表和数据目录表中都有一份索引值,那么这两个表有什么区别?一般将具有相同属性的值放到同一个节区中,这也就是说同一个节区的值只是保护属性相同,但是他们的用途不一定是一样的,但是在同一数据目录表中的数据的作用是相同的,比如输入函数表中只会保存输入函数的相关信息,输出函数表中只会保存输出函数的信息,而输入输出函数在PE文件中可能都位于.text这个节中。

输入函数表

输入函数:一般将那些在本程序中调用,但是它的代码不在本程序中的函数称为输入函数,输入函数一般都在另外一个独立的dll中。

在之前谈到PE头的时候说到,在PE头中有一个结构是数据目录表,它的结构如下:

IMAGE_DATA_DIRECTORY STRUCT
VirtualAddress DWORD ? ; 数据的起始RVA
isize DWORD ? ; 数据块的长度
IMAGE_DATA_DIRECTORY ENDS

这个结构大小为8,相对于PE文件头的偏移为0x78。

在PE文件中,通过一个数组来保存多个数据目录表的信息,而输入函数表则是这个数组的第二个元素。

而输入表是以一个 IMAGE_IMPORT_DESCRIPTOR(简称IID) 的数组开始。

每个被 PE文件链接进来的 DLL文件都分别对应一个 IID数组结构。

在这个 IID数组中,并没有指出有多少个项(就是没有明确指明有多少个链接文件),但它最后是以一个全为NULL(0) 的 IID 作为结束的标志。

IMAGE_IMPORT_DESCRIPTOR

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

OriginalFirstThunk

它指向first thunk,IMAGE_THUNK_DATA,该 thunk 拥有 Hint 和 Function name 的地址。

TimeDateStamp

该字段可以忽略。如果那里有绑定的话它包含时间/数据戳(time/data stamp)。如果它是0,就没有绑定在被导入的DLL中发生。

在最近,它被设置为0xFFFFFFFF以表示绑定发生。

ForwarderChain

一般情况下我们也可以忽略该字段。在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。

它可被设置为0xFFFFFFFF以代表没有forwarder。

Name

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

如:KERNEL32.DLL)。

FirstThunk

它包含由IMAGE_THUNK_DATA定义的 first thunk数组的虚地址,通过loader用函数虚地址初始化thunk。

在Orignal First Thunk缺席下,它指向first thunk:Hints和The Function names的thunks。

这个OriginalFirstThunk 和 FirstThunk明显是亲家,两家伙首先名字就差不多哈。那他们有什么不可告人的秘密呢?

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

我们可以看出由于是union结构,所以IMAGE_THUNK_DATA 事实上是4个字节大小。

这个共用体是怎么使用的呢:

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。

当 IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。

接下来说明IMAGE_IMPORT_BY_NAME 结构:

IMAGE_IMPORT_BY_NAME STRUCT
Hint WORD ?
Name BYTE ?
IMAGE_IMPORT_BY_NAME ENDS

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0。

Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

输入函数表的加载

从上面的图上来看,OriginalFirstThunk与FirstThunk指向的是同一个数据结构,在PE文件中既可以通过OriginalFirstThunk来找到函数名,也可以通过FirstThunk来找到函数名,为什么会出现两个指针指向同一个数据结构的现象呢,其实这个与PE文件的加载有关

第一个数组(由 OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。

第二个数组(由 FirstThunk 所指向)事实上是由 PE 装载器重写的。

PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,也就是说此时的FirstThunk 不在指向这个INAGE_IMPORT_BY_NAME结构,而是真实的函数的RVA。因此我们称为输入地址表(IAT)。

所以,当我们的 PE 文件装载内存后准备执行时,刚刚的图就会转化为下图:

实验操作

我们来编译一个具体的程序,源代码如下:

#include <windows.h>

int WINAPI WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow
)
{
MessageBox( NULL, TEXT("Hello, welcome to Fishc.com!"),
TEXT("Welcome!"), MB_OKCANCEL | MB_OK
); return 0;
}

这个程序就是弹出一个MessageBox,通过W32Dasm静态反汇编发现MessageBox函数所在地址应该在0x0042A2AC

在数据目录表中根据OriginalFirstThunk 项获取函数名称

用UE打开这个PE文件,发现输入函数表的RVA = 0x0002A000



在节表中查询发现它是在.idata这个节中



通过之前说的公式,可以得到,这个RVA在文件中的偏移地址为0x0002A000 - 0x0002A000 + 0x00028000 = 0x00028000读取在这个位置的信息发现,OriginalFirstThunk = 0x0002A15C,这个偏移,发现它仍然在这个节中,通过上述公式计算得出,他在文件中的偏移地址为:0x0002815C



从这个位置得到的值来看,它的最高值为0,也就是IMAGE_THUNK_DATA保存的是函数名的字符串,字符串的RVA为0x0002A2DC,通过计算得到它在文件中的偏移为:0x000282DC.



从图上可以看出这个地址所对应的值正好是函数的名称MessgeBoxA

通过FirstThunk成员找到函数名称

首先根据PE文件的内容,可以知道,输入函数表在PE文件的偏移为0x00028000,而根据这个结构来看,FirstThunk在x00028000 + 16 = 0x00028010的位置,在这位置,我们发现它里面的值为0x0002A2AC



计算得到在磁盘中的偏移为0x000282AC,在PE文件中这个值为0x0002A2DC,它的最高位仍然为0,也就是说这个地址保存的内容为函数名称。

另外我们发现这个值与之前用OriginalFirstThunk 寻址到的函数名称所在RVA一样,也就是说到此成功找到函数名称

查找函数在内存的偏移地址

根据上面所说的内容,只有当这个PE文件被加载到内存中,PE加载器才会将IMAGE_IMPORT_BY_NAME

结构中的值替换为对应函数的地址,所以要查找函数的地址就需要先将PE文件加载到内存,然后再将内存中的数据抓取下来,最后再来分析得出这个函数的偏移地址。

其实这个工作可以由lordPE工具来帮忙完成。首先是启动程序,然后打开lordPE,找到程序的进程,然后选择dump full抓取全部即可



这样会生成一个dump文件,分析这个文件,就可以得出相应的内容:

由于这个是内存镜像的拷贝,所以在这在内存中的RVA就是在文件中的偏移。

首先得到导入表的偏移为0x0002a000,这个值里面存储的值为0x0002A15C,这个值是OriginalFirstThunk的值,通过这个值找到对应的IMAGE_THUNK_DATA地址:0x0002A2DC

我们发现这个值得高地址为是0,那么它所指向的应该就是函数名称,我们寻址到这个地址,发现它正好是函数名称



接下来,再来解析函数地址,在0x0002a010中找到对应的FirstThunk值,这个值为0x0002A2AC,它是指向一个IMAGE_THUNK_DATA结构,在这个地址处,发现它的值为0x77D507EA,这个值的最高位为1,所以它对应的是一个函数地址,它的低32位是一个函数编号,此时0x0002A2AC指向的不在是一个IMAGE_IMPORT_BY_NAME结构,而是函数地址的偏移,而这个程序是由VC6.0编译而成,VC6默认的加载地址为0x00400000,所以基址 + 偏移地址就是函数的正确地址,也就是0x0042A2AC,与之前用静态反汇编得到的值相同

PE文件详解(六)的更多相关文章

  1. PE文件详解(八)

    本文转载自小甲鱼PE文件详解系列教程原文传送门 当应用程序需要调用DLL中的函数时,会由系统将DLL中的函数映射到程序的虚拟内存中,dll中本身没有自己的栈,它是借用的应用程序的栈,这样当dll中出现 ...

  2. PE文件详解(四)

    本文转自小甲鱼的PE文件详解系列原文传送门 到此为止,小甲鱼和大家已经学了许多关于 DOS header 和 PE header 的知识.接下来就该轮到SectionTable (区块表,也成节表). ...

  3. PE文件详解(三)

    本文转自小甲鱼的PE文件详解系列传送门 PE文件到内存的映射 在执行一个PE文件的时候,windows 并不在一开始就将整个文件读入内存的,二十采用与内存映射文件类似的机制. 也就是说,windows ...

  4. MyBatis 映射文件详解(六)

    MyBatis 配置文件类型 MyBatis配置文件有两种类型,如下: 全局配置文件(如 mybatis-config.xml) Mapper XML 映射文件(如 UserMapper.xml) 上 ...

  5. PE文件详解(九)

    本篇文章转载自小甲鱼的一篇日志,原文地址 我们知道,Windows 将程序的各种界面定义为资源,包括加速键(Accelerator).位图(Bitmap).光标(Cursor).对话框(Dialog ...

  6. PE文件详解(七)

    本文转载自小甲鱼PE文件讲解系列原文传送门 这次主要说明导出表,导出表一般记录着文件中函数的地址等相关信息,供其他程序调用,常见的.exe文件中一般不存在导出表,导出表更多的是存在于dll文件中.一般 ...

  7. PE文件详解(五)

    在前面几节中经常提到相对虚拟地址RVA,在这篇博客中主要说明这个概念.本来是想接着转载小甲鱼的,但是我自己根据这篇文章和他的视频来学习的时候,发现在RVA与文件的相对偏移地址进行转化的时候,那块我看不 ...

  8. PE文件详解二

    本文转自小甲鱼的PE文件相关教程,原文传送门 咱接着往下讲解IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用! 接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构 ...

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

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

随机推荐

  1. java正则表达式总结

    最近工作要使用文件上传解析,上传还好,但是在解析文件的时候,却踩到了好多坑,今天就说说其中的一块吧,正则匹配. 由于上传的文件统一都是csv文件,所以在解析文本的时候,肯定要碰到正则表达式的,先解释一 ...

  2. 超详细的 Linux CentOS yum 源的配置与使用【转发+新增】

    一.yum 简介 yum,是Yellow dog Updater, Modified 的简称,是杜克大学为了提高RPM 软件包安装性而开发的一种软件包管理器.起初是由yellow dog 这一发行版的 ...

  3. 关于《Head First Python》一书中print_lol()函数的思考

    关于<Head First Python>一书中print_lol()函数的思考 在<Head First Python>第一章中,讲述到Python处理复杂数据(以电影数据列 ...

  4. 【USACO】玉米实验(单调队列)

    Description 约翰决定培育新的玉米品种以提高奶牛的产奶效率.约翰所有的土地被分成 N ×N 块,其中第 r行第 c 列的玉米质量为 Ar,c.他打算找一块质量比较均匀的土地开始自己的实验.经 ...

  5. 快速学习Bash

    作者:Vamei 出处:http://www.cnblogs.com/vamei 严禁转载. Shell是Linux下经典的文本互动方式,而Bash是现在最常用的一种Shell.我在这里总结了Bash ...

  6. web调用客户端程序

    背景 最近做一个集成需求,我们是B/S架构的,对方是C/S架构的,对方直接扔过来一个EXE连OCX都没有,让我们调用,也就是说,我们需要通过js程序去调用他们的客户端程序并传入多个参数,当时内心是崩溃 ...

  7. Akka Cluster简介与基本环境搭建

      akka集群是高容错.去中心化.不存在单点故障以及不存在单点瓶颈的集群.它使用gossip协议通信以及具备故障自动检测功能. Gossip收敛   集群中每一个节点被其他节点监督(默认的最大数量为 ...

  8. php编码的一些小规范

    本文同时发表在https://github.com/zhangyachen/zhangyachen.github.io/issues/104 最近在整理线上的hhvm warning,虽然每天产生百万 ...

  9. bzoj 3571: [Hnoi2014]画框

    Description 小T准备在家里摆放几幅画,为此他买来了N幅画和N个画框.为了体现他的品味,小T希望能合理地搭配画与画框,使得其显得既不过于平庸也不太违和.对于第 幅画与第 个画框的配对,小T都 ...

  10. bzoj 1492: [NOI2007]货币兑换Cash

    Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个 ...