PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。
PE文件主要分为以下几种:
种类 | 主扩展名 |
---|---|
可执行系列 | .exe .scr |
库系列 | .dll .ocx .cpl .drv |
驱动程序系列 | .sys .vxd |
对象文件系列 | .obj |
首先来看一下PE文件的基本结构
DOS头
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // 魔术数字
WORD e_cblp; // 文件最后页的字节数
WORD e_cp; // 文件页数
WORD e_crlc; // 重定义元素个数
WORD e_cparhdr; // 头部尺寸,以段落为单位
WORD e_minalloc; // 所需的最小附加段
WORD e_maxalloc; // 所需的最大附加段
WORD e_ss; // 初始的SS值(相对偏移量)
WORD e_sp; // 初始的SP值
WORD e_csum; // 校验和
WORD e_ip; // 初始的IP值
WORD e_cs; // 初始的CS值(相对偏移量)
WORD e_lfarlc; // 重分配表文件地址
WORD e_ovno; // 覆盖号
WORD e_res[4]; // 保留字
WORD e_oemid; // OEM标识符(相对e_oeminfo)
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留字
LONG e_lfanew; // 新exe头部的文件地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
//注:该结构中只有两个重要的成员变量,分别是e_magic和e_lfanew。
NT头包含标志位DWORD Signature和标准PE头以及可选PE头,DOS头与标准PE头字节固定,可选PE头字节不固定
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //4个字节的PE标志,通常设置成00004550h,其ASCII码为PE00,这个字段是PE文件头的开始
IMAGE_FILE_HEADER FileHeader; //文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader;//可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
标准PE头
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //程序运行的CPU型号,作用是区别这个exe是哪个CPU可以跑的.重要.
WORD NumberOfSections; //节的数量 (可以理解为汇编中区的个数)现在我们有两个,一个.rdata 一个.text
DWORD TimeDateStamp; //时间戳,文件的创建时间 --程序的编译时间,参考用,没有实际作用
//DWORD PointerToSymbolTable; //符号表地址 我们使用的PDB文件(里面有函数吗什么的)都存放在这个表中,不过微软是单独生成的PDB文件,所以这个字段没用,主要是给别人用
//DWORD NumberOfSymbols; //符号表大小
WORD SizeOfOptionalHeader; //可选头大小,这个字段很重要.因为要通过这个字段,才知道可选头是多大,而不懂PE的人求选项头都是用sizeof()求出来的.所以真正的选项头大小要靠这个字段
WORD Characteristics; //文件属性,描述文件信息的,每个位有不同含义,可执行文件值为10F,即第0 1 2 3 8位置1
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
可选PE头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; /*机器型号,判断是PE是32位:10B还是64位:20B*/
//BYTE MajorLinkerVersion; /*连接器版本号高版本*/
//BYTE MinorLinkerVersion; /*连接器版本号低版本,组合起来就是 5.12 其中5是高版本,C是低版本*/
DWORD SizeOfCode; //代码节的总大小(512为一个磁盘扇区),必须是FileAlignment整数倍 没用
DWORD SizeOfInitializedData; //初始化数据的节的总大小,也就是.data必须是FileAlignment整数倍 没用
DWORD SizeOfUninitializedData; //未初始化数据的节的大小,也就是 .data ?必须是FileAlignment整数倍 没用
DWORD AddressOfEntryPoint; //程序执行入口(OEP) RVA(相对偏移) 重要
DWORD BaseOfCode; //代码的节的起始RVA(相对偏移)也就是代码区的偏移,偏移+模块首地址定位代码区
DWORD BaseOfData; //数据结的起始偏移(RVA),同上 没用
DWORD ImageBase; //内存镜像基址,程序在内存中执行时的加载基址 内存中的程序入口地址一般都是OEP的值加上这个地址的值 最重要
DWORD SectionAlignment; /*内存中的节对齐*/
DWORD FileAlignment; /*文件中的节对齐*/
//WORD MajorOperatingSystemVersion; /*操作系统版本号高位*/
//WORD MinorOperatingSystemVersion; /*操作系统版本号低位*/
// WORD MajorImageVersion; /*PE版本号高位*/
//WORD MinorImageVersion; /*PE版本号低位*/
// WORD MajorSubsystemVersion; /*子系统版本号高位*/
//WORD MinorSubsystemVersion; /*子系统版本号低位*/
//DWORD Win32VersionValue; /*32位系统版本号值,注意只能修改为4 5 6表示操作系统支持nt4.0 以上,5的话依次类推*/
DWORD SizeOfImage; //整个程序在内存中占用的空间(PE映尺寸),可以比实际值大,必须是SectionAlignment整数倍 重要
DWORD SizeOfHeaders; //所有头(头的结构体大小)+节表的大小,严格按照FileAlignment对齐 重要
DWORD CheckSum; //校验和,对于驱动程序,可能会使用,用于判断文件是否被修改 有用
WORD Subsystem; /*文件的子系统 :重要*/
WORD DllCharacteristics; /*DLL文件属性,也可以成为特性,可能DLL文件可以当做驱动程序使用*/
DWORD SizeOfStackReserve; /*预留的栈的大小*/
DWORD SizeOfStackCommit; /*立即申请的栈的大小(分页为单位)*/
DWORD SizeOfHeapReserve; /*预留的堆空间大小*/
DWORD SizeOfHeapCommit; /*立即申请的堆的空间的大小*/
DWORD LoaderFlags; /*与调试有关*/
DWORD NumberOfRvaAndSizes; //目录项 下面的成员,数据目录结构的项目数量 有用
IMAGE_DATA_DIRECTORY DataDirectory[16];/*数据目录,默认16个,16是宏,这里方便直接写成16*/
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;