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

本文涉及的产品
公网NAT网关,每月750个小时 15CU
简介: 在可执行文件PE文件结构中,通常我们需要用到地址转换相关知识,PE文件针对地址的规范有三种,其中就包括了`VA`,`RVA`,`FOA`三种,这三种该地址之间的灵活转换也是非常有用的,本节将介绍这些地址范围如何通过编程的方式实现转换。VA(Virtual Address,虚拟地址):它是在进程的虚拟地址空间中的地址,用于在运行时访问内存中的数据和代码。VA是相对于进程基址的偏移量。在不同的进程中,相同的VA可能映射到不同的物理地址。RVA(Relative Virtual Address,相对虚拟地址):它是相对于模块基址(Module Base Address)的偏移量,用于定位模块内部的数

在可执行文件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 许可协议。转载请注明出处!

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
基于阿里云,构建一个企业web应用上云经典架构,让IT从业者体验企业级架构的实战训练。
目录
相关文章
|
4月前
|
存储 数据中心 云计算
逻辑存储和物理存储各代表什么?区别是什么?
逻辑存储和物理存储各代表什么?区别是什么?
|
15天前
|
网络协议 网络架构
IP 地址的结构和类型
【4月更文挑战第12天】
32 0
IP 地址的结构和类型
|
15天前
端口地址转换(PAT)与私有IP的映射
【4月更文挑战第12天】
17 1
|
3月前
|
存储
一文搞清楚字节的名称和关系,告诉你ib和B有什么区别?
一文搞清楚字节的名称和关系,告诉你ib和B有什么区别?
|
5月前
|
编译器 C++ Windows
9.4 Windows驱动开发:内核PE结构VA与FOA转换
本章将继续探索内核中解析PE文件的相关内容,PE文件中FOA与VA,RVA之间的转换也是很重要的,所谓的FOA是文件中的地址,VA则是内存装入后的虚拟地址,RVA是内存基址与当前地址的相对偏移,本章还是需要用到`《内核解析PE结构导出表》`中所封装的`KernelMapFile()`映射函数,在映射后对其PE格式进行相应的解析,并实现转换函数。
40 0
9.4 Windows驱动开发:内核PE结构VA与FOA转换
|
8月前
|
存储 Shell
2.12 PE结构:实现PE字节注入
本章笔者将介绍一种通过Metasploit生成ShellCode并将其注入到特定PE文件内的Shell注入技术。该技术能够劫持原始PE文件的入口地址,在PE程序运行之前执行ShellCode反弹,执行后挂入后台并继续运行原始程序,实现了一种隐蔽的Shell访问。而我把这种技术叫做字节注入反弹。
80 0
2.12 PE结构:实现PE字节注入
|
8月前
|
Windows
2.8 PE结构:资源表详细解析
在Windows PE中,资源是指可执行文件中存放的一些固定不变的数据集合,例如图标、对话框、字符串、位图、版本信息等。PE文件中每个资源都会被分配对应的唯一资源ID,以便在运行时能够方便地查找和调用它们。PE文件中的资源都被组织成一个树形结构,其中最顶层为根节点(Root),下一级为资源类型(Type),再下一级为资源名称(Name),最终是实际的资源内容。PIMAGE_RESOURCE_DIRECTORY是Windows PE可执行文件中的一个结构类型,用于描述资源(Resource)的树形结构,其中包括了每个资源的类型(Type)、名称(Name)和语言(Language),以及指向下一
85 0
2.8 PE结构:资源表详细解析
|
8月前
|
存储 Windows
2.4 PE结构:节表详细解析
节表(Section Table)是Windows PE/COFF格式的可执行文件中一个非常重要的数据结构,它记录了各个代码段、数据段、资源段、重定向表等在文件中的位置和大小信息,是操作系统加载文件时根据节表来进行各个段的映射和初始化的重要依据。节表中的每个记录则被称为`IMAGE_SECTION_HEADER`,它记录了一个段的各种属性信息和在文件中的位置和大小等信息,一个文件可以由多个`IMAGE_SECTION_HEADER`构成。
94 0
2.4 PE结构:节表详细解析
|
8月前
|
存储 编译器 数据安全/隐私保护
2.11 PE结构:添加新的节区
在可执行PE文件中,节(section)是文件的组成部分之一,用于存储特定类型的数据。每个节都具有特定的作用和属性,通常来说一个正常的程序在被编译器创建后会生成一些固定的节,通过将数据组织在不同的节中,可执行文件可以更好地管理和区分不同类型的数据,并为运行时提供必要的信息和功能。节的作用是对可执行文件进行有效的分段和管理,以便操作系统和加载器可以正确加载和执行程序。
100 2
|
8月前
|
存储 算法 编译器
2.7 PE结构:重定位表详细解析
重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变
92 0
2.7 PE结构:重定位表详细解析