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


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

  • 相关阅读:
    Unity可视化Shader工具ASE介绍——5、ASE快捷键和常用节点介绍
    linux安装mysql
    Python实现【贪吃蛇大作战】+源码
    【语音识别入门】Python音频处理示例(含完整代码)
    Java数据结构-哈希表
    M2 MacBookAir售价是多少 M2 MacBookAir配置如何
    2022乐鑫数字芯片提前批笔试
    msvcp140.dll丢失的有哪些解决方法,丢失msvcp140.dll是什么意思
    windows每天定时重启 Win11 Win10定时重启 windows定时重启系统 windows每天定时重启
    MMDeploy部署实战系列【第二章】:mmdeploy安装及环境搭建
  • 原文地址:https://www.cnblogs.com/LyShark/p/17696463.html