重定位表简介
重定位表是在一个PE中的代码被加载到任意一个内存地址时,用来描述相关操作数的变化规律的数据结构。通过重定位技术,代码运行在内存任意位置时,可以避免因操作数的定位错误而导致失败
第一个重定位表的地址位于IMAGE_DATA_DIRECTORY
的第6个成员, 重定位表结构如下
1 2 3 4 5 6
| typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock;
} 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是永远不变的。
特别说明
- 一般情况下,EXE都是可以按照ImageBase的地址进行加载的,因为EXE拥有独立4GB空间,但DLL不是,DLL是当EXE使用它,才加载到相关EXE的进程空间
- 为了提高搜索速度,模块间地址也要对齐,模块地址对齐为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);
for (int i = 0; pRelocFileOffset->VirtualAddress; i++) {
printf("**********\n当前第%d个块\n", i + 1);
PWORD pwBlockOffset = (PWORD)pRelocFileOffset + 4;
DWORD dwItemCount = (pRelocFileOffset->SizeOfBlock - 8) / 2;
for (int o = 0; o < dwItemCount; o++) { if ((*(pwBlockOffset + o) & 0x3000) == 0x3000) { 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();
}
|
执行结果如下