继续PE系列笔记的更新
PE其它笔记索引可前往:
PE文件笔记一 PE介绍
前面学习了在块表中提到了VA和FOA,这次来学习它们之间的转换
VA转FOA
在先前的笔记PE文件笔记六 节表和节中,已经有提到关于VA、RVA和FOA的概念
|
对应结构体成员 |
英文全称 |
含义 |
VA |
_IMAGE_SECTION_HEADER.VirtualAddress |
Virtual Address |
在内存中的虚拟地址 |
RVA |
_IMAGE_SECTION_HEADER.VirtualAddress |
Relative Virtual Address |
相对虚拟地址 |
FOA |
_IMAGE_SECTION_HEADER.PointerToRawData |
File Offset Address |
文件偏移地址 |
接下来继续学习VA和FOA的转换
在学习转换之前,先探寻一下为什么要学习它们之间的转换?
为什么学习VA与FOA转换
在这里要引入一个问题:如何改变一个全局变量的初始值
在逆向基础笔记十二 汇编 全局和局部 变量中已经说过了全局变量和局部变量的区别
- 如果一个全局变量有初始值,那么它的初始值一定是存储在PE文件中的
- 如果一个全局变量没有初始值,那么在PE文件中就没有存储它的位置,只有当PE文件加载到内存中时,才会给它分配空间
学习RVA与FOA的转换后,就可以修改程序中的数据后一劳永逸,无需每次都用CE等内存工具搜索修改等等
全局变量初始值demo
为了学习,写一个小的程序,该程序输出全局变量的值和全局变量地址
代码
#include <stdio.h>
int global = 0x610;
int main(int argc, char* argv[])
{
printf("address:%X\n", &global);
printf("value:0x%X\n", global);
getchar();
return 0;
}
运行结果

修改全局变量初始值
可以看到全局变量对应的地址为4198B0
那么是不是直接去PE文件中找到这个地址就行呢?当然不是
首先要明确,此时得到的全局变量地址是运行态时的地址,也就是VA(在内存中的虚拟地址)
VA = ImageBase + RVA
即:在内存中的虚拟地址 = 镜像基地址 + 相对虚拟地址
而 镜像基地址为扩展PE头中的ImageBase成员,是已知的
于是可以得到RVA = VA - ImageBase
而其在PE文件中的地址为FOA(文件偏移地址)
最终问题就也就变成了 RVA与FOA的转换
VA到FOA转换流程
1.得到RVA的值:RVA = VA - ImageBase
2.判断RVA是否位于PE文件头中
2.1如果是:FOA=RVA
2.2如果不是:判断RVA位于哪个节,差值 = RVA - 节.VirtualAddress(RVA),FOA = 节.PointerToRawData + 差值

按照流程转换
1.得到RVA的值:RVA = VA - ImageBase
首先用查看该PE文件的ImageBase
这里采用的PE工具为Detect It Easy,简称DIE

得到ImageBase为0x400000
于是可以得到RVA = VA - ImageBase = 0x4198B0 - 0x400000 = 0x198B0
2.判断RVA是否位于PE文件头中
可以用WinHex 找到PE文件头的部分

可以看到PE文件头的最后一位地址为:1F7
RVA = 0x198B0 显然超出了PE文件头的大小
3.判断RVA属于哪个节
RVA>=节.VirtualAddress
RVA<节.VirtualAddress + 当前节内存对齐后的大小=节.VirtualAddress +[(Max{节.Misc,节.SizeOfRawData})÷SectionAlignment]向上取整×SectionAlignment
- 节.SizeOfRawData是节文件对齐后的大小
- 节.Misc是节的实际大小
关于节内存对齐大小为什么如此计算可以回顾PE文件笔记六 节表和节
内存对齐后的大小 = [Max{实际的大小,文件对齐后的大小}÷内存对齐]向上取整×内存对齐
向上取整的意思就是 如果除后的结果为整数就直接为结果,如果除后的结果带小数则取整然后加一
例子:[5÷2]向上取整= 2.5取整+1=2+1=3,[4÷2]向上取整=2
使用PE工具 DIE,查看各个节的信息:

根据比较,可以发现RVA=0x198B0 属于第三个节 .data中
因为第三个节的VirtualAddress=0x19000,文件对齐后的大小=节.SizeOfRawData在DIE中显示为R.Size = 0xa00
实际大小=节.Misc再DIE显示为V.Size=0x12e0
Max{节.Misc,节.SizeOfRawData}=Max{0x12e0,0xa00}=0x12e0
内存对齐后的大小 = (0x12e0÷内存对齐)向上取整×内存对齐 = (0x12e0÷0x1000)向上取整 × 0x1000=2 × 0x1000=0x2000
RVA>=0x19000
RVA<0x19000+ 内存对齐后的大小=0x19000+0x2000=0x1B000
差值 = RVA - 节.VirtualAddress = 0x198B0 - 0x19000 = 0x8B0
PointerToRawData 在DIE工具中显示为Offset,为0x16E00
FOA = 节.PointerToRawData + 差值 = 0x16E00 + 0x8B0 = 0x176B0
用WinHex查看相应位置的数值

