-
-
[原创]PE格式学习之导出表
-
发表于: 2019-3-20 15:37 5359
-
最近几天在温习PE格式,特此发帖纪录一下自己的学习过程,感谢论坛提供的学习环境和氛围。
PE即Portable Executable:32位或64位Windows操作系统使用的可执行程序或者动态链接库的文件格式。所以我首先贴出微软官方关于PE格式(导出表)的在线说明文档的链接:1feK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6V1L8$3y4K6i4K6u0W2L8h3W2U0M7X3!0K6L8$3k6@1i4K6u0W2j5$3!0E0i4K6u0r3k6h3&6Q4x3X3c8#2M7#2)9J5c8Y4N6A6L8X3c8G2N6%4y4Q4x3V1k6V1k6i4y4C8N6r3!0H3i4K6u0r3k6r3g2T1N6h3N6Q4x3V1k6H3k6g2)9J5k6r3k6G2M7X3#2S2N6q4)9J5x3%4c8Z5k6g2)9J5k6r3g2V1j5i4c8S2i4K6u0V1M7$3g2U0N6r3W2G2L8W2)9J5k6r3W2E0j5h3N6W2i4K6u0V1L8$3&6D9P5g2!0q4c8W2!0n7b7#2)9^5b7#2!0q4z5q4)9^5b7W2!0n7x3g2!0q4y4W2)9&6y4W2)9^5y4#2!0q4y4W2!0n7x3q4!0n7y4q4!0q4y4g2!0n7z5g2!0n7x3#2!0q4y4g2!0m8y4g2!0n7c8q4!0q4y4#2)9&6b7g2)9^5y4q4!0q4y4g2)9^5c8W2!0m8c8W2!0q4y4q4!0n7b7W2!0m8y4g2!0q4y4#2)9&6b7W2!0n7y4q4!0q4y4W2)9^5c8g2!0m8y4g2!0q4y4#2)9^5x3W2!0n7z5g2!0q4y4g2)9^5y4#2!0n7b7W2!0q4z5g2)9&6x3#2!0n7c8g2!0q4y4W2)9^5c8g2!0m8y4g2!0q4y4#2)9&6y4g2!0m8y4g2!0q4z5q4!0n7c8W2)9^5y4#2!0q4y4W2)9&6b7#2!0m8b7#2!0q4y4g2!0n7z5q4)9&6y4W2!0q4x3#2)9^5x3q4)9^5x3R3`.`.
关于前4个字段在这里就不再赘述,我们主要看一下后面的7个字段,在这7个字段中其中有4个是RVA,我们在解析时需要先将RVA转换为FOA,我的转换思路如下:
根据PE文件加载时的特性:PE文件头直接放入内存空间,不需要进行拉伸;节则根据节表的VirtualAddress进行拉伸,所以在进行转换时,只需要分别判断即可。
导出表并不是DLL文件的专属,一个EXE文件也可以拥有导出表,可以看一下我们最常用的OD,我下面的测试例子是用的DLL。现在我们来着重说明以下几个字段:
2.AddressOfFunctions:导出函数的RVA数组。这些是可执行代码和数据部分中导出的函数和数据的实际地址。通过NumberOfFunctions的说明我们知道函数地址表中的数据并不一定都是有效的,如果函数地址表的某一项内容为0x00000000,即为无效项
解析未按连续的序号导出的DLL文件,调试查看函数地址表的内容
3.AddressOfNames:
指向导出名称的指针数组(RVA),数组是一系列以null结尾的ASCII字符串,按升序排序。
4.AddressOfNameOrdinals:与名称指针表的成员(即导出名称的指针数组)相对应的序号数组。它们的关系是一一对应的;因此,名称指针表和序号表必须具有相同数量的成员。导出序号表是导出地址表中的16位索引数组(即每个序号都是导出地址表的索引),必须从序号中减去序数基数(即Base),以获得导出地址表中的真实索引。
相信讲到这里大家在控制台解析并打印出一个文件的导出表的内容应该不会有什么难度了,但是你会发现你的解析和其他工具解析的显示内容不一样,那是因为解析的过程并没有按照函数地址表和函数名称表及函数序号表的工作方式,这三张表才是整个导出表的难点和精髓,想了解导出表的工作原理,我们应该想到的是:为什么微软要设计3张表,我们先来看一下比较常用的函数GetProcAddress,函数原型如下
我们都知道想要获取一个模块中的函数的地址,参数2——lpProcName可以是名称,也可以是函数的序号。设计三张表的原因归根结底就是函数导出是可以按照函数名的方式导出,也可以按照序号的方式导出。
它们的工作原理如下:
当另一个映像文件按名称导入函数时,Win32加载程序在导入名称表(AddressOfNames)中搜索匹配的字符串。如果找到匹配的字符串,则通过在序号表(AddressOfNameOrdinals)中查找相应的成员来识别关联的序号。查找到的序号是导出地址表(AddressOfFunctions)的索引,它提供了所需函数的实际位置。
当另一个映像文件按序号导入函数时,不必在名称指针表中搜索匹配的字符串。直接在序号表(AddressOfNameOrdinals)中查找相应的成员来识别关联的序号,查找到的序号是导出地址表(AddressOfFunctions)的索引,它提供了所需函数的实际位置。
附上我的部分解析代码:
解析效果如下(界面模仿的LordPE):
映像文件(image file):可执行文件是指.EXE文件或DLL。 映像文件可以被认为是“内存映像”。通常使用术语“映像文件”而不是“可执行文件”,因为后者有时被认为仅表示.EXE文件。
如有错误,请大家指正!