PE解释器之PE文件结构

        PE文件是由许许多多的结构体组成的,程序在运行时就会通过这些结构快速定位到PE文件的各种资源,其结构大致如图所示,从上到下依次是Dos头、Nt头、节表、节区和调试信息(可选)。其中Dos头、Nt头和节表在本文中统称为PE文件头(因为SizeOfHeaders就是这三个头的总大小)、节区则称为节,所以也可以说PE文件是由PE文件头和节组成。
  PE文件头保存着整个PE文件的索引信息,可以帮助PE装载器定位资源,而节则保存着整个PE文件的所有资源。正因为如此,所以存在着这样的说法:头是节的描述,节是头的具体化。

一:IMGAGE_DOS_HEADER

typedef struct _IMAE_DOS_HEADER {       WORD e_magic;        **重要成员 相对该结构的偏移0x00**WORD e_cblp;WORD e_cp;WORD e_crlc;WORD e_cparhdr;WORD e_minalloc;WORD e_maxalloc;WORD e_ss;WORD e_sp;WORD e_csum;WORD e_ip;WORD e_cs;WORD e_lfarlc;WORD e_ovno;WORD e_res[4];WORD e_oemid;WORD e_oeminfo;WORD e_res2[10];LONG e_lfanew;        **重要成员 相对该结构的偏移0x3C**
} IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;

        当我们用16进制编辑器打开一个PE文件时,就会发现所有PE文件的前两个字节都是MZ,用十六进制表示是4D 5A,这两个字母就是Mark Zbikowski的姓名缩写,他是最初的MS-DOS设计者之一。如果把PE文件的这两个字节修改成其他数据,运行该PE文件就会无法正常运行(跳出黑窗口打印Program too big to fit in memory然后闪退,有兴趣的朋友可以尝试下)。这里可以证明当PE文件运行时,首先就会检测这两个字节,如果不是MZ则会退出运行。

        在该结构体中另一个重要成员就是最后一个成员e_lfanew。该成员的大小是LONG类型4个字节。之所以说它重要是因为它保存着IMAGE_NT_HEADERS32这个结构体在PE文件中的偏移地址,PE文件运行时只有通过该成员才能定位到PE签名(也就是IMAGE_NT_HEADERS32结构体的起始位置)。

二:IMAGE_DOS_STUB (了解)

        在IMAGE_DOS_HEADER结构体后面紧跟着就是IMAGE_DOS_STUB程序,它是运行在MS-DOS下的可执行程序,当可执行文件运行于MS-DOS下时,这个程序会打印This program cannot be run in DOS mode这条消息。用户可以自己更改该程序,MS-DOS程序当前是可有可无的,如果你想使文件大小尽可能的小可以省掉MS-DOS程序,同时把前面的参数都清0。

三:IMAGE_NT_HEADERS32

        

