PE文件结构学习笔记

PE 的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。"portable executable"(可移植的执行体)意味着此文件格式是跨win32平台的 即使Windows运行在非IntelCPU上,任何win32平台的PE装载器都能识别和使用该文件格式。所有 win32执行体 (除了VxD和16位的Dll)都使用PE文件格式,所以熟知PE结构有助于对操作系统的深刻理解



   图1 PE文件结构框架


上图是 PE文件结构的总体层次分布。所有的PE文件都要以一个简单的DOS MZ header开始。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ header 之后的 DOS stubDOS stub实际上是个有效的 EXE,在不支持 PE文件格式的操作系统中,它将简单显示一个错误提示。

DOS stub后面就是PE header, PE header PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ header 中找到 PE header 的起始偏移量。因而跳过了 DOS stub 直接定位到真正的文件头 PE header

 PE header 接下来的数组结构 section table(节表)。 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。PE文件的真正内容划分成块,称之为sections(节)。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。节的划分是基于各组数据的共同属性而不是逻辑概念。有了节表,就能定位节。

对PE的物理结构有了大致了解之后,再大致了解一下装载PE的文件的步骤:

  1. PE文件被执行,PE装载器检查 DOS MZ header 里的 PE header 偏移量。如果找到,则跳转到 PE header
  2. PE装载器检查 PE header 的有效性。如果有效,就跳转到PE header的尾部。
  3. 紧跟 PE header 的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
  4. PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(引入表)逻辑部分。

1 PE的基本概念

1.1 DOS头

DOS MZ header 又命名为 IMAGE_DOS_HEADER.。其中只有两个域比较重要: e_magic 包含字符串"MZ",e_lfanew 包含PE header在文件中的偏移量。

       图2 DOS MZ header结构


1.2 DOS 存根

接下来的DOS stub实际上是个EXE,当当前系统不支持PE文件结构时它能输出一个错误提示“This program requires Windows”


1.3 PE header

PE头是一个IMAGE_NT_HEADERS类型的结构,下面是这个结构在WINNT.H中的定义:
typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature是一个标志变量,这个就是当我们判断一个文件是否是PE文件时第二步需要判断的,若这个值等于"PE\0\0"时就是一个PE文件。

第二个成员是一个IMAGE_FILE_HEADER类型的对象,通常我们叫它映像文件头,这个结构在头文件中的定义是:
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

这里对比较重要的成员做出说明,NumberOfSections这个成员保存了节的数目,SizeOfOptionalHeader这个成员保存了PE头中OptionalHeader这个成员的大小,最后一个成员Characteristics是一个关于文件的标记,即这个文件是EXE还是DLL文件。

1.4  OptionalHeader

它是一个IMAGE_OPTIONAL_HEADER32类型的对象,这个结构的定义是:


术语--RVA 代表相对虚拟地址(Relative virtual address)。

可选头中比价重要的有:

AddressOfEntryPoint: 

PE装载器准备运行的PE文件的第一个指令的RVA。若您要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。

ImageBase PE文件的优先装载地址。比如,如果该值是400000h,PE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地

址区域已被其他模块占用,那PE装载器会选用其他空闲地址。 

SectionAlignment:
   内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,

则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。 

FileAlignment:

文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,

则下一节必定位于偏移量400h: 即使偏移量512和1024之间还有很多空间没被使用/定义。

MajorSubsystemVersion
MinorSubsystemVersion :
    win32子系统版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感。 

SizeOfImage:
   内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。

SizeOfHeaders:

 所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量。 

Subsystem:
    NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI 和 Windows CUI (控制台)。 
DataDirectory:
    一IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等。非常重要!!


1.5 section table

节表就相当于书的目录,而书中的各个章节就相当于PE文件结构中的节,通过目录我们能很快找到书中我们感兴趣的内容,同样

