复习可执行文件的文件结构——PE。PE文件是微软Windows操作系统上的程序文件,意为可移植的可执行的文件。PE的段头直接沿用的COFF 的段头结构。
1、PE的结构
DOS头: 是DOS命令窗口下可以执行,其实没有PE文件也是可以执行的(听说是老一辈习惯啦DOS命令下执行,就加上去啦)。
NT头: 是PE中最大的结构体啦,其中有签名,文件头和可选头。
节区头: 定义(代码,数据,资源等的大小,起始位置,权限等)
2、DOS头
DOS结构体
e_magic:所有PE开头都有DOS签名 “MZ”,这是以一个名叫Mark Zbikowski的DOS可执行文件的设计者首字母命名的
e_lfanew:指向NT头的位置,long类型,占4个字节。例:
3、NT头
NT结构体
第一个参数是: 一个PE标志。在一个有效的PE文件里,Signature字段被设置为00004550h。
第二个参数是: IMAGE_FILE_HEADER结构体。
第三个参数是: OptionalHeader结构体。
NT文件头
|
|
NT可选头结构体
|
|
一共31个字段成员,6个重要的
RVA也叫作OEP
AddressOfEntryPoint 持有EP 的RVA 值
基址
SizeOfHeader PE 头的大小
Subsystem 用来区分系统驱动文件与普通可执行文件。
DataDirectory数组
重点是最后一个成员IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
因为DataDirectory数组里保存了导入表(用了哪些dll),导出表,TLS(Thread Local Storage) Directory等RVA和大小的信息
倒数第二个变量决定NumberOfRvaAndSizes数组长度
在LoadPE工具中,文件头显示信息,如下:
DataDirectory数组
IMAGE_DATA_DIRCTORY结构如下:
一个是RVA,一个是大小
data directory数据目录在WINNT.H中定义为
在LoadPE工具中,数据目录显示信息,如下:
4、RVA和RAW
理解PE 最重要的一个部分就是理解文件从磁盘到内存地址的映射过程,做逆向的人员,只有熟练地掌握才能跟踪到程序的调用过程和位置,才能分析和寻找漏洞。
对于文件和内存的映射关系,其实很简单,他们通过一个简单的公式计算而来:
换算公式是这样的:
RAW - PointToRawData(磁盘文件中节区起始位置) = RVA(相对虚拟地址) - VirtualAddress
寻找过程就是先找到RVA 所在的段,然后根据公式计算出文件偏移。因为我们通过逆向工具,可以在内存中查找到所在的RVA,进而我们就可以计算出在文件中所在的位置,这样,就可以手动进行修改。
VA与RVA公式是这样的:
RVA = VA(虚拟地址) - ImageBase(基址)
结果:
RAW = VA - ImageBase - VirtualAddress + PointerToRawData
比如:
VA=0x003A20F4 , ImageBase =0x003A0000
可以看到0x20F4地址位于VirtualAddress 为0x2000的.rdata节,偏移为0x20F4 - 0x2000 = 0xF4
观察节表,.rdata的PointerToRawData为0xE00,字符串在磁盘中的地址为0xE00 + 0xF4 = 0xEF4
使用公式:
RAW = VA - ImageBase - VirtualAddress + PointerToRawData = 0x003A20F4 - 0x003A0000 - 0x2000 + 0xE00 = 0xEF4
用winhex打开二进制文件
5、IAT与EAT
IAT
一个普通PE文件的运行往往需要导入多个库文件,在PE文件运行时如何找到库文件中函数的准确入口是程序正确运行的保证。IAT就是提供这样保证的一个机制。IAT总得来说是一张表,表内存储着每个库文件函数在内存中的地址。
结构体
第一个成员是一个联合体:一般给出的是OriginalFirstThunk的值,这个值是INT的地址,INT(Import Name Table)是一个存储了库文件函数名称的表
第二个成员是时间戳
第三个成员是ForwarderChain
第四个成员是Name,存储的是库名称字符数组的地址
第五个成员是FirstThunk,存储的是IAT表的地址
第一步:
PE加载器读取结构体成员的值,Name成员找到库名称,然后将库文件加载到内存中来。
第二步:
PE加载器读取OriginalFirstThunk值获得INT地址,然后依次读取INT各项的值,根据函数的标号获取函数的地址
第三步:
根据FirstThunk的值获取IAT的地址,将上一步获得地址送入IAT中存储。
理解:读取IID(结构体)成员name获取库名->load(库)->读取IID的成员,获取INT的地址->读取函数名并获取地址->读取IID的成员,获取IAT的地址->将得到函数地址存入IAT中->重复直到INT为NULL
EAT
EAT对应的结构体为IMAGE_EXPORT_DESCRIPTOR,位置信息存储在可选头DataDirectory[0]中。
一般PE文件此项值应为0,代表不存在这个表项,只有库文件,才会含有这个表项。
结构体成员包括特征值,时间戳,版本信息等。重要的成员是Name,存储着库文件的名字;Base存储着函数标号从哪里开始;NumberOfFunctions存储着函数的数量;NumberOfNames存储着函数名称的数量(一般情况下这两项相同);AddressOfFunctions函数地址数组的首地址;AddressOfNames函数名称地址数组的首地址;AdressOfNameOrdinals,存储着函数标号的地址信息。
个人思考:记得以前学习的时候,把VA与VirtualAddress看成一个相同的,导致转化的时候很矛盾,现在再看,真是自己可以静心好好的学,再次理解IAT的运行机制。
参考:
https://www.jianshu.com/p/af9766222816
https://www.cnblogs.com/aguoshaofang/p/5021759.html