吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7274|回复: 29
收起左侧

[调试逆向] PE文件笔记七 VA与FOA转换

  [复制链接]
lyl610abc 发表于 2021-4-2 16:47
本帖最后由 lyl610abc 于 2021-4-4 13:55 编辑

继续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;
}

运行结果

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出来的地址可能不准确)

GlobalVariety.zip

110.7 KB, 下载次数: 70, 下载积分: 吾爱币 -1 CB

免费评分

参与人数 6吾爱币 +5 热心值 +6 收起 理由
aLty + 1 我很赞同!
初七的果子狸 + 1 + 1 谢谢@Thanks!
Alutemurat + 1 + 1 谢谢@Thanks!
HuZH + 1 + 1 谢谢@Thanks!
lnxceo + 1 + 1 用心讨论,共获提升!
debug_cat + 1 + 1 大佬牛,如此高产。

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

zidy2012 发表于 2022-3-20 12:15
lyl610abc 发表于 2021-4-13 14:52
建议在虚拟机XP环境下测试,否则printf出来的地址可能不准确
高版本的系统有ASLR(地址随机化)保护,所以 ...

Win7系统可以关闭ASLR,经过测试,关闭以后地址固定,比如上述打印的全局变量地址,堆栈地址,可以通过以下批处理脚本进行设置

[Bash shell] 纯文本查看 复制代码
@ECHO OFF
SETLOCAL

@ECHO 打开或关闭DEP和ASLR
@ECHO 请以管理员身份运行此脚本
@ECHO 打开选en,关闭选di
[url=home.php?mod=space&uid=855438]@set[/url] /p Choice=开关选项(en/di):%=%

@if "%Choice%"=="en" goto :ENABLE
@if "%Choice%"=="di" goto :DISABLE

:ENABLE
	bcdedit.exe /set {current} nx AlwaysOn
	reg delete "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v "MoveImages"
	goto :END
:DISABLE
	bcdedit.exe /set {current} nx AlwaysOff
	reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v "MoveImages" /t REG_DWORD /d 0 /f
	goto :END

:END
@echo DEP和ASLR已设置完毕,请重启虚拟机!
@echo 请将待调试程序存放到虚拟机本地磁盘,不要使用共享文件夹!!!
pause
debug_cat 发表于 2021-4-2 17:55
 楼主| lyl610abc 发表于 2021-4-2 18:01
莫问刀 发表于 2021-4-2 17:55
这是要起飞的节奏

还早着呢,还有太多笔记没更新
后续还有自写保护壳、脱壳、保护模式、驱动开发等很多内容没更新
感觉自己的基础不是很牢固,借着写笔记复习一遍,更新完才有点底气找工作
等我之后有空的话,目标是一天三更
debug_cat 发表于 2021-4-2 18:12
lyl610abc 发表于 2021-4-2 18:01
还早着呢,还有太多笔记没更新
后续还有自写保护壳、脱壳、保护模式、驱动开发等很多内容没 ...

你这样更新,我的硬币不够啊
我也是基础知识不够稳,现在都不敢去面试
TE777 发表于 2021-4-3 07:01
太厉害了。。
senur 发表于 2021-4-3 07:43
仰望大佬。
whngomj 发表于 2021-4-3 09:58
谢谢分享,学习了。
xioumu 发表于 2021-4-3 17:01
大神,感谢分享了。
ksah874911 发表于 2021-4-3 19:02

这是起飞了
yqjys123 发表于 2021-4-3 21:52
谢谢您的分享。使我们的学习更有乐趣。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-26 07:44

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表