参考书籍:《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\0\0”
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; //文件创建日期和时间
DWORD PointerToSymbolTable; //指向符号表(用于调试)
DWORD NumberOfSymbols; //符号表中的符号数量(用于调试)
WORD SizeOfOptionalHeader; //扩展头结构长度
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; //链接器版本号
BYTE MinorLinkerVersion; //链接器版本号
DWORD SizeOfCode; //所有含代码节的总大小
DWORD SizeOfInitializedData; //初始化的数据长度。
DWORD SizeOfUninitializedData; //未初始化的数据长度。
DWORD AddressOfEntryPoint; //程序入口的RVA
DWORD BaseOfCode; //代码段起始地址的RVA
DWORD BaseOfData; //数据段起始地址的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;