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

转载 2012年03月25日 11:14:47

原文出自:www.fishc.com

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


当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一并装入地址空间,再根据DLL 文件中的函数导出信息对被执行文件的IAT 进行修正。

基础补充:很多朋友可能看到这里会有点懵,各位看官请允许小甲鱼啰嗦一下,照顾初学者。我们都明白Windows 在加载一个程序后就在内存中为该程序开辟一个单独的虚拟地址空间,这样的话在各个程序自己看来,自己就拥有几乎任意地址的支配权,所以他自身的函数想放在哪个地址自己说了算。

有一些函数很多程序都会用到,为每一个程序写一个相同的函数看起来似乎有点浪费空间,因此Windows就整出了动态链接库的概念,将一些常用的函数封装成动态链接库,等到需要的时候通过直接加载动态链接库,将需要的函数整合到自身中,这样就大大的节约了内存中资源的存放。如图:





有一个重要的概念需要记住:
动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一体”的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。所以,动态链接库用小甲鱼的话说就是“寄生虫”! )

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

友情提示:扩展名为.exe 的PE 文件中一般不存在导出表,而大部分的.dll 文件中都包含导出表。但注意,这并不是绝对的。例如纯粹用作资源的.dll 文件就不需要导出函数啦,另外有些特殊功能的.exe 文件也会存在导出函数。所以,世事无绝对……好了,我们接下来就对导出表的结构进行分析。



导出表结构


导出表(Export Table)中的主要成分是一个表格,内含函数名称、输出序数等。序数是指定DLL 中某个函数的16位数字,在所指向的DLL 文件中是独一无二的。在此我们不提倡仅仅通过序数来索引函数的方法,这样会给DLL 文件的维护带来问题。例如当DLL 文件一旦升级或修改就可能导致调用改DLL 的程序无法加载到需要的函数。

数据目录表的第一个成员指向导出表,是一个IMAGE_EXPORT_DIRECTORY(以后简称IED)结构,IED 结构的定义如下:

IMAGE_EXPORT_DIRECTORY STRUCT
	Characteristics		DWORD	?	; 未使用,总是定义为0
	TimeDateStamp		DWORD	?      ; 文件生成时间
	MajorVersion		WORD	?	; 未使用,总是定义为0
	MinorVersion		WORD	?	; 未使用,总是定义为0
	Name			DWORD	?	; 模块的真实名称
	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 项函数入口地址对应的名称就是“MyFunction”复杂吧? 没事,接着看你就懂了,别放弃哦~)

整个流程跟其他PE 结构一样说起来复杂,但看图说话倒是挺容易的。所以小甲鱼还是本着实事求是的精神&……%¥#踏踏实实画图让大家好理解一点吧,来,请上图:






1. 从序号查找函数入口地址

下边小甲鱼带大家来模拟一下Windows 装载器查找导出函数入口地址的整个过程。如果已知函数的导出序号,如何得到函数的入口地址呢 ?

Windows 装载器的工作步骤如下:
  1. 定位到PE 文件头
  2. 从PE 文件头中的 IMAGE_OPTIONAL_HEADER32 结构中取出数据目录表,并从第一个数据目录中得到导出表的RVA
  3. 从导出表的 Base 字段得到起始序号
  4. 将需要查找的导出序号减去起始序号,得到函数在入口地址表中的索引
  5. 检测索引值是否大于导出表的 NumberOfFunctions 字段的值,如果大于后者的话,说明输入的序号是无效的
  6. 用这个索引值在 AddressOfFunctions 字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA 值,当函数被装入内存的时候,这个RVA 值加上模块实际装入的基地址,就得到了函数真正的入口地址

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

如果已知函数的名称,如何得到函数的入口地址呢?与使用序号来获取入口地址相比,这个过程要相对复杂一点!

Windows 装载器的工作步骤如下:
  1. 最初的步骤是一样的,那就是首先得到导出表的地址
  2. 从导出表的 NumberOfNames 字段得到已命名函数的总数,并以这个数字作为循环的次数来构造一个循环
  3. 从 AddressOfNames 字段指向得到的函数名称地址表的第一项开始,在循环中将每一项定义的函数名与要查找的函数名相比较,如果没有任何一个函数名是符合的,表示文件中没有指定名称的函数
  4. 如果某一项定义的函数名与要查找的函数名符合,那么记下这个函数名在字符串地址表中的索引值,然后在 AddressOfNamesOrdinals 指向的数组中以同样的索引值取出数组项的值,我们这里假设这个值是x
  5. 最后,以 x 值作为索引值,在 AddressOfFunctions 字段指向的函数入口地址表中获取的 RVA 就是函数的入口地址

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

因此只能通过在内存中动态查找的方法来实现获取API 的地址。关于病毒代码具体的实现分析,小甲鱼在今后将跟大家共同研究讨论这个话题~



输出表结构实例分析(具体过程将在视频中演示,这里不啰嗦啦~)

工具:PEinfo.exe, UltraEdit, W32DasmV10.0
解剖对象:Counter.dll

相关文章推荐

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

当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一并装入地址空间,再根据DLL 文件中的函数导出信息...

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

上一讲:小甲鱼PE详解之输入表(导入表)详解2小甲鱼PE详解之输出表(导出表)详解(PE详解09)当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) ...
  • fish_c
  • fish_c
  • 2011年07月29日 00:37
  • 1008

PE导出表(输出表)学习笔记

导出表

认识PE文件格式 - 输出表(Export)

当一个dll中的函数能被exe文件、或其他dll调用时,这样的函数称为“输出函数”。创建一个dll,实际上创建了一组能被exe或其他dll调用的函数,当该dll被装入内存时,PE装载器根据该dll中的...

小甲鱼pe详解打包

  • 2015年03月11日 22:39
  • 11.47MB
  • 下载

小甲鱼PE详解之IMAGE_NT_HEADERS结构定义即各个属性的作用

PE Header 是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,里边包含着许多PE装载器用到的重要字段。下边小甲鱼将为大家详细讲解哈~首先是IMAGE_NT_HEADERS 结构...

小甲鱼PE详解之IMAGE_OPTIONAL_HEADER32 结构定义即各个属性的作用

接着我们来谈谈 IMAGE_OPTIONAL_HEADER 结构,正如名字的意思,这是一个可选映像头,是一个可选的结构,但是呢,实际上上节课我们讲解的 IMAGE_FILE_HEADER 结构远远不足...

小甲鱼PE详解之区块描述、对齐值以及RVA详解

通常,区块中的数据在逻辑上是关联的。PE 文件一般至少都会有两个区块:一个是代码块,另一个是数据块。每一个区块都需要有一个截然不同的名字,这个名字主要是用来表达区块的用途。例如有一个区块叫.rdata...
  • HK_5788
  • HK_5788
  • 2015年09月05日 14:25
  • 592

小甲鱼PE详解之输入表(导入表)详解2(PE详解08)

上一讲:小甲鱼PE详解之输入表(导入表)详解1在此之前,我们已经对这个输入表进行了一些实践和理解,这有助于大家对这个概念更进一步的加深认识。小甲鱼觉得,越是复杂的问题我们应该越是去动手操作它,认识它,...
  • fish_c
  • fish_c
  • 2011年07月29日 00:32
  • 2007

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

在开始讲解输入表(导入表)概念之前,请允许小甲鱼童鞋用简短的几句话来总结之前我们学过的内容,并做进一步的思想综合提升,注意咯! 首先,我们知道PE 文件中的数据被载入内存后根据不同页面属性被划分...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:小甲鱼PE详解之输出表(导出表)详解(PE详解09)
举报原因:
原因补充:

(最多只允许输入30个字)