大神论坛

找回密码
快速注册
查看: 435 | 回复: 0

[原创] PE学习笔记七 VA与FOA转换

主题

帖子

6

积分

初入江湖

UID
31
积分
6
精华
威望
12 点
违规
大神币
68 枚
注册时间
2021-04-10 16:01
发表于 2021-04-19 22:10
本帖最后由 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.VirtualAddressVirtual Address在内存中的虚拟地址
RVA_IMAGE_SECTION_HEADER.VirtualAddressRelative Virtual Address相对虚拟地址
FOA_IMAGE_SECTION_HEADER.PointerToRawDataFile 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;
}

运行结果

image-20210402151017936


修改全局变量初始值

可以看到全局变量对应的地址为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 + 差值


image-20210404135433847


按照流程转换

1.得到RVA的值:RVA = VA - ImageBase

首先用查看该PE文件的ImageBase

这里采用的PE工具为Detect It Easy,简称DIE

image-20210402151418941


得到ImageBase为0x400000

于是可以得到RVA = VA - ImageBase = 0x4198B0 - 0x400000 = 0x198B0


2.判断RVA是否位于PE文件头中

可以用WinHex 找到PE文件头的部分

image-20210402151652326


可以看到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,查看各个节的信息:

image-20210402152412226


根据比较,可以发现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查看相应位置的数值

image-20210402153045465


修改数值查看结果

找到了FOA的地址后,修改对应地址的数据,并保存

image-20210402153316707


再运行程序得到

image-20210402153533987

可以看到原本程序中的数据已经被修改了

代码实现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;
}

运行结果

image-20210404134754827

可以看到计算出来的结果和前面手动计算的一致


FOA转VA

理解了前面的VA转FOA后,再来学习一下它的逆过程:FOA转VA

现在将前面转换的结果:0x000176B0拿来:

image-20210402153045465

尝试逆推出地址


FOA转VA转换流程

1.判断FOA是否位于PE文件头中

1.1如果是:FOA=RVA

1.2如果不是:判断FOA位于哪个节,差值 =  FOA - 节.PointerToRawData ,RVA = 差值 + 节.VirtualAddress(RVA)

2.VA = ImageBase + RVA


image-20210404135449806


按照流程转换

1.判断FOA是否位于PE文件头中

显然FOA在PE文件头之外

1.2.判断FOA位于哪个节

FOA>=节.PointerToRawData

FOA<节.PointerToRawData + 当前节文件对齐后的大小=节.PointerToRawData+节.SizeOfRawData


再次用PE工具 DIE查看节的信息:

image-20210403202147543


根据比较,可以发现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;
}

运行结果

image-20210404135218584

可以看到计算出来的结果和前面手动计算的一致


说明

学会了VA与FOA的转换后,后面就可以开始对程序做一些坏坏的事了(❁′◡`❁)

刚开始使用VC6环境编译,发现编译出来的程序,文件对齐和内存对齐数值是一致的,没法起到较好的学习作用,于是改用VS2015编译出兼容XP的且文件对齐和内存对齐不同的程序,方便学习

PS:在文件对齐等于内存对齐的情况下,RVA就直接等于FOA了

RVA和FOA之间的差异归根结底就是在于文件对齐和内存对齐的差异上

下方隐藏内容附上编译出来的程序和修改过的程序,对应为GlobalVariety.exe和GlobalVariety2.exe,有兴趣的可以去修改试试(建议在虚拟机XP环境下测试,否则printf出来的地址可能不准确)


下方隐藏内容为本帖所有文件下载链接:

游客你好,如果您要查看本帖隐藏链接需要登录才能查看, 请先登录

返回顶部