《初识PE》导出表

转自:http://www.blogfshare.com/pe-export.html

(二).导出表

当PE文件被执行的时候,Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中的函数导出信息对被执行文件的IAT表进行修正。

Windows 在加载一个程序后就在内存中为该程序开辟一个单独的虚拟地址空间,这样的话在各个程序自己看来,自己就拥有几乎任意地址的支配权,所以他自身的函数想放在哪个地址自己说了算。有一些函数很多程序都会用到,为每一个程序写一个相同的函数看起来似乎有点浪费空间,因此Windows就整出了动态链接库的概念,将一些常用的函数封装成动态链接库,等到需要的时候通过直接加载动态链接库,将需要的函数整合到自身中,这样就大大的节约了内存中资源的存放。

动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一体”的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。

导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL 文件可以向系统提供导出函数的名称、序号和入口地址等信息,比便Windows 加载器通过这些信息来完成动态连接的整个过程。

扩展名为.exe 的PE 文件中一般不存在导出表,而大部分的.dll 文件中都包含导出表。但注意,这并不是绝对的。例如纯粹用作资源的.dll 文件就不需要导出函数啦,另外有些特殊功能的.exe 文件也会存在导出函数。

一、导出表的结构

1.获取导出表的位置

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

2.导出表的组成

导出表中为每个导出函数定义了导出序号,但函数名的定义是可选的。对于定义了函数名的函数来说,既可以使用名称导出,也可以使用序号导出:对于没有定义函数名的函数来说,只能使用序号来导出。

导出表的起始位置有一个IMAGE_EXPORT_DIRECTORY结构,与导入表中有多个IMAGE_IMPORT_DESCRIPTOR结构不同,导出表中只有一个IMAGE_EXPORT_DIRECTORY结构,定义如下:

IMAGE_EXPORT_DIRECTORY STRUCT
Characteristics DWORD ? ; 未使用,总是定义为0
TimeDateStamp DWORD ?       ; 文件生成时间
MajorVersion WORD ? ; 未使用,总是定义为0
MinorVersion WORD ? ; 未使用,总是定义为0
Name      DWORD ? ; 模块的真实名称RVA
Base        DWORD ? ; 基数,加上序数就是函数地址数组的索引值
NumberOfFunctions DWORD ? ; 导出函数的总数
NumberOfNames DWORD ? ; 以名称方式导出的函数的总数
AddressOfFunctions DWORD ? ; 指向输出函数地址的RVA
AddressOfNames DWORD ? ; 指向输出函数名字的RVA
AddressOfNameOrdinals DWORD ? ; 指向输出函数序号的RVA

IMAGE_EXPORT_DIRECTORY ENDS

这个结构中的一些字段并没有被使用,有意义的字段说明如:

①Name

一个RVA 值,指向一个定义了模块名称的字符串。如即使Kernel32.dll 文件被改名为”Ker.dll”,仍然可以从这个字符串中的值得知其在编译时的文件名是”Kernel32.dll”。

②NumberOfFunctions

文件中包含的导出函数的总数。

③NumberOfNames

被定义函数名称的导出函数的总数,显然只有这个数量的函数既可以用函数名方式导出。也可以用序号方式导出,剩下的NumberOfFunctions 减去NumberOfNames 数量的函数只能用序号方式导出。该字段的值只会小于或者等于 NumberOfFunctions 字段的值,如果这个值是0,表示所有的函数都是以序号方式导出的。

④AddressOfFunctions

一个RVA 值,指向包含全部导出函数入口地址的双字数组。数组中的每一项是一个RVA 值,数组的项数等于NumberOfFunctions 字段的值。

⑤Base

导出函数序号的起始值,将AddressOfFunctions 字段指向的入口地址表的索引号加上这个起始值就是对应函数的导出序号。假如Base 字段的值为x,那么入口地址表指定的第1个导出函数的序号就是x;第2个导出函数的序号就是x+1。总之,一个导出函数的导出序号等于Base 字段的值加上其在入口地址表中的位置索引值。

⑥AddressOfNames 和 AddressOfNameOrdinals

均为RVA 值。前者指向函数名字符串地址表。这个地址表是一个双字数组,数组中的每一项指向一个函数名称字符串的RVA。数组的项数等NumberOfNames 字段的值,所有有名称的导出函数的名称字符串都定义在这个表中;后者指向另一个word 类型的数组(注意不是双字数组)。数组项目与文件名地址表中的项目一一对应,项目值代表函数入口地址表的索引,这样函 数名称与函数入口地址关联起来。

举个例子,加入函数名称字符串地址表的第n 项指向一个字符串“MyFunction”,那么可以去查找 AddressOfNameOrdinals 指向的数组的第n 项,假如第n 项中存放的值是x,则表示AddressOfFunctions 字段描述的地址表中的第x 项函数入口地址(aaaa)对应的名称就是“MyFunction”,这时这个函数的全部信息就可以如下描述。

函数名称:MyFunction    导出序号:Base的值+x    入口地址:aaaa

可以看到,AddressOfNameOrdinals字段描述的数组仅仅起了一个桥梁的作用。

3.从序号查找入口地址

已知函数的导出序号,如何得到入口地址呢?

①定位到PE 文件头。

②从PE 文件头中的 IMAGE_OPTIONAL_HEADER32 结构中取出数据目录表,并从第一个数据目录中得到导出表的RVA。

③从导出表的 Base 字段得到起始序号。

④将需要查找的导出序号减去起始序号,得到函数在入口地址表中的索引。

⑤检测索引值是否大于导出表的 NumberOfFunctions 字段的值,如果大于后者的话,说明输入的序号是无效的。

