DOS头
DOS头部分的存在见证了PE的强大兼容性。为了保持与16位系统的兼容,在PE里依旧保留了16位系统下的标准可执行程序执行时所必需的文件头部(DOS MZ头)和指令代码(DOS Stub)
DOS头分为两部分,DOS MZ头和DOS Stub(即指令字节码)。大部分情况下,这些指令实现的功能都非常简单,根本不会涉及重定位信息。再往后的PE头和PE数据区可以看做是16位系统下的可执行文件的冗余数据。
DOS MZ头 (IMAGE_DOS_HEADER)
在Windows的PE格式中,DOS MZ头的定义如下:
用记事本打开任何一个镜像文件,其头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。
重要的就是第一个和最后一个,第一个标志了这个文件是哪种类型,最后一个就是PE头的偏移地址。
DOS Stub
由于DOS Stub的大小不固定,因此DOS头的大小也是不固定的。
NT头
PE头标识(Signature)
紧跟在DOS Stub后面的是PE头标识Signature。与大部分文件格式的头部结构一样,PE头部信息中有一个四字节的标识,该标识位于指针IMAGE_DOS_HEADER.e_lfanew指向的位置。其内容固定,对应于ASCⅡ码的字符串“PE\0\0”。
标准PE头(IMAGE_FILE_HEADER)
标准PE头IMAGE_FILE_HEADER紧跟在PE头标识后,即位于IMAGE_DOS_HEADER的e_lfanew值+4的位置。由此位置开始的20个字节为数据结构标准PE头IMAGE_FILE_HEADER的内容。该结构在微软的官方文档中被称为标准通用对象文件格式(Common Object File Format,COFF)头。它记录了PE文件的全局属性,如该PE文件运行的平台、PE文件类型(是EXE文件还是DLL文件)、文件中存在的节的总数等,其详细定义如下:
Machine
机器数,标识CPU的数字。
值 | 描述 |
0x0 | 适用于任何类型处理器 |
0x1d3 | Matsushita AM33处理器 |
0x8664 | x64处理器 |
0x1c0 | ARM小尾处理器 |
0xebc | EFI字节码处理器 |
0x14c | Intel 386或后继处理器及其兼容处理器 |
0x200 | Intel Itanium处理器 |
0x9041 | Mitsubishi M32R小尾处理器 |
0x266 | MIPS16处理器 |
0x366 | 带FPU的MIPS处理器 |
0x466 | 带FPU的MIPS16处理器 |
0x1f0 | PowerPC小尾处理器 |
0x1f1 | 带符点运算支持的PowerPC处理器 |
0x166 | MIPS小尾处理器 |
0x1a2 | Hitachi SH3处理器 |
0x1a3 | Hitachi SH3 DSP处理器 |
0x1a6 | Hitachi SH4处理器 |
0x1a6 | Hitachi SH5处理器 |
0x1c2 | Thumb处理器 |
0x169 | MIPS小尾WCE v2处理器 |
NumberOfSections
节数,节的数目,Windows加载器限制节的最大数目为96,如果将该值设置为0,则操作系统装载时会提示不是有效的Win32程序。如果想在PE中增加或删除节,必须变更此处的值。
TimeDateStamp
时间/日期标记,UTC时间1970年1月1日00:00起的总秒数的低32位,它指出文件何时被创建。低32位存放的值是自1970年1月1日00:00时开始到创建时间为止的总秒数。
PointerToSymbolTable
COFF符号表的文件偏移。如果不存在COFF符号表,此值为0。对于映像文件来说,此值为0,因为微软已经不赞成在PE中使用COFF调试信息了。
NumberOfSymbols
符号表中元素的数目。由于字符串表紧跟在符号表后,所以可以利用这个值来定位字符串表。对于映像文件来说,此值为0,主要用于调试。
SizeOfOptionalHeader
指定结构IMAGE_OPTIONAL_HEADER32的长度,默认情况下这个值等于00e0h(224);如果是64位PE文件,该结构的默认大小为00F0h(240)。
用户可以自己定义这个值的大小,不过需要注意两点:
- 更改完以后,需要自行将文件中IMAGE_OPITONAL_HEADER32的大小扩充为你指定的值(一般以0补足)。
- 扩充完以后,要维持文件中的对齐特性(比如在HelloWorld.exe中,此处增加了8个字节后,一定在后面相应地删除8个字节,以保证.text节起始位置处于0400h)。
Characteristics
文件属性标志字段,它的不同数据位定义了不同的文件属性。这是一个很重要的字段,不同的定义将影响系统对文件的装入方式。比如,当位13为1时,表示这是一个DLL文件,那么系统将使用调用DLL入口函数的方式执行文件入口函数;当位13为0时,表示这是一个普通的可执行文件,系统直接跳到入口处执行。对于普通的可执行PE文件来说,这个字段的值一般是010fh,而对于DLL文件来说,这个字段的值一般是210eh。
当第0位为1时,表明此文件不包含基址重定位信息,因此必须将其加载到文件头中指定的基地址字段位置。如果进程空间此处的基地址被占用,加载器会报错。在程序运行前如果发现文件中存在可重定位信息,链接器会执行移除可执行文件中的重定位信息的操作。
位置 | 描述(为1时) |
0 | 它表明此文件不包含基址重定位信息,因此必须被加载到其首选基地址上。如果基地址不可用,加载器会报错 |
1 | 它表明此镜像文件是合法的,可执行的 |
2 | 不存在行信息,保留,必须为0 |
3 | 不存在符号信息,保留,必须为0 |
4 | 调整工作集,保留,必须为0 |
5 | 应用程序可以处理大于2GB的地址 |
6 | 保留,必须为0 |
7 | 小尾方式, 保留,必须为0 |
8 | 机器类型基于32位体系结构 |
9 | 调试信息已经从此镜像文件中移除 |
10 | 如果此镜像文件在可移动介质上,完全加载它并把它复制到交换文件中(不可从可移动盘运行) |
11 | 如果此镜像文件在网络介质上,完全加载它并把它复制到交换文件中(不能从网络上运行) |
12 | 此镜像文件是系统文件,而不是用户程序(如驱动程序,不能直接运行) |
13 | 此镜像文件是动态链接库(DLL) |
14 | 此文件只能运行于单处理器机器上 |
15 | 大尾方式,保留,必须为0 |
当第1位为1时,表明此映像文件是合法的,可以运行。如果未设置此标志,表明出现了链接器错误。
当第7位为1时,表示文件为小尾方式,即内存中,最低有效位LSB位于最高有效位MSB的前面,与第15位的大尾方式(MSB在前,LSB在后)一样,都不赞成使用该标志,最好将其设置为0。
当第10位为1时,如果此映像文件在可移动存储介质上,那么加载器将完全加载它并把它复制到内存交换文件中。
当第11位为1时,如果此映像文件在网络上,那么加载器也将完全加载它并把它复制到内存交换文件中。
当第13位为1时,表明此映像文件是动态链接库(DLL)。这样的文件总被认为是可执行文件,尽管它们并不能直接运行。
可执行文件的标志位设置为010fh,即第0、1、2、3、8位分别被设置为1,表示该文件为可执行文件,不含重定位信息,不含符号和行号信息,文件只在32位平台运行。
扩展PE头(IMAGE_OPTIONAL_HEADER32)
偏移 | 大小 | 英文名 | 中文名 | 描述 |
0 | 2 | Magic | 魔数 | 这个无符号整数指出了镜像文件的状态。如果为010BH,则表示该文件为PE32;如果为0107h,则表示文件为ROM映像;如果为020BH,则表示该文件为PE32+,即64位下的PE文件 |
2 | 1 | MajorLinkerVersion | 链接器的主版本号 | 链接器的主版本号,对执行没有任何影响。 |
3 | 1 | MinorLinkerVersion | 链接器的次版本号 | 链接器的次版本号,对执行没有任何影响。 |
4 | 4 | SizeOfCode | 代码节大小 | 所有代码节的总和 ,一般放在“.text”节里。如果有多个代码节的话,它是所有代码节的和。必须是FileAlignment的整数倍,是在文件里的大小。 |
8 | 4 | SizeOfInitializedData | 已初始化数大小 | 所有包含已经初始化的数据的节的总大小 ,一般放在“.data”节里。如果有多个这样的节话,它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。 |
12 | 4 | SizeOfUninitializedData | 未初始化数大小 | 所有包含未初始化的数据的节的总大小,一般放在“.bss”节里。如果有多个这样的节话,它是所有这些节的和。必须是FileAlignment的整数倍,是在文件里的大小。 |
16 | 4 | AddressOfEntryPoint | 入口点 | 当可执行文件被加载进内存时其入口点RVA。对于一般程序镜像来说,它就是启动地址。为0则从ImageBase开始执行。对于dll文件是可选的。 |
20 | 4 | BaseOfCode | 代码基址 | 当镜像被加载进内存时代码节的开头RVA。必须是SectionAlignment的整数倍。 |
24 | 4 | BaseOfData | 数据基址 | 当镜像被加载进内存时数据节的开头RVA。(在64位文件中此处被并入紧随其后的ImageBase中。)必须是SectionAlignment的整数倍。 |
28/24 | 4 / 8 | ImageBase | 镜像基址 | 当加载进内存时镜像的第1个字节的首选地址。它必须是64K的倍数。DLL默认是10000000H。Windows CE 的EXE默认是00010000H。Windows 系列的EXE默认是00400000H。 |
32 | 4 | SectionAlignment | 内存对齐 | 当加载进内存时节的对齐值(以字节计)。它必须≥FileAlignment。默认是相应系统的页面大小。 |
36 | 4 | FileAlignment | 文件对齐 | 用来对齐镜像文件的节中的原始数据的对齐因子(以字节计)。它应该是界于512和64K之间的2的幂(包括这两个边界值)。默认是512。如果SectionAlignment小于相应系统的页面大小,那么FileAlignment必须与SectionAlignment相等。 |
40 | 2 | MajorOperatingSystemVersion | 主系统的主版本号 | 操作系统的版本号可以从“我的电脑”→“帮助”里面看到,Windows XP是5.1。5是主版本号,1是次版本号 |
42 | 2 | MinorOperatingSystemVersion | 主系统的次版本号 | |
44 | 2 | MajorImageVersion | 镜像的主版本号 | |
46 | 2 | MinorImageVersion | 镜像的次版本号 | |
48 | 2 | MajorSubsystemVersion | 子系统的主版本号 | |
50 | 2 | MinorSubsystemVersion | 子系统的次版本号 | |
52 | 2 | Win32VersionValue | 保留,必须为0 | 子系统版本的值,暂时保留未用,必须设置为0。比如将此处的值更改为696C6971h,程序运行将失败。 |
56 | 4 | SizeOfImage | 镜像大小 | 当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数。 |
60 | 4 | SizeOfHeaders | 头大小 | 所有头的总大小,向上舍入为FileAlignment的倍数。可以以此值作为PE文件第一节的文件偏移量。 |
64 | 4 | CheckSum | 校验和 | 镜像文件的校验和。计算校验和的算法被合并到了Imagehlp.DLL 中。以下程序在加载时被校验以确定其是否合法:所有的驱动程序、任何在引导时被加载的DLL以及加载进关键Windows进程中的DLL。 |
68 | 2 | Subsystem | 子系统类型 | 运行此镜像所需的子系统 |
70 | 2 | DllCharacteristics | DLL标识 | DLL文件属性。它是一个标志集,不是针对DLL文件的,而是针对所有PE文件的。 |
72 | 4 / 8 | SizeOfStackReserve | 堆栈保留大小 | 最大栈大小。CPU的堆栈。默认是1MB。 |
76/80 | 4 / 8 | SizeOfStackCommit | 堆栈提交大小 | 初始提交的堆栈大小。默认是4KB。 |
80/88 | 4 / 8 | SizeOfHeapReserve | 堆保留大小 | 最大堆大小。编译器分配的。默认是1MB。这个堆的句柄可以通过调用GetProcessHeap函数获得。 |
84/96 | 4 / 8 | SizeOfHeapCommit | 堆栈交大小 | 初始提交的局部堆空间大小。默认是4KB。 |
88/104 | 4 | LoaderFlags | 保留,必须为0 | 加载标志 |
92/108 | 4 | NumberOfRvaAndSizes | 目录项数目 | 数据目录项的个数。由于以前发行的Windows NT的原因,它只能为16。 |
数据目录(DataDirectory)
偏移(PE32/PE32+) | 大小 | 英文名 | 描述 |
96/112 | 8 | Export Table | 导出表的地址和大小。 |
104/120 | 8 | Import Table | 导入目录表的地址和大小。 |
112/128 | 8 | Resource Table | 资源表的地址和大小。 |
120/136 | 8 | Exception Table | 异常表的地址和大小。 |
128/144 | 8 | Certificate Table | 属性证书表的地址和大小。 |
136/152 | 8 | Base Relocation Table | 基址重定位表的地址和大小。 |
144/160 | 8 | Debug | 调试数据起始地址和大小。 |
152/168 | 8 | Architecture | 保留,必须为0 |
160/176 | 8 | Global Ptr | 将被存储在全局指针寄存器中的一个值的RVA。这个结构的Size域必须为0 |
168/184 | 8 | TLS Table | 线程局部存储(TLS)表的地址和大小。 |
176/192 | 8 | Load Config Table | 加载配置表的地址和大小。 |
184/200 | 8 | Bound Import | 绑定导入查找表的地址和大小。 |
192/208 | 8 | IAT | 导入地址表的地址和大小。 |
200/216 | 8 | Delay Import Descriptor | 延迟导入描述符的地址和大小。 |
208/224 | 8 | CLR Runtime Header | CLR运行时头部的地址和大小。(已废除) |
216/232 | 8 | 保留,必须为0 | 保留,必须为0 |
Comments | 1 条评论
Fine way of explaining, and fastidious paragraph to
take facts concerning my presentation subject matter, which i am going to convey in university.