在可执行文件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; // 计算RVADWORD FOA = pSection[each].PointerToRawData + (RVA - pSection[each].VirtualAddress); // 计算FOAreturn 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); // 得到FOAreturn 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); // 计算出RVADWORD VA = RVA + pNtHead->OptionalHeader.ImageBase; // 计算出VAreturn 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 许可协议。转载请注明出处!