⑥用这个索引值在 AddressOfFunctions 字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA 值,当函数被装入内存的时候,这个RVA 值加上模块实际装入的基地址,就得到了函数真正的入口地址。

4.从函数名称查找入口地址

已知导出函数的名称,如何得到入口地址呢?

①最初的步骤是一样的,那就是首先得到导出表的地址。

②从导出表的NumberOfNames 字段得到已命名函数的总数,并以这个数字作为循环的次数来构造一个循环。

③从AddressOfNames 字段指向得到的函数名称地址表的第一项开始,在循环中将每一项定义的函数名与要查找的函数名相比较,如果没有任何一个函数名是符合的,表示文件中没有指定名称的函数。

④如果某一项定义的函数名与要查找的函数名符合,那么记下这个函数名在字符串地址表中的索引值,然后在AddressOfNamesOrdinals 指向的数组中以同样的索引值取出数组项的值,我们这里假设这个值是x。

⑤最后,以 x 值作为索引值,在 AddressOfFunctions 字段指向的函数入口地址表中获取的 RVA 就是函数的入口地址。

一般情况下病毒程序就是通过函数名称查找入口地址的,因为病毒程序作为一段额外的代码被附加到可执行文件中的,如果病毒代码中用到某些 API 的话,这些 API 的地址不可能在宿主文件的导出表中为病毒代码准备好。因此只能通过在内存中动态查找的方法来实现获取API 的地址。

时间: 2024-10-15 04:34:28

《初识PE》导出表的相关文章

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

上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,DataDirectory中的每一项都可以用这个函数获取,函数原型如下: PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size); Base:模

《初识PE》导入表

最近听别人讲的我晕晕乎乎的,于是上网上百度下,感觉这篇还不错.  链接:http://www.blogfshare.com/pe-export.html 一.导入表简介 在编程中常常用到"导入函数"(Import functions),导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL中,在调用者程序中只保留一些函数信息,包括函数名及其驻留的DLL名等. 于磁盘上的PE 文件来说,它无法得知这些输入函数在内存中的地址,只有当PE 文件被装入内存后

PE知识复习之PE的导出表

PE知识复习之PE的导出表 一丶简介 在说明PE导出表之前.我们要理解.一个PE可执行程序.是由一个文件组成的吗. 答案: 不是.是由很多PE文件组成.DLL也是PE文件.如果我们PE文件运行.那么就需要依赖DLL.系统DLL就是Kerner32.dll user32.dll等等.这些都是PE文件. 什么是导出表: 导出表就是当前的PE文件提供了那些函数.给别人用. 举个例子: PE文件相当于一个饭店.那么菜单就是导出表. 导出表解盲: 有人认为exe可执行文件.没有导出表.而DLL有导出表.这

PE文件学习

本文链接:http://blog.csdn.net/u012763794/article/details/51469477 1.介绍 什么是PE文件? PE文件是windows操作系统下使用的可执行文件格式.32位就直接叫PE或PE32,64位的就PE+或PE32+,注意不是PE64哦!!!! 学习PE文件其实就是学习结构体,里面储存了如何加载到内存,从何处开始运行,运行需要那些dll,需要多大的栈和内存等 初识PE文件 看看大概包括那些结构体吧 2.PE头 下面实例是利用notepad.exe

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

分析恶意驱动

用IDA也有好些时间了,以前就只会用F5功能玩无壳无保护的裸驱动,感觉太坑了,这两天就开始看网上大牛的逆向. 今天记录一下sudami曾经逆向过的fuck.sys.第一遍自己走的时候漏掉了好多东西,然后看他的新驱动,一步步比较, 最后把驱动文件分析的比较明朗了. 首先这个文件有的别多的花指令,jz,jnz .jb,jnb.jo,jno,js.jns. 通过idc脚本去除花指令,快捷键是shift+f2 这是DriverEntry里面的代码,首先传递[ebp+0Ch] 给函数,了解栈帧的话,就知道

PE文件结构与函数导出表——详解与实例

PE文件结构与函数导出表--详解与实例 随着windows系统从Xp升级到Win7.Win8, 从32位升级到64位,PE文件结构在整体未变的情况下发生了一些小的变动,一方面是推荐的程序装载地址未采用,另一方面,导出函数序号不再是简单的升序,而是一定程度上的进行了乱序.本文首先对PE文件结构进行了详尽的解说,接着介绍了如何得出函数导出表,整个过程采用SysWoW64目录下的wininet.dll实例进行说明.在介绍过程中,明确指出了Win7.Win8等新系统相对Xp带来的区别. 文章链接:htt

小甲鱼PE详解之输入表(导出表)详解(PE详解09)

小甲鱼PE详解之输出表(导出表)详解(PE详解09) 当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一并装入地址空间,再根据DLL 文件中的函数导出信息对被执行文件的IAT 进行修正. ( 基础补充:很多朋友可能看到这里会有点懵,各位看官请允许小甲鱼啰嗦一下,照顾初学者.我们都明白Windows 在加载一个程序后就在内存中为该程序开辟一个单独的虚拟地址空间,这样的话在各个程序自己看来,自己就拥有几乎

取PE文件的引入表和导出表

直接上代码(这里列出C++和Delphi的代码),Delphi代码中包含导入及导出文件和函数列表,PE结构可参阅资料,很多很详细,需要注意的是,本例中是映射到内存,不是通过PE装载器装入的,所以对于节的RVA地址需要转换成为文件偏移地址. Delphi代码 [delphi] view plaincopy unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Fo