在PE当中,含有导出表和导入表,两者的功能是什么呢?
一.功能
导入表:在PE文件中供自己使用的函数在一张表当中,便于寻找和使用
导出表:在PE文件中供其他PE文件使用的函数在一张表中,便于其他PE文件进行使用
今天着重介绍导入表
二.误区
在大众对PE的认知当中,可能会认为exe文件只有导入表,没有导出表(即只有供自己使用的函数),dll即有导入表又有导出表,其实并不然,exe在本质原理上是可以具有导出表的,只是在使用时并没有那方面的需求,所以就没有提供导出表。
三.具体分析
要想分析导出表具体内容,首先要知道如何去定位导出表
在PE可选头中,观察最后一个属性,数据项目录
在该属性中,含有16个结构体大小为8的结构体
前两个结构体分别代表导出表和导入表的RVA值及大小(注意,这里是RVA,即我们在文件中观察时,要明确内存对齐和文件对齐,即RVA 和 FOA 的转换)
这里写了一个简单的DLL,进行到=导出表的分析(该DLL实现了加减乘除四个功能),下面给出DLL源码
// newdll.cpp: implementation of the newdll class. // ////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "newdll.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// int __stdcall Plus(int x,int y){ return x+y; } int __stdcall Min (int x,int y){ return x-y; } int __stdcall Mul (int x,int y){ return x*y; } int __stdcall Div (int x,int y){ return x/y; } extern "C" __declspec(dllexport) __stdcall int Plus(int x,int y); extern "C" __declspec(dllexport) __stdcall int Min(int x,int y); extern "C" __declspec(dllexport) __stdcall int Mul(int x,int y); extern "C" __declspec(dllexport) __stdcall int Div(int x,int y);
用Winhex打开该dll文件进行分析,这里要求必须熟练掌握PE结构
通过查找发现,从000001C8开始是节表区域,因为可选头与节表是无缝连接的,所以我们可以从节表开始区向前找,因为数据目录项中,每一个结构体为8个字节,有16个结构体,根据该特征向前查找即可
标记区域即数据目录项,因为前面了解到,前8个字节分别代表导出表的RVA和大小,所以得知,导出表的RVA为2DF10(该DLL内存对齐和文件对齐相同,所以不需要RVA 和 FOA 的转换)
转到2DF10查看,该处即为导出表
要理解表中数据的含义,就要求掌握导出表中的属性
着重介绍几个属性
Name //dll的名称 Base //基数索号 = 序号-基数 NumberOfFunctions //函数的总数 NumberOfNames //有名函数的总数(在dll中函数可以用名称命名也可以用名称命名) AddressOfFunctions //导入函数地址表RVA AddressOfNames //导入函数名称表RVA AddressOfNameOrdinals //导入函数序号表RVA
在导入表中,又存在着三张表,即导入函数地址表,导入函数名称表,导入函数序号表,这三张表至关重要,是获取函数的路径
当得出这个表关系时,就可以真正理解当通过函数名称查询函数地址时,它是经过怎样的步骤进行查询的
首先,如果要查询Mul,就会先去函数名称表当中查询Mul所在表中的索引号是多少,这里可以看到为2,然后拿着这个2去函数序号表找索引为2的内容,可以发现为3,然后拿着3去找函数地址表中所对应的索引内容,即0000100A,这样就找到了函数的地址
如果是按照函数的序号查询,这里就会用到基数(Base)的概念,因为在默认情况下(即不改变函数序号的情况下),函数序号都是从1开始的,如果改变了,就需要基数的介入,即在函数序号中最小的数当作0,依次加1,其实就相当于0,1,2,3以此类推,这样就可以直接拿序号去函数地址表通过相应的基数进行查找,就能找到相应的函数地址