当前位置: 首页 > news >正文

Windows逆向工程提升之PE文件的基本概念

  • 公开视频 -> 链接点击跳转公开课程
  • 博客首页 -> ​​​链接点击跳转博客主页

目录

PE文件状态

一、PE 文件的基本概念

1. 什么是 PE 文件?

2. PE 文件的历史

二、PE 文件的两种状态

三、PE 文件的文件状态结构

1. DOS 头(MS-DOS Header)

2. PE 头(PE Header)

3. 节表(Section Table)

4. 数据目录表(Data Directory)

四、PE 文件的内存状态结构

1. 文件状态 vs 内存状态的对比

2. 加载器行为

五、加载 PE 文件的过程

六、重要 PE 数据结构的详细解析

1. 导入表(Import Table)

2. 导出表(Export Table)


PE文件状态

一、PE 文件的基本概念

1. 什么是 PE 文件?

PE 是 Microsoft 提出的可移植可执行文件格式,用于描述程序在磁盘上的存储格式以及加载到内存后运行的布局。

  • 文件扩展名.exe.dll.sys

  • 用途:应用程序、动态链接库(DLL)、驱动程序等。

  • 架构兼容性:支持 x86、x64、ARM 等多种架构。

2. PE 文件的历史

PE 文件格式基于 COFF(Common Object File Format,通用目标文件格式),并进行了扩展以适应 Windows 操作系统。


二、PE 文件的两种状态

PE 文件存在以下两种状态:

  1. 文件状态:PE 文件静态存储在磁盘中,描述文件的布局和内容。

    • 定义:PE文件在磁盘上的存储布局,主要用于文件的存储和传输。

    • 特点

      • 文件大小固定。

      • 内容经过压缩或加密处理。

      • 数据段和代码段是分离的。

  2. 内存状态: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 头的主要部分:
  1. 签名(Signature)

    • 固定为 PE\0\0

    • 表明这是一个有效的 PE 格式文件。

  2. 文件头(File Header)

    • 包含 PE 文件的基本信息,如目标架构和头大小。

    • 字段:

      • Machine:指定处理器架构(如 0x14C 表示 x86, 0x8664 表示 x64)。

      • NumberOfSections:文件的节数量。

      • TimeDateStamp:文件的编译时间戳。

  3. 可选头(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 文件时,其逻辑如下:

  1. 映射基址:根据文件的 ImageBase 字段分配内存空间。

  2. 解压节表:按节标头重新排列节的内容,解压到内存中。

  3. 处理动态链接

    • 从导入表加载外部 DLL。

    • 利用 IAT(Import Address Table,导入地址表)解析导入函数地址。

  4. 执行入口点:跳转到 AddressOfEntryPoint 指示的入口地址。


五、加载 PE 文件的过程

磁盘文件(文件状态) --> 加载器(操作系统) --> 内存映射(内存状态)

以下是 PE 加载进程的关键步骤:

  1. 读取文件头

    • 验证 DOS 头和 PE 头。
  2. 分配内存

    • 根据 ImageBase 和 SizeOfImage 分配内存空间。
  3. 映射节

    • 将磁盘上的各节按内存对齐复制到相应的内存区域。
  4. 加载依赖

    • 解析导入表并加载需要的动态链接库(DLL)。
  5. 修复重定位

    • 如果加载基址与 ImageBase 不同,则修改代码中对绝对地址的引用。
  6. 执行入口点

    • 跳转到入口点,正式启动程序。

六、重要 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;

相关文章:

  • YOLOV3 深度解析:目标检测的高效利器
  • 大语言模型怎么进行记忆的
  • TDengine 安全部署配置建议
  • 人工智能、机器学习与深度学习:概念解析与内在联系
  • ALTER CONVERSION使用场景
  • 树莓派(Raspberry Pi)中切换为国内的软件源
  • CSS- 4.6 radiu、shadow、animation动画
  • Python 与 Java 在 Web 开发中的深度对比:从语言特性到生态选型
  • GPT-4.1特点?如何使用GPT-4.1模型,GPT-4.1编码和图像理解能力实例展示
  • 【SPIN】PROMELA并发编程(SPIN学习系列--3)
  • 【Dify 前端源码解读系列】聊天组件功能分析文档
  • 解决Windows磁盘管理中因夹卷导致的无法分区问题
  • go 数据类型转换
  • LeetCode-滑动窗口-找到字符串中所有字母异位词
  • 【力扣刷题】LeetCode763-划分字母区间
  • 力扣网-复写零
  • 【Go】从0开始学习Go
  • 力扣每日一题5-19
  • OpenMV IDE 的图像接收缓冲区原理
  • leetcode 74. Search a 2D Matrix
  • 讲述“外国货币上的中国故事”,《世界钱币上的中国印记》主题书刊出版发布
  • 交通运输局男子与两名女子办婚礼?官方通报:未登记结婚,开除该男子
  • 国家统计局答澎湃:我国投资的潜力依然巨大,支撑投资增长的有利因素仍然比较多
  • 人民日报头版:紧盯“学查改”,推动作风建设走深走实
  • 九江银行落地首单畜牧业转型金融业务,助推传统农业绿色智能
  • 当“小铁人”遇上青浦,看00后如何玩转长三角铁三