重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。
当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变长度数据的形式存储在一个或多个叫做重定位块(relocation block)的数据结构中。
解析重定位表需要通过PIMAGE_BASE_RELOCATION
这个关键结构体来实现,PIMAGE_BASE_RELOCATION
是一个指向重定位表(Relocation Table)的指针类型,它是Windows PE可执行文件中用于支持动态基地址重定位(Dynamic Base Relocation)
的结构体类型。在2GB以上的虚拟地址下,Windows使用了Dynamic Base Relocation
技术来提高系统的安全性,PIMAGE_BASE_RELOCATION
就是在这种情况下使用的。
由于Windows系统中DLL文件并不能每次都能加载到预设的基址上,因此基址重定位主要应用于DLL文件中,通常涉及到直接寻址的指令就需要重定位,重定位信息是在编译时,由编译器生成并被保存在可执行文件中的,在程序被执行前,由操作系统根据重定位信息修正代码,这样在开发程序的时候就不用了考虑重定位问题了,我们还是使用上面的这段汇编代码。
00D21000 | 6A 00 | push 0x0 |
00D21002 | 68 0030D200 | push main.D23000 |
00D21007 | 68 0730D200 | push main.D23007 |
00D2100C | 6A 00 | push 0x0 |
00D2100E | E8 07000000 | call <JMP.0x00D2101A> | call MessageBox
00D21013 | 6A 00 | push 0x0 |
00D21015 | E8 06000000 | call <JMP.0x00D21020> | call ExitProcess
00801017 | CC | int3 |
00D2101A | FF25 0820D200 | jmp dword ptr ds:[<&0x00D22008>] | 导入函数地址
00D21020 | FF25 0020D200 | jmp dword ptr ds:[<&0x00D22000>] | 导入函数地址
如上jmp dword ptr ds:[<&0x00D22008>]
这段代码就是一句需要重定位的代码,当程序的基地址位于0x00D20000
时,这段代码中的函数可以被正常调用,但有时程序会开启基址随机化,或DLL被动态装载等问题,此时基地址可能会发生变化,那么上面的汇编指令调用就会失效,这就意味着这些地址需要被修正。
此时我们假设程序基址变为了0x400000
,那么jmp dword ptr ds:[<&0x00D22008>]
这条指令就需要被修正,修正算法可以描述为,将直接寻址指令中的地址加上模块实际装入地址与模块建议装入地址之差,为了进行运算需要3个数据,首先是需要修正机器码地址,其次是模块建议装入地址,最后是模块的实际装入地址。
在这3个数据中,模块的建议装入地址已经在PE文件头中定义了,而模块的实际装入地址时Windows装载器在装载文件时确定的,事实上PE文件重定位表中保存的仅仅只是,一大堆需要修正的代码的地址。
重定位表IMAGE_BASE_RELOCATION解析
重定位表会被单独存放在.reloc
命名的节中,重定位表的位置和大小可以从数据目录中的第6个IMAGE_DATA_DIRECTORY
结构中获取到,该表的组织方式时以0x1000
页为一块,每一块负责一页,从PE文件头获取到重定位表地址后,就可以顺序读取到所有表结构,每个重定位块以一个IMAGE_BASE_RELOCATION
结构开头,后面跟着在本页中使用的所有重定位项,每个重定位项占用16字节,最后一个节点是一个使用0填充的_IMAGE_BASE_RELOCATION
标志表的结束,其结构如下所示:
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress; // 需重定位数据的起始RVA
DWORD SizeOfBlock; // 本结构与TypeOffset总大小
WORD TypeOffset[1]; // 原则上不属于本结构
} IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;
TypeO?set的元素个数 = (SizeOfBlock - 8 )/ 2 TypeO?set的每个元素都是一个自定义类型结构
struct
{
WORD Offset:12; // 大小为12Bit的重定位偏移
WORD Type :4; // 大小为4Bit的重定位信息类型值
}TypeOffset; // 这个结构体是A1Pass总结的
PIMAGE_BASE_RELOCATION指针指向PE文件中的重定位表(Relocation Table)的起始地址,重定位表是一个可变长度的数据结构,其中包含了一组以4个字节为单位的记录,每个记录表示一个需要修正的地址及其操作类型。
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
每个重定位表项(Relocation Table Entry)包括两部分:前16位表示需要修正的地址的偏移量(Offset),后16位则表示需要对该地址进行什么样的修正操作(Relocation Type)。普通的重定位项类型有如下几种:
- IMAGE_REL_BASED_ABSOLUTE:表示不需要进行任何修正;
- IMAGE_REL_BASED_HIGHLOW:表示需要将地址中的低16位和高16位分别进行修正;
- IMAGE_REL_BASED_DIR64:表示需要对64位指针进行修正;
当读者需要遍历这个表时,首先可以通过NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress
获取到重定位表的相对信息,并通过(PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA))
得到重定位表的FOA文件地址,在Reloc-