0x0 基础知识
- 当PE文件被执行的时候,Windows加载器将文件装入内存并将导入表登记的动态链接库(一般是DLL格式)文件一并装入地址空间,再根据DLL文件中的函数导出信息对被执行文件的IAT进行修正。
- (基础补充:我们都明白Windows在加载一个程序后就在内存中为该程序开辟一个单独的虚拟地址空间,这样的话在各个程序自己看来,自己就拥有几乎任意地址的支配权,所以他自身的函数想放在哪个地址自己说了算。有一些函数很多程序都会用到,为每一个程序写一个相同的函数看起来似乎有点浪费空间,因此Windows就整出了动态链接库的概念,将一些常用的函数封装成动态链接库,等到需要的时候通过直接加载动态链接库,将需要的函数整合到自身中,这样就大大的节约了内存中资源的存放。如图:
- 有一个重要的概念需要记住:动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是”一体“的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。所以,动态链接库用小甲鱼的话说就是”寄生虫”)
问:那导出表是干啥用的呢?
答:导出表就是记载着动态链接库的一些导出信息。通过导出表,DLL文件可以向系统提供导出函数的名称、序号和入口地址等信息,以便Windows加载器通过这些信息来完成动态连接的整个过程。答:
- 数据目录表中指向导出表
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
- 导出表 是 用来描述 模块(dll)中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址。
- 函数导出的方式有两种:
- 一种是 按 名字 导出
- 一种是 按 序号 导出
- 下面看一下导出表的定义吧:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
结构还算比较简单,具体每一项的含义如下:
- Characteristics:现在没有用到,一般为0。
- TimeDateStamp:导出表生成的时间戳,由连接器生成。
- MajorVersion,MinorVersion:看名字是版本,实际貌似没有用,都是0。
- Name:模块的名字,一个RVA值,指向了一个定义了模块名称的字符串。
- Base:序号的基数,按序号导出函数的序号值从Base开始递增。
- NumberOfFunctions:所有导出函数的数量。
- NumberOfNames:按名字导出函数的总数。被定义函数名称的导出函数的总数,显然只有这个数量的函数既可以用函数名方式导出。也可以用序号方式导出,剩下的NumberOfFunctions减去NumberOfNames数量的函数只能用序号方式导出。该字段的值只会小于或者等于NumberOfFunctions字段的值,如果这个值是0,表示所有的函数都是以序号方式导出的
- AddressOfFunctions:一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同。
- AddressOfNames:一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字。
- AddressOfNameOrdinals:一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号。
在图中,AddressOfNames 指向一个数组,数组里保存着一组 RVA,每个RVA指向一个字符串,这个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的对应项。获取导出函数地址时,先在AddressOfNames中找到对应的名字,比如Func2,他在AddressOfNames中是第二项,然后从AddressOfNameOrdinals中取出第二项的值,这里是2,表示函数入口保存在AddressOfFunctions这个数组中下标为2的项里,即第三项,取出其中的值,加上模块基地址便是导出函数的地址。如果函数是以序号导出的,那么查找的时候直接用序号减去Base,得到的值就是函数在AddressOfFunctions中的下标。在图中,AddressOfNames 指向一个数组,数组里保存着一组 RVA,每个RVA指向一个字符串,这个字符串即导出的函数名,与这个函数名对应的是AddressOfNameOrdinals中的对应项。获取导出函数地址时,先在AddressOfNames中找到对应的名字,比如Func2,他在AddressOfNames中是第二项,然后从AddressOfNameOrdinals中取出第二项的值,这里是2,表示函数入口保存在AddressOfFunctions这个数组中下标为2的项里,即第三项,取出其中的值,加上模块基地址便是导出函数的地址。如果函数是以序号导出的,那么查找的时候直接用序号减去Base,得到的值就是函数在AddressOfFunctions中的下标。
0x1 实现代码
© 版权声明
THE END
暂无评论内容