PE文件在重定位过程中会用到基址重定位表
向进程的虚拟内存加载PE文件(EXE/DLL/SYS)时,文件会被加载到PE头的ImageBase所指的地址处。若加载的是DLL(SYS)文件,且在ImageBase位置处已经加载了其他DLL(SYS)文件,那么PE装载器就会将其加载到其他未被占用的空间。这就涉及PE文件重定位的问题,PE重定位是指PE文件无法加载到ImageBase所指位置,而是被加载到其他地址时发生的一系列的处理行为。
使用SDK(Software Development Kit,软件开发工具包)或Visual C++创建PE文件时,EXE默认的ImageBase为00400000,DLL默认的ImageBase为10000000。此外,
使用DDK(DriverDevelopment Kit,驱动开发工具包)创建的SYS文件默认的ImageBase为10000。
如图:A.DLL
被加载到TEST.EXE进程的10000000地址处。此后,B.DLL
试图加载到相同地址(10000000)时,PE装载器将B.DLL加载到另一个尚未被占用的地址(3C000000)处
创建好进程后,EXE文件会首先加载到内存,所以在EXE中无须考虑重定位的问题。但是
WindowsVista之后的版本引入了ASLR安全机制,每次运行EXE文件都会被加载到随机地址,这样大大增强了系统安全性。
ASLR机制也适用于DLL/SYS文件。对于各OS的主要系统DLL,微软会根据不同版本分别赋予不同的ImageBase地址。同一系统的kernel32.dll、user32.dll等会被加载到自身固有的ImageBase,所以,系统的DLL实际不会发生重定位问题。
以 notepad.exe
为例,看看PE重定位时发生了什么,使用010查看可以发现其基址 ImageBase
是 01000000h
然后用OD运行
在ASLR 的机制下,发现程序被加载到了 00b60000
位置处
从图中指令可以看到,方框中进程的内存地址以硬编码形式存在。地址 B610FC
、b61100
是 .text
节区的IAT区域,地址 b6C0A4
是.data
节区的全局变量。每当在OllyDbg中重启notepad.exe(Restart(Ctrl+F2)),地址值就随加载地址的不同而改变。像这样,使硬编码在程序中的内存地址随当前加载地址变化而改变的处理过程就是PE重定位。
无法加载到 ImageBase
地址时,若未进行过PE重定位处理,应用程序就不能正常运行(因发生“内存地址引用错误”,程序异常终止)
原理很简单
基址重定位表地址位于PE头的DataDirectory
数组的第六个元素(数组索引I为5)
可以看到基址重定位表的地址为 RVA 2F000
上图的基址重定位表中罗列了硬编码地址的偏移(位置)。读取这张表就能获得准确的硬编码地址偏移。基址重定位表是 IMAGE_BASE_RELOCATION
结构体数组。
//
// Based relocation format.
//
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MIPS_JMPADDR 5
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define IMAGE_REL_BASED_IA64_IMM64 9
#define IMAGE_REL_BASED_DIR64 10
IMAGE_BASE_RELOCATION
结构体的第一个成员为 VirtualAddress
,它是一个基准地址(BaseAddress),实际是RVA值。第二个成员为SizeOfBlock,指重定位块的大小。
最后一项TypeOffset数组不是结构体成员,而是以注释形式存在的,表示在该结构体之下会出现WORD类型的数组,并且该数组元素的值就是硬编码在程序中的地址偏移。
010 Editor
显示的是文件中的原始数据,需要将 RVA 转换为文件偏移地址(FOA),所以要查看RVA 21000h
就看FOA 2AE00h
PEview可以直接看RVA
FOA(文件偏移地址) | 数据 | 注释 |
---|---|---|
2AE00 | 00001000 | VirtualAddress |
2AE04 | 00000150 | SizeOfBlock |
2AE08 | 3420 | TypeOffset |
2AE0A | 342D | TypeOffset |
2AE0C | 3436 | TypeOffset |
由 IMAGE_BASE_RELOCATION
结构体的定义可知,VirtualAddress
成员(基准地址的值为 1000h
,SizeOfBlock成员的值为 150h
。也就是说,表中显示的TypeOffest数组的基准地址(起始地址)为RVA 1000,块的总大小为150(这些块按照基准地址分类,以数组形式存在)。块的末端显示为0。TypeOffset值为2个字节(16位)大小,是由4位的Type与12位的Offset合成的。比如,TypeOffset值为3420,解析如表所示。
虽然010里面没有显示出
type
但是元数据里面是有的
类型(4位) | 偏移(12位) |
---|---|
3 | 420 |