在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了VARVAFOA三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通过编程的方式实现转换。

如下是三种格式的异同点:

  • VA(Virtual Address,虚拟地址):它是在进程的虚拟地址空间中的地址,用于在运行时访问内存中的数据和代码。VA是相对于进程基址的偏移量。在不同的进程中,相同的VA可能映射到不同的物理地址。
  • RVA(Relative Virtual Address,相对虚拟地址):它是相对于模块基址(Module Base Address)的偏移量,用于定位模块内部的数据和代码。RVA是相对于模块基址的偏移量,通过将模块基址和RVA相加,可以计算出相应的VA。
  • FOA(File Offset Address,文件偏移地址):它是相对于文件起始位置的偏移量,用于定位可执行文件中的数据和代码在文件中的位置。通过将文件偏移地址和节表中的指定节的起始位置相加,可以计算出相应的FOA。

VA虚拟地址转换为FOA文件偏移

VA地址代指的是程序加载到内存后的内存地址,而FOA地址则代表文件内的物理地址,通过编写VA_To_FOA则可实现将一个虚拟地址转换为文件偏移地址,该函数的实现方式,首先得到ImageBase镜像基地址,并得到NumberOfSections节数量,有了该数量以后直接循环,通过判断语句将节限定在一个区间内该区间dwVA >= Section_Start && dwVA <= Section_Ends,当找到后,首先通过VA-ImageBase得到当前的RVA地址,接着通过该地址减去VirtualAddress并加上PointerToRawData文件指针,即可获取到文件内的偏移。

#include <iostream>
#include <Windows.h>
#include <ImageHlp.h> #pragma comment(lib,"Imagehlp.lib") // 读取NT头
PIMAGE_NT_HEADERS GetNtHeader(PVOID ImageBase)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return NULL;
} PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)ImageBase + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
return NULL;
} return pNtHeaders;
} // 读取PE结构的封装
HANDLE OpenPeFile(LPTSTR FileName)
{
HANDLE hFile, hMapFile, lpMapAddress = NULL;
DWORD dwFileSize = 0; // CreateFile 既可以创建文件,也可以打开文件,这里则是打开文件的含义
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 0;
} // 获取到文件大小
dwFileSize = GetFileSize(hFile, NULL); // 创建文件的内存映像
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL);
if (hMapFile == NULL)
{
return 0;
} // 读取映射中的内存并返回一个句柄
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize);
if (lpMapAddress != NULL)
{
return lpMapAddress;
} return 0;
} // 将 VA(虚拟地址) --> 转换为 FOA(文件偏移)
DWORD VA_To_FOA(HANDLE ImageBase, DWORD dwVA)
{
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
DWORD NumberOfSectinsCount = 0;
DWORD dwImageBase = 0; pNtHead = GetNtHeader(ImageBase);
pSection = IMAGE_FIRST_SECTION(pNtHead); dwImageBase = pNtHead->OptionalHeader.ImageBase;
NumberOfSectinsCount = pNtHead->FileHeader.NumberOfSections;
for (int each = 0; each < NumberOfSectinsCount; each++)
{
// 获取节的开始地址与结束地址
DWORD Section_Start = dwImageBase + pSection[each].VirtualAddress;
DWORD Section_Ends = dwImageBase + pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize;
// 判断当前的VA地址落在了那个节上
if (dwVA >= Section_Start && dwVA <= Section_Ends)
{
DWORD RVA = dwVA - pNtHead->OptionalHeader.ImageBase; // 计算RVA
DWORD FOA = pSection[each].PointerToRawData + (RVA - pSection[each].VirtualAddress); // 计算FOA
return FOA;
}
}
return -1;
} int main(int argc, char * argv[])
{
HANDLE lpMapAddress = NULL; // 打开PE文件
lpMapAddress = OpenPeFile(L"d://lyshark.exe"); // 转换
DWORD FOA = VA_To_FOA(lpMapAddress, 0x401000);
printf("VA --> FOA 结果为: %x \n", FOA); system("pause");
return 0;
}

