一个PE程序总是以一个64字节的DOS header结构开头,目的就是为了如果程序在DOS中运行,DOS会识别它为正确的EXE并进而运行DOS stub,它的作用就是输出字符串”This program cannot run in DOS mode.”然后退出。
下面就是DOS Header在C语言中的结构表示(来自MinGW的winnt.h)
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // Magic DOS 签名 MZ (4Dh 5Ah)
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标识符
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留字
LONG e_lfanew; // PE header的起始偏移量
} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;
首先声明, WORD表示unsigned short int(2bytes), 而LONG和后面会出现的DWORD表示unsigned int(4 bytes),其定义符合C99标准。
我们只关心其中两个成员,e_magic是Magic Number,它总是等于0x4d5a,也就是MZ这两个字符,这是为了几纪念Mark Zbikowsky,MS-DOS设计者之一。
e_lfanew指向的是PE header,这才是PE文件真正的开始,Win32EXE加载器会读取其中的地址并找到PE header,DOS stub因此被跳过。我们可以来实践一下下,用WinHEX打开我们的范例程序testPE.exe。
PICTURE MISSING
可以看到前两个byte,确实是MZ表示一个合法的EXE文件,图中的前四行(0h~3Fh)共64字节即为IMAGE_DOS_HEADER结构,e_lfanew是最后四个字节,从图中我们可以读出,位于3Ch~3Fh的依次是D8,00,00,00,因为机器中整型存放遵循高位在高地址处的原则,所以实际的值是00,00,00,D8,我们顺势找到D8h,马上就发现了PE两个字,从这里开始便是真正的PE header。那么位于40h到D7h 的东西从PE结构图就可以看出是DOS stub,其实就是一段汇编代码,我们就不管了。