1 基本概念
下表描述了贯穿于本文中的一些概念:
名称 描述 是“虚拟地址”而不是“物理地址”。为什么不是“物理地址”呢?因为数据在内存的位置经常在变,这样可以节省内存地址 开支、避开错误的内存位置等的优势。同时用户并不需要知道具体的“真实地址”,因为系统自己会为程序准备好内存空间的(只要内存足够大) 包含以EXE文件为代表的“可执行文件”、以DLL文件为代表的“动态链接库”。为什么用“镜像”?这是因为他们常常镜像文件 被直接“复制”到内存,有“镜像”的某种意思。看来西方人挺有想象力的哦^0^ RVA 节 VA 英文全称Relatively Virtual Address。偏移(又称“相对虚拟地址”)。相对镜像基址的偏移。 节是PE文件中代码或数据的基本单元。原则上讲,节只分为“代码节”和“数据节”。 英文全称Virtual Address。基址 2 概览 x86都是32位的,IA-64都是64位的。64位Windows需要做的只是修改PE格式的少数几个域。这种新的格式被称为PE32+。它并没有增加任何新域,仅从PE格式中删除了一个域。其余的改变就是简单地把某些域从32位扩展到64位。在大部分情况下,你都能写出同时适用于32位和64位PE文件的代码。 EXE文件与DLL文件的区别完全是语义上的。它们使用的是相同的PE格式。惟一的不同在于一个位,这个位用来指示文件应该作为EXE还是DLL。甚至DLL文件的扩展名也完全也是人为的。你可以给DLL一个完全不同的扩展名,例如.OCX控件和控制面板小程序(.CPL)都是DLL。
图1 解释了Microsoft PE可执行文件格式:
PE文件总体上分为“头”和“节”。“头”是“节”的描述、简化、说明,“节”是“头”的具体化。
3 文件头
PE文件的头分为DOS头、NT头、节头。注意,这是本人的分法,在此之前并没有这种分法。这样分法会更加合理,更易理解。因为这三个部分正好构成SizeOfHeaders所指的范围,所以将它们合为“头”。这里的3个头与别的文章的头的定义会有所区别。 节头紧跟在NT头后面。
3.1 DOS头(PE文件签名的偏移地址就是大小)
用记事本打开任何一个镜像文件,其头2个字节必为字符串“MZ”,这是Mark Zbikowski的姓名缩写,他是最初的MS-DOS设计者之一。然后是一些在MS-DOS下的一些参数,这些参数是在MS-DOS下运行该程序时要用到的。在这些参数的末尾也就是文件的偏移0x3C(第60字节)处是是一个4字节的PE文件签名的偏移地址。该地址有一个专用名称叫做“E_lfanew”。这个签名是“PE00”(字母“P”和“E”后跟着两个空字节)。紧跟着E_lfanew的是一个MS-DOS程序。那是一个运行于MS-DOS下的合法应用程序。当可执行文件(一般指exe、com文件)运行于MS-DOS下时,这个程序显示“This program cannot be run in DOS mode(此程序不能在DOS模式下运行)”
这条消息。用户也可以自己更改该程序,有些还原软件就是这么干的。同时,有些程序既能运行于DOS又能运行于Windows下就是这个原因。Notepad.exe整个DOS头大小为224个字节,大部分不能在DOS下运行的Win32文件都是这个值。MS-DOS程序是可有可无的,如果你想使文件大小尽可能的小可以省掉MS-DOS程序,同时把前面的参数都清0。
3.2 NT头(244或260个字节)
紧跟着PE文件签名之后,是NT头。NT头分成3个部分,因为第2部分在32与64位系统里有区别,第3部分虽然也是头,但实际很不像“头”。 第1部分(20个字节)
偏移 0 2 4 8 16 18 大小 2 2 4 8 2 2 Machine 英文名 中文名 机器数 节数 时间/日期标记 标识CPU的数字。参考3.2.1节“机器类型”。 节的数目。Windows加载器限制节的最大数目为96。 描述 NumberOfSections TimeDateStamp 已经废除 SizeOfOptionalHeader FillCharacteristics UTC时间1970年1月1日00:00起的总秒数的低32位,它指出文件何时被创建。 第2部分+第3部分的总大小。这个大小在32位和64位文件中是不同的。对于32位文件可选头大小 文件特征值 位文件来说,它是240。 指示文件属性的标志。参考3.2.2节“特征”。 第2部分(96或112个字节)
偏移 大小 英文名 中文名 描述 这个无符号整数指出了镜像文件的状态。 0 2 Magic 魔数 0x10B表明这是一个32位镜像文件。 0x107表明这是一个ROM镜像。 0x20B表明这是一个64位镜像文件。 2 3 1 1 MajorLinkerVersion MinorLinkerVersion 链接器的主版本号 链接器的次版本号 链接器的主版本号。 链接器的次版本号。 一般放在“.text”节里。如果有多个代码节的话,4 4 SizeOfCode 代码节大小 它是所有代码节的和。必须是FileAlignment的整数倍,是在文件里的大小。 一般放在“.data”节里。如果有多个这样的节话,8 4 SizeOfInitializedData 已初始化数大小 它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。 一般放在“.bss”节里。如果有多个这样的节话,12 4 SizeOfUninitializedData 未初始化数大小 它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。 16 4 AddressOfEntryPoint 入口点 当可执行文件被加载进内存时其入口点RVA。对于一般程序镜像来说,它就是启动地址。为0则从ImageBase开始执行。对于dll文件是可选的。 当镜像被加载进内存时代码节的开头RVA。必20 4 BaseOfCode 代码基址 须是SectionAlignment的整数倍。 当镜像被加载进内存时数据节的开头RVA。(在24 4 BaseOfData 数据基址 64位文件中此处被并入紧随其后的ImageBase中。)必须是SectionAlignment的整数倍。 当加载进内存时镜像的第1个字节的首选地址。28/24 4/8 ImageBase 镜像基址 它必须是64K的倍数。DLL默认是10000000H。Windows CE 的EXE默认是00010000H。Windows 系列的EXE默认是00400000H。 当加载进内存时节的对齐值(以字节计)。它必32 4 SectionAlignment 内存对齐 须≥FileAlignment。默认是相应系统的页面大小。 用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)。它应该是界于512和64K之间的2的幂(包括这两个边界值)。默认是512。36 4 FileAlignment 文件对齐 如果SectionAlignment小于相应系统的页面大小,那么FileAlignment必须与SectionAlignment相等。 操作系统的版本号可以从“我的电脑”→“帮助”40 2 MajorOperatingSystemVersion 主系统的主版本号 里面看到,Windows XP是5.1。5是主版本号,1是次版本号 42 44 46 48 50 52 56 2 2 2 2 2 2 4 MinorOperatingSystemVersion MajorImageVersion MinorImageVersion MajorSubsystemVersion MinorSubsystemVersion Win32VersionValue SizeOfImage 主系统的次版本号 镜像的主版本号 镜像的次版本号 子系统的主版本号 子系统的次版本号 保留,必须为0 镜像大小 当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数。 所有头的总大小,向上舍入为FileAlignment的60 4 SizeOfHeaders 头大小 倍数。可以以此值作为PE文件第一节的文件偏移量。 镜像文件的校验和。计算校验和的算法被合并到了Imagehlp.DLL 中。以下程序在加载时被校验64 4 CheckSum 校验和 以确定其是否合法:所有的驱动程序、任何在引导时被加载的DLL以及加载进关键Windows进程中的DLL。 68 2 Subsystem 子系统类型 运行此镜像所需的子系统。参考后面的“Windows子系统”部分。 70 72 76/80 80/88 84/96 2 4/8 4/8 4/8 4/8 DllCharacteristics SizeOfStackReserve SizeOfStackCommit SizeOfHeapReserve SizeOfHeapCommit LoaderFlags NumberOfRvaAndSizes DLL标识 堆栈保留大小 堆栈提交大小 堆保留大小 堆栈交大小 保留,必须为0 目录项数目 参考后面的“DLL特征”部分。 最大栈大小。CPU的堆栈。默认是1MB。 初始提交的堆栈大小。默认是4KB。 最大堆大小。编译器分配的。默认是1MB。 初始提交的局部堆空间大小。默认是4KB。 数据目录项的个数。由于以前发行的Windows 92/108 4 NT的原因,它只能为16。 88/104 4 第3部分数据目录(128个字节)
偏移 大小 (PE32/PE32+) 96/112 104/120 112/128 120/136 128/144 136/152 144/160 152/168 160/176 168/184 176/192 184/200 192/208 200/216 208/224 216/232 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 Export Table Import Table Resource Table Exception Table Certificate Table Base Relocation Table Debug Architecture Global Ptr TLS Table Load Config Table Bound Import IAT 导出表的地址和大小。参考5.1节“.edata” 导入目录表的地址和大小。参考5.2.1节“.idata” 资源表的地址和大小。参考5.6节“.rsrc” 异常表的地址和大小。参考5.3节“.pdata” 属性证书表的地址和大小。参考6节“属性证书表” 基址重定位表的地址和大小。参考5.4节“.reloc” 调试数据起始地址和大小。 保留,必须为0 将被存储在全局指针寄存器中的一个值的RVA。这个结构的Size域必须为0 线程局部存储(TLS)表的地址和大小。 加载配置表的地址和大小。参考5.5节“加载配置结构” 绑定导入查找表的地址和大小。参考5.2.2节“导入查找表” 导入地址表的地址和大小。参考5.2.4节“导入地址表” 英文名 描述 Delay Import Descriptor 延迟导入描述符的地址和大小。 CLR Runtime Header CLR运行时头部的地址和大小。(已废除) 保留,必须为0 3.2.1 机器类型 Machine域可以取以下各值中的一个来指定CPU类型。镜像文件仅能运行于指定处理器或者能够模拟指定处理器的系统上。 值 0x0 描述 适用于任何类型处理器