上述代码运行后即可获取到内存地址0x401000对应的文件地址为0x1000,读者可自行打开WinHex验证是否相等,如下图所示;

RVA相对地址转换为FOA文件偏移

所谓的相对地址则是内存地址减去基址所获得的地址,该地址的计算同样可以使用代码实现,如下RVA_To_FOA函数可用于将一个相对地址转换为文件偏移,如果内存VA地址是0x401000而基址是0x400000那么相对地址就是0x1000,将相对地址转换为FOA文件偏移,首相要将相对地址加上基址,我们通过相对地址减去PointerToRawData数据指针即可获取到文件偏移。

#include <iostream>
#include <Windows.h>
#include <ImageHlp.h> #pragma comment(lib,"Imagehlp.lib") // 读取NT头
PIMAGE_NT_HEADERS GetNtHeader(PVOID ImageBase)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return NULL;
} PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)ImageBase + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
return NULL;
} return pNtHeaders;
} // 读取PE结构的封装
HANDLE OpenPeFile(LPTSTR FileName)
{
HANDLE hFile, hMapFile, lpMapAddress = NULL;
DWORD dwFileSize = 0; // CreateFile 既可以创建文件,也可以打开文件,这里则是打开文件的含义
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 0;
} // 获取到文件大小
dwFileSize = GetFileSize(hFile, NULL); // 创建文件的内存映像
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL);
if (hMapFile == NULL)
{
return 0;
} // 读取映射中的内存并返回一个句柄
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize);
if (lpMapAddress != NULL)
{
return lpMapAddress;
} return 0;
} // 将 RVA(虚拟地址) --> 转换为 FOA(文件偏移)
DWORD RVA_To_FOA(HANDLE ImageBase, DWORD dwRVA)
{
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
DWORD NumberOfSectinsCount = 0;
DWORD dwImageBase = 0; pNtHead = GetNtHeader(ImageBase);
pSection = IMAGE_FIRST_SECTION(pNtHead); dwImageBase = pNtHead->OptionalHeader.ImageBase;
NumberOfSectinsCount = pNtHead->FileHeader.NumberOfSections;
for (int each = 0; each < NumberOfSectinsCount; each++)
{
DWORD Section_Start = pSection[each].VirtualAddress; // 计算RVA开始位置
DWORD Section_Ends = pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize; // 计算RVA结束位置 if (dwRVA >= Section_Start && dwRVA <= Section_Ends)
{
DWORD VA = pNtHead->OptionalHeader.ImageBase + dwRVA; // 得到VA地址
DWORD FOA = pSection[each].PointerToRawData + (dwRVA - pSection[each].VirtualAddress); // 得到FOA
return FOA;
}
}
return -1;
} int main(int argc, char * argv[])
{
// 打开文件
HANDLE lpMapAddress = NULL;
lpMapAddress = OpenPeFile(L"d://lyshark.exe"); // 计算地址
DWORD FOA = RVA_To_FOA(lpMapAddress, 0x1000);
printf("RVA --> FOA 结果为: %x \n", FOA); system("pause");
return 0;
}

我们还是以上述功能为例,计算相对地址0x1000的文件偏移,则可以得到0x1000的文件偏移值,如下图所示;

FOA文件偏移转换为VA虚拟地址

将文件内的偏移地址FOA转换为内存虚拟地址,在转换时首先通过VirtualAddress节虚拟地址加上,文件偏移地址减去PointerToRawData数据域指针,得到相对地址,再次加上ImageBase基地址即可获取到实际虚拟地址。

