- 公开视频 -> 链接点击跳转公开课程
- 博客首页 -> 链接点击跳转博客主页
目录
PE文件状态
一、PE 文件的基本概念
1. 什么是 PE 文件?
PE 是 Microsoft 提出的可移植可执行文件格式,用于描述程序在磁盘上的存储格式以及加载到内存后运行的布局。
-
文件扩展名:
.exe
、.dll
、.sys
。 -
用途:应用程序、动态链接库(DLL)、驱动程序等。
-
架构兼容性:支持 x86、x64、ARM 等多种架构。
2. PE 文件的历史
PE 文件格式基于 COFF(Common Object File Format,通用目标文件格式),并进行了扩展以适应 Windows 操作系统。
二、PE 文件的两种状态
PE 文件存在以下两种状态:
-
文件状态:PE 文件静态存储在磁盘中,描述文件的布局和内容。
-
定义:PE文件在磁盘上的存储布局,主要用于文件的存储和传输。
-
特点:
-
文件大小固定。
-
内容经过压缩或加密处理。
-
数据段和代码段是分离的。
-
-
-
内存状态:PE 文件被加载到内存后,描述程序运行所需的布局。
-
定义:PE文件被加载到内存后的运行时状态。
-
特点:
-
文件被操作系统加载到内存的特定位置。
-
各个节被映射到内存的特定区域。
-
已经完成重定位和导入表的解析。
-
-
三、PE 文件的文件状态结构
一个典型的 PE 文件由多个数据段组成,其结构如下(按从文件开头到结尾的顺序):
1. DOS 头(MS-DOS Header)
-
大小:64字节。
-
作用:
-
兼容早期的 DOS 程序。
-
包含 DOS 程序启动的入口点(通常显示"MZ"错误提示)。
-
为 PE 加载器提供 PE 头偏移地址。
-
-
关键字段:
-
e_magic
:固定值为MZ
,标识 DOS 文件头。 -
e_lfanew
:PE 头的偏移量。
-
2. PE 头(PE Header,又名 NT Header)
-
紧跟 DOS 头,是 PE 文件的核心。
-
包含文件的全局信息和加载信息。
PE 头的主要部分:
-
签名(Signature)
-
固定为
PE\0\0
。 -
表明这是一个有效的 PE 格式文件。
-
-
文件头(File Header)
-
包含 PE 文件的基本信息,如目标架构和头大小。
-
字段:
-
Machine
:指定处理器架构(如0x14C
表示 x86,0x8664
表示 x64)。 -
NumberOfSections
:文件的节数量。 -
TimeDateStamp
:文件的编译时间戳。
-
-
-
可选头(Optional Header)
-
包含更多的详细信息。
-
字段:
-
AddressOfEntryPoint
:入口点的虚拟地址(RVA)。 -
ImageBase
:加载到内存的基地址。 -
SectionAlignment
和FileAlignment
:内存与磁盘对齐。
-
-
3. 节表(Section Table)
节表位于 PE 头之后,描述文件中的各个部分(或节),比如代码节、数据节等。
-
特点:
-
每个节都有一个节头(Section Header)。
-
文件状态中的节可能被压缩或对齐,而在内存状态中会被解压和重组。
-
-
常见节名称:
-
.text
:代码节,包含程序的可执行代码。 -
.data
:数据节,存储已初始化的全局变量。 -
.rdata
:只读数据节,例如字符串和导入表。 -
.bss
:未初始化数据。 -
.rsrc
:资源节,存储图标、对话框等资源。
-
4. 数据目录表(Data Directory)
数据目录表位于可选头中,是 PE 文件中关键存储信息的索引,通过该表可以定位导入表、导出表等重要结构。
-
关键项:
-
导入表(Import Table):记录程序使用到的外部函数或库(如 kernel32.dll)。
-
导出表(Export Table):记录程序导出的函数或全局变量信息。
-
重定位表(Relocation Table):支持可执行文件的动态基址。
-
四、PE 文件的内存状态结构
当 PE 文件被加载到内存后,其结构发生了一些显著的变化:
1. 文件状态 vs 内存状态的对比
表格
项目 | 文件状态 | 内存状态 |
---|---|---|
位置(基地址) | 磁盘上的物理地址 | 加载内存中的虚拟地址 |
节对齐方式 | 文件对齐,受 FileAlignment 限制 | 内存对齐,受 SectionAlignment 限制 |
代码和数据布局 | 节可能被压缩,并存储在不同偏移位置中 | 节被解压并按逻辑顺序放置到内存中 |
加载基址 | 无特定要求 | 按 ImageBase 加载到指定的内存地址 |
例如:
-
如果节的 FileAlignment 为 512 字节,各节在文件中按 512 字节对齐存储。
-
如果节的 SectionAlignment 为 4096 字节,文件加载到内存后按 4KB 对齐。
2. 加载器行为
当 Windows 的 PE 加载器处理 PE 文件时,其逻辑如下:
-
映射基址:根据文件的
ImageBase
字段分配内存空间。 -
解压节表:按节标头重新排列节的内容,解压到内存中。
-
处理动态链接:
-
从导入表加载外部 DLL。
-
利用 IAT(Import Address Table,导入地址表)解析导入函数地址。
-
-
执行入口点:跳转到
AddressOfEntryPoint
指示的入口地址。
五、加载 PE 文件的过程
磁盘文件(文件状态) --> 加载器(操作系统) --> 内存映射(内存状态)
以下是 PE 加载进程的关键步骤:
-
读取文件头:
- 验证 DOS 头和 PE 头。
-
分配内存:
- 根据
ImageBase
和SizeOfImage
分配内存空间。
- 根据
-
映射节:
- 将磁盘上的各节按内存对齐复制到相应的内存区域。
-
加载依赖:
- 解析导入表并加载需要的动态链接库(DLL)。
-
修复重定位:
- 如果加载基址与
ImageBase
不同,则修改代码中对绝对地址的引用。
- 如果加载基址与
-
执行入口点:
- 跳转到入口点,正式启动程序。
六、重要 PE 数据结构的详细解析
1. 导入表(Import Table)
导入表记录了 PE 文件需要调用的外部函数或库。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD OriginalFirstThunk; // 指向未解析的名称表
DWORD TimeDateStamp; // 时间戳
DWORD ForwarderChain; // 转发的 DLL 链
DWORD Name; // DLL 的名称字符串
DWORD FirstThunk; // 指向实际的导入地址表 (IAT)
} IMAGE_IMPORT_DESCRIPTOR;
-
OriginalFirstThunk:指向未解析的函数名称(存储函数的名字或序号)。
-
FirstThunk:指向最终被解析的地址,用于函数调用。
2. 导出表(Export Table)
导出表描述了 PE 文件提供给其他程序调用的函数或变量。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 导出 DLL 的名字
DWORD Base; // 导出函数的起始序号
DWORD NumberOfFunctions; // 导出函数数量
DWORD NumberOfNames; // 导出名称数量
DWORD AddressOfFunctions; // 导出函数地址表
DWORD AddressOfNames; // 函数名称表
DWORD AddressOfNameOrdinals; // 函数名称到序号的映射表
} IMAGE_EXPORT_DIRECTORY;