重定位表(relocation table)是在可执行文件或动态链接库(DLL)中用于指示系统在加载时对代码和数据进行重新定位的数据结构。在可执行文件或 DLL 中,代码和数据通常是使用相对地址进行引用的。当将可执行文件或 DLL 加载到内存中时,如果加载地址与文件中的基地址不匹配,就需要对代码和数据进行重新定位,以确保引用的地址正确。
本节必须掌握的知识点:
重定位表
重定位表的结构与解析
重定位表的修改
6.1.1 重定位表
重定位表记录了需要进行重新定位的位置和相关的信息。在加载可执行文件或 DLL 时,系统会根据重定位表的信息来计算并更新代码和数据的地址,使其与实际加载地址相匹配。
重定位表通常由链接器在生成可执行文件或 DLL 时自动生成。它包含了需要进行重定位的节区、重定位类型和目标地址等信息。在加载时,操作系统会解析重定位表并根据其中的信息来进行重定位操作。
重定位表是可执行文件和 DLL 的重要组成部分,它确保了代码和数据在加载时能够正确地定位到正确的地址。对于编写和调试底层代码或需要手动进行加载的场景,了解和理解重定位表是很有帮助的。
■代码重定位
PE文件头中的ImageBase字段为PE建议OS操作系统将程序导入内存中的首地址,在加载多任务时,如果申请的ImageBase出现冲突,OS操作系统会重新分配程序导入的首地址。
举例
●例一:
对于某个dll,若ImageBase为0x2000000,0x2002000指向IAT表,并在程序中出现以下调用IAT中函数的代码:
call dword ptr [0x2002000]
call dword ptr [0x2002004]
...
当OS没有按ImageBase给定的基址导入,而是导入0x4000000时,会出现:0x4002000指向IAT表,而代码没有改变,因此需要修正。
OS建一张表格,将涉及ImageBase的代码操作数(全局变量或函数地址)加入到该表格中并修正(即加上0x2000000)。
●例二:
重定位表用于在程序加载到内存中时,进行内存地址的修正。假设一个简单程序test.exe需要三个动态链接库dll(a.dll,b.dll,c.dll),假设test.exe的ImageBase为400000H,而a.dll、b.dll、c.dll的基址ImageBase均为10000000H。那么操作系统的加载程序在将test.exe加载进内存时,直接复制其程序到400000H开始的虚拟内存中,接着一一加载a.dll、b.dll、c.dll。
假设先加载a.dll,如果test.exe的ImageBase + SizeOfImage + 1000H不大于10000000H,则a.dll直接复制到10000000H开始的内存中;当b.dll加载时,虽然其基址也为10000000H,但是由于10000000H已经被a.dll占用,则b.dll需要重新分配基址,比如加载程序经过计算将其分配到12000000H的地址。c.dll同样经过计算将其加载到15000000H的地址。
但是b.dll和c.dll中有些地址是根据ImageBase固定的,被写死了的,而且是绝对地址不是相对偏移地址。比如b.dll中存在一个call 0x10034560,这是一个绝对地址,其相对于ImageBase的地址为δ = 0x10034560 – 0x10000000 = 0x34560H;而此时的内存中b.dll存在的地址是12000000H开始的内存,加载器分配的ImageBase和b.dll中原来默认的ImageBase(10000000H)相差了2000000H,因此该call的值也应该加上这个差值,被修正为0x12034560H,那么δ = 0x12034560H – 0x12000000H = 0x34560H则相对不变。如果call的地址不修正,会导致call指令跳转的地址不是实际要跳转的地址,获取不到正确的函数指令,程序则不能正常运行。
6.1.2 重定位表的结构与解析
IMAGE_BASE_RELOCATION 是 Windows PE 文件格式中的一个数据结构,用于指示系统在加载可执行文件或动态链接库(DLL)时对代码和数据进行重定位。它包含在可执行文件或 DLL 的重定位节区(.reloc)中。
■IMAGE_BASE_RELOCATION 结构:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 需要进行重定位的代码或数据的虚拟地址
DWORD SizeOfBlock; // 该重定位块的大小
// 具体的重定位项
WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
●IMAGE_BASE_RELOCATION 结构包含以下字段:
1.VirtualAddress:需要进行重定位的代码或数据的虚拟地址。这个地址是相对于基地址(加载时的起始地址)的偏移量,用于定位需要进行重定位的位置。
重定位项占用空间字节数的算法:
32位PE文件
Sum=2*n+4+4 ;2*n个地址+4字节的页面起始RVA+4字节的本页的重定位项个数
2表示每个重定位地址占用两个字节的空间,其中高四位为标记值,低12位为页内偏移地址。
64位PE文件
Sum=2*n+4+4 ;2*n个地址+4字节的页面起始RVA+4字节的本页的重定位项个数
8表示每个重定位地址占用四个字节的空间,其中高四位为标记值,低12位为页内偏移地址。
2.SizeOfBlock:该重定位块的大小,包括了所有的重定位项。它指示了该块的长度,以便在解析时快速定位到下一个重定位块。
该结构体有两个成员:一个是地址,一个是大小。一个重定位表的字节数由SizeOfBlock表示,(不同块的SizeOfBlock大小不一)。每一个块记录了1000H即4KB大小的内存中需要重定位信息的地址(一页大小)。
实际项数 = (SizeOfBlock - 8) / 2 ;8为sizeof IMAGE_BASE_RELOCATION 的字节数。
3.重定位项地址以VirtualAdress为该页的基址,偏移地址占两个字节(1000H最多需要12bit即可:0~FFFH)。所以两个字节的低12位为偏移地址,而高4位就是一个标记,在32位PE文件中,当此标记为0011(3)时低12为才有效,否则该2个字节可能是为了对齐而产生的,并且为对齐而产生的字节其值全为0。
在64位系统中,这个标记值为10。由于重定位表的SizeOfBlock大小不确定,新的Block的重定位信息的结构体接着上一个Block,4字节对齐后开始,而当出现一个_IMAGE_BASE_RELOCATION结构体的值全为0时,表明重定位表结束。
这里只提出一部分比较短的Block信息,可以很明显地看到当需要重定位信息的记录长度是4Byte的倍数时,不存在高四位为0000的情况,当其不为4的倍数时,就有一个因对齐而产生的数据。
●SizeOfBlock高四位的含义
如下表所示:
值 |
常量符号 |
含义 |