修改数值查看结果
找到了FOA的地址后,修改对应地址的数据,并保存

再运行程序得到

可以看到原本程序中的数据已经被修改了
代码实现VA转FOA
代码
#include <stdio.h>
#include <malloc.h>
#include <windows.h>
#include <winnt.h>
#include <math.h>
#define IMAGE_FILE_MACHINE_AMD64 0x8664
UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT rva = va - nt->OptionalHeader.ImageBase;
printf("rva:%X\n", rva);
UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS);
printf("PeEnd:%X\n", PeEnd);
if (rva < PeEnd) {
printf("foa:%X\n", rva);
return rva;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;
if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
int offset = rva - sectionArr[i]->VirtualAddress;
int foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}
}
}
UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT rva = va - nt->OptionalHeader.ImageBase;
printf("rva:%X\n", rva);
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
printf("PeEnd:%X\n", PeEnd);
if (rva < PeEnd) {
printf("foa:%X\n", rva);
return rva;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;
if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
int offset = rva - sectionArr[i]->VirtualAddress;
int foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}
}
}
int main(int argc, char* argv[])
{
_IMAGE_DOS_HEADER* dos;
HANDLE hFile = CreateFileA("C:\\Users\\lyl610abc\\Desktop\\GlobalVariety.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0);
LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
dos = (_IMAGE_DOS_HEADER*)pFile;
printf("dos->e_magic:%X\n", dos->e_magic);
DWORD* peId;
peId = (DWORD*)((UINT)dos + dos->e_lfanew);
printf("peId:%X\n", *peId);
WORD* magic;
magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
printf("magic:%X\n", *magic);
switch (*magic) {
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
printf("32位程序\n");
_IMAGE_NT_HEADERS* nt;
nt = (_IMAGE_NT_HEADERS*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**) malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
_IMAGE_SECTION_HEADER* sectionHeader;
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
int cnt = 0;
while(cnt< nt->FileHeader.NumberOfSections){
_IMAGE_SECTION_HEADER* section;
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER)*cnt);
sectionArr[cnt++] = section;
printf("%s\n", section->Name);
}
VaToFoa32(0x4198B0,dos, nt, sectionArr);
break;
}
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
printf("64位程序\n");
_IMAGE_NT_HEADERS64* nt;
nt = (_IMAGE_NT_HEADERS64*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
_IMAGE_SECTION_HEADER* sectionHeader;
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64));
int cnt = 0;
while (cnt < nt->FileHeader.NumberOfSections) {
_IMAGE_SECTION_HEADER* section;
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
sectionArr[cnt++] = section;
printf("%s\n", section->Name);
}
break;
}
default:
{
printf("error!\n");
break;
}
}
return 0;
}
运行结果

可以看到计算出来的结果和前面手动计算的一致
FOA转VA
理解了前面的VA转FOA后,再来学习一下它的逆过程:FOA转VA
现在将前面转换的结果:0x000176B0拿来:

尝试逆推出地址
FOA转VA转换流程
1.判断FOA是否位于PE文件头中
1.1如果是:FOA=RVA
1.2如果不是:判断FOA位于哪个节,差值 = FOA - 节.PointerToRawData ,RVA = 差值 + 节.VirtualAddress(RVA)
2.VA = ImageBase + RVA

按照流程转换
1.判断FOA是否位于PE文件头中
显然FOA在PE文件头之外
1.2.判断FOA位于哪个节
FOA>=节.PointerToRawData
FOA<节.PointerToRawData + 当前节文件对齐后的大小=节.PointerToRawData+节.SizeOfRawData
再次用PE工具 DIE查看节的信息:

