PE文件结构

PE文件结构

EP说:“快去学PE!”,遂有了这篇博客。

啥是PE?

如果说EP是EPsilon的话,PE就一定是nolisPE了罢......
好吧,PE全称Windows PE文件,他是Windows下可执行程序的一个统称。常见的有可执行程序(.exe),驱动(.sys),动态链接库(.dll)等。最初设计用来提高程序在不同操作系统上的移植性,但实际上这种文件格式仅用在 Windows 系列操作系统下。PE文件是指 32 位可执行文件,也称为PE32。64位的可执行文件称为 PE+ 或 PE32+,是PE(PE32)的一种扩展形式(请注意不是PE64)。
先来几张图助助兴:


基础知识

PE文件结构

一般分为以下四个部分:

  1. DOS头:是用来兼容 MS-DOS 操作系统的,目的是当这个文件在 MS-DOS 上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode. 还有一个目的,就是指明 NT 头在文件中的位置。
  2. NT头:包含 windows PE 文件的主要信息,其中包括一个 'PE' 字样的签名,PE文件头(IMAGE_FILE_HEADER)和 PE可选头(IMAGE_OPTIONAL_HEADER32)。
  3. 节表:是PE文件后续节的描述,windows根据节表的描述加载每个节。
  4. :每个节实际上是一个容器,可以包含 代码、数据 等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义。

VA(虚拟地址)与RVA(相对虚拟地址)

当一个PE文件被加载到内存中以后,我们称之为"映象 "(image),一般来说,PE文件在硬盘上和在内存里是不完全一样的,被加载到内存以后其占用的虚拟地址空间要比在硬盘上占用的空间大一些,这是因为各个节在硬盘上是连续的,而在内存中是按页对齐的,所以加载到内存以后节之间会出现一些“空洞”。
因为存在这种对齐,所以在PE结构内部,表示某个位置的地址采用了两种方式:

  • 针对在硬盘上存储文件中的地址,称为原始存储地址或物理地址,表示距离文件头的偏移
  • 针对加载到内存以后映象中的地址,称为 相对虚拟地址(RVA),表示相对内存映象头的偏移

然而CPU 的某些指令是需要使用绝对地址的,比如取全局变量的地址,传递函数的地址,以及编译后的汇编指令中肯定需要用到绝对地址而不是相对映象头的偏移,因此PE文件会建议操作系统将其加载到某个内存地址(这个叫基地址。段地址其实就是一种基地址,但基地址并不等于就是段地址)。编译器便根据这个地址求出代码中一些 全局变量和函数的地址,并将这些地址用到对应的指令中。
因此可以得出VA = RVA + IMAGEBASE

DOS头

DOS头



DOS头目前有用的东西只有e_magic和e_lfanew(这个怎么读?e_了发new?)两个。

  • e_magic用来标志这是一个PE文件
  • e_lfanew用来标志NT头的偏移。

在010 Editor中可以看见e_lfanew的值是0x00000120(大部分情况为小端存储),而在00120h的地方恰好为NT头的起始位置。

DOS存根

DOS存根则是一段简单的DOS程序,主要用来输出类似“This program cannot be run in DOS mode.”的提示语句。即使没有DOS存根,程序也能正常执行。在程序运行了但没完全运行的时候(比如在64位的机器上运行16位的程序)输出字符串。

NT头


可以看出NT头分为三个部分,标识符、文件头、可选头。

标识符

固定死的PE..

文件头

可选头

别被名字骗了,它不可选,包含了一大坨东西。

数据目录表

同样包含一大坨,导入导出表较为重要。

导出表

导出表是用来描述模块(.dll)中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被记录在导出表中,这样通过GetProcAddress()函数就能动态获取到函数的地址。
函数导出的方式有两种:

  • 一种是按名字导出,
  • 一种是按序号导出。

这两种导出方式在导出表中的描述方式也不相同。

导入表

导入表在PE文件加载时,会根据这个表里的内容加载依赖的模块(.dll),并填充所需函数的地址。

重定位表

为了解决像call这样的使用VA的代码,在模块基址发生变化时还能准确定位而产生的表。步骤如下:

  1. 编译的时候由编译器识别出哪些项使用了模块内的直接VA,比如push、一个全局变量、函数地址,这些指令的操作数在模块加载的时候就需要被重定位。
  2. 链接器生成PE文件的时候将编译器识别的重定位的项纪录在一张表里,这张表就是重定位表,保存在 DataDirectory中,序号是IMAGE_DIRECTORY_ENTRY_BASERELOC。
  3. PE文件加载时,PE加载器分析重定位表,将其中每一项按照现在的模块基址进行重定位。

其他人的博客

EPsiloniPlayForSG两位学长的博客是跟着同一个视频学的。其中有一些非常骚的操作,像是自行添加一个节和代码,然后让程序先执行自己添加的内容。在读其他人的博客的时候听说有些壳和病毒就会单独加一个节什么的。按理说搞完理论知识确实应该实践一波的,但是我懒,不想实践,摆烂了......

博客抄完了,溜了~

posted @ 2023-01-12 19:48  Clovershrub  阅读(274)  评论(0编辑  收藏  举报