重定位表对于EXE文件是没有必要的,因为EXE程序是第一个被载入内存的模块。而DLL却是必须需要重定位信息的,因为DLL不一定加载到它默认的ImageBase上。重定位表作为一个单独的区块放到区块表中,其名字一般为.reloc。
PE文件通过将所有可能的修改的数值存放到一个数组里,以一种统一的方式进行修正。
每个重定位信息以一个IMAGE_BASE_RELOCATION开始,采用类似页分割的方式,由许多重定位块组成,每个块是4KB的大小。
IMAGE_BASE_RELOCATION结构如下
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//RVA
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION,* PIMAGE_BASE_RELOCATION;
#define IMAGE_SIZEOF_BASE_RELOCATION 8
VirtualAddress是要修正的数据的基地址。
SizeOfBlock是该重定位结构的大小,重定位结构的大小是不定的,在SizeBlock后面紧跟着的就是一个TypeOffset数组,一个大小是2字节,其中高4位代表修正的类型,低16位是修正的偏移。它的大小与VirtualAddress相加就是要修正的RVA地址。当出现一个_IMAGE_BASE_RELOCATION结构体的值全为0时,表明重定位表结束。
重定位是DATA_DIRECTORY的第6项。
TypeOffset高4位的可能取值:
IMAGE_REL_BASED_DIR64 出现在64位的PE文件中。对于x86而言,所有的基址重定位类型都是IMAGE_REL_BASED_HIGHLOW。并且在一组重定位信息结束的地方会出现IMAGE_REL_BASED_ABSOLUTE用于对齐操作。
下面来实战下。
跳到重定位开始处。
其VirtualAddress是0x1000,重定位结构的大小是0x10,也就是说,后面跟着的TypeOffset数组的个数是(0x10 - 2 * sizeof(DWORD)) / sizeof(TypeOffset),即4个。
重定位数据分别是
- 30 0f
- 30 23
- 00 00
- 00 00
很明显的看出最后两个是用于对齐而用的,所以真正有用的是前两个TypeOffset,他们的类型是3,说明类型是IMAGE_REL_BASED_HIGHLOW,所以我们知道了要修正的数据位置为0x100f,0x1023,换算成文件偏移也就是0x60f,0x623。
所以执行PE文件前,Loader会遍历重定位表,找到要修正的数据,然后用 (实际映像地址-默认基址)+该重定位数据地址。