PE 结构 ——笔记整理

PE文件是Windows操作系统下的可执行文件,是微软在UNIX的COFF的基础上制作而成的,PE文件是指32位Windows操作系统的可执行文件(也称之为PE32) ,64位的Windows操作系统的可执行文件为 PE32+或者PE+,是PE32的一种扩展性形式.
一个PE文件的起始部分也就是PE头里边一般都存储着,这个PE文件在运行过程中所需要的资源以及空间的大小,当然还包括要加载那些DLL文件到内存中,等等一系列信息.而这些信息以结构体的方式存储在PE头里边.
PE文件在制作之初,DOS程序还大行其道,因此微软在制作PE文件格式的时候考虑到了对于DOS文件的兼容性,所以在PE文件之中添加了一个字段:IMAGE_DOS_HEADER,用来兼容DOS格式的文件.
在PE文件里边,从DOS头(DOS HEADER)到节区头(Section HEADER)称为PE头,剩余部分(节区)称为PE体,在文件当中使用偏移(offest)来表示位置,在内存中使用VA表示位置。因为文件在加载到内存当中之后,文件的节区的大小位置等等这些因素都会改变(造成这个现象的主要原因是文件当中和内存中所采用的对齐方式不同)
VA(VirtualAddress)是进程虚拟内存的绝对地址,RVA(RelativeVirtualAddress)是相对虚拟地址,以某一个地址为基准所得到的相对地址。二者之间的关系:

VA=ImageBass(基址)+RVA(相对地址)

除此之外 还要注意:在32位操作系统中,VA的取值范围为00000000~FFFFFFFF(因为在32位的操作系统中,每个进程有4GB的虚拟内存)
PE头中的第一个结构:DOS头

DOS头结构里边有两个重要的字段 分别是:e_magic字段和e_lfanew字段 ,这两个字段的内容是 :
e_magic字段的内容是MZ头,也就是我们常见的MZ标签(4D5A),另一个字段的值指向NT头。
除此之外还需要注意一点,DOSheader的大小是固定的40字节。

PE文件头:
DOS头结束之后 就是PE结构相关结构的NT映像头(IMAGE_NT_HEADER),上边所说的DOS头的第一个结构IMAGE_DOS_HEADER结构的e_lfanew字段就指向PE文件头,计算方式:
NTHeader=ImageBase+Dos_Header->e_lfanew
NTHeader结构的内容主要有3个字段组成,最左边的数据 是相对于PE文件头(NTHeader)的偏移

IMAGE_NT_HEADER
{
    +0h      Signature             DWORD           这个是PE文件的标识  ”PE“
    +4h      File Header           DWORD   IMAGE_FILE_HEADER
    +18h    OptionalHeader         DWORD           IMAGE_OPTIONAL_HEADER
}

第一个字段是PE文件的标志 ”PE“,该字段的值是固定的0x00004550(因为是一个DWORD型的数据),ASCII对应的就是”PE00“
第二个和第三个字段分别是 File_Header 和 OptionalHeader(可选头),在标准PE文件里边,可选头的大小是E0,当然可选头的大小可以比E0大。
FileHeader的结构:
在这里插入图片描述
其中,需要注意的有以下几个字段:
Machine 表示文件运行的平台
NumberOfSection 表示文件节表的数量
TimeDataStamp 表示文件的时间戳
SizeOfOptionalHeader 表示文件的可选头大小
Characteristics 标识当前文件的类型(exe或者dll或者sys文件)

https://blog.csdn.net/sxr__nc/article/details/103528360    

里边关于判断文件的类型也是根据这个结构体里边的内容进行判断的。
可选头的结构:
在这里插入图片描述
可选头里边的重要字段相对比较多,主要理解对于导出表和导入表的遍历
在可选头里边如果要想遍历导入导出表,那么就不可避免的遇到一个问题,就是RVA转RAW。
其实这个问题的实质就是,怎么通过虚拟内存地址找到数据在源文件中的文件偏移
附上源代码:(只实现了一个地址的转化)

