PE_导出表

导出表概念

一般情况下,PE中的导出表存在于动态链接库文件里。但不能简单地认为EXE中没有导出表,例如WinWord.exe文件里就有;也不能简单地认为所有的DLL中都有导出表,例如一些专门存放资源位文件的DLL里就没有导出表。
导出表的主要作用是将PE中存在的函数引出到外部,以便其他人可以使用这些函数,实现代码的重用。

导出表的作用

Windows装载器在进行PE装载时,会将导入表中登记的所有DLL一并装入,然后根据DLL的导出表中对导入函数的描述修正导入表的IAT值。 通过导入表,DLL文件向调用它的程序或系统提供导出函数的名称,序号,以及入口地址等信息。
综上所述,导出表的作用如下:

  • 可以通过导出表分析不认识的动态链接库文件所提供的功能
  • 向调用者提供输出函数指令在模块中的起始地址

导出表数据结构(IMAGE_EXPORT_DIRECTORY)

IMAGE_EXPORT_DIRECTORY	STRUCT
	Characteristics				DWORD		?	;0000h	-标志,未用
	TimeDateStamp				DWORD		?	;0004h	-时间戳
	MajorVersion				WORD		?	;0008h	-未用
	MinorVersion				WORD		?	;000ah	-未用
	nName						DWORD		?	;000ch	-指向该导出表的文件名字符串
	nBase						DWORD		?	;0010h	-导出函数的起始序号
	NumberOfFunctions			DWORD		?	;0014h	-所有的导出函数个数
	NumberOfNames				DWORD		?	;0018h	-以函数名导出的函数个数
	AddressOfFunctions			DWORD		?	;001ch	-导出函数地址表RVA
	AddressOfNames				DWORD		?	;0020h	-函数名称地址表RVA
	AddressOfNameOrdinals		DWORD		?	;0024h	-函数序列地址表
IMAGE_EXPORT_DIRECTORY	ENDS

导入表的IMAGE_IMPORT_DESCRIPTOR个数与调用的动态链接库个数相等,而导出表的IMAGE_EXPORT_DIRECTORY只有一个。

IMAGE_EXPORT_DIRECTORY字段说明:

IMAGE_EXPORT_DIRECTORY.nName

+000ch,双字,该字段指示的地址指向了一个以“\0”结尾的字符串,字符串记录了导出表所在的文件的最初文件名

IMAGE_EXPORT_DIRECTORY.NumberOfFunctions

+0014h,双字。该字段定义了文件中导出函数的总个数

IMAGE_EXPORT_DIRECTORY.NumberOfNames

+0018h,双字。在导出表中,有些函数是定义名字的,有些事没有定义名字的。该字段记录了所有定义名字函数的个数。 如果此值为0,则表示所有的函数都没有定义名字。
NumberOfNames和NumberOfFunctions的关系是前者小于后者(NumberOfNames < NumberOfFunctions)。(其实这关系也不一定,如果多个名字指向一个函数则此结论不成立)

IMAGE_EXPORT_DIRECTORY.AddressOfFunctions

+001ch,双字。该指针指向了全部导出函数的入口地址的起始。 从入口地址开始为双字数组,数组的个数由字段IMAGE_EXPORT_DIRECTORY.NumberOfFunctions决定。导出函数的每一个地址按函数的编号顺序依次往后排开。

IMAGE_EXPORT_DIRECTORY.nBase(编号)

+0010h,双字。导出函数编号的起始值。DLL中的第一个导出函数并不从0开始的,某个导出函数的编号等于从AddressOfFunctions开始的顺序号加上这个值,如下:

在这里插入图片描述

IMAGE_EXPORT_DIRECTORY.AddressOfNames

+0020h,双字,该值为一个指针,该指针指向的位置是一连串的双字值,这些双字值均指向了对应的定义了函数名的函数的字符串地址。这一连串的双字个数为NumberOfNames

在这里插入图片描述
特别说明:
1、函数的真正的名字在文件中位置是不确定的。
2、但函数名称表中是按名字排序的。
也就是说,A开头的函数在AddressOfNames排在最前面;
但AXXXXXX这个真正的名字,可能排在BXXXXX后面。
3、如果想打印名字,要先将AddressOfNames转换为FOA。

IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals

+0024h,双字。该值也是一个指针,与AddressOfNames是一一对应关系(注意,是一一对应),所不同的是,AddressOfNames指向的是字符串的指针数组,而AddressOfNameOrdinals则指向了该函数在AddressOfFunction中的索引值
注意:
索引值是一个,而非双字。 该值与函数编号是两个不同的概念,两者的之间的关系为:

