基本概念
PE文件的全称是"Portable Executable File Format",也就是可移植的执行体,目前是Windows平台上的主流可执行文件格式。
PE文件使用的是一个平面地址空间,所有的代码和数据都合并在了一起,组成了一个完整的文件结构。文件内容被分成了不同的部分区段,区段中包含文件基本信息、数据、代码,各个区段按页大小对齐,每个区段都有自己的属性,如是否可读、可写和可执行。
PE文件结构
PE文件的整体结构如下:主要分为头部和区段(也称节表)两个部分,头部又包括Dos头、NT头(PE签名、PE文件头、PE可选头)、区段表,区段包括代码段、数据段等。
接下来,我们就分别介绍各个结构的内容。
Dos头
Dos头的结构内容如下:
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; //PE文件标志 MZ
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; //NT头的偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
在Dos头中,我们只需要关注两个成员,即第一成员e_magic和最后一成员e_lfanew。e_magic是PE文件的前两个字节,也是PE文件的标志,值为4D 5A,即MZ。e_lfanew成员是NT头结构相对于Dos头的偏移值,即文件中的位置。
使用WinHex查看一个PE文件的结构,可以看到,文件起始两个字节的内容为MZ,Dos头的最后一成员e_lfanew的值为00000118h,即NT头所在的位置为0x118h处。
查看文件的0x118位置处,可以查看到NT头起始的PE标志,这就是NT头开始的位置。
NT头
NT头在Dos头后面,主要包含PE标志、文件头和可选头三个部分,其中文件头和可选头非常重要,它们包含着PE文件的大部分重要信息。
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
(1)PE标志
正如上面我们看到的那样,在Dos头偏移e_lfanew大小的位置处,就是NT头,前四个字节大小就是50 45 00 00,也就是PE。这个值同样可以作为我们检查PE文件合法性的标志。
(2)文件头
文件头的结构如下:
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:目标文件运行的CPU类型,PE文件可以在多种类型的机器上使用,不同类型机器的指令对应的机器码不同。这里Machine的值可以作为我们判断32位PE或64位PE的标准。
②NumberOfSections:区段的数量,区段中一般包含.data,.text,.idata,.tls以及自定义段。
③SizeOfOptionalHeader:文件头后面的可选头的大小。
④Characteristics:文件属性信息,一般可以通过该值判断PE文件的类型(exe、Dll、sys)。
(3)可选头
可选头的结构如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; //PE32或PE64
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //代码段大小
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; //程序入口点
DWORD BaseOfCode; //代码段基址
DWORD BaseOfData; //数据段基址
DWORD ImageBase; //映像加载到内存后的基址
DWORD SectionAlignment; //内存对齐粒度
DWORD FileAlignment; //文件对齐粒度
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //映像大小
DWORD SizeOfHeaders; //头部大小
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
①Magic:通常用来判断PE文件的类型,PE32为0x10B,PE64为0x20B。
②SizeOfCode:代码段大小。
③AddressOfEntry:程序的入口点。Dll文件的入口点为DllMain(),一般在进程创建和退出或线程的创建和销毁时才会调用;进程的入口点并不直接指向main()或wmain()函数,而是指向运行时的库代码,再由库代码中调用main和wmain函数。
④ImageBase:PE文件在被加载到内存时,预计要加载到的内存基址。文件中的所有偏移地址都是以ImageBase为基础进行偏移的。
⑤SectionAlignment:PE文件加载到内存中后,各个区段的对齐粒度,这个值一般为0x1000h。
⑥FileAlignment:PE文件未加载时,文件中区段的对齐粒度,一般为0x200h。
⑦DataDirectory:数据目录,这是一个数组,里面保存着16个IMAGE_DATA_DIRECTORY结构体,每个结构体用来维护一个包含重要信息的表,成员包含一个表的起始地址和表的大小。因为数据目录中有16个成员,也就维护了16张包含重要信息的表。每个IMAGE_DATA_DIRECTORY的结构如下所示:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //起始地址
DWORD Size; //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
区段表
前面我们知道,PE文件的数据和代码等都是存储在区段中,那么如何获得关于这些区段的信息呢,那么就需要用到我们的区段表了。每个区段都有自己的区段名,区段起始地址,区段大小,区段属性等信息,这些信息都保存在_IMAGE_SECTION_HERADER结构中,一般区段的数量都是由文件头中的NumberOfSections进行指定,由多少个区段就有多少个_IMAGE_SECTION_HERADER结构体。下面是区段表的结构:
typedef struct _IMAGE_SECTION_HEADER
{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //区段名
union{
DWORD PhysicalAddress;
DWORD VirtualSize;
}
Misc;
DWORD VirtualAddress; //区段的内存偏移地址
DWORD SizeOfRawData; //区段的大小
DWORD PointerToRawData; //区段的文件偏移地址
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //区段的属性
}
总结
以上就是PE文件头部的基本信息了,主要包括Dos头,NT头和区段表等结构。下面是我整理的PE文件头部结构图,仅供参考。