简单来说:
-
DOS 头是历史的产物,它的唯一使命是引导系统找到 PE 头。
-
PE 头是文件的总说明书和目录,它告诉操作系统:
-
这个文件是给什么 CPU 的(文件头)。
-
它有多少个部分(文件头)。
-
从哪里开始执行(可选头)。
-
它想被加载到内存的哪个位置(可选头)。
-
它依赖哪些外部函数(可选头中的导入表)。
-
它的图标、界面等资源在哪里(可选头中的资源表)。
-
理解这两个头是分析、调试、逆向或开发 Windows 程序的基础。
1. DOS头 (DOS Stub / DOS Header)
这是PE文件最起始的部分,主要是为了向后兼容古老的MS-DOS操作系统。
-
历史原因:当你在MS-DOS系统中运行一个Windows程序时,DOS需要能识别它,并显示一句提示信息(如“此程序不能在DOS模式下运行”),而不是直接崩溃。DOS头就是为此而设。
-
结构:它是一个标准的
IMAGE_DOS_HEADER结构体,长度固定为64字节。 -
关键字段:
-
e_magic(WORD): DOS签名,固定为0x5A4D,即 ASCII 字符MZ(Mark Zbikowski,早期DOS开发者)。这是识别文件是否为有效PE文件的第一个标志。 -
e_lfanew(DWORD): 这是整个DOS头中最关键的字段。它存储了一个偏移量(从文件开头算起),指向真正的PE文件头(即IMAGE_NT_HEADERS)的位置。通过这个值,程序可以跳过DOS Stub,直接定位到PE核心内容。
-
-
DOS Stub:紧跟在
IMAGE_DOS_HEADER后面的是一小段可执行的DOS代码和一段提示字符串。在现代操作系统中,这段代码不会被执行(除非你在DOS下运行)。它的存在就是为了兼容性。
作用总结:DOS头是一个“兼容性桥梁”,e_lfanew是这座桥上指向PE核心区域的路标。
2. PE头 (PE Header / IMAGE_NT_HEADERS)
这是PE文件的核心元数据区,包含了让Windows加载器正确加载和执行该文件的所有关键信息。它位于由e_lfanew指向的位置。
PE头本身是一个大结构体IMAGE_NT_HEADERS,它又包含三个部分:
a) PE签名 (Signature)
-
Signature(DWORD): PE文件标识,固定为0x00004550,即 ASCII 字符PE\0\0。这是识别PE文件的第二个、也是最重要的标志。加载器会检查此处是否为“PE”。
b) 文件头 (File Header - IMAGE_FILE_HEADER)
这是一个固定长度的结构体,描述了文件的基本属性。
-
Machine(WORD): 目标CPU架构。例如,0x014C代表 Intel i386(32位),0x8664代表 x64,0xAA64代表 ARM64。 -
NumberOfSections(WORD): 区段的数量。指明了后面紧跟着的“区段表”中有多少个条目(例如.text,.data,.rdata等)。这是解析文件的关键。 -
TimeDateStamp(DWORD): 文件创建的时间戳。 -
PointerToSymbolTable,NumberOfSymbols: 调试信息相关,现代工具链已较少使用。 -
SizeOfOptionalHeader(WORD): 可选头的大小。非常重要,因为它指明了接下来的IMAGE_OPTIONAL_HEADER结构有多大。 -
Characteristics(WORD): 文件属性标志位。例如,是DLL还是EXE,是否是可执行的,是否支持32位机等。
c) 可选头 (Optional Header - IMAGE_OPTIONAL_HEADER32/64)
虽然叫“可选”,但对于可执行文件(EXE/DLL)来说它是必须存在的。它包含了加载和运行程序所需的大部分关键信息。其结构分为32位和64位版本。
-
关键字段:
-
Magic(WORD): 标识可选头类型。0x10B代表 PE32(32位),0x20B代表 PE32+(64位)。 -
AddressOfEntryPoint(DWORD): 程序入口点(RVA)。这是加载器完成所有准备工作后,开始执行代码的地址(相对于加载基址的偏移)。 -
ImageBase(DWORD/DWORD64): 优先加载基址。EXE/DLL希望被加载到的内存地址。对于DLL,如果此地址被占用,则需进行“重定位”。 -
SectionAlignment,FileAlignment: 内存对齐和文件对齐粒度。例如,内存对齐通常为0x1000(4KB),文件对齐可能为0x200(512字节)。 -
SizeOfImage(DWORD): 加载到内存后整个映像的大小。是所有区段按内存对齐后的大小总和。 -
SizeOfHeaders(DWORD): 所有头(DOS头+PE头+区段表)的总大小,通常也是文件中第一个区段起始位置的偏移量。 -
Subsystem(WORD): 子系统类型。例如,1为Native驱动,2为GUI图形界面,3为CUI控制台。 -
NumberOfRvaAndSizes,DataDirectory(数组): 数据目录表。这是极其重要的部分。它是一个有16个元素的数组,每个元素是一个IMAGE_DATA_DIRECTORY结构(包含虚拟地址和大小),指向各种关键数据结构的RVA。例如:-
导出表
-
导入表
-
资源表
-
重定位表
-
基址重定位表
-
TLS表
-
等...
-
-
总结与关系图
plaintext
文件起始 (0x0) ├── **IMAGE_DOS_HEADER (64字节)** │ ├── e_magic: "MZ" (0x5A4D) // DOS签名 │ └── e_lfanew: 0x000000E0 // **指向PE头的偏移** │ ├── **DOS Stub** (一小段DOS代码和提示信息) │ └── **IMAGE_NT_HEADERS** (起始于 e_lfanew 指向的位置) ├── Signature: "PE\0\0" (0x00004550) // **PE签名** ├── **IMAGE_FILE_HEADER** (文件头) │ ├── Machine: 0x014C │ ├── NumberOfSections: 0x0005 │ └── SizeOfOptionalHeader: 0x00E0 │ └── **IMAGE_OPTIONAL_HEADER** (可选头 - 核心) ├── Magic: 0x010B ├── AddressOfEntryPoint: 0x00001234 ├── ImageBase: 0x00400000 ├── ... └── **DataDirectory[16]** // **数据目录,定位各种表** ├── [0] 导出表 RVA & Size ├── [1] 导入表 RVA & Size <- **加载器找DLL函数的关键** └── ... 紧跟PE头之后的就是 **区段表 (Section Table)**,然后是实际的 **区段数据**(如 .text, .data)。
把PE文件想象成一栋大楼(后面有简化版)
1. DOS头 - 大楼的老式门牌(为了照顾老居民)
想象这栋楼建于80年代,当时街道编号还是旧系统。现在楼门口还保留着:
-
门口有个牌子写着"MZ"
-
就像老式门牌号,告诉老DOS系统:“这里确实有栋楼”
-
-
牌子上最关键的一句话: “真正的地址簿在楼内第188步的位置”
-
这就是
e_lfanew!它是个指针,告诉你怎么找到大楼真正的重要信息
-
-
门口还有个老式信箱
-
里面是DOS Stub,一段古老的文字:“本大楼需要Windows系统才能进入”
-
纯粹为了历史兼容性,现在基本用不上
-
一句话记DOS头:它就是个“指路牌”,主要作用是告诉你PE头在哪里。
2. PE头 - 大楼的总控制室
跟着DOS头指的路,你来到了大楼的总控室。这里有三样核心文件:
a) 身份认证(PE签名)
-
墙上挂着金色大字:“PE”
-
这是大楼的身份证明:“没错,我就是PE大楼”
b) 大楼基本信息卡(文件头)
像大楼的房产证:
text
[ 大楼基本信息 ] 建筑类型:32位/64位 ✓ 楼层数(区段数):5层 ✓ 大楼设计图纸大小:224页 ✓ 特点:可执行、支持调试、非DLL ✓
关键数字:NumberOfSections(楼层数)告诉你有多少层(区段),决定了后面要看几张楼层图纸。
c) 大楼使用手册(可选头)
这是最重要的文件!告诉你怎么使用这栋楼:
text
=== 大楼核心信息 === 1. 大楼首层入口:0x1234号房间 (AddressOfEntryPoint - 程序开始执行的地方) 2. 理想地基地址:0x400000 (ImageBase - 大楼希望建在内存的这个位置) 3. 大楼内存占地面积:0x28000平方米 (SizeOfImage - 加载到内存后占多大) 4. 各楼层对齐方式: 文件里:每512字节对齐 内存中:每4096字节对齐 5. 大楼类型:带图形界面 (Subsystem - GUI还是控制台)
d) 大楼功能索引表(数据目录)
这是手册中最有用的部分!像大楼的“各部门电话簿”:
text
[ 快速联系表 ] 1. 出口部(导出表):负责对外服务 2. 进口部(导入表):需要哪些外部DLL帮助 3. 资源部(资源表):图标、图片、文字等 4. 搬家部(重定位表):如果地基被占,如何调整 5. 安全部(安全证书):数字签名 ... (共16个重要部门)
这个索引表是加载器的导航地图!比如要找DLL函数(进口部),就查第2项。
简化版例子
整个PE文件 = 一栋智能大楼
-
DOS头 = 门口的老式信箱和指路牌
-
主要是为了照顾还能用DOS的老人
-
关键信息:“去大楼总控室怎么走”(
e_lfanew)
-
-
PE头 = 大楼总控室
-
门口挂牌:“PE大楼”(PE签名)
-
房产证:几层楼、什么结构(文件头)
-
使用手册:入口在哪、占多大地方(可选头)
-
电话簿:各部门联系表(数据目录)
-
-
之后的内容 = 大楼各楼层
-
.text层 = 代码层(员工办公区)
-
.data层 = 数据层(仓库)
-
.rsrc层 = 资源层(装修材料库)
-
加载器如何看这栋楼
Windows加载器就像大楼管理员:
-
走到门口:看到“MZ”牌子,知道这是栋楼
-
看指路牌:按
e_lfanew找到总控室 -
进总控室:
-
看到“PE”招牌,确认是PE大楼
-
翻房产证:哦,有5层楼
-
看使用手册:入口在0x1234,希望建在0x400000
-
查电话簿:需要找哪些外部DLL(进口部)
-
-
开始搬楼:按手册把各楼层放进内存合适位置
-
按门铃:走到入口地址0x1234,敲门:“程序,开始运行吧!”
1万+

被折叠的 条评论
为什么被折叠?



