在可執行檔案PE檔案結構中,通常我們需要用到地址轉換相關知識,PE檔案針對地址的規範有三種,其中就包括了VA
,RVA
,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_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
轉換為記憶體虛擬地址,在轉換時首先通過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 許可協定。轉載請註明出處!