索引值=编号-nBase。

如下图导入表中“Hint/Name”中的Hint值是AddressOfFunctions 的索引值,并非编号。
在这里插入图片描述

导出表结构各字段关系图

如图所示,AddressOfNames中的函数是从Function2开始的,也就是说,这里假设Function1只提供编号访问;其nBase为200h,所以对应的AddressOfNameOrdinals是0001h,但最终函数Function1的编号为:索引值+nBase的值,即0201h。

在这里插入图片描述
在这里插入图片描述注:保存地址的相关表中的内容全是RVA。

总结(为什么要分成3张表?)

1、函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开。
2、函数地址表是不是一定大于函数名称表?
未必,一个相同的函数地址,可能有多个不同的名字。
3、如何根据函数的名字获取一个函数的地址?
在这里插入图片描述4、如何根据函数的导出序号(AddressOfNameOrdinals)获取一个函数的地址?
在这里插入图片描述

实例分析

1.查看导出表位置
在这里插入图片描述RVA = 0X2140
SIZE = 0X8F(SIZE大小没什么用)
FOA = 0X940

2.找出对应字段值
在这里插入图片描述

90 21 00 00	// nName
01 00 00 00	//nBase
04 00 00 00	//NumberOfFunctions
04 00 00 00	//NumberOfNames

68 21 00 00	//AddressOfFunctions

RVA = 0x2168
FOA = 0x968

在这里插入图片描述在这里插入图片描述


78 21 00 00	//AddressOfNames

RVA = 0x2178
FOA = 0x978
在这里插入图片描述
在这里插入图片描述


88 21 00 00	//AddressOfNameOrdinals

RVA = 0X2188
FOA = 0X988
在这里插入图片描述在这里插入图片描述

查找函数地址(两种方法)

根据编号查找函数地址

一,定位到PE头;
二,从PE文件头中找到数据目录,表项的第一个双字值是导出表的起始RVA;
三,从导出表的nBase字段得到起始序号;
四,函数编号减去起始序号得到的是函数在AddressOfFunctions中的索引号
五,通过查询AddressOfFunctions指定索引位置的值,找到虚拟地址;
六,将虚拟地址加上该动态链接库在被导入到地址空间后的基址,即为函数的真实入口地址。

注:不建议使用编号查找函数地址。因为有很多的动态链接库汇总标识的编号与对应的函数并不一致,通过这种方法找到的函数地址往往是错误的

根据名字查找函数地址

一,定位到PE头;
二,从PE文件中找到数据目录表,表项的第一个双字值是导出表的起始RVA;
三,从导出表中获取NumberOfNames字段的值,以便构造一个循环,根据此值确定循环的次数;
四,从AddressOfNames字段指向的函数名称数组的第一项开始,与给定的函数名字进行匹配;如果匹配成功,则记录从AddressOfNames开始的索引号;
五,通过索引号再去检索AddressOfNameOridinals数组,从同样索引的位置找到函数的地址索引;
六,通过查询AddressOfFuncs指定函数地址索引位置的值,找到虚拟地址;
七,将虚拟地址加上该动态链接库在被导入到地址空间的基地址,即为函数的真实入口地址。

遍历导出表

需完成如下要求:
在这里插入图片描述

导出表的应用

在这里插入图片描述

导出函数覆盖

在这里插入图片描述

修改导出结构中的函数地址

还是上面实例中的导出表部分内容。
交换以下两个值的内容
在这里插入图片描述程序改变运行状态。

注意:
在这里插入图片描述
在实际操作中不赞成使用该技术。(至于为什么?可能是环境控制不便容易失败吧)

覆盖函数地址部分的指令代码

第二种常见的覆盖技术,是将AddressOfFunctions指向的地址空间指令字节码实施覆盖。这种技术又衍生处两种:

  • 暴力覆盖,即将所有的代码全部替换为新代码,新代码可能含有原来代码的全部功能,也可能不包含原有代码功能
  • 完美覆盖,通过构造指令,实施新代码和原代码的共存和无遗漏运行。

说明:有点类似ROP链的攻击方法,实例可自行百度参考。
注意:一定要多考虑下代码的重定位问题。

导出私有函数

这里就不码字和截图了,实现这个功能思想其实很简单,就是让程序自己告诉自己把藏着的私有函数吐出来。
这里分享一位大佬朋友的链接,最后面有导出私有函数的实例。
寻梦&之璐
里面还有其他的好文章,大家也能多学习学习。

结束

如有疑问欢迎私聊交流学习。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值