#include <iostream>
#include <Windows.h>
#include <ImageHlp.h> #pragma comment(lib,"Imagehlp.lib") // 读取NT头
PIMAGE_NT_HEADERS GetNtHeader(PVOID ImageBase)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)ImageBase; if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
return NULL;
} PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)ImageBase + pDosHeader->e_lfanew);
if (pNtHeaders->Signature != IMAGE_NT_SIGNATURE)
{
return NULL;
} return pNtHeaders;
} // 读取PE结构的封装
HANDLE OpenPeFile(LPTSTR FileName)
{
HANDLE hFile, hMapFile, lpMapAddress = NULL;
DWORD dwFileSize = 0; // CreateFile 既可以创建文件,也可以打开文件,这里则是打开文件的含义
hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return 0;
} // 获取到文件大小
dwFileSize = GetFileSize(hFile, NULL); // 创建文件的内存映像
hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, dwFileSize, NULL);
if (hMapFile == NULL)
{
return 0;
} // 读取映射中的内存并返回一个句柄
lpMapAddress = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwFileSize);
if (lpMapAddress != NULL)
{
return lpMapAddress;
} return 0;
} // 将 FOA(文件偏移) --> 转换为 VA(虚拟地址)
DWORD FOA_To_VA(HANDLE ImageBase, DWORD dwFOA)
{
PIMAGE_NT_HEADERS pNtHead = NULL;
PIMAGE_FILE_HEADER pFileHead = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
DWORD NumberOfSectinsCount = 0;
DWORD dwImageBase = 0; pNtHead = GetNtHeader(ImageBase);
pSection = IMAGE_FIRST_SECTION(pNtHead); dwImageBase = pNtHead->OptionalHeader.ImageBase;
NumberOfSectinsCount = pNtHead->FileHeader.NumberOfSections;
for (int each = 0; each < NumberOfSectinsCount; each++)
{
DWORD PointerRawStart = pSection[each].PointerToRawData; // 文件偏移开始位置
DWORD PointerRawEnds = pSection[each].PointerToRawData + pSection[each].SizeOfRawData; // 文件偏移结束位置 if (dwFOA >= PointerRawStart && dwFOA <= PointerRawEnds)
{
DWORD RVA = pSection[each].VirtualAddress + (dwFOA - pSection[each].PointerToRawData); // 计算出RVA
DWORD VA = RVA + pNtHead->OptionalHeader.ImageBase; // 计算出VA
return VA;
}
}
return -1;
} int main(int argc, char * argv[])
{
// 打开文件
HANDLE lpMapAddress = NULL;
lpMapAddress = OpenPeFile(L"d://lyshark.exe"); // 转换
DWORD VA = FOA_To_VA(lpMapAddress, 0x1000);
printf("FOA --> VA 结果为: 0x%X \n", VA); system("pause");
return 0;
}

运行后即可将文件偏移0x1000转换为内存虚拟地址0x401000如下图所示;

本文作者: 王瑞

本文链接: https://www.lyshark.com/post/ccb722fb.html

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

2.14 PE结构:地址之间的转换的更多相关文章

  1. 【PE结构】由浅入深PE基础学习-菜鸟手动查询导出表、相对虚拟地址(RVA)与文件偏移地址转换(FOA)

    0 前言 此篇文章想写如何通过工具手查导出表.PE文件代码编程过程中的原理.文笔不是很好,内容也是查阅了很多的资料后整合出来的.希望借此加深对PE文件格式的理解,也希望可以对看雪论坛有所贡献.因为了解 ...

  2. 套接字编程相关函数(1:套接字地址结构、字节序转换、IP地址转换)

    1. 套接字地址结构 1.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在<netinet/in.h>头文件中.下 ...

  3. C# IP地址与整数之间的转换

    IP地址与整数之间的转换 1.IP地址转换为整数 原理:IP地址每段可以看成是8位无符号整数即0-255,把每段拆分成一个二进制形式组合起来,然后把这个二进制数转变成一个无符号的32位整数. 举例:一 ...

  4. 【Go】IP地址转换:数字与字符串之间高效转换

    转载:https://blog.thinkeridea.com/201903/go/ip2long.html IP 地址库中 IP 地址的保存格式一般有两种,一种是点分十进制形式(192.168.1. ...

  5. Java中String与Date格式之间的转换

    转自:https://blog.csdn.net/angus_17/article/details/7656631 经常遇到string和date之间的转换,把相关的内容总结在这里吧: 1.strin ...

  6. 【转】pe结构详解

    (一)基本概念 PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等, 事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是 ...

  7. Win32汇编-编写PE结构解析工具

    汇编语言(assembly language)是一种用于电子计算机.微处理器.微控制器或其他可编程器件的低级语言,亦称为符号语言.在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地 ...

  8. 羽夏笔记——PE结构(不包含.Net)

    写在前面   本笔记是由本人独自整理出来的,图片来源于网络.本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正. 如有好的建议,欢迎反馈.码字不易,如果本篇文章有帮助你 ...

  9. 羽夏壳世界—— PE 结构(上)

    羽夏壳世界之 PE 结构(上),介绍难度较低的基本 PE 相关结构体.

  10. CString-int-string-char-BSTR之间的转换

    一.CString, int, string, char*之间的转换 string 转 CString CString.Format("%s", string.c_str());c ...

