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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//将RVA转换为FOA
DWORD Rva2Foa(DWORD rva, lpPEFILE lpPEFile) {
if (rva < lpPEFile->h_op->SizeOfHeader) {
return rva;
}

lpPETable tableBase = lpPEFile->pet;
lpPEHPE peHeader = lpPEFile->h_pe;
DWORD tableNum = peHeader->NumberOfSections;

for (DWORD num = 0; num < tableNum; num++) {
PETable table = tableBase[num];
//rva处于当前节内
if (rva >= table.VirtualAddress && rva <= table.VirtualAddress + table.VirtualSize) {
DWORD offset = rva - table.VirtualAddress;
DWORD foa = offset + table.PointerToRawData;
return foa;
}
}

return NULL;
}

//将FOA转换为RVA
DWORD Foa2Rva(DWORD foa, lpPEFILE lpPEFile) {
if (foa < lpPEFile->h_op->SizeOfHeader) {
return foa;
}

lpPETable tableBase = lpPEFile->pet;
lpPEHPE peHeader = lpPEFile->h_pe;
DWORD tableNum = peHeader->NumberOfSections;

for (DWORD num = 0; num < tableNum; num++) {
PETable table = tableBase[num];
if (foa > table.PointerToRawData && foa <= table.PointerToRawData + table.SizeOfRawData) {
DWORD offset = foa - table.PointerToRawData;
DWORD rva = offset + table.VirtualAddress;
return rva;
}
}

return NULL;
}

PE结构(番外)RVA与FOA
http://dubhehub.github.io/blogs/2024050116220046929.html
Author
Sin
Posted on
May 1, 2024
Licensed under