PE有两种不同的状态:1.FileBuffer文件状态 2.ImageBuffer内存状态。
当需求为:改变一个变量的值,知道它在内存状态下的地址,我们需要找到他在文件状态下的地址对它进行修改。或者反之,我们知道它在文件状态下的地址、找出他在内存状态下的地址。就需要RVA与FOA相互转换。
RVA与FOA相互转换主要会使用到节表中的信息,所以我们先需要复习一下节表中的信息。
节表
节表存在于可选PE头之下,它的作用是包含个节的信息。如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //占8个字节,分别是各个节的名字
union {
DWORD PhysicalAddress;
DWORD VirtualSize;//节数据没有对齐后的大小
} Misc; // 联合体 该节在没有对齐前的真实尺寸,该值可以不准确
DWORD VirtualAddress; //节区内存中节的偏移
DWORD SizeOfRawData; // 节区在文件中对齐后的大小
DWORD PointerToRawData; // 在文件中的偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
VA:virtualaddress,在内存中的虚拟地址。如0x00403000。
RVA:相对虚拟偏移. 就是偏移地址。如0x00403000的RVA就是0x00403000-ImageBase = 0x0000300。
FOA:是文件中的偏移地址。
使用PETool解析System32下的notepad.exe节表信息如下。
如何转换?
1.RVA2FOA
即我们现在知道内存状态下的偏移,需要找到文件状态下的偏移。
步骤如下图:
step1:内存中的地址减去内存基址得到偏移,即RVA。
step2:循环遍历节表中各个节的信息,判断在哪个节中。(需要满足:内存偏移+节数据没对齐的大小>image_panyi>内存偏移)
step3:找出在哪个节后,减去该节在内存中的偏移(VirturalAddress)得到在该节中的相对偏移。
step4:上一步得到的该节的相对偏移+该节在文件中的偏移(PointToRawData),即得到FOA
2.FOA2RVA
现在我们已经知道如何从内存中的偏移转化为文件中的偏移。现在是它的逆过程
step1:文件中的地址减去文件基址,得到在文件中的偏移,即FOA。
step2:循环遍历节表中各个节的信息,判断在哪个节中。(文件对齐+文件偏移>file_panyi>文件偏移)
step3:找出在哪个节后,减去该节在文件中的偏移(VirturalAddress)得到在该节中的相对偏移。
step4:上一步得到的该节的相对偏移+该节在内存中的偏移(VirtualAddress),即得到RVA。
代码如下:
包括:exe->filebuffer->imagebuffer->new->buffer及RVA2FOA和FOA2RVA
// test_lc_01.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "string.h"
#include <malloc.h>
#include <windows.h>
// exe->filebuffer 返回值为计算所得文件大小
int ReadPEFile(char* file_path,PVOID* pFileBuffer)
{
FILE* pfile = NULL; // 文件指针
DWORD file_size = 0;
LPVOID pTempFilebuffer = NULL;
// 打开文件
pfile = fopen(file_path,"rb"); // 如果有新的指针,就要进行判断
if(!pfile)
{
printf("打开exe文件失败!\n");//如果分配失败就要关闭文件、释放动态内存、指针指向NULL
return 0;
}
// 读取文件大小
fseek(pfile,0,SEEK_END);
file_size = ftell(pfile);
fseek(pfile,0,SEEK_SET);
// 分配空间
pTempFilebuffer = malloc(file_size); // 如果有新的指针,就要进行判断
if(!pTempFilebuffer)
{
printf("分配空间失败!\n");//如果分配失败就要关闭文件、释放动态内存、指针指向NULL
fclose(pfile);
return 0 ;
}
// 将数据读取到内存中
size_t n = fread(pTempFilebuffer,file_size,1,pfile);
if(!n)
{
printf("数据读取到内存中失败!\n"); //如果分配失败就要关闭文件、释放动态内存、指针指向NULL
fclose(pfile);
free(pTempFilebuffer);
return 0 ;
}
// 关闭文件(已经读取到内存了)
*pFileBuffer = pTempFilebuffer;
pTempFilebuffer = NULL;
fclose(pfile);
return file_size;
}
// filebuffer -> imagebuffer
DWORD CopyFileBufferToImageBuffer(PVOID pFileBuffer,PVOID* pImageBuffer)
{
// 初始化PE头部结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
// 初始化IMAGE_BUFFER指针(temparay)
LPVOID pTempImagebuffer = NULL;
if(!pFileBuffer)
{
printf("(2pimagebuffer阶段)读取到内存的pfilebuffer无效!\n");
return 0 ;
}
// 判断是否是可执行文件
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) // IMAGE_DOS_SIGNATURE是4字节,将pFileBuffer强制类型转换为4字节指针类型(PWORD)
{
printf("(2pimagebuffer阶段)不含MZ标志,不是exe文件!\n");
return 0;
}
//强制结构体类型转换pDosHeader
pDosHeader = PIMAGE_DOS_HEADER(pFileBuffer);
//判断是否含有PE标志
if(*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) // 注意指针的加法是:去掉一个*后的类型相加。必须转换为DWORD类型再加减。
{ //相加后的和 强制类型转换为4字节指针类型(PWORD) IMAGE_NT_SIGNATURE 4BYTES
printf("(2pimagebuffer阶段)不是有效的PE标志!\n");
return 0;
}
// 强制结构体类型转换
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4);
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
// 分配动态内存
pTempImagebuffer = malloc(pOptionHeader->SizeOfImage);
if(!pTempImagebuffer)
{
printf("分配动态内存失败!\n");
free(pTempImagebuffer);
return 0;
}
// 初始化动态内存
memset(pTempImagebuffer,0,pOptionHeader->SizeOfImage);
// 拷贝头部
memcpy(pTempImagebuffer,pDosHeader,pOptionHeader->SizeOfHeaders);
// 循环拷贝节表
PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
for(DWORD i = 0;i<pPEHeader->NumberOfSections;i++,pTempSectionHeader++)
{
memcpy((void*)((DWORD)pTempImagebuffer+pTempSectionHeader->VirtualAddress),(void*)((DWORD)pFileBuffer+pTempSectionHeader->PointerToRawData),pTempSectionHeader->SizeOfRawData);
}
// 返回数据
*pImageBuffer = pTempImagebuffer;
pTempImagebuffer = NULL;
return pOptionHeader->SizeOfImage;
}
//imagebuffer->newbuffer
DWORD CopyImageBufferToNewBuffer(PVOID pImageBuffer,PVOID* pNewBuffer)
{
// 初始化PE头部结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
// 初始化NEW_BUFFER指针(temparay)
LPVOID pTempNewbuffer = NULL;
// 判断pImageBuffer是否有效
if(!pImageBuffer)
{
printf("(2pnewbuffer阶段)读取到内存的pimagebuffer无效!\n");
return 0;
}
//判断是不是exe文件
if(*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("(2pnewbuffer阶段)不含MZ标志,不是exe文件!\n");
return 0;
}
// 强制结构体类型转换
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
if(*((PDWORD)((DWORD)pImageBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("(2pnewbuffer阶段)不是有效的PE标志!\n");
return 0;
}
// 强制结构体类型转换
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4); // 这里必须强制类型转换
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
//获取new_buffer的大小
int new_buffer_size = pOptionHeader->SizeOfHeaders;
for(DWORD i = 0;i<pPEHeader->NumberOfSections;i++)
{
new_buffer_size += pSectionHeader[i].SizeOfRawData; // pSectionHeader[i]另一种加法
}
// 分配内存(newbuffer)
pTempNewbuffer = malloc(new_buffer_size);
if(!pTempNewbuffer)
{
printf("(2pnewbuffer阶段)分配Newbuffer失败!\n");
return 0;
}
memset(pTempNewbuffer,0,new_buffer_size);
// 拷贝头部
memcpy(pTempNewbuffer,pDosHeader,pOptionHeader->SizeOfHeaders);
// 循环拷贝节区
PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
for(DWORD j = 0;j<pPEHeader->NumberOfSections;j++,pTempSectionHeader++)
{ //PointerToRawData节区在文件中的偏移,VirtualAddress节区在内存中的偏移地址,SizeOfRawData节在文件中对齐后的尺寸
memcpy((PDWORD)((DWORD)pTempNewbuffer+pTempSectionHeader->PointerToRawData),(PDWORD)((DWORD)pImageBuffer+pTempSectionHeader->VirtualAddress),pTempSectionHeader->SizeOfRawData);
}
//返回数据
*pNewBuffer = pTempNewbuffer; //暂存的数据传给参数后释放
pTempNewbuffer = NULL;
return new_buffer_size; // 返回计算得到的分配内存的大小
}
//newbuffer->存盘
int newbuffer_write2_exe(PVOID NewFileBuffer,DWORD FileSize, char* FilePath)
{
FILE* fp1 = fopen(FilePath,"wb");
if(fp1 != NULL)
{
fwrite(NewFileBuffer,FileSize,1,fp1);
}
fclose(fp1);
return 1;
}
// RVA转换成FOA
DWORD convertRVAtoFOA(DWORD pRVA,PVOID pFileBuffer,PVOID pImageBuffer)
{
// 初始化PE头部结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
// 判断pImageBuffer是否有效
if(!pImageBuffer)
{
printf("(RVA转换成FOA阶段)读取到内存的pimagebuffer无效!\n");
return 0;
}
//判断是不是exe文件
if(*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("(RVA转换成FOA阶段)不含MZ标志,不是exe文件!\n");
return 0;
}
// 强制结构体类型转换
pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
if(*((PDWORD)((DWORD)pImageBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("(RVA转换成FOA阶段)不是有效的PE标志!\n");
return 0;
}
// 强制结构体类型转换
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4); // 这里必须强制类型转换
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
int image_panyi = pRVA; // pRVA是在内存中的偏移偏移
printf("image_panyi:%#x\n",image_panyi);
// 循环查找在那个imagebuffer节中
PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
for(DWORD i = 0;i<pPEHeader->NumberOfSections;i++,pTempSectionHeader++)
{ //判断 : Misc.VirtualSize+ VirtualAddress 内存偏移+节数据没对齐的大小>image_panyi>内存偏移 VirtualAddress (即是在文件的哪个节中)
if((image_panyi>=pTempSectionHeader->VirtualAddress) && (image_panyi<pTempSectionHeader->VirtualAddress+pTempSectionHeader->Misc.VirtualSize))
{
return image_panyi-pTempSectionHeader->VirtualAddress+pTempSectionHeader->PointerToRawData;
}
}
return 0;
}
// FOA转换成RVA
DWORD convertFOAtoRVA(DWORD pFOA,PVOID pFileBuffer,PVOID pImageBuffer)
{
// 初始化PE头部结构体
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
PIMAGE_SECTION_HEADER pSectionHeader = NULL;
// 判断pImageBuffer是否有效
if(!pFileBuffer)
{
printf("(FOA转换成RVA阶段)读取到内存的pimagebuffer无效!\n");
return 0;
}
//判断是不是exe文件
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("(FOA转换成RVA阶段)不含MZ标志,不是exe文件!\n");
return 0;
}
// 强制结构体类型转换
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
if(*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("(FOA转换成RVA阶段)不是有效的PE标志!\n");
return 0;
}
// 强制结构体类型转换
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+4); // 这里必须强制类型转换
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader+pPEHeader->SizeOfOptionalHeader);
int file_panyi = pFOA; // pRVA是文件中的偏移偏移
printf("file_panyi:%#x\n",file_panyi);
// 循环查找在那个imagebuffer节中
PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
for(DWORD i = 0;i<pPEHeader->NumberOfSections;i++,pTempSectionHeader++)
{ //判断 : 文件对齐+文件偏移>file_panyi>文件偏移 (即是在文件的哪个节中)
if((file_panyi>=pTempSectionHeader->PointerToRawData) && (file_panyi<pTempSectionHeader->PointerToRawData+pTempSectionHeader->SizeOfRawData))
{
return file_panyi-pTempSectionHeader->PointerToRawData+pTempSectionHeader->VirtualAddress;
}
}
return 0;
printf("地址转换失败!\n");
}
void operate_pe()
{ // 初始化操作
PVOID pFileBuffer = NULL;
PVOID pImageBuffer = NULL;
PVOID pNewFileBuffer = NULL;
DWORD NewFileBufferSize = 0;
//char file_path[] = "D:\\Lib\\IPMSG2007.exe";
char file_path[] = "C:\\Windows\\System32\\notepad.exe";
char write_file_path[] = "D:\\Lib\\cp_notepad.exe";
// exe->filebuffer
int ret1 = ReadPEFile(file_path,&pFileBuffer); // &pFileBuffer(void**类型) 传递地址对其值可以进行修改
printf("exe->filebuffer 返回值为计算所得文件大小:%#x\n",ret1);
// filebuffer -> imagebuffer
int ret2 = CopyFileBufferToImageBuffer(pFileBuffer,&pImageBuffer);
printf("filebuffer -> imagebuffer返回值为计算所得文件大小:%#x\n",ret2);
//imagebuffer->newbuffer
int FileSize = CopyImageBufferToNewBuffer(pImageBuffer,&pNewFileBuffer);
printf("imagebuffer->newbuffer返回值为计算所得文件大小:%#x\n",FileSize);
//newbuffer->存盘
//int ret4 = newbuffer_write2_exe(pNewFileBuffer,FileSize, write_file_path);
//printf("newbuffer->存盘返回值为:%d\n",ret4);
int pRVA = 0x00021178;
int pFOA = 0x00020450;
int ret_FOA = convertRVAtoFOA(pRVA,pFileBuffer,pImageBuffer);
printf("内存偏移%#x 转换为文件中的偏移:%#x\n",pRVA,ret_FOA);
int ret_RVA = convertFOAtoRVA(pFOA,pFileBuffer,pImageBuffer);
printf("文件偏移%#x 转换为内存中的偏移:%#x\n",pFOA,ret_RVA);
}
int main()
{
operate_pe();
getchar();
return 0;
}
结果如下:
实现了文件偏移与内存偏移之间的相互转化。