随机推荐

  1. Github疯传!200本计算机经典书籍!

    好书在精不在多,每一本经典书籍都值得反复翻阅,温故而知新! 下面分享几本计算机经典书籍,都是我自己看过的. 重构 改善既有代码的设计 就像豆瓣评论所说的,看后有种醍醐灌顶.欲罢不能的感觉.无论你是初学 ...

  2. 【爬虫+数据清洗+可视化】用Python分析“淄博烧烤“的评论数据

    目录 一.背景介绍 二.爬虫代码 2.1 展示爬取结果 2.2 爬虫代码讲解 三.可视化代码 3.1 读取数据 3.2 数据清洗 3.3 可视化 3.3.1 IP属地分析-柱形图 3.3.2 评论时间 ...

  3. liunx操作系统下配置服务器

    centos7 下配置服务器基本步骤 1,yum install  服务器名称 2,关闭防火墙,配置服务器配置文件,开启服务, 3,创建文件,设置访问权限, 4,本地登陆,测试服务器能否连通

  4. App性能测试之SoloPi

    SoloPi简介 SoloPi是蚂蚁金服开发的一款无线化.非侵入.免Root的Android专项测试工具.直接操控安卓系统的手机或智能设备,即可完成自动化的功能.性能.兼容性.以及稳定性测试等工作,降 ...

  5. debug.exe的使用

    debug.exe的使用 debug.exe 是 Windows 操作系统自带的一个命令行调试工具,用于在 DOS 环境下进行汇编语言级别的调试操作.它可以让程序员以来自底层的方式逐步执行代码并检查每 ...

  6. SpringBoot集成支付宝 - 少走弯路就看这篇

    最近在做一个网站,后端采用了SpringBoot,需要集成支付宝进行线上支付,在这个过程中研究了大量支付宝的集成资料,也走了一些弯路,现在总结出来,相信你读完也能轻松集成支付宝支付. 在开始集成支付宝 ...

  7. 一分钟学一个 Linux 命令 - find 和 grep

    前言 大家好,我是 god23bin.欢迎来到<一分钟学一个 Linux 命令>系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天需要你花两分钟时间来学习下,因为今天要介绍的是 ...

  8. ARC114F Permutation Division

    题意 给定一个 \(1 \sim N\) 的排列,Alice 把它划分成 \(k\) 段,Bob 把这 \(k\) 段任意排列.Alice 想让字典序最小,Bob 想让字典序最大.请问最后的排列. 数 ...

  9. GGTalk 开源即时通讯系统源码剖析之:虚拟数据库

    继上篇<GGTalk 开源即时通讯系统源码剖析之:服务端全局缓存>详细介绍了 GGTalk 对需要频繁查询数据库的数据做了服务端全局缓存处理,以降低数据库的读取压力以及加快客户端请求的响应 ...

  10. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-6-元素定位大法-下篇

    1.简介 上一篇主要是讲解我们日常工作中在使用Playwright进行元素定位的一些比较常用的定位方法的理论基础知识以及在什么情况下推荐使用.今天这一篇讲解和分享一下,在日常中很少用到或者很少见的定位 ...