PE结构详解
EXE和DLL文件之间的区别完全是语义上的,因为他们使用完全相同的PE格式。而唯一的区别就是用一个字段标识出这个文件是EXE还是DLL。
64位Windows只是对PE格式做了一些简单的修饰,新格式叫PE32+。并没有任何新的结构加进去,改变的只是简单的将32位字段扩展成64位。
PE格式定义的主要地方位于我们的头文件winnt.h,这个头文件中几乎能找到关于PE文件的所有定义
PE文件中的数据结构一般都有32位和64位之分,一般在名称上会表现出来:例如IMAGE_NT_HEADERS32 或 IMAGE_NT_HEADER64
我们的讲解主要是基于现在主流的32位PE格式来进行研究的,64位请依此类推,因为64位版本只是对32位做了一些简单的扩展,结构几乎是一摸一样的。
PE的基本概念
PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。
文件的内容被分割为不同的区块,块中包含代码或数据。各个区块按页边界来对齐,区块没有大小限制,是一个连续的结构。
此外,每个块有自己在内存中的一套属性,比如说这个区块是否包含代码、是否只读或可读/写等。
认识PE文件不是作为单一内存映射文件被装入内存是很重要的。
Windows加载器(又称PE装载器)遍历PE文件并决定文件的哪一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。
当磁盘文件一旦被装入内存中,磁盘上的数据结构布局和内存中的数据结构布局是一致的。
这样如果知道在磁盘的数据结构中寻找一些内容,那么几乎都能在被装入到内存映射文件中找到相同的信息。
但数据之间的相对位置可能改变,其某项的偏移地址可能区别于原始的偏移位置,不管怎样,所有表现出来的信息都允许从磁盘文件偏移到内存偏移的转换。
基地址ImageBase:内存中的头地址,就是模块的句柄,通过HMODULE GetModuleHandle(LPCTSTR lpModuleName)函数可以获取
相对虚拟地址RVA:在内存中,每一个区块虚拟地址,相对于基地址的偏移(相减)
文件偏移地址:就是在PE文件在硬盘中,各个区块相对文件头的偏移
MS - DOS 头部
每个PE文件是以一个DOS程序开始的,有了它,一旦程序在DOS下执行,DOS才能识别出这是有效的执行体。
PE 文件的第一个字节起始于一个传统的MS-DOS 头部,被称作 IMAGE_DOS_HEADER。
MS-DOS头部占据了PE文件的头64个字节,描述它内容的结构如下:
//WINNT.H
typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE头部
USHORT e_magic; // 魔术数字
USHORT e_cblp; // 文件最后页的字节数
USHORT e_cp; // 文件页数
USHORT e_crlc; // 重定义元素个数
USHORT e_cparhdr; // 头部尺寸,以段落为单位
USHORT e_minalloc; // 所需的最小附加段
USHORT e_maxalloc; // 所需的最大附加段
USHORT e_ss; // 初始的SS值(相对偏移量)
USHORT e_sp; // 初始的SP值
USHORT e_csum; // 校验和
USHORT e_ip; // 初始的IP值
USHORT e_cs; // 初始的CS值(相对偏移量)
USHORT e_lfarlc; // 重分配表文件地址
USHORT e_ovno; // 覆盖号
USHORT e_res[4]; // 保留字
USHORT e_oemid; // OEM标识符(相对e_oeminfo)
USHORT e_oeminfo; // OEM信息
USHORT e_res2[10]; // 保留字
LONG e_lfanew; // 新exe头部的文件地址 DWORD双字,4个字节
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
第一个域e_magic,被称为魔术数字,它被用于表示一个MS-DOS兼容的文件类型。所有MS-DOS兼容的可执行文件都将这个值设为0x5A4D,表示ASCII字符MZ。MS-DOS头部之所以有的时候被称为MZ头部,就是这个缘故。还有许多其它的域对于MS-DOS操作系统来说都有用,但是对于Windows NT来说,这个结构中只有一个有用的域——最后一个域e_lfnew,一个4字节的文件偏移量,PE文件头部就是由它定位的。对于Windows NT的PE文件来说,PE文件头部是紧跟在MS-DOS头部和实模式程序残余之后的。
PE 文件头
PE 文件头(PE Header)紧挨着DOS stub
PE Header 是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,里边包含着许多PE装载器用到的重要字段。
执行体在支持PE文件结构的操作系统中执行时,PE装载器将从IMAGE_DOS_HEADER结构中的e_lfanew字段里找到PE Header的起始偏移量,加上基地址就得到PE文件头的指针。
PNTHeader = ImageBase + dosHeader -> e_lfanew
IMAGE_NT_HEADER 结构
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; 04h //00004550h PE的标志
IMAGE_FILE_HEADER FileHeader;//一个结构,文件头的说明
IMAGE_OPTIONAL_HEADER OptionalHeader;//一个结构,文件头的说明
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;