最详细PE文件格式讲解!!!!!

news2024/11/16 5:30:48

一:简单介绍和必须知道的知识点:

        在介绍PE文件格式的细节前,仔细看一下下面图,该图展示了PE文件格式的大概布局,学习时建议配合使用PE工具——stud_PE。

1.1PE的基本概念

        PE文件使用的是一个平面地址空间,所以代码和数据都合并在一起,组成了一个很大的结构。文件的内容被分割为不同的区块(Section,也叫区段,节等)区块中包含代码或者数据,各个区块按照页边界对齐。区块木有大小限制,是一个连续结构。每个块都有它自己的内存中的一套属性,例如此块是否含有代码,是否可读或者写等。

        知道PE文件不是作为单一内存映射文件被载入内存是很重要的。PE装载器遍历PE文件并决定文件的那一部分被映射,这种映射方式是将文件较高的偏移位置映射到较高的内存地址中。磁盘文件一旦被载入内存,磁盘上的数据结构布局和内存中的数据结构布局就是一致的。这样,如果在磁盘的数据结构中找一些内容,就和被载入的内存文件找到的内容一样。但是数据之间的相对位置可能会改变,某项的偏移地址可能区别于原始的偏移位置。

1.1.1 基地址

        当PE文件通过加载器载入内存后,内存中的版本称为模块(Module),映射文件的起始地址称为模块句柄(hModule),可以通过模块句柄访问内存中的其他数据结构。这个起始内存地址也称为基地址(ImageBase)。

基地址的值是由PE文件本身设定的。按照默认设置,用 VsuaC++建立的EXE文件的基地址是400000h(6位),DLL文件的基地址是10000000h(8位)。

1.1.2  虚拟地址

        在Windows系统中,PE文件被系统加载器映射到内存中。每个程序都有自己的虚拟空间,这个虚拟空间的内存地址被称为虚拟地址(Virtual Address,VA )。

1.1.3 相对虚拟地址

        为了避免出现绝对内存地址引入了相对虚拟地址(RVA)的概念。RVA只是内存中的一个简单的,相对于PE文件载入地址的偏移量,它是一个”相对“地址(或者叫 偏移量)

        例如:一个文件从400000h处载入,而且它的代码区块开始于401000h处,代码区块的RVA计算方法如下:

        目标地址(401000h)- 载入地址(400000h)= RVA (1000h)

相对的知道了:

        虚拟地址(VA) = 基地址(ImageBase) + 相对虚拟地址(RVA)

1.1.4 文件偏移地址

        当PE文件存储在磁盘中时,某个数据的位置相当于文件头的偏移量称为文件偏移地址(FileOffset)或者物理地址(RAW Offset)。文件偏移地址从PE文件的第一个字节开始计数,起始值为0.

二:MS-DOS头部

        每个PE文件都是以一个DOS程序开始的,有了它,一旦程序在DOS下执行,DOS就能执行识别出这是一个有效的执行体,然后运行紧随MZ header的DOS stub(DOS块)。DOS stub实际上是一个有效的EXE ,在不支持PE文件格式的操作系统上它将显示一个错误提示,类似于字符串”This program cannot be run in MS-DOS mode“。程序员也会根据自己的意图实现完整的DOS代码。这个stub部分不重要,我们通常把DOS MZ头与DOS stub合称为DOS文件头。

IMAGE_DOS_HEADER的结构体定义如下: 

        其中有俩个字段比较重要,分别是e_magic 和 e_lfanew。e_magic字段(1字节大小)的值需要被设置为 5A4D。这个值有一个#define,名为IMAGE_DOS_SIGNATURE,在ASII表示法里它的值为”MZ“,是MS-DOS的创建者之一Mark Zbikowski名字的缩写。e_lfanew 字段是真正的PE文件头的相对偏移(RVA),指向真正的PE头的文件偏移位置,占用4字节,位于文件开始偏移3ch字节处。

        CPU属于小端类,字符存储时低位在低位,及在前,高位在后。将次序恢复后,e_lfanew的值为0000000Bh,这个值就是真正的偏移量。

 三:PE文件头

        紧跟DOS stub的是PE文件头(PE Header)。”PE Header“是PE相关结构NT映像头的简称(IMAGE_NT_HEADERS),其中包含许多重要字段。当执行体在执行PE文件结构时,装载器将会从IMAGE_DOS_HEADER结构的e_lfanew字段里找到PE Heard的起始偏移量,用其加上基地址,得到PE文件头的指针。

                                        PNTHeard = ImageBase + dosHeader->e_lfanew

       3.1 Signature字段 

        3.2 IMAGE_FILE_HEADER 结构

 其中最重要的是指出IMAGE_OPTIONAL_HEADER的大小。下面介绍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;

