在可執行文件PE文件結構中,通常我們需要用到地址轉換相關知識,PE文件針對地址的規範有三種,其中就包括了`VA`,`RVA`,`FOA`三種,這三種該地址之間的靈活轉換也是非常有用的,本節將介紹這些地址範圍如何通過編程的方式實現轉換。VA(Virtual Address,虛擬地址):它是在進程的虛擬... ...
在可執行文件PE文件結構中,通常我們需要用到地址轉換相關知識,PE文件針對地址的規範有三種,其中就包括了VA
,RVA
,FOA
三種,這三種該地址之間的靈活轉換也是非常有用的,本節將介紹這些地址範圍如何通過編程的方式實現轉換。
如下是三種格式的異同點:
- 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 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17696463.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!