PE文件详解1——PE文件头部解析

参考书籍:《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;


原文地址:https://www.cnblogs.com/Toring/p/6628303.html