本帖最后由 ttegame 于 2021-04-19 22:10 编辑
PE学习笔记系列 PE学习笔记一 PE介绍 PE学习笔记二 PE文件的两种状态 PE学习笔记三 DOS部分 PE学习笔记四 PE文件头之标准PE头 PE学习笔记五 PE文件头之扩展PE头 PE学习笔记六 节表和节 PE学习笔记七 RVA与FOA转换 PE学习笔记八 实战之HOOK程序添加弹窗 PE学习笔记九 实战之HOOK程序添加弹窗续 PE学习笔记十 扩大节 PE学习笔记十一 新增节 PE学习笔记十二 修正内存对齐 PE学习笔记十三 合并节 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代码// 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出来的地址可能不准确)
下方隐藏内容为本帖所有文件下载链接:
游客你好,如果您要查看本帖隐藏链接需要登录才能查看,
请先登录
|