前面学习了在块表中提到了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
代码
 复制代码 隐藏代码// PE.cpp : Defines the entry point for the console application. // #include <stdio.h> #include <malloc.h> #include <windows.h> #include <winnt.h> #include <math.h> //在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义 #define IMAGE_FILE_MACHINE_AMD64  0x8664
  //VA转FOA 32位 //第一个参数为要转换的在内存中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //得到RVA的值:RVA = VA - ImageBase     UINT rva = va - nt->OptionalHeader.ImageBase;     //输出rva     printf("rva:%X\n", rva);     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS);     //输出PeEnd     printf("PeEnd:%X\n", PeEnd);     //判断rva是否位于PE文件头中     if (rva < PeEnd) {         //如果rva位于PE文件头中,则foa==rva,直接返回rva即可         printf("foa:%X\n", rva);                 return rva;     }     else {         //如果rva在PE文件头外         //判断rva属于哪个节         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 {             //计算差值= RVA - 节.VirtualAddress             int offset = rva - sectionArr[i]->VirtualAddress;             //FOA = 节.PointerToRawData + 差值             int foa = sectionArr[i]->PointerToRawData + offset;             printf("foa:%X\n", foa);             return foa;         }
      }
  }
  //VA转FOA 64位 //第一个参数为要转换的在内存中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //得到RVA的值:RVA = VA - ImageBase     UINT rva = va - nt->OptionalHeader.ImageBase;     //输出rva     printf("rva:%X\n", rva);     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);     //输出PeEnd     printf("PeEnd:%X\n", PeEnd);     //判断rva是否位于PE文件头中     if (rva < PeEnd) {         //如果rva位于PE文件头中,则foa==rva,直接返回rva即可         printf("foa:%X\n", rva);         return rva;     }     else {         //如果rva在PE文件头外         //判断rva属于哪个节         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 {             //计算差值= RVA - 节.VirtualAddress             int offset = rva - sectionArr[i]->VirtualAddress;             //FOA = 节.PointerToRawData + 差值             int foa = sectionArr[i]->PointerToRawData + offset;             printf("foa:%X\n", foa);             return foa;         }
      }
  } int main(int argc, char* argv[]) {     //创建DOS对应的结构体指针     _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;     //输出dos->e_magic,以十六进制输出     printf("dos->e_magic:%X\n", dos->e_magic);
      //创建指向PE文件头标志的指针     DWORD* peId;     //让PE文件头标志指针指向其对应的地址=DOS首地址+偏移     peId = (DWORD*)((UINT)dos + dos->e_lfanew);     //输出PE文件头标志,其值应为4550,否则不是PE文件     printf("peId:%X\n", *peId);
      //创建指向可选PE头的第一个成员magic的指针     WORD* magic;     //让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小     magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));     //输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序     printf("magic:%X\n", *magic);     //根据magic判断为32位程序还是64位程序     switch (*magic) {     case IMAGE_NT_OPTIONAL_HDR32_MAGIC:     {         printf("32位程序\n");         //确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了         //创建指向PE文件头的指针         _IMAGE_NT_HEADERS* nt;         //让PE文件头指针指向其对应的地址         nt = (_IMAGE_NT_HEADERS*)peId;         printf("Machine:%X\n", nt->FileHeader.Machine);         printf("Magic:%X\n", nt->OptionalHeader.Magic);
          //创建一个指针数组,该指针数组用来存储所有的节表指针         //这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组         _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");         //确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了         //创建指向PE文件头的指针         _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[nt->FileHeader.NumberOfSections],声明了一个动态数组         _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
          //创建指向块表的指针         _IMAGE_SECTION_HEADER* sectionHeader;         //让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64         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
代码
 复制代码 隐藏代码// PE.cpp : Defines the entry point for the console application. // #include <stdio.h> #include <malloc.h> #include <windows.h> #include <winnt.h> #include <math.h> //在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义 #define IMAGE_FILE_MACHINE_AMD64  0x8664
  //VA转FOA 32位 //第一个参数为要转换的在内存中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //得到RVA的值:RVA = VA - ImageBase     UINT rva = va - nt->OptionalHeader.ImageBase;     //输出rva     printf("rva:%X\n", rva);     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS);     //输出PeEnd     printf("PeEnd:%X\n", PeEnd);     //判断rva是否位于PE文件头中     if (rva < PeEnd) {         //如果rva位于PE文件头中,则foa==rva,直接返回rva即可         printf("foa:%X\n", rva);                 return rva;     }     else {         //如果rva在PE文件头外         //判断rva属于哪个节         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 {             //计算差值= RVA - 节.VirtualAddress             UINT offset = rva - sectionArr[i]->VirtualAddress;             //FOA = 节.PointerToRawData + 差值             UINT foa = sectionArr[i]->PointerToRawData + offset;             printf("foa:%X\n", foa);             return foa;         }
      }
  }
  //VA转FOA 64位 //第一个参数为要转换的在内存中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //得到RVA的值:RVA = VA - ImageBase     UINT rva = va - nt->OptionalHeader.ImageBase;     //输出rva     printf("rva:%X\n", rva);     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);     //输出PeEnd     printf("PeEnd:%X\n", PeEnd);     //判断rva是否位于PE文件头中     if (rva < PeEnd) {         //如果rva位于PE文件头中,则foa==rva,直接返回rva即可         printf("foa:%X\n", rva);         return rva;     }     else {         //如果rva在PE文件头外         //判断rva属于哪个节         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 {             //计算差值= RVA - 节.VirtualAddress             UINT offset = rva - sectionArr[i]->VirtualAddress;             //FOA = 节.PointerToRawData + 差值             UINT foa = sectionArr[i]->PointerToRawData + offset;             printf("foa:%X\n", foa);             return foa;         }
      }
  }
  //FOA转VA 32位 //第一个参数为要转换的在文件中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT FoaToVa32(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS);     //判断FOA是否位于PE文件头中     if (foa < PeEnd) {         //如果foa位于PE文件头中,则foa==rva,直接返回foa+ImageBase即可         printf("va:%X\n", foa+nt->OptionalHeader.ImageBase);         return foa + nt->OptionalHeader.ImageBase;     }     else {         //如果foa在PE文件头外         //判断foa属于哪个节         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 {             //计算差值= FOA - 节.PointerToRawData              UINT offset = foa - sectionArr[i]->PointerToRawData;             //RVA = 差值 + 节.VirtualAddress(RVA)             UINT rva =  offset+ sectionArr[i]->VirtualAddress;             printf("va:%X\n", rva + nt->OptionalHeader.ImageBase);             return rva + nt->OptionalHeader.ImageBase;         }     }     return 0; }
  //FOA转VA 64位 //第一个参数为要转换的在文件中的地址:VA //第二个参数为指向dos头的指针 //第三个参数为指向nt头的指针 //第四个参数为存储指向节指针的数组 UINT FoaToVa64(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {     //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小     UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);     //判断FOA是否位于PE文件头中     if (foa < PeEnd) {         //如果foa位于PE文件头中,则foa==rva,直接返回foa+ImageBase即可         printf("va:%X\n", foa + nt->OptionalHeader.ImageBase);         return foa + nt->OptionalHeader.ImageBase;     }     else {         //如果foa在PE文件头外         //判断foa属于哪个节         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 {             //计算差值= FOA - 节.PointerToRawData              UINT offset = foa - sectionArr[i]->PointerToRawData;             //RVA = 差值 + 节.VirtualAddress(RVA)             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[]) {     //创建DOS对应的结构体指针     _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;     //输出dos->e_magic,以十六进制输出     printf("dos->e_magic:%X\n", dos->e_magic);
      //创建指向PE文件头标志的指针     DWORD* peId;     //让PE文件头标志指针指向其对应的地址=DOS首地址+偏移     peId = (DWORD*)((UINT)dos + dos->e_lfanew);     //输出PE文件头标志,其值应为4550,否则不是PE文件     printf("peId:%X\n", *peId);
      //创建指向可选PE头的第一个成员magic的指针     WORD* magic;     //让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小     magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));     //输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序     printf("magic:%X\n", *magic);     //根据magic判断为32位程序还是64位程序     switch (*magic) {     case IMAGE_NT_OPTIONAL_HDR32_MAGIC:     {         printf("32位程序\n");         //确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了         //创建指向PE文件头的指针         _IMAGE_NT_HEADERS* nt;         //让PE文件头指针指向其对应的地址         nt = (_IMAGE_NT_HEADERS*)peId;         printf("Machine:%X\n", nt->FileHeader.Machine);         printf("Magic:%X\n", nt->OptionalHeader.Magic);
          //创建一个指针数组,该指针数组用来存储所有的节表指针         //这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组         _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");         //确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了         //创建指向PE文件头的指针         _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[nt->FileHeader.NumberOfSections],声明了一个动态数组         _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);
          //创建指向块表的指针         _IMAGE_SECTION_HEADER* sectionHeader;         //让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64         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出来的地址可能不准确)
GlobalVariety.zip