根据比较,可以发现FOA=0x176B0属于第三个节 .data中
因为第三个节的PointerToRawData=0x16e00(在DIE中显示为Offset),文件对齐后的大小=节.SizeOfRawData在DIE中显示为R.Size = 0xa00
FOA>=0x16e00
FOA<节.PointerToRawData + 当前节文件对齐后的大小=节.PointerToRawData+节.SizeOfRawData=0x16e00+0xa00=0x17800
差值 = FOA - 节.PointerToRawData = 0x176B0 - 0x16e00 = 0x8B0
RVA = 差值 + 节.VirtualAddress(RVA) = 0x8B0 + 0x19000 = 0x198B0
2.VA = ImageBase + RVA
VA = ImageBase + RVA = 0x400000+0x198B0 = 0x4198B0
得到的VA和先前的值一致,转换完毕
代码实现FOA转VA
代码
#include <stdio.h>
#include <malloc.h>
#include <windows.h>
#include <winnt.h>
#include <math.h>
#define IMAGE_FILE_MACHINE_AMD64 0x8664
UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT rva = va - nt->OptionalHeader.ImageBase;
printf("rva:%X\n", rva);
UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS);
printf("PeEnd:%X\n", PeEnd);
if (rva < PeEnd) {
printf("foa:%X\n", rva);
return rva;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;
if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
UINT offset = rva - sectionArr[i]->VirtualAddress;
UINT foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}
}
}
UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT rva = va - nt->OptionalHeader.ImageBase;
printf("rva:%X\n", rva);
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
printf("PeEnd:%X\n", PeEnd);
if (rva < PeEnd) {
printf("foa:%X\n", rva);
return rva;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;
if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
UINT offset = rva - sectionArr[i]->VirtualAddress;
UINT foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}
}
}
UINT FoaToVa32(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS);
if (foa < PeEnd) {
printf("va:%X\n", foa+nt->OptionalHeader.ImageBase);
return foa + nt->OptionalHeader.ImageBase;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (foa >= sectionArr[i]->PointerToRawData && foa < (sectionArr[i]->PointerToRawData + sectionArr[i]->SizeOfRawData)) {
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
UINT offset = foa - sectionArr[i]->PointerToRawData;
UINT rva = offset+ sectionArr[i]->VirtualAddress;
printf("va:%X\n", rva + nt->OptionalHeader.ImageBase);
return rva + nt->OptionalHeader.ImageBase;
}
}
return 0;
}
UINT FoaToVa64(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
if (foa < PeEnd) {
printf("va:%X\n", foa + nt->OptionalHeader.ImageBase);
return foa + nt->OptionalHeader.ImageBase;
}
else {
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
if (foa >= sectionArr[i]->PointerToRawData && foa < (sectionArr[i]->PointerToRawData + sectionArr[i]->SizeOfRawData)) {
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
printf("没有找到匹配的节\n");
return -1;
}
else {
UINT offset = foa - sectionArr[i]->PointerToRawData;
UINT rva = offset + sectionArr[i]->VirtualAddress;
printf("va:%X\n", rva + nt->OptionalHeader.ImageBase);
return rva + nt->OptionalHeader.ImageBase;
}
}
return 0;
}
int main(int argc, char* argv[])
{
_IMAGE_DOS_HEADER* dos;
HANDLE hFile = CreateFileA("C:\\Users\\sixonezero\\Desktop\\GlobalVariety.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0);
LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
dos = (_IMAGE_DOS_HEADER*)pFile;
printf("dos->e_magic:%X\n", dos->e_magic);
DWORD* peId;
peId = (DWORD*)((UINT)dos + dos->e_lfanew);
printf("peId:%X\n", *peId);
WORD* magic;
magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
printf("magic:%X\n", *magic);
switch (*magic) {
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
printf("32位程序\n");
_IMAGE_NT_HEADERS* nt;
nt = (_IMAGE_NT_HEADERS*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**) malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
_IMAGE_SECTION_HEADER* sectionHeader;
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
int cnt = 0;
while(cnt< nt->FileHeader.NumberOfSections){
_IMAGE_SECTION_HEADER* section;
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER)*cnt);
sectionArr[cnt++] = section;
printf("%s\n", section->Name);
}
VaToFoa32(0x4198B0,dos, nt, sectionArr);
FoaToVa32(0x176B0, dos, nt, sectionArr);
break;
}
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
printf("64位程序\n");
_IMAGE_NT_HEADERS64* nt;
nt = (_IMAGE_NT_HEADERS64*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
_IMAGE_SECTION_HEADER* sectionHeader;
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64));
int cnt = 0;
while (cnt < nt->FileHeader.NumberOfSections) {
_IMAGE_SECTION_HEADER* section;
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
sectionArr[cnt++] = section;
printf("%s\n", section->Name);
}
VaToFoa32(0x4198B0,dos, nt, sectionArr);
FoaToVa32(0x176B0, dos, nt, sectionArr);
break;
}
default:
{
printf("error!\n");
break;
}
}
return 0;
}
运行结果

可以看到计算出来的结果和前面手动计算的一致
说明
学会了VA与FOA的转换后,后面就可以开始对程序做一些坏坏的事了(❁′◡`❁)
刚开始使用VC6环境编译,发现编译出来的程序,文件对齐和内存对齐数值是一致的,没法起到较好的学习作用,于是改用VS2015编译出兼容XP的且文件对齐和内存对齐不同的程序,方便学习
PS:在文件对齐等于内存对齐的情况下,RVA就直接等于FOA了
RVA和FOA之间的差异归根结底就是在于文件对齐和内存对齐的差异上
下面附上编译出来的程序和修改过的程序,对应为GlobalVariety.exe和GlobalVariety2.exe,有兴趣的可以去修改试试(建议在虚拟机XP环境下测试,否则printf出来的地址可能不准确)