2.14 PE结构:地址之间的转换

pe,结构,地址,之间,转换 · 浏览次数 : 125

小编点评

文件偏移0x1000转换为内存虚拟地址0x401000的步骤如下: 1. 打开文件,获取到文件大小。 2. 创建文件的内存映像。 3. 读取映射中的内存并返回一个指针。 4. 将指针转换为内存虚拟地址。 5. 计算出VA。 6. 打印FOA的结果。 文件偏移0x1000转换为内存虚拟地址0x401000的过程需要使用一些内存操作的知识,例如指针、内存地址、文件大小等。

正文

在可执行文件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结构:地址之间的转换相似的内容:

2.14 PE结构:地址之间的转换

在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了`VA`,`RVA`,`FOA`三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通过编程的方式实现转换。VA(Virtual Address,虚拟地址):它是在进程的虚拟地址空间中的地址,用于在运行时访问内存中的数据和代码。VA是相对于进程基址的偏移量。在不同的进程中,

Blazor前后端框架Known-V1.2.14

# V1.2.14 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行。 - Gitee: [https://gitee.com/known/Known](https://gitee.com/known/Known) - Github:[https:

Dash 2.14版本开始支持动态回调注册!

本文示例代码已上传至我的Github仓库https://github.com/CNFeffery/dash-master 大家好我是费老师,就在昨晚,Dash框架发布了其2.14.0新版本,新增的功能中,有一项非常令人兴奋,那就是其针对回调函数这一Dash中的核心概念,新增了动态回调函数注册的支持�

[转帖]我63岁,夫妻俩退休工资15000,回农村养老不到一年,落荒而逃

[一种声音]我63岁,夫妻俩退休工资15000,回农村养老不到一年,落荒而逃 xilei 发布于 2023-2-14 9:36:00 我63岁,夫妻俩退休工资15000,回农村养老不到一年,我们落荒而逃 1、 我是老袁,江西人,一个儿子在上海工作,儿子和儿媳都有不错的单位。孙子住寄宿学校,也不需要我

【转帖】使用 LuaRocks 安装 Apache APISIX 依赖项时,为什么会导致超时、安装缓慢或安装失败?

使用 LuaRocks 安装 Apache APISIX 依赖项时,为什么会导致超时、安装缓慢或安装失败?# http://apisix.incubator.apache.org/zh/docs/apisix/2.14/FAQ/ 可能是因为使用的 LuaRocks 服务器延迟过高。 为了解决这个问题

Maven依赖管理

本文主要记录Maven依赖管理中关于依赖传递和依赖范围的知识 Maven项目示例 创建3个maven项目,分配依赖log4j 1.2.12, 1.2.13, 1.2.14版本。 com.leo project1

14.2 Socket 反向远程命令行

在本节,我们将继续深入探讨套接字通信技术,并介绍一种常见的用法,实现反向远程命令执行功能。对于安全从业者而言,经常需要在远程主机上执行命令并获取执行结果。本节将介绍如何利用 `_popen()` 函数来启动命令行进程,并将输出通过套接字发送回服务端,从而实现远程命令执行的功能。在实现反向远程命令执行时,我们可以使用 `_popen(buf, "r")` 函数来执行特定的命令,并将其输出重定向到一个

华为云发布分布式编译构建系统CodeArts Build

摘要:2月14日,华为云发布分布式编译构建系统CodeArts Build,旨在支撑企业实现高效的软件开发,缩短产品上市周期,帮助企业的软件产品快速形成关键竞争力。 本文分享自华为云社区《唯快不破!华为云发布分布式编译构建系统CodeArts Build》,作者: 华为云头条 。 在数字化时代,软件

Spring Boot 3.1中如何整合Spring Security和Keycloak

在今年2月14日的时候,Keycloak 团队宣布他们正在弃用大多数 Keycloak 适配器。其中包括Spring Security和Spring Boot的适配器,这意味着今后Keycloak团队将不再提供针对Spring Security和Spring Boot的集成方案。但是,如此强大的Ke

共攀元宇宙新高峰,2月14日沈阳元宇宙产业峰会邀您携手领跑2023

摘要:沈阳元宇宙产业峰会暨第三届华为云VR开发应用大赛颁奖典礼将于2023年2月14日携四大亮点粉墨登场,与大家共攀XR与元宇宙的发展新高峰,携手领跑2023新纪元! 元宇宙的浪潮正一波一波的席卷神州大地。据统计,截止2023年1月,全国31个省、直辖市均出台了元宇宙相关的发展建议。上海、四川、河南