参考书籍:《WindowsPE文件权威指南》
MSDN中winnt.h是PE文件定义的最终决定者。
EXE文件与DLL文件之间的区别完全是语义上的,二者PE结构完全相同。唯一区别用一个字段标示处这个文件是exe还是dll。许多DLL扩展,如OCX控件,控制面板等都是DLL,它们有一样的实体。
64位的Windows只是对PE格式做了一些简单的修饰,新格式叫PE32+。没有新的结构加进去,其余的改变只是简单地将以前的32位字段扩展为64位字段。
1.PE文件基本结构:
PE文件的头分为DOS头、NT头、节头。注意,这是本人的分法。这样分法会更加合理,更易理解。因为这三个部分正好构成SizeOfHeaders所指的范围,所以将它们合为“头”。
2.文件头
2.1 DOS头
用记事本打开任何一个镜像文件,其头2个字节必为字符串“MZ”,这是Mark Zbikowski的姓名缩写,他是最初的MS-DOS设计者之一。然后是一些在MS-DOS下的一些参数,这些参数是在MS-DOS下运行该程序时要用到的。在这些参数的末尾也就是文件的偏移0x3C(第60字节)处是是一个4字节的PE文件签名的偏移地址。该地址有一个专用名称叫做“E_lfanew”。这个签名是“PE00”(字母“P”和“E”后跟着两个空字节)。紧跟着E_lfanew的是一个MS-DOS程序。那是一个运行于MS-DOS下的合法应用程序。当可执行文件(一般指exe、com文件)运行于MS-DOS下时,这个程序显示“This
program cannot be run in DOS mode(此程序不能在DOS模式下运行)”这条消息。用户也可以自己更改该程序,有些还原软件就是这么干的。同时,有些程序既能运行于DOS又能运行于Windows下就是这个原因。Notepad.exe整个DOS头大小为224个字节,大部分不能在DOS下运行的Win32文件都是这个值。MS-DOS程序是可有可无的,如果你想使文件大小尽可能的小可以省掉MS-DOS程序,同时把前面的参数都清0。
2.1.1 DOS头结构体
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD e_magic; // Magic number //EXE标志 "MZ" 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; // Maximum extra paragraphs needed //所需的最大附加段 WORD e_ss; // Initial (relative) SS value //初始的SS值 WORD e_sp; // Initial SP value //初始的SP值 WORD e_csum; // Checksum //补码校检值 WORD e_ip; // Initial IP value //初始的IP值 WORD e_cs; // Initial (relative) CS value //初始的CS值 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) //OEM标识符 WORD e_oeminfo; // OEM information; e_oemid specific //OEM信息 WORD e_res2[10]; // Reserved words //保留字 LONG e_lfanew; // File address of new exe header //PE头相对于文件的偏移地址 } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;注意:DOS头后是一个整个DOS Stub字节块,其内容随使用的链接器的不同而不同,PE中并没有与之相关的结构
2.2 NT头
紧跟着PE文件签名之后,是NT头。NT头分为三个部分,1.PE文件标识2.PE文件头3.PE扩展头2.2.1 NT头结构体
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //PE文件标识 “PE ” IMAGE_FILE_HEADER FileHeader; //PE文件头 IMAGE_OPTIONAL_HEADER32 OptionalHeader; //PE扩展头 } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
(1)PE文件标准PE头(用于判断PE文件是exe还是dll,得到节的总量)
typedef struct _IMAGE_FILE_HEADER { WORD Machine; //运行平台 WORD NumberOfSections; //PE中节的数量 DWORD TimeDateStamp; <span style="white-space:pre"> </span> //文件创建日期和时间 DWORD PointerToSymbolTable;<span style="white-space:pre"> </span> //指向符号表(用于调试) DWORD NumberOfSymbols; <span style="white-space:pre"> </span> //符号表中的符号数量(用于调试) WORD SizeOfOptionalHeader; <span style="white-space:pre"> </span> //扩展头结构长度 WORD Characteristics; //文件属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
(2)PE扩展头(虽然是扩展头,但更像真正的PE头,非常重要,包含各种属性)
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; //魔幻字 0x10b 32位 0x20b 64位 BYTE MajorLinkerVersion; <span style="white-space:pre"> </span> //链接器版本号 BYTE MinorLinkerVersion; <span style="white-space:pre"> </span> //链接器版本号 DWORD SizeOfCode; <span style="white-space:pre"> </span> //所有含代码节的总大小 DWORD SizeOfInitializedData; <span style="white-space:pre"> </span> //初始化的数据长度。 DWORD SizeOfUninitializedData; <span style="white-space:pre"> </span> //未初始化的数据长度。 DWORD AddressOfEntryPoint; <span style="white-space:pre"> </span> //程序入口的RVA DWORD BaseOfCode; <span style="white-space:pre"> </span> //代码段起始地址的RVA DWORD BaseOfData; <span style="white-space:pre"> </span> //数据段起始地址的RVA // // NT additional fields. // DWORD ImageBase; //映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。 DWORD SectionAlignment; //内存中节对齐粒度,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。 DWORD FileAlignment; //文件中节对齐粒度,SectionAlignment必须大于或等于FileAlignment。 WORD MajorOperatingSystemVersion; //所需操作系统的版本号 WORD MinorOperatingSystemVersion; // WORD MajorImageVersion; //映象的版本号,这个是开发者自己指定的,由连接器填写。 WORD MinorImageVersion; // WORD MajorSubsystemVersion; //所需子系统版本号 WORD MinorSubsystemVersion; // DWORD Win32VersionValue; //保留,必须为0。 DWORD SizeOfImage; //映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。 DWORD SizeOfHeaders; //所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。 DWORD CheckSum; //映象文件的校验和。 WORD Subsystem; //运行该PE文件所需的子系统 WORD DllCharacteristics; //DLL的文件属性,只对DLL文件有效 DWORD SizeOfStackReserve; //运行时为每个线程栈保留内存的大小。 DWORD SizeOfStackCommit; //运行时为每个线程栈保留内存的大小。 DWORD SizeOfHeapReserve; //运行时为进程堆保留内存大小。 DWORD SizeOfHeapCommit; //运行时进程堆初始占用内存大小。 DWORD LoaderFlags; //保留,必须为0。 DWORD NumberOfRvaAndSizes; //数据目录的项数 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录,这是一个数组,定义了PE文件中所有出现的不同类型的数据目录信息 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
2.3 数据目录项(节头)
注:总的数据目录一共由16个相同的_IMAGE_DATA_DIRECTORY组成。
2.3.1 结构体
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //数据的起始RVA DWORD Size; //数据块的大小 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
2.3.2 节表项的数据结构
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8个字节的节名 union { // DWORD PhysicalAddress; //节区大小 DWORD VirtualSize; } Misc; DWORD VirtualAddress; //节区RVA地址 DWORD SizeOfRawData; //在文件中对齐后的地址 DWORD PointerToRawData; //在文件中的偏移 DWORD PointerToRelocations; //在OBJ文件中使用 DWORD PointerToLinenumbers; //行号表的位置(调试用) WORD NumberOfRelocations; //OBJ文件中使用 WORD NumberOfLinenumbers; //行号表中行号的数量 DWORD Characteristics; //节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;