本文主要是介绍PE格式系列_0x05:输出表和重定位表(.reloc),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
提示:PE文件的格式就先介绍这么多,不建议继续学习重定位表和输出表了,因为边际收益在下降;其实学会了输入表的手动分析,重定位表和输出表的分析是类似的,也要简单很多
只要知道当发生基址重定位时会涉及到重定位表,dll会有一份输出给别人用的函数列表涉及到输出表就可以了,用到哪个再系统学一下就ok了
建议是好的,但是不写完这部分总感觉烂尾了,所以花了点业余时间将几个重要的表知识整理了一下
文章目录
- 1.输出表
- 2.基址重定位表
- 3.编写PE格式展示工具
- 4.参考
1.输出表
输出表简单理解就是一个表格,记录输出函数的信息
// Export Format
typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD Characteristics; //未使用,总是0DWORD TimeDateStamp;WORD MajorVersion;WORD MinorVersion;DWORD Name; //DLL的真实名称,如ntdll.dllDWORD Base; //基数,序号减去这个基数就是函数地址数组(EAT)的索引DWORD NumberOfFunctions; //EAT相关的DWORD NumberOfNames; //ENT相关的DWORD AddressOfFunctions; // RVA from base of image,指向函数地址数组(地址数组,EAT)DWORD AddressOfNames; // RVA from base of image,函数名字的指针地址(名字数组,ENT)DWORD AddressOfNameOrdinals; // RVA from base of image,指向输出序号数组
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
流程:PE装载器访问PE文件输出表时,通过AddressOfFunctions
获取一个函数的地址,通过AddressOfNames
获取一个函数的名称,但是此时还没有建立对应关系,通过AddressOfNameOrdinals
来建立名称和地址的联系
注意:一个名称只有一个地址,但是一个地址可以关联多个名称(类似于别名)
示例1:DllDemo中只有一个导出函数MsgBox
#1.查看加载基址
0:006> lmvm DllDemo
start end module name
004c0000 004c6000 DllDemo (deferred) #2.查看输出表相关信息汇总
0:006> !dh DllDemo -e_IMAGE_EXPORT_DIRECTORY 004c4000 (size: 00000045) #绝对地址Name: DllDemo.DLL Characteristics: 00000000 Ordinal base: 1. Number of Functions: 1. Number of names: 1. EAT: 004c4028.ordinal hint target name1 0 004C1008 MsgBox#3.dump内存,根据数据格式手动分析(节选)
0:006> dd /c 1 004c4000
004c400c 00004032 #Name,da 00004032+004c0000可直接查看dll名称
004c4010 00000001 #Base
004c4014 00000001 #NumberOfFunctions
004c4018 00000001 #NumberOfNames
004c401c 00004028 #EAT
004c4020 0000402c #ENT
004c4024 00004030 #AddressOfNameOrdinals
#查询dll名称
0:006> da 00004032+004c0000
004c4032 "DllDemo.DLL"
#查询EAT
0:006> dd 00004028+004c0000 L1
004c4028 00001008 #得到的是RVA
#查询ENT
0:006> dd 0000402c+004c0000 L1
004c402c 0000403e #得到的是RVA
0:006> da 0000403e++004c0000
004c403e "MsgBox"
使用PE工具查看输出表
示例2:kernel32.dll的输出表汇总查看
0:006> !dh kernel32 -e
_IMAGE_EXPORT_DIRECTORY 77382d30 (size: 0000dc14)
Name: KERNEL32.dll
Characteristics: 00000000
Ordinal base: 1.
Number of Functions: 1607.
Number of names: 1607.
EAT: 77382d58.ordinal hint target name4 0 AcquireSRWLockExclusive5 1 AcquireSRWLockShared6 2 77310AC0 ActivateActCtx7 3 77310400 ActivateActCtxWorker
...
2.基址重定位表
通常重定位表只有dll文件和开了ASLR的exe才需要关心,因为此时不能保证加载时默认的装载地址不会被其他模块占用
- 如果没有被占用,就没重定位什么事了
- 如果被占用,就需要重定位表对需要重定位的数据进行修正
简单理解,对于PE加载器来说,他不关心需要修正的地址的具体用途,只要知道有一组需要修正的数据即可
原理:比如基址默认是ImageBase,一个地址相对于ImageBase的偏移是B;加载到内存中的真正基址是ImageBaseNew,则B修正后有如下公式:
B_new = (ImageBaseNew - ImageBase) + B
下面是重定位的示意图
在PE文件中,重定位表一般单独作为一个section,用.reloc
表示
基址重定位数据采用类似按页分割的组织方式,是由许多重定位块串联成的,有如下要求:
-
每个重定位块:大小都是4KB的重定位信息(
_IMAGE_BASE_RELOCATION
是每个块的起始结构)结束标志:
VirtualAddress
为0
的一个_IMAGE_BASE_RELOCATION
结构 -
每个重定位数据:必须是以4 bytes对齐的,TypeOffset数组记录要重定位的地址
结束标志:SizeOfBlock会指定大小
// Based relocation format.
typedef struct _IMAGE_BASE_RELOCATION {DWORD VirtualAddress; //重定位数据的开始RVA地址,需要重定位地址要加上这个值DWORD SizeOfBlock;
// WORD TypeOffset[1]; //重定位项数组,高4位(重定位类型) + 低12位(重定位地址)
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
其中:TypeOffset描述的是每个重定位项的数组,定义中是一个可变数组,高4位的类型如下
// Based relocation types.
#define IMAGE_REL_BASED_ABSOLUTE 0 //什么也不做,存在是为4 bytes对齐和标识TypeOffset数组结束
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3 //x86文件都是这种类型,重定位指向的整个地址都需要修正
3.编写PE格式展示工具
如果写过壳,会发现写PE分析工具很简单;要充分利用微软的ImageHlp库提供的操作PE Image的函数,编写思路:
- 1.检查文件格式
- 2.读取File Headers和Optional Headers的内容
- 3.得到数据目录表信息
- 4.得到区块表信息
- 5.得到输出表、输入表等结构信息
下面是一个简单实现的PE查看器截图:
简单使用StudPE验证写的PE工具的正确性,基本信息获取都是正确的
4.参考
- 1.《加密与解密》第11章 PE文件格式
- 2.《逆向工程核心原理》第13章 PE文件格式
这篇关于PE格式系列_0x05:输出表和重定位表(.reloc)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!