PE,即移植的执行体。在Windows平台下,所有的可执行文件(包括EXE文件、DLL文件、SYS文件、COM文件)均使用PE文件结构。使用PE文件结构的可执行文件也成为PE文件。Windows系统下的可执行文件中包含着各种数据,包括代码、数据、资源等。虽然windows系统下的可执行文件包含如此众多的类型数据,但其存放都是有序的、结构化的、完全依赖于PE文件结构对各种数据的管理。
1.MZ头部是真正的DOS头部,其开始的两个字节为"MZ",该部分用于程序在DOS系统下的加载,它的结构被定义为IMAGE_DOS_HEADER,DOS头是为了该可执行程序可以兼容DOS系统。通常情况下,Win32的PE程序不能在DOS下运行,因此保留了这样一个简单的DOS程序用于提醒:不能运行于DOS程序下。
(1)struct IMAGE_DOS_HEADER{
WORD e_magic //这两个字节保存"MZ"
...........
........... //中间的成员不常用省略
LONG e_lfanew //这是最后一个成员 保存PE头部的位置
2.PE头保存着Windows系统加载可执行文件的重要信息。PE头部有IMAGE_NT_HEADERS(包含PE标识符,文件头IMAGE_FILE_HEADER、可选头IMAGE_OPTIONAL_HEADER。)定义。PE头部在PE文件中的位置不是固定不变的,PE头部的位置由DOS头部的某个字段给出。
(1)IMAGE_NT_HEADERS包含PE标识符,文件头IMAGE_FILE_HEADER、可选头IMAGE_OPTIONAL_HEADER。
struct IMAGE_HEADER32{
DWORD Signatyre;
IMAGE_FILE_HEADER;
IMAGE_OPTIONAL_HEADER
}
其中的DWORD型就是PE标识符,该值非常重要,如果要简单地判断一个文件是否是一个PE文件,实现就要判断DOS头部的开始字节是否是“MZ”。如果是“MZ”头部,则通过DOS头部找到PE头部,接着判断PE头部的前四个字节是否为“PE\0\0”,如果是的话就是一个有效的PE文件。
(2)IMAGE_FILE_HEADER的起始位置取决于PE头部的起始位置,除了IMAGE_DOS_HEADER的位置除外,其他头部的位置都依赖于PE头部的起始位置IMAGE_FILE_HEADER结构包含PE文件的一些基础信息。
struct IMAGE_FILE_HEADER{
WORD Machine; //0x014c 支持Intel类型的CPU
WORD NUmberOfSections; //PE文件的节区有几个 举例是3个
DWORD TimeDataStamp //文件时何时被创建的
DWORD PointerToSymbols
DWORD NumberOfSymbols //这两个字段几乎不被使用
WORD SizeofOptionHeader //指定IMAGE_OPTIONAL_HEADER的大小,大小时可变的
WORD Charateristics //指定文件的类型
}
Charateristics:
0x0001:文件中不存在重定位信息
0x0002:文件可执行
0x0004:行号信息已从文件中删除
0x0008:符号信息已经从文件中删除
0x2000:DLL文件
0x1000:系统文件
0x0100:目标平台为32位
加入其值为:0x010F,则0x010F=0x0001+0x0002+0x0004+0x0008+0x0100,即该文件不存在定位信息、可执行、行号和符号信息已经从文件中删除、目标平台为32位
(3)IMAGE_OPTIONAL_HEADER:虽然称作可选头,其实是必须存在的头。可选头紧挨着文件头(MAGE_FILE_HEADER),大小由文件头中的参数SizeofOptionHeader给出。不能直接sizeof(IMAGE_OPTIONAL_HEADER)
所以可选头的起始位置=文件头结束位置+1;
所以可选头的结束位置=可选头的起始位置+SizeofOptionHeader-1;
可选头是对文件头的一个补充,文件头主要描述文件的相关信息,可选头主要管理PE文件被操作系统装载时所需要的信息。成员非常多。
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase; //文件被加载在内存的首选地址。DLL默认值 0x10000000. EXE默认值 0x00400000.
DWORD SectionAlignment; //节表被装入内存的对齐值 0X1000 即4kb
DWORD FileAlignment; //节表在磁盘文件中的对齐值 有可能0x1000(加载速度快) 或者 0x200(节省空间)
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //可执行文件 装入内存后的总大小
DWORD SizeOfHeaders; //整个PE头部的大小 DOS头 PE头 节表的总大小
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; //正下边的数组大小为16
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录 导入表、导出表、资源、重定位等数据的信息在此
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
3.节表:程序的组织按照属性的不同被保存在不同的节中,在PE头部之后就是一个数据结构的字表。描述字表的结构体是IMAGE_SECTION_HEADER,如果PE文件中由N个节,那么节表就是由N个IMAGE_SECTION_HEADER组成的数组。节表中存储了各个节的属性、文件位置、内存位置等相关信息。
(1)IMAGE_SECTION_HEADER:节表中的每一个IMAGE_SECTION_HEADER都存放着可执行文件被映射到内存中所在的位置信息,节的个数由IMAGE_FILE_HEADER给出。
struct IMAGE_SECTION_HEADER{
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //用来保存节表名称 节名通常以"."开头,例如第一节总是:".txt"
union {
DWORD PhySicalAdress;
DWORD VirtualSize; //实际的节表项大小
}Misc;
DWORD VirtualAddress; //该节表项载入内存后的相对虚拟地址,这个地址是按照内存对齐的(0x1000,4KB)
DWORD SizeofRawData; //该节表项在磁盘中的大小、通常是对齐后的
DWORD PointerToRawData; //该节表项在磁盘中的偏移地址
..................
DWORD Characterristics;
Characterristics成员和IMAGE_FILE_HEADER中的用法是一样的:
0x00000020: 该节区含有代码
0x10000000: 该节区可共享
0x20000000: 该节区为可执行
0x40000000: 该节区可读
0x80000000: 该节区可写
将一个exe文件用C32Asm以16进制模式打开后:
.txt的位置是(节表起始处):0x00000210
节表结束的位置在 B 处 : 0X00000327
所以整个节表的大小:0X00000327-0x00000210=0x00000117
而一个IMAGE_SECTION_HEADER的大小是40字节:转换为16进制是0x00000028
0x00000117/0x00000028=6;
所以共有6个节表,由图看分别是:.txt .rdata .data .idata .rsrc .reloc
4.节表数据:PE文件的真正程序部分就保存在节数据部分。在PE结构中,有几个节表就有几个节表数据,根据节表的属性、地址等信息。程序的数据就分布在节表指定的位置中。