2.14 PE結構:地址之間的轉換

2023-09-12 18:01:27

在可執行檔案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 許可協定。轉載請註明出處!