typedef struct _IMAGE_NT_HEADERS {DWORD                   Signature;         **重要成员 PE签名 相对该结构的偏移0x00**IMAGE_FILE_HEADER       FileHeader;        **重要成员 结构体 相对该结构的偏移0x04**IMAGE_OPTIONAL_HEADER32 OptionalHeader;    **重要成员 结构体 相对该结构的偏移0x18**
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

        这个结构体是整个PE文件的核心,它是由一个Signature、一个IMAGE_FILE_HEADER结构体、一个IMAGE_OPTIONAL_HEADER32结构体组成的。所以从整体看来这个结构比较简单,但实际上其内部结构较为复杂,我将会在下方对两个结构体进行详细的介绍。
  Signature
  也称作PE签名,这个成员和DOS头的MZ标记一样都是一个PE文件的标准特征,只不过这个成员是DWORD类型大小为4字节,如果把这个PE签名修改后,程序也是不会正常运行的(跳出黑窗口打印This program cannot be run in DOS mode然后闪退,可能是因为修改PE签名后无法识别后续内容的关系吧)。

        如果把MZ标志和PE签名同时改变的话,其效果和只修改MZ是一样的,可见程序在载入时是先检测MZ标志然后才检测PE签名的。

四:IMAGE_FILE_HEADER

typedef struct _IMAGE_FILE_HEADER {WORD  Machine;                    **        机器号     相对该结构的偏移0x00**WORD  NumberOfSections;           **重要成员 节区数量   相对该结构的偏移0x02**DWORD TimeDateStamp;              **        时间戳     相对该结构的偏移0x04**DWORD PointerToSymbolTable;       **        符号表偏移  相对该结构的偏移0x08**DWORD NumberOfSymbols;            **        符号表数量  相对该结构的偏移0x0C**WORD  SizeOfOptionalHeader;       **重要成员 可选头大小  相对该结构的偏移0x10**WORD  Characteristics;            **重要成员 PE文件属性  相对该结构的偏移0x12**
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine:
  所表示的是计算机的体系结构类型,也就是说这个成员可以指定该PE文件能够在32位还是在64位CPU上执行。如果强行更改该数值程序就会报错。该成员可以是以下的数值:

NumberOfSections:
  它的含义就是当前PE文件的节区数量,虽然它是大小是两个字节,但是在windows加载程序时会将节区的最大数量限制为96个。

TimeDateStamp:
  它的含义是时间戳,用于表示该PE文件创建的时间,时间是从国际协调时间也就是1970年1月1日00:00起开始计数的,计数单位是秒。例如0x5CFBB225的计算方法如下:

通过计算,从1970年到2024年一共有*个闰年(通过闰年计算器获得),到1月有*天。秒:0x5CFBB225 % 60 = 33
分:0x5CFBB225 / 60 % 60 = 3
时:0x5CFBB225 / 3600 % 24 = 13
日:0x5CFBB225 / 3600 / 24 - (365 * 49 + 12) - 151 + 1 = 1
月:(0x5CFBB225 / 3600 / 24 - (365 * 49 + 12)) / 30 + 1 = 1
年:0x5CFBB225 / 3600 / 24 / 365 + 1970 = 2024
结果为:2024年1月1日 13:03:33

SizeOfOptionalHeader
  它存储该PE文件的可选PE头的大小,在32位PE文件中可选头大小为0xE0,64位可选头大小为0xF0。正因为如此,所以就必须通过该成员来确定可选PE头的大小。
  Characteristics
  它描述了PE文件的一些属性信息,比如是否可执行,是否是一个动态连接库等。该值可以是一个也可以是多个值的和,具体定义如下:

五:IMAGE_OPTIONAL_HEADER32

typedef struct _IMAGE_OPTIONAL_HEADER {WORD                 Magic;                        **魔术字                     偏移0x00BYTE                 MajorLinkerVersion;           **链接器主版本                偏移0x02BYTE                 MinorLinkerVersion;           **链接器副版本                偏移0x03DWORD                SizeOfCode;                   **所有含代码的节的总大小       偏移0x04DWORD                SizeOfInitializedData;        **所有含初始数据的节的总大小    偏移0x08DWORD                SizeOfUninitializedData;      **所有含未初始数据的节的总大小  偏移0x0C    DWORD                AddressOfEntryPoint;          **程序执行入口地址             偏移0x10   重要DWORD                BaseOfCode;                   **代码节的起始地址             偏移0x14DWORD                BaseOfData;                   **数据节的起始地址             偏移0x18DWORD                ImageBase;                    **程序首选装载地址             偏移0x1C   重要DWORD                SectionAlignment;             **内存中节区对齐大小           偏移0x20   重要DWORD                FileAlignment;                **文件中节区对齐大小           偏移0x24   重要WORD                 MajorOperatingSystemVersion;  **操作系统的主版本号           偏移0x28WORD                 MinorOperatingSystemVersion;  **操作系统的副版本号           偏移0x2AWORD                 MajorImageVersion;            **镜像的主版本号               偏移0x2CWORD                 MinorImageVersion;            **镜像的副版本号               偏移0x2EWORD                 MajorSubsystemVersion;        **子系统的主版本号             偏移0x30WORD                 MinorSubsystemVersion;        **子系统的副版本号             偏移0x32DWORD                Win32VersionValue;            **保留,必须为0               偏移0x34DWORD                SizeOfImage;                  **镜像大小                    偏移0x38   重要DWORD                SizeOfHeaders;                **PE头大小                    偏移0x3C   重要DWORD                CheckSum;                     **校验和                      偏移0x40WORD                 Subsystem;                    **子系统类型                   偏移0x44WORD                 DllCharacteristics;           **DLL文件特征                  偏移0x46DWORD                SizeOfStackReserve;           **栈的保留大小                 偏移0x48DWORD                SizeOfStackCommit;            **栈的提交大小                 偏移0x4CDWORD                SizeOfHeapReserve;            **堆的保留大小                 偏移0x50DWORD                SizeOfHeapCommit;             **堆的提交大小                 偏移0x54DWORD                LoaderFlags;                  **保留,必须为0                偏移0x58DWORD                NumberOfRvaAndSizes;          **数据目录的项数               偏移0x5CIMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

下方只对一些相对重要的成员进行讲解:
  Magic
  这个无符号整数指出了镜像文件的状态,此成员可以是以下的值:

AddressOfEntryPoint
  该成员保存着文件被执行时的入口地址,它是一个RVA。如果想要在一个可执行文件中附加了一段代码并且要让这段代码首先被执行,就可以通过更改入口地址到目标代码上,然后再跳转回原有的入口地址。

  ImageBase
  该成员指定了文件被执行时优先被装入的地址,如果这个地址已经被占用,那么程序装载器就会将它载入其他地址。当文件被载入其他地址后,就必须通过重定位表进行资源的重定位,这就会变慢文件的载入速度。而装载到ImageBase指定的地址就不会进行资源重定位。
  对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他的DLL使用,所以DLL文件中必须包含重定位信息以防万一。因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 成员中,DLL 文件对应的IMAGE_FILE_RELOCS_STRIPPED位总是为0,而EXE文件的这个标志位总是为1。

  SectionAlignment
  该成员指定了文件被装入内存时,节区的对齐单位。节区被装入内存的虚拟地址必须是该成员的整数倍,以字节为单位,并且该成员的值必须大于等于FileAlignment的值。该成员的默认大小为系统的页面大小。
  FileAlignment
  该成员指定了文件在硬盘上时,节区的对齐单位。节区在硬盘上的地址必须是该成员的整数倍,以字节为单位,并且该成员的值必须大于等于FileAlignment的值。该值应为200h到10000h(含)之间的2的幂。默认为200h。如果SectionAlignment的值小于系统页面大小,则FileAlignment的值必须等于SectionAlignment的值。

  SizeOfImage
  该成员指定了文件载入内存后的总体大小,包含所有的头部信息。并且它的值必须是SectionAlignment的整数倍。
  SizeOfHeaders
  该成员指定了PE文件头的大小,并且向上舍入为FileAlignment的倍数,值的计算方式为:

SizeOfHeaders = (e_lfanew/*DOS头部*/ + 4/*PE签名*/ +sizeof(IMAGE_FILE_HEADER) +SizeOfOptionalHeader + /*NT头*/sizeof(IMAGE_SECTION_HEADER) * NumberOfSections) / /*节表*/FileAlignment  *FileAlignment +FileAlignment;    /*向上舍入 一般该结果不可能是FileAlignment的整数倍,所以直接加上FileAlignment还是没问题的 */

 NumberOfRvaAndSizes
  该成员指定了可选头中目录项的具体数目,由于以前发行的Windows NT的原因,它只能为10h。

六:IMAGE_SECTION_HEADER

#define IMAGE_SIZEOF_SHORT_NAME              8typedef struct _IMAGE_SECTION_HEADER {BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];         **节区名                 偏移0x00union {DWORD PhysicalAddress;DWORD VirtualSize;                         **节区的虚拟大小          偏移0x08      重要} Misc;                                     DWORD VirtualAddress;                        **节区的虚拟地址          偏移0x0C      重要  DWORD SizeOfRawData;                         **节区在硬盘上的大小       偏移0x10      重要DWORD PointerToRawData;                      **节区在硬盘上的地址       偏移0x14      重要DWORD PointerToRelocations;                  **指向重定位项开头的地址   偏移0x18DWORD PointerToLinenumbers;                  **指向行号项开头的地址     偏移0x1CWORD  NumberOfRelocations;                   **节区的重定位项数         偏移0x20WORD  NumberOfLinenumbers;                   **节区的行号数            偏移0x22DWORD Characteristics;                       **节区的属性              偏移0x24       重要
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

 Name
  这是一个8字节的ASCII字符串,长度不足8字节时用0x00填充,该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理。可执行文件不支持长度超过8字节的节名。对于支持超过字节长度的文件来说,此成员会包含斜杠(/),并在后面跟随一个用ASCII表示的十进制数字,该数字是字符串表的偏移量。
  Misc.VirtualSize
  这个成员在一个共用体中,这个共用体中还有另外一个成员,由于用处不大我们就不讲解了,主要讲解VirtualSize的含义。这个成员指定了该节区装入内存后的总大小,以字节为单位,如果此值大于SizeOfRawData的值,那么大出的部分将用0x00填充。这个成员只对可执行文件有效,如果是obj文件此成员的值为0。
  VirtualAddress
  指定了该节区装入内存虚拟空间后的地址,这个地址是一个相对虚拟地址(RVA),它的值一般是SectionAlignment的整数倍。它加上ImageBase后才是真正的虚拟地址。
  SizeOfRawData
  指定了该节区在硬盘上初始化数据的大小,以字节为单位。它的值必须是FileAlignment的整数倍,如果小于Misc.VirtualSize,那么该部分的其余部分将用0x00填充。如果该部分仅包含未初始化的数据,那么这个值将会为零。
  PointerToRawData
  指出零该节区在硬盘文件中的地址,这个数值是从文件头开始算起的偏移量,也就是说这个地址是一个文件偏移地址(FOA)。它的值必须是FileAlignment的整数倍。如果这个部分仅包含未初始化的数据,则将此成员设置为零。
  Characteristics

        该成员指出了该节区的属性特征。其中的不同数据位代表了不同的属性,这些数据位组合起来就是这个节的属性特征        

总表:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/592029.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据毕业设计:基于python淘宝数据采集分析可视化系统 商品销量数据分析 计算机毕业设计(附源码+文档)✅

毕业设计:2023-2024年计算机专业毕业设计选题汇总(建议收藏) 毕业设计:2023-2024年最新最全计算机专业毕设选题推荐汇总 🍅感兴趣的可以先收藏起来,点赞、关注不迷路,大家在毕设选题&#xff…

微软真是活菩萨,面向初学者的机器学习、数据科学、AI、LLM课程统统免费

微软真是活菩萨,面向初学者的机器学习、数据科学、AI、LLM课程统统免费 大家好,我是老章 推荐几个质量上乘且完全免费的微软开源课程 面向初学者的机器学习课程 **地址:**https://microsoft.github.io/ML-For-Beginners/#/ 学习经典机器学…

Mysql 下载与安装教程(详细介绍与总结)

一:版本介绍 首先,我们需要先进入官网进行下载,在官网中有好几个版本,那么这里我分别简述一下MySQL各个版本区别: 1:企业版,MySQL Enterprise Edition 需要付费的,可以免费试用30天…

超声波传感器(附:c语言测距代码)

一、引言 超声波传感器是一种利用超声波进行检测的装置,具有非接触、高精度、抗干扰能力强等优点。在工业自动化、医疗诊断、环境监测等领域,超声波传感器发挥着重要的作用。本文将深入探讨超声波传感器的原理、应用,并通过C语言代码示例来展…

Windows使用IIS服务搭建WebDAV站点结合内网穿透公网访问

文章目录 1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访问测试 4. 安装Raidrive客户端4.1 连接WebDav服务器4.2 连接成功4.2 连接成功总结: 自己用Windows Server搭建了家用NAS主机&…

汉诺塔问题

问题: Hanoi(汉诺)塔问题。这时一个古典的数学问题,是一个递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座 A,B,C(如下图)。开始时A座上有64个盘子,盘子大小不等&#xff0c…

C++模板进阶操作 ---非类型模板参数、模板的特化以及模板的分离编译

本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…

MyBatis标签及其应用示例

MyBatis标签及其应用示例 1. select 1.1 标签属性 id唯一的标识符parameterType传给此语句的参数的全路径名或别名如:com.xxx.xxx.demo.entity.User或userresultType语句返回值类型或别名。如果是集合List,此处填写集合的泛型T,而不是集合…

启动springboot时报错 APPLICATION FAILED TO START 包冲突

启动springboot时报错 APPLICATION FAILED TO START 包冲突 problem 具体日志如下 *************************** APPLICATION FAILED TO START ***************************Description:An attempt was made to call a method that does not exist. The attempt was made fr…

[python]matplotlib

整体图示 .ipynb 转换md时候图片不能通知携带&#xff0c;所有图片失效&#xff0c;不过直接运行代码可以执行 figure figure,axes与axis import matplotlib.pyplot as plt figplt.figure() fig2plt.subplots() fig3,axsplt.subplots(2,2) plt.show()<Figure size 640x480 …

云原生学习系列之基础环境准备(虚拟机搭建)

最近由于工作需要开始学习云原生相关内容&#xff0c;为方便学习操作&#xff0c;准备在外网搭建自己的环境&#xff0c;然后进行相关的练习&#xff0c;搭建环境的第一步便是虚拟机的安装。 基础软件 这里我用到的是CentOS-7-x86_64的操作系统。 链接&#xff1a;https://pa…

Eureka注册及使用

一、Eureka的作用 Eureka是一个服务注册与发现的工具&#xff0c;主要用于微服务架构中的服务发现和负载均衡。其主要作用包括&#xff1a; 服务提供者将自己注册到Eureka Server上&#xff0c;包括服务的地址和端口等信息。服务消费者从Eureka Server上获取服务提供者的地址…

Go(Golang)的10个常见代码片段用于各种任务

探索有用的Go编程代码片段 提供“前10名”Go&#xff08;Golang&#xff09;代码片段的明确列表是具有挑战性的&#xff0c;因为代码片段的实用性取决于您试图解决的具体问题。然而&#xff0c;我可以为您提供十个常用的Go代码片段&#xff0c;涵盖了各种任务和概念&#xff1…

【驱动序列】简单聊聊开发驱动程序的缘由和驱动程序基本信息

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂》&#xff0c;这是《驱动程序》专栏序列文章。 这是2024年第4篇文章&#xff0c;此篇文章是结合了C#知识点实践序列文章&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xf…

树莓派4B-Python使用PyCharm的SSH协议在电脑上远程编辑程序

目录 前言一、pycharm的选择二、添加SSH的解释器使用总结 前言 树莓派的性能始终有限&#xff0c;不好安装与使用高级一点的程序编辑器&#xff0c;如果只用thonny的话&#xff0c;本人用得不习惯&#xff0c;还不如PyCharm&#xff0c;所以想着能不能用电脑中的pycharm来编写…

IO作业2.0

思维导图 1> 使用fread、fwrite完成两个文件的拷贝 #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) {if(argc ! 3) //判断外部参数 {printf("The terminal format is incorrect\n");r…

OpenGL FXAA抗锯齿算法(Qt,Consloe版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经提供了使用VCG读取Mesh的方式,接下来就需要针对读取的网格数据进行一些渲染操作了。在绘制Mesh数据时总会遇到图形的抗锯齿问题,OpenGL本身已经为我们提供了一种MSAA技术,但该技术对于一些实时渲染性能有…

计算机组成原理——冯诺依曼计算机硬件框图

存储器&#xff1a;存放数据和程序 运算器&#xff1a;算术运算和逻辑运算 控制器&#xff1a;指挥程序的运算 输入设备&#xff1a;将信息转化成机器能识别的形式 输出设备&#xff1a;将结果转化成人们熟悉的形式

Centos安装Kafka(KRaft模式)

1. KRaft引入 Kafka是一种高吞吐量的分布式发布订阅消息系统&#xff0c;它可以处理消费者在网站中的所有动作流数据。其核心组件包含Producer、Broker、Consumer&#xff0c;以及依赖的Zookeeper集群。其中Zookeeper集群是Kafka用来负责集群元数据的管理、控制器的选举等。 由…

使用Apache Commons SCXML实现状态机管理

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们程序员在开发过程中&#xff0c;经常会遇到需要管理不同状态和状态之间转换的场景。比如&#xff0c;一个在线购物的订单&#xff0c;它可能有“新建订单”、“已支付”、“配送中”、“已完成”等状态。在这…