通过节表我们很快能找到PE文件中的各个节。(注意:多个数据只要是具有共同属性我们就能把它放在同一节

Name: 这个成员是一个字节型的数组,这就是节的名字,不过这个数组的上限是8,最多只能保存8个字符,还有就是它不是一个ASCIIZ字符串,因为它不是以null结尾的。

VirtualAddress:
      本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。

PointerToRawData:
     这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 

Characteristics:
     包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。


1.6 输入表

可执行文件使用来自于其他dll的代码或数据时,称为输入。当PE文件装入时,Windows加载器的工作之一就是定位所有被输入的函数和数据,并且让正在被装入的
PE文件可以使用那些地址。这个过程是通过PE文件的输入表(Import Tab 也称之为导入表)完成的,输入表中保存的是函数名和其驻留的dll名等,动态连接所需输信息,
输入表在软件外壳技术上的地位十分重要,因此在研究外壳的技术时一定要掌握这部分知识。

IMAGE_NT_HEADER->IMAGE_OPTIONAL_HEADER32->IMAGE_DATA_DIRECTORY的第二个成员指向输入表,输入表以一个IMAGE_IMPORT_DESCRIPTOR

(简称IDD)开始,(IID) IMAGE_IMPORT_DESCRIPTOR的结构包含如下5个字段: 

OriginalFirstThunk, TimeDateStamp, ForwarderChain, Name, FirstThunk 


OriginalFirstThunk (INT)
该字段是指向一32位以00结束的RVA偏移地址串,此地址串中每个地址描述一个输入函数,它在输入表中的顺序是不变的。 

TimeDateStamp 
一个32位的时间标志,有特殊的用处。 

ForwarderChain 
输入函数列表的32位索引。 

Name 
DLL文件名(一个以00结束的ASCII字符串)的32位RVA地址。 

FirstThunk (IAT)
该字段是指向一32位以00结束的RVA偏移地址串,此地址串中每个地址描述一个输入函数,它在输入表中的顺序是可变的。



1.7 输出表

当PE装载器执行一个程序,它将相关DLLs都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关DLLs中的真实函数地址来修正主程

序。PE装载器搜寻的是DLLs中的引出函数。

输出表结构如下:

输出表的设计是为了方便PE装载器工作。首先,模块必须保存所有输出函数的地址以供PE装载器查询。模块将这些信息保存在AddressOfFunctions域指向的数组中,而数组元素数目存放在NumberOfFunctions域中。 因此,如果模块引出40个函数,则AddressOfFunctions指向的数组必定有40个元素,而NumberOfFunctions值为40。PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同。



1.8 基址重定位

在32位代码中,涉及到直接寻址的指令都是需要重定位的。对操作系统来说,其任务就是在对可执行程序透明的情况下完成重定位操作。

在现实中,重定位信息是在编译的时候由编译器生成并保留在可执行文件中的,在程序被执行前由操作系统根据重定位表信息修正代码,这样在开发程序的时候就不用考虑重定位问题了。重定位信息在PE文件中被存放在重定位表中。在PE文件没有被加载到预期的imagebase位置时,PE加载器根据下面的重定位表来确定哪些指令数据是需要修改的。
重点就是重定位表的结构和使用方法了。



指令(数据)的指针 = (数据项低12位+VirtualAddress)+ PE文件实际被映射的基址
重定位结果 = 需要重定位数据+( PE文件实际被映射的基址-ImageBase)



1.9 资源

资源一般使用树来保存,通常包含3层,在NT下,最高层是类型,然后是名字,最后是语言。 
一个PE文件是否包含资源文件,通常检测块表(Section Table)中是否含有'.rsrc',不过这个方法对有些PE文件无效。


1.10 TLC

线程本地存储TLS(Thread Local Storage)。TLS的作用是能将数据和执行的特定的线程联系起来。实现TLS有两种方法:静态TLS和动态TLS。





第一次写博客,排版什么都比较随意,发现把学过的东西记录下来确实学起来效率高一点。

由于我也是第一次真正接触PE,所以有些概念理解的不是很准确,虽然现在水平还不是很高,但尽力了就行了,希望大家多多包含,有时间大家可以看看原版。


最后附上一张简略的PE结构图:

-------------*-------------------------------------------------*
                 | DOS Header(IMAGE_DOS_HEADER) | -->64 Byte
DOS头部  --------------------------------------------------
                 | DOS Stub                                            | -->112 Byte
-------------*-------------------------------------------------*
                 | "PE"00 (Signature)                              | -->4 Byte
                  -------------------------------------------------
                 | IMAGE_FILE_HEADER                        | -->20 Byte
PE文件头 --------------------------------------------------
                 | IMAGE_OPTIONAL_HEADER32          | -->96 Byte
                 ---------------------------------------------------
                 | 数据目录表                                           | -->128 Byte
-------------*--------------------------------------------------*
                 | IMAGE_SECTION_HEADER                 | -->40 Byte
                 ---------------------------------------------------
   块表       | IMAGE_SECTION_HEADER                 | -->40 Byte
                  --------------------------------------------------
                 | IMAGE_SECTION_HEADER                 | -->40 Byte  
-------------*--------------------------------------------------*
                 |.text                                                       | -->512 Byte
                 ---------------------------------------------------
    块          |.rdata                                                   | -->512 Byte
                 ---------------------------------------------------
                 |.data                                                     | -->512 Byte
-------------*-------------------------------------------------*
                 | COFF行号                                            | -->NULL
                 ---------------------------------------------------
调试信息 | COFF符号表                                         | -->NULL
                 ---------------------------------------------------
                 | Code View 调试信息                             | -->NULL
-------------*--------------------------------------------------*
--------->>>摘自互联网



参考书籍:《加密与解密》、《逆向工程核心原理》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值