用16进制工具查看结构情况:

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

2.NumherOlSections:区块(Section)的数目,块表紧跟在IMAGE NT HEADERS 后面。

3.TimeDateStamp:表示文件的创建时间。这个值是自1970年1月1日以来用格林威治时间(GMT)计算的秒数,是一个比文件系统的日期/时间更精确的文件创建时间指示器。将这个值翻译为易读的字符串需要使用_cime 函数(它是时区敏感型的)。另一个对此字段计算有用的函数是 gmtime。

4.PointerToSymbolTable:COFF 符号表的文件偏移位置(参见 Microsoft 规范的 5.4节)。因为采用了较新的 debug格式,所以 COF 符号表在 PE 文件中较为少见。在 Visual Studio.NET 出现之前COFF 符号表可以通过设置链接器开关(/DEBUGTYPE:COFF)来创建。COFF 符号表几乎总能在目标文件中找到,若没有符号表存在,将此值设置为0。

5.NumberOrSymhols:如果有 COFF 符号表,它代表其中的符号数目。COFF 符号是一个大小固定的结构,如果想找到 COFF符号表的结束处,需要使用这个域。

6.Size0f0ptionalHeader:紧跟 IMAGE_FILE HEADER,表示数据的大小。在 PE 文件中,这个数据结构叫作 IMAGE OPTIONAL, HEADER,其大小依赖于当前文件是 32 位还是 64 位文件。对 32位 PE 文件,这个域通常是 00F0h;对 64位 PE32+ 文件,这个域是 00F0h。不管怎样,这些是要求
的最小值,较大的值也可能会出现。

7. Characterislics:文件属性,有选择地通过几个值的运算得到。这些标志的有效值是定义于winnt.h 内的 IMAGE FILE xxx值,具体如表 11.2所示。普通 EXE 文件的这个字段的值一般是 010fh.DLL文件的这个字段的值一般是 2102h。

3.3IMAGE_OPTIONAL_HEARDER结构

typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;                        **魔术字                     偏移0x00
  BYTE                 MajorLinkerVersion;           **链接器主版本                偏移0x02
  BYTE                 MinorLinkerVersion;           **链接器副版本                偏移0x03
  DWORD                SizeOfCode;                   **所有含代码的节的总大小       偏移0x04
  DWORD                SizeOfInitializedData;        **所有含初始数据的节的总大小    偏移0x08
  DWORD                SizeOfUninitializedData;      **所有含未初始数据的节的总大小  偏移0x0C    
  DWORD                AddressOfEntryPoint;          **程序执行入口地址             偏移0x10   重要
  DWORD                BaseOfCode;                   **代码节的起始地址             偏移0x14
  DWORD                BaseOfData;                   **数据节的起始地址             偏移0x18
  DWORD                ImageBase;                    **程序首选装载地址             偏移0x1C   重要
  DWORD                SectionAlignment;             **内存中节区对齐大小           偏移0x20   重要
  DWORD                FileAlignment;                **文件中节区对齐大小           偏移0x24   重要
  WORD                 MajorOperatingSystemVersion;  **操作系统的主版本号           偏移0x28
  WORD                 MinorOperatingSystemVersion;  **操作系统的副版本号           偏移0x2A
  WORD                 MajorImageVersion;            **镜像的主版本号               偏移0x2C
  WORD                 MinorImageVersion;            **镜像的副版本号               偏移0x2E
  WORD                 MajorSubsystemVersion;        **子系统的主版本号             偏移0x30
  WORD                 MinorSubsystemVersion;        **子系统的副版本号             偏移0x32
  DWORD                Win32VersionValue;            **保留,必须为0               偏移0x34
  DWORD                SizeOfImage;                  **镜像大小                    偏移0x38   重要
  DWORD                SizeOfHeaders;                **PE头大小                    偏移0x3C   重要
  DWORD                CheckSum;                     **校验和                      偏移0x40
  WORD                 Subsystem;                    **子系统类型                   偏移0x44
  WORD                 DllCharacteristics;           **DLL文件特征                  偏移0x46
  DWORD                SizeOfStackReserve;           **栈的保留大小                 偏移0x48
  DWORD                SizeOfStackCommit;            **栈的提交大小                 偏移0x4C
  DWORD                SizeOfHeapReserve;            **堆的保留大小                 偏移0x50
  DWORD                SizeOfHeapCommit;             **堆的提交大小                 偏移0x54
  DWORD                LoaderFlags;                  **保留,必须为0                偏移0x58
  DWORD                NumberOfRvaAndSizes;          **数据目录的项数               偏移0x5C
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 

