首先,我们来看一下PE头的结构体,如下:
typedef struct _IMAGE_NT_HEADERS {
0x00h DWORD Signature; //PE标识
0x04h IMAGE_FILE_HEADER FileHeader;//标准PE头
0x18h IMAGE_OPTIONAL_HEADER32 OptionalHeader;//扩展PE头
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
PE标识:这4个字节的值不能改也不能删,操作系统在启动一个程序时会检测这个值;
标准PE头结构体如下:
typedef struct _IMAGE_FILE_HEADER {
0x04h WORD Machine;//可执行文件的目标CPU类型
0x06h WORD NumberOfSections; //节数目(紧跟在PE头后面)
0x08h DWORD TimeDateStamp;//编译器编译时间(1970年以来GMT时间 //计算的秒数)
0x0Ch DWORD PointerToSymbolTable; //指向符号表(用于调试)
0x10h DWORD NumberOfSymbols; //符号表中的符号数量(用于调试)
0x14h WORD SizeOfOptionalHeader; //可扩展头大小(紧跟在文件头后,32位系 //统一般为00E0h,64为一般为00F0h)
0x16h WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
下面我们用Winhex随便打开一个程序来观察一下标准PE头部20个字节,如下图:
Machine属性:可执行文件的目标CPU类型,此值为0x0时表示能在任意平台下运行,为0x14C时表示可以在Intel 386及后续平台上运行,为0x8664时表示能在X64平台上运行,图中这个程序能在Intel 386及后续平台上运行;我们可以从Windows目录下的System32和SysWOW64目录下分别将64位和32位的notepad.exe用Winhex打开观察此属性,注意64位的notepad.exe放在System32目录下。如下图:
NumberOfSections属性:表示当前PE文件中有多少个节,即我们前几篇中提到的节数据。
TimeDateStamp属性:记录了编译器编译的时间,跟平时右键属性中的创建事件和修改时间无关。我们可以做以下实验,先看以下我们的32位notepad.exe文件的属性相关时间,然后修改这个属性,保存后重新查看属性,来验证其是否有关系。未修改前如下图:
修改属性:
修改后:
发现并没有变化。
SizeOfOptionalHeader属性:我们可以看第一张图的32位程序,
其值为0xE0;
Characteristics属性:用于记录当前可执行程序的属性,其值需要对照下图做解析:
如图是我们的notepad.exe文件该字段的值为:0x0102,转换为二进制则是:
0000 0001 0000 0010从右往左分别对应0~15位,则这个值得第1位和第8位为1,根据上图我们就知道这个文件是可执行且只能在32位平台上运行的。
接下来我们看一下扩展PE头结构体,如下:
typedef struct _IMAGE_OPTIONAL_HEADER {
0x18h WORD Magic;//标志字,值0x10B为32位程序,0x20B64位程序(重要)
//一下五个字段了解
0x1Ah BYTE MajorLinkerVersion; //连接程序主版本号
0x1Bh BYTE MinorLinkerVersion; //连接程序福版本号
0x1Ch DWORD SizeOfCode; //所有代码区块的总大小
0x20h DWORD SizeOfInitializedData;//所有已初始化数据的总大小
0x24h DWORD SizeOfUninitializedData;//所有未初始化数据的总大小
0x28h DWORD AddressOfEntryPoint;//程序执行入口RVA(重要)
0x2Ch DWORD BaseOfCode;//代码区块起始RVA
0x30h DWORD BaseOfData; //数据区块起始RVA
//以下属于NT结构增加的领域
0x34h DWORD ImageBase; //程序首选装载地址(重要)
0x38h DWORD SectionAlignment;//内存中区块的对齐值大小(重要)
0x3Ch DWORD FileAlignment; //文件中区块的对齐值大小(重要)
0x40h WORD MajorOperatingSystemVersion;//要求操作系统最低主版本号
0x42h WORD MinorOperatingSystemVersion;//要求操作系统的最低福版本号
0x44h WORD MajorImageVersion;//镜像主版本号
0x46h WORD MinorImageVersion;//镜像福版本号
0x48h WORD MajorSubsystemVersion;//最低子系统主版本号
0x4Ah WORD MinorSubsystemVersion;//最低子系统福版本号
0x4Ch DWORD Win32VersionValue;//保留,必须为0(没有被病毒感染时)
0x50h DWORD SizeOfImage;//映像装入内存的总尺寸(内存对齐的倍数)(重要)
0x54h DWORD SizeOfHeaders;//所有头加区块表的大小(文件对齐值得整数倍)(重要)
0x58h DWORD CheckSum;//映像校验和(重要)
0x5Ch WORD Subsystem; //子系统,驱动程序(1),图形界面(2),控制台(3)
0x5Eh WORD DllCharacteristics; //文件属性,不只针对DLL
0x60h DWORD SizeOfStackReserve;//初始化时栈的大小
0x64h DWORD SizeOfStackCommit; //初始化时实际提交栈的大小
0x68h DWORD SizeOfHeapReserve; //初始化时保留的堆大小
0x6Ch DWORD SizeOfHeapCommit; //初始化时实际提交栈的大小
0x70h DWORD LoaderFlags; //与调试有关,默认为0
0x74h DWORD NumberOfRvaAndSizes;//下边数据目录的项数(重要一般为0x10)
0x78h IMAGE_DATA_DIRECTORY (重要)DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;
Magic:上面我们用文件属性位来判断当前文件是32位还是64位并不是太准确,这个字段才是判断程序时否是32位程序最准确的字段,值0x10B为32位程序,0x20B为64位程序;
ImageBase:表示当前PE文件在执行时在虚拟地址的什么地方开始展开,实际加载进内存中的值不一定跟这个字段值一直。
AddressOfEntryPoint:这个字段比较有用,表示程序开始执行的时候从哪里开始执行,一定要配合ImageBase字段使用,这个值是相对于ImageBase开始偏移的。
SizeOfImage:展开后的PE文件在内存中占多大由这个值决定;
CheckSum:操作系统用来判断当前的可执行程序有没有被人给改掉,以两个字节为单位相加,结果在加上文件的长度。
DllCharacteristics:此字段拆分后查如下图所示的表;