PE文件结构简述

简述

PE文件结构分为五个部分:

  • DOS文件头

  • DOS加载模块

  • PE文件头

  • 区段表

  • 区段
    PE文件描述

  • PE文件中显示的所有地址基本都是RVA,倘若想要换算成VA一般需要加上基地址ImageBase与文件偏移地址RVA。
  • 虚拟地址(VA=virtual address):每个32位PE文件被加载到内存的时候都会被分配4GB的虚拟内存,虚拟地址等于“程序的相对虚拟地址+基地址”。
  • 相对虚拟地址(RVA=relatively virtual address):相对虚拟地址是当PE文件被展开式相比较于文件开头的地址,这个地址加上imagebase就是真正的虚拟地址。
  • 程序入口点EOP:程序开始执行的地址。

Dos头

Dos头的特征是4D5A也就是MZ开头。这里面比较有用的信息是最后两个字节(Dos头只有64字节)。这里借用知乎上的一张图来表示,图中描绘的已经很清楚。
在这里插入图片描述
需要了解的就是开头两个字节跟结尾是个字节,最后是个字节代表PE头的RVA,且如果是合法的PE文件,则PE文件头的开始数据一定是5045,如上图所示,Dos头的最后是个字节代表PE文件头的RVA是000000F0至于为什么是倒着读的,是因为小端存储的原因,跟cpu的架构有关系。

这边简单说一下小端存储,具体用法就是,如果你想写在程序中的数据为 1 2 3 4,那么你必须先传4 再传3 直到1,这样子计算机读取出来的数据才是1234,如上图,我们必须传入 f0 00 00 00 ,计算机读出来的数据才能是000000f0。

其实Dos头中还有其他的数据,不过日常中不怎么用到,所以在这里不做过多的阐述。


PE头

windows环境中会直接跳过前两个部分直接从PE文件头开始,所以直接略过这两个部分。PE文件头大小为224字节左右,因为有个可选头所以实际长度不定。里面包含许多关于PE文件的整体信息,这些信息主要是描述PE文件的大概情况,但是更细节信息在区段表中。
在这里插入图片描述
如上图所示,PE文件头分为三个结构,第一个为签名字段,第二个为文件头字段,第三个为可选头字段。

signature字段

在一个PE文件中Signature字段被设置为4550h,ASCII码为”PE00“。

image_file_header字段

structIMAGE_FILE_HEADER
{
WORD Machine;//运行平台
WORD NumberOfSections;//区块表的个数
DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数
DWORD PointerToSymbolicTable;//指向符号表的指针
DWORD NumberOfSymbols;//符号表的数目
WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0
WORD Characteristics;//文件的属性值
}

image_optional_header字段

typedefstruct_IMAGE_OPTIONAL_HEADER
{
+18h WORD Magic;// 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion;// 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion;// 链接程序的次版本号
+1Ch DWORD SizeOfCode;// 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData;// 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData;// 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint;// 程序执行入口RVA
+2Ch DWORD BaseOfCode;// 代码的区块的起始RVA
+30h DWORD BaseOfData;// 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
+34h DWORD ImageBase;// 程序的首选装载地址
+38h DWORD SectionAlignment;// 内存中的区块的对齐大小
+3Ch DWORD FileAlignment;// 文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion;// 可运行于操作系统的主版本号
+46h WORD MinorImageVersion;// 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion;// 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion;// 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage;// 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders;// 所有头 + 区块表的尺寸大小
+58h DWORD CheckSum;// 映像的校检和
+5Ch WORD Subsystem;// 可执行文件期望的子系统
+5Eh WORD DllCharacteristics;// DllMain()函数何时被调用,默认为 0
+60h DWORD SizeOfStackReserve;// 初始化时的栈大小
+64h DWORD SizeOfStackCommit;// 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve;// 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit;// 初始化时实际提交的堆大小
+70h DWORD LoaderFlags;// 与调试有关,默认为 0
+74h DWORD NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

这里大多数选项都很重要,例如数据段代码段入口RVA,或者说内存中区块对齐大小与文件中区块对齐大小imagebase等等。下面给出几个建议熟悉的选项:

入口点EOP                #一般跟base of code值一样
基地址Imagebase      #一般为00400000
SectionAlignment      #一般为 0x1000
fileAligment                #一般为0x0200

重点说一下下面这个选项也就是数据目录表:

数据目录表,保存了各种表的RVA及大小。
来看一下数据目录的定义:
IMAGE_DATA_DIRECTORY STRUCT
{
VirtualAddress DWORD ? ; 数据的起始RVA
Size DWORD ? ; 数据块的长度
IMAGE_DATA_DIRECTORY ENDS
}
数据目录表十分重要,例如下面的前两项,就可以到具体的位置去看程序从dll文件中导入了什么函数。
在这里插入图片描述


区段表

区段表分为.text表、.data表、.rsrc表,区段表相当于区段的目录,里面包含着每个区段的信息,如区段名称、区段大小、区段基地址、区段偏移地址等。

.text:代码段,是在编译或汇编结束时产生的一种块,它的内容全部是指令代码。也有的编译器将该段命名为.code
.data:初始化的数据块,是初始化的数据块,包含那些编译时被初始化的变量、字符串
.idata:输入表,包含其他外来dll的函数和数据信息,也就是输入表,也有人称之为导入表。
.rsrc:资源数据块,包含模块的全部资源数据,如图标、菜单、位图等。
.reloc:重定位表,用于保存基址的重定位表。即当装在程序不能按照连接器所指定的地址装载文件是,需要对指令或已经初始化的变量进行调整,该块中也包含了调整过程中所需要的一些数据,如果装载能够正常装在则忽略此段中的数据。
.edata:导出表,是pe文件的输出表,以供其他模块使用,并不是每个pe文件都有此数据段,因为有的文件并不需要输出一些函数,该数据段常见于动态连接库文件中。
.radata:存放调试目录、说明字符串,该数据块并不常见主要是用于存放一些调试信息。
在这里插入图片描述
区段表的具体内容如下图所示:
在这里插入图片描述


区段

.text:存在可执行文件的二进制机器码,免杀主要战场;
.data:初始化的数据块,比如全局变量等
.idata:可执行文件所使用的动态链接库等外接函数与文件信息,如输入输出表信息
输入表:程序调用系统dll函数或第三方dll函数的信息
输出表:程序提供自身函数给第三方程序的信息
.rsrc:存放程序的资源,如图标、菜单等
在这里插入图片描述
这里面存储的是各个区段的具体二进制数据。


个人见解

PE文件感觉可以理解成是一堆汽车零件与一些说明书,Dos头与各种头信息与区段表就像是说明书,后面的各种区段就类似于一些零件。在PE文件没有被加载的时候,就类似于所有的零件被装在一个袋子里面,袋子里有着组装那些零件的说明书。

当PE文件被加载的时候,就类似于建造汽车中工人开始读这个说明书,先看是不是真的袋子里装的是汽车零件,这对应着计算机看Dos头与PE的signature段判断是否是合法的PE文件。

判断完成后,继续看说明书,对袋子里面的零件进行组装,直到组装好整个汽车。这个过程就类似于计算机根据PE的文件头与可选头与区段表,将区段里面的数据加载到指定的内存中,使得程序运行。

综上所述,各种头信息与区段表信息就是说明书,各个区段的数据就是零件。而PE文件就是装有零件与说明书的袋子。PE文件执行就类似于取出零件根据说明书建造具体的物件。


参考:https://zhuanlan.zhihu.com/p/47075612

猜你喜欢

转载自blog.csdn.net/qq_41874930/article/details/107762592
今日推荐