① Magic:这是一个标记字,说明文件是 ROM 映像(0107h)还是普通可执行的映像(010Bh),一般是 010Bh。如果是 PE32+,则是 020Bh。

Size0fCode:有 IMAGE SCN_CNT_CODE 属性的区块的总大小(只人不舍 ),这个值是向上对齐某一个值的整数倍。例如,本例是 200h,即对齐的是一个磁盘扇区字节数(200h)的整数倍。在通常情况下,多数文件只有1个 Code 块,所以这个字段和.text 块的大小匹配。

⑤ SizeOfInitializedData:已初始化数据块的大小,即在编译时所构成的块的大小(不包括代码段 )。但这个数据不太准确。


⑥ SizeOfUninitializedData:未初始化数据块的大小,装载程序要在虚拟地址空间中为这些数据约定空间。这些块在磁盘文件中不占空间,就像“UninitializedData”这一术语所暗示的一样,这些块在程序开始运行时没有指定值。未初始化数据通常在.bss块中。


⑦ Address0fEntnyPoint:程序执行人口 RVA。对于 DLL,这个入口点在进程初始化和关闭时及线程创建和毁灭时被调用。在大多数可执行文件中,这个地址不直接指向 Main、WinMain 或 DlMain函数,而指向运行时的库代码并由它来调用上述函数。在 DLL中,这个域能被设置为 0,此时前面
提到的通知消息都无法收到。链接器的 /OENTRY 开关可以设置这个域为 0。⑧ BaseOfCode:代码段的起始 RVA。在内存中,代码段通常在 PE 文件头之后,数据块之前。在 Microsof 链接器生成的可执行文件中,RVA 的值通常是1000h。Borland的 Tlink32 用 ImageBase加第1个 Code Section 的 RVA,并将结果存入该字段。

⑧ BaseOfCode: 代码段的起始RVA。在内存中,代码段通常在 PE文件头之后,数据块之前。  Microsoft链接器生成的可执行文件中, RVA的值通常是1000h Borland  Tlink32  ImageBase  加第1个 Code Section RVA,  并将结果存入该字段。


BaseOfData:数据段的起始 RVA。数据段通常在内存的末尾,即 PE 文件头和 Code Section之后。可是,这个域的值对于不同版本的 Microso 链接器是不一致的,在 64 位可执行文件中是不会出现的。


⑩ ImageBase:文件在内存中的首选载入地址。如果有可能(也就是说,如果目前没有其他文件占据这块地址,它就是正确对齐的并且是一个合法的地址),加载器会试图在这个地址载入 PE 文件。如果 PE 文件是在这个地址载人的,那么加载器将跳过应用基址重定位的步骤。

⑪SectionAlignment: 载入内存时的区块对齐大小。每个区块被载人的地址必定是本字段指定数值的整数倍。默认的对齐尺寸是目标 CPU的页尺寸。对运行在Windows 9x/Me下的用户模式可执行 文件,最小的对齐尺寸是每页1000h(4KB)  这个字段可以通过链接器的/ALIGN 开关来设置。在 IA-64 上,这个字段是按8KB排列的。

