PE文件头和PE内存映像的文件头大小是一样的.节的数据在内存和磁盘文件的大小是不一样的,节表项记录了内存映像中节的起始RVA(IMAGE_SECTION_HEADER -> VirtualAddress),也同样记录了本节在文件中的起始偏移FOA(IMAGE_SECTION_HEADER -> PointerToRawData).由于节在内存中是线性排列的,因此如果找到下一个节的内存起始RVA就能知道上一个节的大小,所有的节组合起来就是PE中除去文件头以外的内容,而文件头在磁盘文件中和内存中是没有变化的.
- 相对虚拟地址(Relative Virtual Address, RVA) = 虚拟地址(VA) - 基地址(ImageBase).相对于基地址的偏移,即RVA是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量.
- 文件偏移地址(File Offset Address, FOA)和内存无关,它是指某个位置距离文件头的偏移.
文件中手动转换示例:
1).执行下述代码获取全局变量地址
DWORD g_Data = 0x12345678;
int main()
{
printf("global data -> [0x%08x] \r\n", g_Data);
system("Pause");
return 0;
}
得到当前全局变量地址(VA)为0x00541008
2).通过WinHex解析文件.获取ImageBase与文件和内存对齐大小
ImageBase = 0x00400000
SectionAlignment = 0x00001000
FileAlignment = 0x00000200
Ps: 如果文件对齐与内存对齐相同为1000h,且节区在内存中大小小于文件中大小那么RVA与FOA一致.这里测试文件不同
RVA = VA - ImageBase = 0x00541008 - 0x00400000 = 0x141008
3).通过WinHex解析文件.获取节区对应文件与内存偏移
这里方便用工具展示结果(.textbss 代表未被初始化的静态内存区。此区段不占用磁盘空间,仅占用内存空间)
判断当前RVA(0x141008)位于哪个节中.公式为RVA ≥ VirtualAddress && RVA < VirtualAddress + VirtualSize
可以得出RVA(0x141008)位于节.data中.
算出RVA在节区对应OFFSET RVA - VirtualAddress = 0x141008 - 0x141000 = 0x8
FOA为OFFSET + PointerToRawData = 0x8 + 0xE7800 = 0xE7808
全局变量对应FOA为0xE7808数据为0x12345678;
修改其对应数据并保存观察效果:
数据被成功修改.
上述示例通过代码实现(全局变量未初始化时在文件中将不会有对应数据,拉伸到内存后才会分配对应内存):
RVA转FOA
DWORD RvaToFoa(IN PVOID pBuffer, IN DWORD dwRva)
{
//定位PE结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)((PUCHAR)pBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((PUCHAR)pOpo + pFil->SizeOfOptionalHeader);
//转换地址是否在头+节表中
if (dwRva < pOpo->SizeOfHeaders)
{
return dwRva;
}
//遍历转换地址在哪个节中
for (size_t i = 0; i < pFil->NumberOfSections; i++)
{
if ((dwRva >= pSec[i].VirtualAddress) && (dwRva < pSec[i].VirtualAddress + pSec[i].Misc.VirtualSize))
{
return dwRva - pSec[i].VirtualAddress + pSec[i].PointerToRawData;
}
}
return 0;
}
FOA转RVA
DWORD FoaToRva(IN PVOID pBuffer, IN DWORD dwFoa)
{
//定位PE结构
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuffer;
PIMAGE_NT_HEADERS pNth = (PIMAGE_NT_HEADERS)((PUCHAR)pBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFil = (PIMAGE_FILE_HEADER)((PUCHAR)pNth + 4);
PIMAGE_OPTIONAL_HEADER pOpo = (PIMAGE_OPTIONAL_HEADER)((PUCHAR)pFil + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pSec = (PIMAGE_SECTION_HEADER)((PUCHAR)pOpo + pFil->SizeOfOptionalHeader);
//转换地址是否在头+节表中
if (dwFoa < pOpo->SizeOfHeaders)
{
return dwFoa;
}
//遍历转换地址在哪个节中
for (size_t i = 0; i < pFil->NumberOfSections; i++)
{
if ((dwFoa >= pSec[i].PointerToRawData) && (dwFoa < pSec[i].PointerToRawData + pSec[i].SizeOfRawData))
{
return dwFoa - pSec[i].PointerToRawData + pSec[i].VirtualAddress;
}
}
return 0;
}