PE(PortableExecute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。那Windows是怎么区分可执行文件和非可执行文件的呢?我们调用LoadLibrary传递了一个文件名,系统是如何判断这个文件是一个合法的动态库呢?这就涉及到PE文件结构了。PE文件的结构一般来说如下图所示:从起始位置开始依次是DOS头,NT头,节表以及具体的节。DOS头是用来兼容MS-DOS操作系统的,目的是当这个文件在MS-DOS上运行时提示一段文字,大部分情况下是:ThisprogramcannotberuninDOSmode.还有一个目的,就是指明NT头在文件中的位置。NT头包含windowsPE文件的主要信息,其中包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER)和PE可选头(IMAGE_OPTIONAL_HEADER32),头部的详细结构以及其具体意义在PE文件头文章中详细描述。节表:是PE文件后续节的描述,windows根据节表的描述加载每个节。节:每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义,未必是上图中的三个。当一个PE文件被加载到内存中以后,我们称之为“映象”(image),一般来说,PE文件在硬盘上和在内存里是不完全一样的,被加载到内存以后其占用的虚拟地址空间要比在硬盘上占用的空间大一些,这是因为各个节在硬盘上是连续的,而在内存中是按页对齐的,所以加载到内存以后节之间会出现一些“空洞”。因为存在这种对齐,所以在PE结构内部,表示某个位置的地址采用了两种方式,针对在硬盘上存储文件中的地址,称为原始存储地址或物理地址表示距离文件头的偏移;另外一种是针对加载到内存以后映象中的地址,称为相对虚拟地址(RVA),表示相对内存映象头的偏移。第1页共13页然而CPU的某些指令是需要使用绝对地址的,比如取全局变量的地址,传递函数的地址编译以后的汇编指令中肯定需要用到绝对地址而不是相对映象头的偏移,因此PE文件会建议操作系统将其加载到某个内存地址(这个叫基地址),编译器便根据这个地址求出代码中一些全局变量和函数的地址,并将这些地址用到对应的指令中。例如在IDA里看上去是这个样子:这里写图片描述这种表示方式叫做虚拟地址(VA)。也许有人要问,既然有VA这么简单的表示方式为什么还要有前面的RVA呢?因为虽然PE文件为自己指定加载的基地址,但是windows有茫茫多的DLL,而且每个软件也有自己的DLL,如果指定的地址已经被别的DLL占了怎么办?如果PE文件无法加载到预期的地址,那么系统会帮他重新选择一个合适的基地址将他加载到此处,这时原有的VA就全部失效了,NT头保存了PE文件加载所需的信息,在不知道PE会加载到哪个基地址之前,VA是无效的,所以在PE文件头中大部分是使用RVA来表示地址的,而在代码中是用VA表示全局变量和函数地址的。那又有人要问了,既然加载基址变了以后VA都失效了,那存在于代码中的那些VA怎么办呢?答案是:重定位。系统有自己的办法修正这些值,到后续重定位表的文章中会详细描述。既然有重定位,为什么NT头不能依靠重定位采用VA表示地址呢(十万个为什么)?因为不是所有的PE都有重定位,早期的EXE就是没有重定位的。我们都知道PE文件可以导出函数让其他的PE文件使用,也可以从其他PE文件导入函数,这些是如何做到的?PE文件通过导出表指明自己导出那些函数,通过导入表指明需要从哪些模块导入哪些函数。导入和导出表的具体结构会在单独的文章中详细解释。一、DOS头DOS头的作用是兼容MS-DOS操作系统中的可执行文件,对于32位PE文件来说,DOS所起的作用就是显示一行文字,提示用户:我需要在32位windows上才可以运行。我认为这是个善意的玩笑,因为他并不像显示的那样不能运行,其实已经运行了,只是在DOS上没有干用户希望看到的工作而已,好吧,我承认这不是重点。但是,至少我们看一下这个头是如何定义的:typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number WORD e_cblp; // Bytes on last page of file WORD e_cp; // Pages in file WORD e_crlc; // Relocations WORD e_cparhdr; // Size of header in paragraphs WORD e_minalloc; // Minimum extra paragraphs needed WORD e_maxalloc; // Maximum extra paragraphs needed WORD e_ss; // Initial (relative) SS value WORD e_sp; // Initial SP value WORD e_csum; // Checksum WORD e_ip; // Initial IP value 第2页共13页 WORD e_cs; // Initial (relative) CS value WORD e_lfarlc; // File address of relocation table WORD e_ovno; // Overlay number WORD e_res[4]; // Reserved words WORD e_oemid; // OEM identifier (for e_oeminfo) WORD e_oeminfo; // OEM information; e_oemid specific WORD e_res2[10]; // Reserved words LONG e_lfanew; // File address of new exe header } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 我们只需要关注两个域:e_magic:一个WORD类型,值是一个常数0x4D5A,用文本编辑器查看该值为‘MZ’,可执行文件必须都是'MZ'开头。e_lfanew:为32位可执行文件扩展的域,用来表示DOS头之后的NT头相对文件起始地址的偏移。二、NT头顺着DOS头中的e_lfanew,我们很容易可以找到NT头,这个才是32位PE文件中最有用的头,定义如下:typedefstruct_IMAGE_NT_HEADERS{DWORDSignature;IMAGE_FILE_HEADERFileHeader;IMAGE_OPTIONAL_HEADER32OptionalHeader;}IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;下图是一张真实的PE文件头结构以及其各个域的取值:第3页共13页Signature:类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是'PE‘。IMAGE_FILE_HEADER是PE文件头,c语言的定义是这样的:typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 每个域的具体含义如下:Machine:该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。#define IMAGE_FILE_MACHINE_UNKNOWN 0 #define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386. #define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little‐endian, 0x160 big‐endian #define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little‐endian 第4页共13页#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little‐endian #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little‐endian WCE v2 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP #define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little‐endian #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little‐endian #define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little‐endian #define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5 #define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little‐Endian #define IMAGE_FILE_MACHINE_THUMB 0x01c2 #define IMAGE_FILE_MACHINE_AM33 0x01d3 #define IMAGE_FILE_MACHINE_POWERPC