⑫  FileAlignment: 磁盘上 PE 文件内的区块对齐大小,组成块的原始数据必须保证从本字段的 倍数地址开始。对于x86 可执行文件,这个值通常是200h 或1000h,  这是为了保证块总是从磁盘的 扇区开始,这个字段的功能等价于NE 格式文件中的段/资源对齐因子。使用不同版本的 Microsoft链 接器,默认值会改变。这个值必须是2的幂,其最小值为200h。而且,如果 SectionAlignment 小于 CPU的页尺寸,这个域就必须与SectionAlignment 匹配。链接器开关/OPT:WIN98设置x86 可执行文 件的对齐值为1000h,/OPT:NOWIN98  设置对齐值为200h。

⑳  SizeOflmage: 映像载入内存后的总尺寸,是指载入文件从 ImageBase 到最后一个块的大小。 最后一个块根据其大小向上取整。

⑳  SizeOfHeaders:MS-DOS 头部、PE 文件头、区块表的总尺寸。这些项目出现在 PE 文件中的 所有代码或数据区块之前,域值四舍五入至文件对齐值的倍数。

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 发布以来  直是16。

!! DataDirectory[16]:数据目录表,由数个相同的IMAGE_DATA_DIRECTORY    结构组成,指向

输出表、输入表、资源块等数据。IMACE_DATA_DIRECTORY的结构定义如下。

以上太繁琐,下面是简化版:

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的整数倍。

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

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

相关文章

Nvidia DPU BlueField 软件概述_AI_卸载_降本增效_DPU时代_内核表示模型

Nvidia_BF_DPU简介 随着摩尔定律的减弱,加速计算和人工智能是较经济实惠的方式实现数据中心能源效率所需的工具。 让我们一起跟随和了解 NVIDIA Grace CPU、NVIDIA L4 GPU 和 NVIDIA BlueField DPU 如何推动数据中心迈向更高效的未来。 NVIDIA BlueField DPU 软件…

快速傅立叶变换FFT学习笔记

什么是FFT? FFT(Fast Fourier Transformation) 是离散傅氏变换(DFT)的快速算法,即快速傅氏变换。FFT使计算机计算离散傅里叶变换所需要的乘法次数大为减少,特别是被变换的抽样点数N越多&#x…

MySQL索引的使用,大大提升你代码的效率

目录 🚀索引使用 🚀最左前缀法则 🚀范围查询 🚀索引失效情况 隐式类型转换是什么? 隐式类型转换的影响 举例说明 无隐式类型转换的情况 存在隐式类型转换的情况 总结 🚀模糊查询 🚀or…

经典面试题-死锁

目录 1.什么是死锁? 2.形成死锁的四个必要条件 3.死锁的三种情况 第一种情况: 举例: 举例: 第二种情况:两个线程 两把锁 举例: 第三种情况:N个线程 M把锁 哲学家进餐问题 1.什么是死锁&…

Docker基础语法

目录 一.docker安装 二.docker基础名词 三.docker基础命令 四.命令别名 五.数据卷 六.挂载本地目录或文件 七.Docker镜像 八.网络 一.docker安装 1.安装yum工具 yum install -y yum-utils device-mapper-persistent-data lvm2 2.安装 docker yum源 yum-config-manag…

Git的安装与配置

目录 前言 Linux-centos:下安装 Linux_ubuntu下安装 创建Git本地仓库 配置用户名和Email 前言 Git是一种版本控制器,能够让我们了解一个文件的历史,以及它的发展过程。通俗的将就是可以记录一个工程的每一次改动和版本迭代的一个管理系统…

书法AI全自动切字+识别算法2.0版发布,草书篆书行书楷书识别准确率超过90%,覆盖书法单字30万张

我们开发的业界识别最准覆盖作品最全的书法AI小程序上线了 书法AI全自动切字识别算法2.0版发布,草书篆书行书楷书识别准确率超过90%,准确率甩百度OCR一条街,覆盖书法单字30万张,遥遥领先同行 我们还可为客户提供书法AI全自动切字a…

Tensorflow2.0笔记 - tensor的合并和分割

主要记录concat,stack,unstack和split相关操作的作用 import tensorflow as tf import numpy as nptf.__version__#concat对某个维度进行连接 #假设下面的tensor0和tensor1分别表示4个班级35名同学的8门成绩和两个班级35个同学8门成绩 tensor0 tf.ones([4,35,8]) tensor1 tf…

