此文档只是记录,加深记忆所用
PE文件的基本概念
1、PE文件的框架结构图示
2、专有术语:
- 模块(Moudule):PE文件通过Windows加载器被装入内存后,内存中的版本就称为模块
- 模块句柄(hMoudule):映射文件的起始地址
- 基地址(ImageBase):初始内存地址
- 相对虚拟地址(RVA【Relative Virtual Address】):相对内存文件起始地址的偏移量
虚拟地址(VA)【也是实际的内存地址】= ImageBase + RVA
- 文件偏移地址(File Offset)& 物理地址(RVA Offset):PE文件存储在磁盘上时,某个数据位置相对于文件头的偏移量
分块解释PE文件的结构
MS-DOS头部
DOS头部由两部分组成:
- DOS_HEADER
- DOS_STUB
DOS头部可以判断是否为有效执行体
其中有两个字段很重要
1、e_magic :字段(一个字的大小)需要被设置为值5A4Dh 它的ASCII值为 “MZ”
2、e_lfanew: 其是真正PE文件头相对偏移(RVA),其指出真正的PE文件头的偏移位置,占用4个字节,位于文件偏移3Ch字节中】
注意:Intel CPU属于little-endian类 字符存储时,低位在前高位在后(磁盘里面的存储)
PE文件头
- PE Header 紧随DOS stub之后,是IMAGE_NT_HEADERS的简称
获取PE 文件头的指针
PNTHeader = ImageBase + dosHeader->e_lfanew
- IMAGE_NT_HEADER由三部分组成
- 1、Signature(PE文件标识)
在有效的PE文件里,Signature字段为00004550h,ASCII码字符是“PE00”,‘PE00’是文件头的开始,DOS中的e_lfanew就是指向“PE00”
- 2、IMAGE_FILE_HEADER(映像文件头)
其中包含PE文件中的一些信息,最重要的是指出了IMAGE_OPTIONAL_HEADER的大小
3、IMAGE_OPTIONAL_HEADER(可选映像头)
fileheader和optonalheader共同构成PE文件头结构
两个重要的对齐
1、SectionAlignment :当被装入内存时的区块对齐大小(ox1000h整数倍)
2、FileAlignment : 磁盘上PE文件内的区块对齐大小
SizeofImage: 映像装入内存之后的总尺寸
- 可从图中看出SectionAlignmen、FileAlignment的大小
- 也可以看出文件偏移与虚拟地址转换
Fie OffSet = RVA - 差值K
file offset = VA - imageBase - 差值K
- DataDirectory[]:数据目录表,有数个相同的IMAGE_DATA_DIRECTORY结构构成,指向输出表,输入表、资源块等数据
- PE文件中定位输出表、输入表和资源等重要数据时,就是从IMAGE_DATA_GIRTECTORY结构开始的。
区块表(Section Table)
区块表紧随IMAGE_NT_HEADERS之后,是一个IMAGE_SECTION_HEADER结构数组
其结构定义如下:
VirtualSize:指出实际的。被使用的区块的大小,是区块在没对齐处理前的实际大小
VirtualAddress:该块装载到内存中的RVA,其数值总是sectionalignment的整数倍
sizeofrawdata:该块在磁盘文件中所占大小
pointertorawdata:该块在磁盘文件中的偏移,这个字段用于给出原始数据在文件中的偏移【字母大小写忽略】
characteristics:块属性(代码/数据/可读/可写等
.text 默认的代码区块
.data默认的读/写数据区块,全局变量、静态变量一般放在这里
.rdata :默认的只读数据区块
.idata : 包含其他外来的DDL的函数及数据信息,即输入表
== .edata==: 输出表
.rsrc :资源
.reloc : 可执行文件的基址重定位
输入表(import Table 简称IT 也叫导入表)
- 可执行文件使用来自其他的DLL的数据或函数时,称为输入
- 输入函数(import function) : 就是被程序调用但其执行代码又不在程序中的函数,这些函数的代码是位于相关的DLL文件中,在调用者程序中只保留相关的函数信息,如函数名、DLL文件名等。对于在磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址,只有当PE文件被装入内存后,Windows加载器才将相关的DLL装入,并将调用函数的指令和函数实际所处的地址联系起来。.
- 调用DLL时有两种过程:
1、隐式链接,这个过程完全由WIndows加载器完成
2、显式链接,意味着必须确定目标DLL已经被加载,然后通过调用LoadLibrary和getprocaddress来寻找API的地址。 - 输入表地址(Import Address Table简称 IAT) :每一个被引入的API在IAT中都有它自己要保留的位置,在那里它将被Windows加载器写入输入函数的地址。
输入表结构
- 数据目录表中的第二成员指向输入表,第一成员指向输出表
- 输入表以一个IMAGE_IMPORT_DESCRIPTOR(简称IID)的数组开始
- 每个被PE文件隐式链接进来的DLL都有一个IID
- IDD结构如下:
Original FirstThunk(Characterristics):包含指向输入名称表(简称INT)的RVA
FirstThunk: 包含指向输入地址表(IAT)的RVA
输入地址表(IAT)
PE装载器首先搜索Original FirstThunk,如果找到,加载程序迭代搜索数组中的每个指针,找到每个IMAGE_IMPORT_BY_NAME结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由FirstThunk指向的IMAGE_THUNK_DATA数组中的元素。故此时,程序依靠IAT提供的函数地址就可以正常运行了啦。
输出表(Import Table)
- 当一个DLL函数能被另一个DLL文件或EXE使用时,它就被称为输出了(exported)。其中输出信息被保存在输出表中,DLL文件通过输出表向系统提供函数输出函数名、序号和入口地址等信息。
- 输出表是数据目录表中第一个成员,指向IMAGE_EXPORT_DIRECTORY(简称IED)
- 相关术语解释如下:
基址重定位
差值 = 870000h-400000 = 470000(加载器会比较基址和实际装入地址1,计算出一个差值)
402000 + 470000 = 872000
403030 + 470000 = 873030
基址重定位结构定义
- 基址重定位表(Base Relocation Table)位于 .reloc的区块 中,通过IMAGE_DIRECTORY_ENTRY_BASERELOC条目来找到它们。
- IMAGE_BASE_RELOCATION结构由三部分构成
1、VirtualAddress是这一组重定位数据开始的RVA地址。各重定位项的地址加上这个值才是该重定位项完整的RVA地址。
2、SizeofBlock 是当前重定位结构的大小,因为VirtualAddress和SizeofBlock 的大小都是固定的·4个字节,故这个项减去8,就是Typeoffset的大小。
3、Typeoffset是一个数组,数组每一项的大小为两个字节,共16位。又分为高4位(代表重定位类型),低12位(重定位地址)加上VirtualAddress即是指向PE映像中需要修改的的地址数据的指针。
实例分析