这篇文章转载自小甲鱼的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. 家居环境监測系统设计(PC上位机版)(手机APP版待定)

    下面是我的毕业设计:家居环境监測系统设计(PC上位机临时版.手机app版待定).本系统採用STC12C5A60S2单片机.结合传感器.分别对空气湿度.空气温度.气压.海拔.进水温度.出水温度.光照强度 ...

  2. EularProject 43: 带条件约束的排列组合挑选问题

    Sub-string divisibility Problem 43 The number, 1406357289, is a 0 to 9 pandigital number because it ...

  3. ES5继承

    原型继承 <script type="text/javascript"> function Father(){}//构造函数 //原型属性 Father.prototy ...

  4. 后台返回json可能会出现的异常解析:java.lang.IllegalStateException: WRITER

    在使用filter做权限管理限制访问时,经常是在数据可以正确返回时,在后台日志中却有这个异常抛出,这个现象让人不禁想去一探究竟. 我要做的是在一个filter中拦截所有的请求,并且根据拿到的请求中的参 ...

  5. MAC系统里JDK版本切换

    1.首先安装需要的JDK版本 JDK7,JDK8则需要自己到Oracle官网下载安装对应的版本.自己安装的JDK默认路径为:/Library/Java/JavaVirtualMachines/jdk1 ...

  6. 我是如何确认线上CLOSE_WAIT产生的原因及如何解决的。

    1.阐述 内部架构:Tomcat应用程序---> nginx ---> 其他Tomcat应用程序,内部Tomcat应用通过nginx调用其他应用. HTTP插件:HttpClient 4. ...

  7. 深入学习rollup来进行打包

    深入学习rollup来进行打包 阅读目录 一:什么是Rollup? 二:如何使用Rollup来处理并打包JS文件? 三:设置Babel来使旧浏览器也支持ES6的代码 四:添加一个debug包来记录日志 ...

  8. find + xargs + cp 遇到文件名中带空格如何处理

    一,需求为查询文件名为ZRSH开头的时间为7月至今的所有文件并打包 1.首先想到的就是find + xargs + cp  格式.. find 2016073* -type f  -name *ZRS ...

  9. 关于VS2017安装的一点扩充说明(15.5)

    其实逆天不推荐自己慢慢离线,找个离线包更新下再打包更快 Key:http://www.cnblogs.com/dunitian/p/4667038.html VS完整卸载工具:https://gith ...

  10. 豹哥嵌入式讲堂:ARM开发之文件详解(2)- linker文件

    大家好,我是豹哥,猎豹的豹,犀利哥的哥.今天豹哥给大家讲的是嵌入式开发里的linker文件. 在前一节课source文件详解里,豹哥给大家系统地介绍了source文件,source文件是嵌入式工程里典 ...