重定位表简介

重定位表是在一个PE中的代码被加载到任意一个内存地址时,用来描述相关操作数的变化规律的数据结构。通过重定位技术,代码运行在内存任意位置时,可以避免因操作数的定位错误而导致失败

第一个重定位表的地址位于IMAGE_DATA_DIRECTORY的第6个成员, 重定位表结构如下

1
2
3
4
5
6
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//重定位块RVA
DWORD SizeOfBlock; //以字节为单位,重定位块的大小
//WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

SizeOfBlock标识了重定位块的大小(包含重定位表),每个重定位块以IMAGE_BASE_RELOCATION结构开始,数量不固定且连续储存,以全0的VirtualAddress为结束

重定位表的作用

程序被装入内存时,其基址由IMAGE_OPTIONAL_HEADER.ImageBase决定(默认为0x40000000),但是如果该位置被别的程序使用,那么操作系统就会重新分配一个基址,这时就需要对所有的重定位信息进行修正

比如:某个DLL中有一些全局变量,编译时全局变量的地址会被编译成绝对地址(ImageBase+RVA),但是EXE加载DLL的时候,DLL中给定的ImageBase可能已被占用,导致该变量的VA无效,此时就需要重定位技术了,因为不管VA怎么变,RVA是永远不变的。

特别说明

  1. 一般情况下,EXE都是可以按照ImageBase的地址进行加载的,因为EXE拥有独立4GB空间,但DLL不是,DLL是当EXE使用它,才加载到相关EXE的进程空间
  2. 为了提高搜索速度,模块间地址也要对齐,模块地址对齐为0x10000,也就是64K

解析重定位表

  • 在重定位表后面的数据部分标识了需要重定位的地址

  • 由于直接寻址指令较多,所以在一些PE文件中存在大量需要修正的重定位地址,每个地址占4字节,如果有n个重定位项,那么需要4*n字节,这样做很浪费空间。重定位表使用基址+偏移的方式来储存需要重定位的RVA

  • 每个重定位块只储存一个物理页的地址,而一个物理页大小为4KB = 0x1000个字节 = 2^12个比特,所以每个需要重定位的地址至少需要2个字节来储存(至少12位二进制才能完全检索4KB的物理页)

  • 综上所诉2个字节16位剩余的高4位用来标记此地址是否需要重定位,如果为3(0b0011)则需要重定位,反之则不需要,说明这个数据是用来数据对齐的。

  • 现在可以理解为什么PE文件的可执行代码一般都是从0x1000开始,而不是PE的基址开始,如果从基址开始,也就是页面的起始地址0x0000,那么定义重定位表块的第一个VirtualAddress就是0,按照重定位块的定义,所有重定位表到此就没了。


遍历重定位表

手动查找太繁琐用代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#define FILEPATH TEXT("D:\\absolution\\temporary\\test.exe")
#define OUTFILEPATH TEXT("D:\\absolution\\temporary\\output.exe")
#include<PETool.h>
#pragma comment(lib,"PETool.lib")


int main() {
LPVOID lpFileBuffer = createFileBuffer(OUTFILEPATH);
PIMAGE_DOS_HEADER pDos =
(PIMAGE_DOS_HEADER)lpFileBuffer;
PIMAGE_NT_HEADERS pNT =
(PIMAGE_NT_HEADERS)((DWORD)lpFileBuffer + pDos->e_lfanew);
PIMAGE_FILE_HEADER pFile =
(PIMAGE_FILE_HEADER)((DWORD)pNT + 4);
PIMAGE_OPTIONAL_HEADER pOption =
(PIMAGE_OPTIONAL_HEADER)((DWORD)pFile + IMAGE_SIZEOF_FILE_HEADER);
PIMAGE_SECTION_HEADER pFirstSection =
(PIMAGE_SECTION_HEADER)((DWORD)pOption + pFile->SizeOfOptionalHeader);

/*重定位表的信息结构*/
IMAGE_DATA_DIRECTORY pReloc = (IMAGE_DATA_DIRECTORY)pOption->DataDirectory[5];

/*获取重定位表的文件偏移*/
PIMAGE_BASE_RELOCATION pRelocFileOffset =
(PIMAGE_BASE_RELOCATION)(RVA2FOA(lpFileBuffer, (DWORD)pReloc.VirtualAddress) + (DWORD)lpFileBuffer);

/*遍历所有重定位块 全0时结束*/
for (int i = 0; pRelocFileOffset->VirtualAddress; i++) {

printf("**********\n当前第%d个块\n", i + 1);

/*往后8个字节找到块数据的偏移*/
PWORD pwBlockOffset = (PWORD)pRelocFileOffset + 4;

/*IMAGE_BASE_RELOCATION占8个字节,一个地址项占2字节*/
DWORD dwItemCount = (pRelocFileOffset->SizeOfBlock - 8) / 2;

for (int o = 0; o < dwItemCount; o++) {
/*判断当前项的高4位是否为3*/
if ((*(pwBlockOffset + o) & 0x3000) == 0x3000) {
/*如果高4位为3,则后12位数据加上VirtualAddress得到需要修正的RVA*/
printf("RVA:0X%X\n", (*(pwBlockOffset + o)&0xFFF) + pRelocFileOffset->VirtualAddress);
}
}
printf("**********\n----------\n\n\n");

/*定位到下一个重定向表*/
pRelocFileOffset = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocFileOffset + pRelocFileOffset->SizeOfBlock);

}

free(lpFileBuffer);
getchar();

}

执行结果如下