PE(Portable Executable),既可移植的执行体。PE文件是Windows下使用的可执行文件格式。PE文件是指32位的可执行文件,也称PE32,64位称PE+或PE32+,是PE32的一种扩展形式(不是PE64)
种类 | 主扩展名 |
---|---|
可执行系列 | EXE、SCR |
库系列 | DLL、OCX、CPL、DRV |
驱动程序系列 | SYS、VXD |
对象文件系列 | OBJ |
OBJ(对象)文件之外的所有文件都是可执行的,DLL、SYS文件虽然不能在Shell里面直接运行,但可以通过其他方法(调试器、服务等)执行。
PE头中存储程序运行时需要的大量的信息,如何加载内存,从何处开始运行,运行中需要哪些DLL,多大的堆栈内存等,都会以结构体的形式存储在PE头中。
PE文件结构从上到下依次是:DOS头,NT头,节表以及具体的节表数据
从DOS头(DOS header)到节区头(Section header)是PE头部分,其下面的节区合称为PE体。
文件中使用偏移(offset),内存中使用VA(Virtual Address,虚拟地址)来表示文件位置。
文件内容一般分为代码节(.text)、数据节(.data)、资源节(.rsrc)。
各节区头定义了各节区在文件或内存中的大小、位置、属性等。
VA(进程虚拟内存的绝对地址)、RVA(Relative Virtual Address,相对虚拟地址)指从某个基准位置(ImageBase)开始的相对地址,VA和RVA满足换算关系:
VA=RVA+ImageBase
PE头内部大多以RVA形式存在。使用相对虚拟地址(RVA)可解决了重定位带来的无法访问的问题,只要相对于基准位置的相对地址没变就可以访问,重定位(PE文件(主要DLL)加载到进程虚拟内存的特定位置,该位置可能已经加载了其他PE文件(DLL),这时必须通过重定位将其加载到其他空白位置),如果用的VA,就访问不到了。
Dos头分为两部分,分别是“MZ头部”和“DOS存根”。MZ头部时真正的Dos头部,开始处两个字节为“MZ”。
DOS头是用来兼容MS-DOS操作系统的,目的是当这个文件在MS-DOS上运行时提示一段文字,大部分情况下是:This program cannot be run in DOS mode.
Dos头还可以指明NT头在文件中的位置。Dos头的结束处标识了NT头的开始位置。
Dos头的结构体定义为IMAGE_DOS_HEADER。有两个重要成员:e_magic和e_lfanew
e_magic:DOS签名(4D5A=>ASCII“MZ”)
e_lfanew:指示NT头的偏移(不同文件可变)
一个叫Mark Zbikowski的开发人员在微软设计了DOS可执行文件,MZ取自其名字的首字母。PE规范,文件开始两个字节为4D5A,在0x00000030处的结尾的地方是e_lfanew的值。
DOS存根在DOS头的下方,大小不固定,即使没有DOS存根,文件也能正常运行,
NT头也是一个结构体:IMAGE_NT_HEADERS,由3个成员组成,第一个签名(Signature)结构体,值为50450000(PE),其余两个为文件头(File Header)和可选头(Optional Header)。真正的PE文件头是位于Dos头的后面的部分。
前面说了节区头中定义了各节区的属性,PE文件中的code(代码)、data(数据)、resource(资源)等按不同属性分类存储在不同的节区,那为什么要按照属性分不同的区呢?
把PE文件创建成多个节区是为了保证程序的安全性,若把data和code放在一个节区中相互纠缠,会引发很多问题,比如在向字符串data写数据时,由于某个原因移溢出了,导致覆盖了后面的code(指令),程序就会崩溃。
类别 | 访问权限 |
---|---|
code | 执行、读取权限 |
data | 非执行、读写权限 |
resource | 非执行、读取权限 |
PE文件从磁盘到内存映射,PE文件加载到内存时,每个节区都要能准确完成内存地值与文件偏移间的映射,方法:
(1)查找RVA所在节区
(2)使用简单公式计算文件偏移(RAW)
根据IMAGE_SECTION_HEADER结构体,换算公式如下:
RAW-PointerToRawData=RVA-VirtualAddress
RAW=RVA-VirtualAddress+PointerToRawData(文件偏移地址)
eg:RVA=5000时,File Offset=?
An:首先查找RVA值所在的节区。
->RVA5000位于第一节区(.text)(假设ImageBase为01000000)
RAW=5000(RVA)-1000(VirtualAddress)+400(PointerToRawData)=4400
IAT(导入地址表):Import Address Table 由于导入函数就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码位于一个或者多个DLL 中.当PE 文件被装入内存的时候,Windows 装载器才将DLL 装入,并将调用导入函数的指令和函数实际所处的地址联系起来(动态连接),这操作就需要导入表完成.其中导入地址表就指示函数 实际地址
IAT和导入表关系:https://blog.csdn.net/a893574301/article/details/77840874