Chinese:
1、当PE文件执行时,Windows装载器将文件装入内存并将导入表登记的动态链接库文件一并装入地址空间,再根据dll文件中的函数导出信息对文件的IAT进行修正。
2、动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看作是一体的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。
3、导出表就是记载着动态链接库的一些导出信息。通过导出表,dll文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows装载器通过这些信息来完成动态链接的整个过程。
4、exe一般不存在导出表,大部分dll都包含导出表。但是这并不是绝对的,exe也可以存在导出函数,纯粹作为资源的dll也不需要导出函数。
5、导出表结构IMAGE_EXPORT_DIRECTORY
IMAGE_EXPORT_DIRECTORY STRUCT Characteristics DWORD ? ;未使用,总是0 TimeDateStamp DOWRD ? ;文件生成时间 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值,指向一个定义了模块名称的字符串。
[NumberOfFunctions]
文件中包含的导出函数的总数。
[NumberOfNames]
被定义函数名称的导出函数的总数,显然只有这个数量的函数既可以用函数名方式导出。也可以用序号方式导出,剩下的NumberOfFunctions减去NumberOfNames数量的函数只能以序号方式导出。
[AddressOfFunctions]
一个RVA值,指向包含全部导出函数入口地址的双字数组。数组中的每一项都是一个RVA,数组的长度等于NumberOfFunctions
[Base]
导出函数序号的起始值,将AddressOfFunctions字段指向的入口地址表的索引号加上这个起始值就是对应函数的导出序号。
[AddressOfNames]
RVA值,指向函数名字符串地址表。
[AddressOfNameOrdinals]
RVA值,指向另一个WORD类型的数组,数组项目与文件名地址表中的项目一一对应,项目值代表函数入口地址表的索引,这样函数名称就和函数入口地址关联起来了。
6、从序号查找函数入口地址
这个方法不推荐,因为如果我改了dll内容的时候,这个时候程序所定位的地址可能就发生错误了。
Windows装载器工作步骤:
(1) 通过PE头的IMAGE_OPTIONAL_HEADER32找到数据目录表,并从数据目录中得到导出表的RVA
(2) 从导出表的Base得到起始序号
(3) 将需要查找的导出序号减去其实序号,得到函数在入口地址表中的索引
(4) 检测索引值是否大于导出表的NumberOfFunctions字段的值,如果大于,则序号无效。
(5) 用这个索引值在AddressOfFunctions字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA,当函数被装入内存时,这个RVA值加上模块实际装入的基地址,就得到了函数真正的入口地址。
7、通过函数名称查找入口地址
(1) 通过PE头的IMAGE_OPTIONAL_HEADER32找到数据目录表,并从数据目录中得到导出表的RVA
(2) 从导出表的NumberOfNames字段得到已命名函数的总数,并以这个数字作为循环的次数来构造一个循环
(3) 从AddressOfNames字段指向得到的函数名称地址表的第一项开始,在循环中将每一项定义的函数名与要查找的函数名比较,如果没有任何一个符合,则没有指定名称的函数
(4) 如果某一项定义的函数名与要查找的函数名符合,那么几下这个函数名在字符串地址表中得索引值,然后在AddressOfNamesOrdinals指向的数组中以同样的索引值取出数组项的值,假设索引为x。
(5) 以x作为索引值,在AddressOfFunctions字段指向的导出函数入口地址表中获取的RVA就是函数真正的入口地址。