#include<stdio.h>
#include<Windows.h>
int main()
{
	LPCSTR lpFileName="D:\\PyCharm Community Edition 2019.2.4\\bin\\IdeaWin32.dll";
	HANDLE hFile;
	hFile=CreateFileA(lpFileName,GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,0);
	if(hFile==INVALID_HANDLE_VALUE)
	{
		MessageBoxA(0,"文件打开失败",0,MB_OK);
		return 0;
	}
	DWORD FileSize=GetFileSize(hFile,0);
	BYTE* lpBuffer=new BYTE[FileSize];
	ReadFile(hFile,lpBuffer,FileSize,0,0);
	/*定义DOS头*/
	PIMAGE_DOS_HEADER pDosHeader=(PIMAGE_DOS_HEADER)lpBuffer;
	/*定义NT头*/
	PIMAGE_NT_HEADERS pNtHeader=(PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew+lpBuffer);
	/*定义可选头*/
	PIMAGE_OPTIONAL_HEADER pOptionalHeader=&(pNtHeader->OptionalHeader);
	/*定义文件头*/
	PIMAGE_FILE_HEADER pFileHeader=&(pNtHeader->FileHeader);
	/*获取节表的数量*/
	DWORD SectionNumber=pFileHeader->NumberOfSections;
	/*获取节的位置,方法,从NT头加上整个NT头的大小*/
	PIMAGE_SECTION_HEADER SectionHeader=(PIMAGE_SECTION_HEADER)(pNtHeader+0x18+pFileHeader->SizeOfOptionalHeader);
	/*定位到数据目录表(通过这种方式获取到的是数据目录表的第一个元素也就是导出表),但是通过IMAGE_EXPORT_DIRECTORY* Export=(IMAGE_EXPORT_DIRECTORY*)((BYTE *)pNtHeader+0x78)这种方式得到的是导出表的结构*/
	PIMAGE_DATA_DIRECTORY pDirectory=(PIMAGE_DATA_DIRECTORY)(pOptionalHeader->DataDirectory);
	/*获取导出表的虚拟地址*/
	DWORD ExportRVA=pDirectory->VirtualAddress;//注意这个地址是一个RVA,是数据块的起始RVA
	/*开始进行转换,RVA->RAW 过程中用到的两个参数:第一个(导出表虚拟地址:ExportRVA)第二个(基址:pDosHeader)*/
	/*转换的思路:1、获取节区表的开始的虚拟地址
	2、获取节区表的结束地址
	3、判断要转换的RVA所在节区
	4、利用公式进行计算转换  RAW=RVA-VA+PointToRAW*/
	for(int i=0;i<SectionNumber;i++)
	{
		/*计算当前节区的开始地址*/
		DWORD SectionBegin=SectionHeader[i].VirtualAddress;//区块的RVA地址
		/*计算当前节区的结束地址*/
		DWORD SectionEnd=SectionHeader[i].VirtualAddress+SectionHeader[i].SizeOfRawData;//(这里的大小在实际使用中是区块的起始RVA+区块的大小,但是在实际使用中要考虑到文件的对齐)
		/*判断要求的RVA是否在当前的节区内*/
		if(ExportRVA>SectionBegin&&ExportRVA<SectionEnd)
		{
			/*如果在当前的节区内*/
			DWORD ExportRAW=ExportRVA-SectionBegin+SectionHeader[i].PointerToRawData;
			printf("转换结果:%x",ExportRAW);
		}
	}
}

源代码在实际的编写过程中,也添加了相应的注释,应该比较好理解,总结出来的话:RVA->RAW 需要经历以下几步:
1:确定RVA所在的节
2:找到所在节之后,用RVA减去所在界的起始地址
3:用上一步的结果再加上该节里边PointToRAW字段的值

总结:
先告一段落

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值