PE文件有两种状态, 一种是在文件中的状态,另外一种是在内存中展开
若我们运行了一个PE文件且知道了某个全局变量的地址, 那么该如何通过这个全局变量地址来获得此变量在文件状态下的地址是什么呢?
RVA(relative Virtual Address), 又称为相对虚拟偏移,简单来说就是在内存状态下的偏移地址
FOA(File Ofseet Address), 又称为文件偏移地址, 就是在文件状态下的偏移地址
下图是PE文件在文件对齐和内存对齐状态下的映像结构图
这里文件对齐值是200,内存对齐的值是1000
内存对齐后的映像分布有个明显的拉伸
计算方法
1.求RVA的值
RVA = 全局变量在内存中的地址 - ImageBase(基址)
2.判断RVA是否位于PE头中或者内存对齐=文件对齐
如果RVA位于PE头中,则RVA = FOA
如果文件对齐 = 内存对齐,则 RVA = FOA
如果不在则进行下述操作
3.判断RVA位于哪个节
假设rva位于X节中,也就是说X节.VirtualAddress <= RVA <= X节.VirtualAddress+X节内存对齐后的大小
差值 = RVA - X节.VirtualAddress
4.得出FOA
FOA = X节.PointerToRawData(X节在文件中的地址) + 差值
C++代码
**代码出处:**https://www.52pojie.cn/thread-1408576-1-1.html
// PE.cpp : Defines the entry point for the console application. // #include#include #include #include #include //在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:%Xn", rva); //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小 UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS); //输出PeEnd printf("PeEnd:%Xn", PeEnd); //判断rva是否位于PE文件头中 if (rva < PeEnd) { //如果rva位于PE文件头中,则foa==rva,直接返回rva即可 printf("foa:%Xn", 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:%Xn", 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:%Xn", 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:%Xn", rva); //找到PE文件头后的地址 = PE文件头首地址+PE文件头大小 UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64); //输出PeEnd printf("PeEnd:%Xn", PeEnd); //判断rva是否位于PE文件头中 if (rva < PeEnd) { //如果rva位于PE文件头中,则foa==rva,直接返回rva即可 printf("foa:%Xn", 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:%Xn", 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:%Xn", 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:%Xn", dos->e_magic); //创建指向PE文件头标志的指针 DWORD* peId; //让PE文件头标志指针指向其对应的地址=DOS首地址+偏移 peId = (DWORD*)((UINT)dos + dos->e_lfanew); //输出PE文件头标志,其值应为4550,否则不是PE文件 printf("peId:%Xn", *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:%Xn", *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:%Xn", nt->FileHeader.Machine); printf("Magic:%Xn", 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("%sn", 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:%Xn", nt->FileHeader.Machine); printf("Magic:%Xn", 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("%sn", section->Name); } break; } default: { printf("error!n"); break; } } return 0; }