【文章标题】: 对PE文件头中的NumberOfRvaAndSizes字段分析
【文章作者】: newjueqi
【作者邮箱】: zengjiansheng1@126.com
【作者QQ号】:190678908(sdf )
【使用工具】:OD, WinHex,相关的dll文件和测试程序(见附件)
【操作平台】: xpSp2
【作者声明】: 本人水平有限,失误之处敬请诸位大侠赐教!
缘由:
在本人发表的“手写DLL文件”(链接:http://bbs.pediy.com/showthread.php?t=75210)文章里,网友莽莽提出了如下的问题:
DWORD NumberOfRvaAndSizes;
(30) NumberOfRvaAndSize:四个字节,数据目录表的数目。这个字段自从NT发布以来就是16。设置为 “00 00 00 10”, 在16进制编辑器里输入“10 00 00 00”。——可发现NumberOfRvaAndSizes不设置成16,程序也可以照样运行?在按照http://bbs.pediy.com/showthread.php?t=48590做EXE的时候,我就没把NumberOfRvaAndSizes设置成16,程序找跑不误。我发现在我做的这个EXE中,设置成0或者1都不行,大于等于2就行了,能解释一下为什么么?
在《加密与解密3》里,对NumberOfRvaAndSize这个字段的解释是数据目录的项数,这个字段从最早的WinNt发布以来一直都是16.
于是本人就对这个奇怪的问题进行分析,结果嘛,就是得到这份分析报告 ^-^
现象分析:
对NumberOfRvaAndSizes的值进行试验,发现如果NumberOfRvaAndSizes的值大于5程序就能正常运行,否则的就出错。好,现在拿出调试利器OD,进行下面的三个试验。
提示一下,如果我们能用OD直接调试这个DLL文件,就会淹没在一堆系统的API里,我们可以通过附件中的测试程序testdll.exe里的DLL函数调用来间接调试这个DLL文件,这样的好处是调试简单,直指问题的核心部分。
实验1:
设置NumberOfRvaAndSizes的值为0,用OD跟踪到图1的代码后就有出错对话框
图1
由图1中的红色框部分可知,GetProcAddress函数调用失败,不能调用DLL里的输出函数ShowMesBox,也就是输出表没有初始化。
实验2:
设置NumberOfRvaAndSizes的值为1,用OD跟踪,今次没有出现实验1 的问题,可以进入到DLL的内部,
一直按F8来到图2所示的代码:
图2
从图2可看到一个问题,所有的数据都没有重定位,也就是程序没有处理重定位信息。
我们现在来看一下输入表的内容。由PE文件头区块信息可得,.text区块的RVA是1000h,idata区块的RVA是3000h,于是可以计算出输入表的内存地址为391000-1000+3000=393000h,在OD里查看393000h,
如下:
00393030 55 53 45 52 33 32 2E 64 6C 6C 00 00 00 00 00 00 USER32.dll......
00393040 50 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P0..............
00393050 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00 ..MessageBoxA...
00393060 >
50 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P0..............
本来在00393060处存放的是IAT表,但我们可看到,现在的地址数值为50 30 00 00,这个数值是PE文件在磁盘里的数值,也就是说DLL的输入表没有初始化。
实验3:
设置NumberOfRvaAndSizes的值为5,用OD跟踪,最后也来到实验2的图2所示的代码,所有的数据都没有重定位,也就是程序没有处理重定位信息。
我们查看一下输入表的内容(输入表地址的计算方法在实验2里有讲述),在OD里查看393000h,如下:
00393030 55 53 45 52 33 32 2E 64 6C 6C 00 00 00 00 00 00 USER32.dll......
00393040 50 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 P0..............
00393050 00 00 4D 65 73 73 61 67 65 42 6F 78 41 00 00 00 ..MessageBoxA...
00393060 >
8A 05 D5 77 00 00 00 00 00 00 00 00 00 00 00 00 ?誻............
由地址00393060h可看到,8A 05 D5 77就是MessageBoxA的系统地址,和实验2不同的是DLL的输入表已经初始化了。
推论:
(1)观察实验1,NumberOfRvaAndSizes的值为0,而数据目录表里Export Table的序号为0,Import Table的序号为1, Base relocation Table(重定位表) 的序号为5,由实验1的结果可知,输出表没有初始化。
(2)观察实验2,NumberOfRvaAndSizes的值为1,而数据目录表里Export Table的序号为0,Import Table的序号为1, Base relocation Table(重定位表) 的序号为5,由实验2的结果可知,Import Table,Base relocation Table(重定位表)都没有初始化。
(3)观察实验3,NumberOfRvaAndSizes的值为5,而数据目录表里Export Table的序号为0,Import Table的序号为1, Base relocation Table(重定位表) 的序号为5,由实验3的结果可知, Base relocation Table(重定位表)都没有初始化。
根据(1)(2)(3)可得出以下一个结论:
NumberOfRvaAndSize的值是影响到应用程序是否能访问到相应的数据表。程序要访问到对应的表,不谨相应的数据表要设置值,而且NumberOfRvaAndSize的值必须大于对应表的序号。例如,程序要访问到重定位表(序号为5),NumberOfRvaAndSize的值必须大于5,程序要访问到输出表(序号为1),NumberOfRvaAndSize的值必须大于1。
经过海风大牛的指点,对以上结论有更好的表达方法,引用如下:
对于NumberOfRvaAndSize的值,
0表示DATA_DIRECTORY一项也没有
1表示DATA_DIRECTORY有1项(即:ExportTable)
2表示DATA_DIRECTORY有2项(即:ExportTable和ImportTable)
以此类推
经过笨笨雄版主的指点,原来在PECOFF里早已说明了使用NumberOfRvaAndSizes的问题,幸好当时没看到,不然这份分析报告就不会诞生,对能力的培养也是一个损失
:
在PECOFF的3.4.3节里论述如下:
注意目录的数目是不固定的。在查看一个特定的目录之前,先检查可选文件头中 的 NumberOfRvaAndSizes的值。
所以,把NumberOfRvaAndSize这个字段的值设置为16就没有任何问题了。
后记,发表这篇文章后,得到看雪众多高手指出其中的不足之处和以后的研究方向,本人不胜感激。这篇文章没有讨论到什么深奥的技术,重在阐述发现问题,解决问题的思路
最后借这个机会问各位大牛一个问题,是在手写DLL时发现的:
按照标准的PE结构编写,数据目录表中的输出表(108h),输入表(110h),重定位表(130h)的正常数值
如下:
00000100 00 40 00 00
00 00 00 00 .@......
00000110 00 30 00 00
00 00 00 00 00 00 00 00 00 00 00 00 .0..............
00000120 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000130 00 50 00 00 10 00 00 00 00 00 00 00 00 00 00 00 .P..............
这样设置没问题,调用这个DLL的输出函数可以正常运行,如下图
但如果把数据目录表中的重定位表的Size字段值改变,无论是变大或者变小,都会出错,其中一个例子如下:
00000130 00 50 00 00
0F 00 00 00 00 00 00 00 00 00 00 00 .P..............
调用这个DLL的输出函数时就会弹出下面的对话框:
所以本人就觉得很奇怪,为什么改变数据目录表中的输出表和输入表的Size字段值没问题,而改变数据目录表中的重定位表的Size字段值就会问题?
网友mavermaver的答复如下:DLL装载API函数的地址必须按重定位信息来确定,所以重定位信息不能改.
但做完实验3后我就不同意这个答复,由实验3的观察可知,就算没有载入重定位信息DLL文件也能加载API函
数。
请知道原因的大侠指点一个小弟,不胜感激
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!