PE结构(番外)RVA与FOA
RVA与FOA的概念
RVA
所谓RVA,即Relative virtual address,直白翻译的话就是“相对虚拟地址”。
先了解下为什么需要这个东西吧。
假设一段程序,其内存起始地址是0x4001000,当前运行的语句则位于0x4002000,那么该语句相对于起始地址来说,偏移了多少字节呢?
0x4002000 - 0x4001000 = 0x1000
即偏移了4096字节,对吧?
我们将这个0x1000,称之为当前运行语句相对于内存起始地址的RVA。
为什么要有这个概念呢?
因为实际的应用中,由于内存起始地址不是一尘不变的,事实上出于安全性考虑,现代操作系统会给程序分配随机的起始内存地址。这就导致如果我们想要记录某个语句在内存中的位置,那么由于其内存地址总是在发生变化而变得不可靠。
仍以上面的例子来说,假如内存起始地址由0x4001000变更为0x4902300,那么我们之前记录的0x4002000这个地址就不再有效了。
但是无论起始地址如何变动,运行语句相对于起始地址的偏移量是不变的,对不对?也就是说,RVA是不会变动的。
那么我们只需要假设程序的内存起始地址为0x00000000,然后记录每个语句相对于0的RVA,之后无论起始地址如何变动,只需要对每个RVA加上这个起始地址,就可以保证程序正常运行了。
这个起始地址,一般就称之为ImageBase,程序运行的入口地址(OEP)则是一个RVA。
真实调度时,需要将OEP加上ImageBase,来确定程序的入口。
FOA
其实了解了RVA的概念之后,FOA就很好了解了。
RVA是某个语句相对于ImageBase的偏移量,这是内存中的故事。
程序在装载到内存之前,是存放在文件中的,那么某个语句相对于文件开始位置的偏移量,就称之为FOA。
两者间的转换逻辑
当我们已知某个语句、某个程序段、又或是某个数据目录页的RVA,想要找到它在文件中的位置时,应该怎么做呢?
情况一,RVA处于PE头部
无论这个RVA处于DOS头范围内、PE头范围内、OP头范围内、或是节表范围内,它相对于ImageBase的偏移量和它相对于文件开始位置的偏移量就一定是相等的。
我们可以使用OP头内的SizeOfHeader来做判断,如果RVA小于SizeOfHeader,那么它其实就是FOA了。
情况二,RVA处于某个节内
这种情况才是常规情况,如果RVA处于程序的某个节中,那么我们就不能直接使用它来作为FOA了。
因为节内的数据,在装入内存中时,存在拉伸过程。其相对于文件开始位置的偏移已经不再可靠了。
我们应当先找到RVA处于哪个节中,即遍历所有节表,判断RVA是否大于该节的起始地址,并且小于该节的起始地址+该节的长度。
找到RVA位于的节后,就可以用RVA减去该节的起始地址,得到相对于该节的偏移量。
再通过该节在文件中的起始位置,加上这个偏移量,得到这段程序在文件中的实际位置。
在节表中,每个节的目录描述了该节的属性。
节的起始地址(RVA):VirtualAddress
该节的长度:PhysicalAddress/VirtualSize
节的起始地址(FOA):PointerToRawData
编程实现
1 |
|