C#,入门教程(30)——扎好程序的笼子,错误处理 try catch

上一篇: C#,入门教程(29)——修饰词静态(static)的用法详解https://blog.csdn.net/beijinghorn/article/details/124683349 程序员语录:凡程序必有错,凡有错未必改! 程序出错的原因千千万&…

基于PSO-BP神经网络的风电功率预测(MATLAB)

作品简介 :关注公众号“电击小子程高兴的MATLAB小屋”获取优惠 主要内容 该模型将粒子群算法与BP神经网络结合用于BP神经网络的训练,即优化BP网络中的连接权值和各项阈值,然后利用神经网络分布式并行处理优势、自适应学习能力以及较好的…

坚持刷题 | 二叉树的最大深度

文章目录 题目考察点代码实现实现总结扩展用迭代的方式实现二叉树最大深度可能的扩展问题 坚持刷题,老年痴呆追不上我,今天刷:二叉树的最大深度 题目 104.二叉树的最大深度 考察点 二叉树的基本实现: 能够定义二叉树节点&…

Apifox接口测试教程(一)接口测试的原理与工具

🔥 交流讨论:欢迎加入我们一起学习! 🔥 资源分享:耗时200小时精选的「软件测试」资料包 🔥 教程推荐:火遍全网的《软件测试》教程 📢欢迎点赞 👍 收藏 ⭐留言 &#x1…

性能优化-OpenCL 介绍

「发表于知乎专栏《移动端算法优化》」 本文首先对 GPU 进行了概述,然后着重地对移动端的 GPU 进行了分析,随后我们又详细地介绍了 OpenCL 的背景知识和 OpenCL 的四大编程模型。希望能帮助大家更好地进行移动端高性能代码的开发。 🎬个人简介…

git本地分支的合并/切换分支时遇到的问题

目录 第一章、本地分支的切换测试1.1)切换之前的master分支下文件内容1.2)切换到develop分支后修改文件1.3)切回master分支出现报错: 第二章、解决方式2.1)方式1:commit提交修改2.2)方式2&#…

柔性数组和C语言内存划分

柔性数组和C语言内存划分 1. 柔性数组1.1 柔性数组的特点:1.2 柔性数组的使用1.3 柔性数组的优势 2. 总结C/C中程序内存区域划分 1. 柔性数组 也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中&#xff…

解决Git添加.gitignore文件后不生效的问题

1. 问题描述 如上图所示,在已存在.gitignore文件且已经提交过的Git管理的项目中,其中.class、.jar文件以及.idea目录内的内容全部都还是被Git管理了,可见.gitignore文件并没有生效。 2. 原因发现 .gitignore文件只能作用于 Untracked Files…

vue+elementUI el-select 中 没有加clearable出现一个或者多个×清除图标问题

1、现象:下方截图多清除图标了 2、在全局common.scss文件中加一个下方的全局样式noClear 3、在多清除图标的组件上层div加noClear样式 4、清除图标去除成功

iOS应用程序混淆加固原理及逆向工具介绍

概要 本文将介绍iOS应用程序混淆加固的原理和常见的加固类型,以及介绍一些常用的逆向工具。同时还会介绍一种代码虚拟化技术,用于进一步保护应用程序不被逆向分析。 引言 移动应用程序开发面临着越来越严峻的安全挑战,特别是在越狱设备上…

k8s1.27.2版本二进制高可用集群部署

文章目录 环境软件版本服务器系统初始化设置关于etcd签名证书etcd集群部署负载均衡器组件安装设置关于k8s自签证书自签CAkube-apiserver 自签证书kube-controller-manager自签证书kube-scheduler自签证书kube-proxy 自签证书admin 自签证书 控制平面节点组件部署**部署kube-api…

Uni-App三甲医院、医保定点三甲医院在线预约挂号系统源码

医院在线预约挂号系统是一种方便患者预约挂号的系统,患者可以通过该系统进行预约挂号,省去了到医院现场排队等待的时间,提高了就诊效率。随着医院信息化水平的不断发展,医院在线预约挂号管理系统已成为医院管理中不可或缺的一部分…