RVA与FOA

VA: 虚拟内存地址(Virtual Address)PE 文件被操作系统加载进内存后的地址。

RVA: 程序在内存中的偏移地址(Relative Virual Address)
PE文件的相对虚拟地址是PE文件中的数据、模块等运行在内存中的实际地址相对PE文件装载到内存的基址之间的距离。举例说明,如果PE文件装入虚拟地址(VA)空间的400000h处,且进程从虚址401000h开始执行,我们可以说进程执行起始地址在RVA 1000h。

FOA:程序在文件,硬盘中的偏移地址(File Offset Address)
文件偏移地址,和内存无关,它是指某个位置距离文件头的偏移。


一个程序的各段在内存和文件中的对齐方式是不一样的。

FOA:文件对齐值是0x200。
RVA:内存对齐是0x1000
PE文件解析(2):RVA与FOA转换和区段表_逆向

区段表

我们在上节已经解析了Dos头和Nt头部分,下面我们来解析下一个部分:区段表部分:
一个程序有很多的区段组成:
.text
.data
.rdata
.idata
.edata
…等等
这些区段都有着各自的信息,每一个块都是一个结构体:

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;//区段在内存中的真实大小
    } Misc;
    DWORD   VirtualAddress;//RVA :区段在内存中的偏移地址
    DWORD   SizeOfRawData;	//区段在文件中对齐后大小 
    DWORD   PointerToRawData;//RVA: 区段在文件中的偏移位置	
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

010editor解析:
PE文件解析(2):RVA与FOA转换和区段表_i++_02

代码解析:

BOOL PE::GetSelctionInfo()
{
	PIMAGE_SECTION_HEADER pSelctionHeader = IMAGE_FIRST_SECTION(pNtHeader);

	for (UINT i = 0; i < pFileHeader->NumberOfSections; i++)
	{
		printf("区段名:%s\n", pSelctionHeader->Name);
		pSelctionHeader++;
	}
	return 0;
}
  1. pFileHeader结构体成员存储着区段的数量:NumberOfSections。
  2. 得到区段表的方法:
  • 利用偏移:NT部分的标准NT头大小是不变的,而可选NT头部分是可变的,但是标准NT头部分有一个成员存储着可选NT头的大小,我们利用 区段表地址 = NT头起始地址 + NT标识区大小(4) + 标准NT头大小 + 可选NT头大小,就可以得到区段表的地址:
PIMAGE_SECTION_HEADER pSelctionHeader =
 (PIMAGE_SECTION_HEADER)((DWORD)pNtHeader + 
 FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) +//FIELD_OFFSET可以直接计算出可选NT头到NT首地址的大小。
 pFileHeader->SizeOfOptionalHeader);
  • 一个宏:
//IMAGE_FIRST_SECTION可以直接得到区段表地址,只需传递NT头的起始位置。
PIMAGE_SECTION_HEADER pSelctionHeader = IMAGE_FIRST_SECTION(pNtHeader);

程序运行如下:
PE文件解析(2):RVA与FOA转换和区段表_PE_03

RVA到FOA的转换

通常情况下,我们需要将RVA转换为FOA:

PE文件解析(2):RVA与FOA转换和区段表_逆向_04

  • 数据的RVA - 区段的RVA = 数据 在内存中距离区段头的距离 s1
  • 数据的FOA - 区段的FOA = 数据 在文件中距离区段头的距离 s2
  • s1 = s2
  • 数据的RVA - 区段的RVA =数据的FOA - 区段的FOA
  • 数据的FOA = 数据的RVA(传参) - 区段的RVA + 区段的FOA

DWORD cPE::RvaToFoa(DWORD rva)
{
	//获得区段表
	/*
	Nt头的起始位置 + 可选头到Nt头的距离 + 可选头的大小
	*/
	PIMAGE_SECTION_HEADER pSelctionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)pNtHeader + FIELD_OFFSET(IMAGE_NT_HEADERS, OptionalHeader) + pFileHeader->SizeOfOptionalHeader);
	// 遍历每一个区段,看看 RVA(某个东西在内存中的偏移地址)是否落在某个区段上
	for (UINT i = 0; i < pFileHeader->NumberOfSections; i++)
	{
		/*
		数据的RVA - 区段的RVA = 数据 在内存中距离区段头的距离 s1
		数据的FOA - 区段的FOA = 数据 在文件中距离区段头的距离 s2
		s1=s2 
		数据的FOA - 区段的FOA = 数据的RVA - 区段的RVA
		所以:已知数据的RVA求数据的FOA: 数据的FOA = 数据的RVA - 区段的RVA + 区段的FOA
		*/
		if (rva >= pSelctionHeader->VirtualAddress && rva < pSelctionHeader->VirtualAddress + pSelctionHeader->Misc.VirtualSize)
		{
			//rva必须在某一个区段的里面, 大于区段头部偏移,小于区段头加大小
			return rva - pSelctionHeader->VirtualAddress + pSelctionHeader->PointerToRawData;
		}
		pSelctionHeader++;
	}
	return 0;
}