1.简介
基址重定位表位于数据目录表中的第六个,它位于安全表的后面。
这个表的作用用来索引那些需要重定位的数据。程序的加载基址一般是0x400000,但是程序导入了众多的DLL,这些DLL的默认加载基址一般都是0x10000000,这就出现了一个问题,当第一个DLL加载到0x10000000处,第二个DLL就无法再加载到0x10000000处了,这时系统会自动分配一个加载基址给DLL文件。这就是需要重定位的根本原因,因为DLL的载入基址变了,有些绝对地址上的变量又不再正确了,需要进行重新定位。
重定位表中的项就是用来索引到需要重定位的数据处,通过一个公式计算出修复后的地址,数据就可以正确被访问了。
2.重定位表的解析
通过数据目录表查看到重定位表的RVA是5000h,转换成offset为0x1a00,我们找到0x1a00处:
我们对比重定位表的结构体来分析:
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
WORD TypeOffset[1];
}IMAGE_BASE_RELOCATION;
需要首先进行说明的是,重定位表的RVA指向的位置是一个数组,里面的元素都是IMAGE_BASE_RELOCATION,每个IMAGE_BASE_RELOCATION只负责4KB大小分页内的重定位信息。因此结构中的VirtualAddress总是0x1000的倍数。
VirtualAddress:需要重定位的数据的RVA,这个值需要加上后面的TypeOffset的低12位才是需要重定位数据的RVA,这么说可能不好理解,我们用本文使用到的程序作为例子,这是它的一部分汇编代码:
Offset HEX Assembly
10001000 55 push ebp
10001001 8BEC mov ebp,esp
10001003 6AFE push 0xFFFFFFFF
10001005 68 10220010 push 0x10002210
程序的ImageBase是0x10000000,VirtualAddress是0x1000,后面要提到的TypeOffset的低12位是0x006,有如下公式成立:
需要重定位的数据位置 = ImageBase + VirtualAddress + TypeOffset低12位
= 0x10000000 + 0x1000 + 0x006
这里计算得到的最终需要重定位的数据地址是0x10001006,我们注意看上面的代码,0x10001005处是push 0x10002210,也就是说push后面的地址0x10002210需要进行重定位。
假设有一个dll实际加载到了0x72ab0000,它的ImageBase却是0x10000000,系统会通过下面的公式进行重定位
重定位后地址 = (真实加载基址-默认加载基址) + 需要进行重定位的地址
= (0x72ab0000 - 0x10000000) + 0x10002210
SizeOfBlock:整个重定位表的大小,IMAGE_BASE_RELOCATION结构的总大小,对应上图中的0x118,实际上本文用到的程序有两个IMAGE_BASE_RELOCATION结构,它们的大小分别是0x118和0x24,合起来是0x13c,正好是数据目录表中标记的值。
TypeOffset:重定位的偏移,这个值加上IMAGE_BASE_RELOCATION中的VirtualAddress就是完整的RVA了,这个字段实际上并不属于IMAGE_BASE_RELOCATION结构体,但SizeOfBlock字段记录的大小包含了这个结构体,这个结构体通常有多个,按顺序进行排列,最后以0x0000作为结尾,结构体本身占2个字节,结构体如下:
struct
{
WORD Offset:12;
WORD Type:4;
}TypeOffset;
Offset:低12位,上面介绍过,它跟VirtualAddress相加就是完整的重定位RVA
Type:重定位信息的类型,有如下类型:
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5
#define IMAGE_REL_BASED_RESERVED 6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9
#define IMAGE_REL_BASED_DIR64 10
值 | 信息 | 宏定义 |
---|---|---|
0 | 无重定位操作,用于4字节对齐 | IMAGE_REL_BASED_ABSOLUTE |
1 | 重定位指向位置的高2个字节需要被修正 | IMAGE_REL_BASED_HIGH |
2 | 重定位指向位置的低2个字节需要被修正 | IMAGE_REL_BASED_LOW |
3 | 重定位指向位置的全部4个字节需要被修正(绝大多数都是这种情况) | IMAGE_REL_BASED_HIGHLOW |
4 | 需要两个TypeOffset配合完成索引 | IMAGE_REL_BASED_HIGHADJ |
5 | … | IMAGE_REL_BASED_MACHINE_SPECIFIC_5 |
6 | 保留 | IMAGE_REL_BASED_RESERVED |
7 | … | IMAGE_REL_BASED_MACHINE_SPECIFIC_7 |
8 | … | IMAGE_REL_BASED_MACHINE_SPECIFIC_8 |
9 | … | IMAGE_REL_BASED_MACHINE_SPECIFIC_9 |
10 | 重定位指向位置的8个字节需要被修正 | IMAGE_REL_BASED_DIR64 |
重定位表中记录的重定位信息的个数可以通过下面的公式进行计算:
重定位个数 = (SizeOfBlock - 8(IMAGE_BASE_RELOCATION的大小)) / 2(每个TypeOffset是2个字节)
总重定位个数 = 所有IMAGE_BASE_RELOCATION结构体相加后的重定位个数
因为数据目录表中记录的重定位表的大小是0x13c,而第一个IMAGE_BASE_RELOCATION只占用了0x118,所以应该还有其他的IMAGE_BASE_RELOCATION,通过0x1a00+0x118得到0x1b18,我们可以看到这个位置是另一个IMAGE_BASE_RELOCATION的起始,它的VirtualAddress是0x2000,它的SizeOfBlock是0x0024,通过将这两个IMAGE_BASE_RELOCATION的SizeOfBlock相加正好等于0x13c,通过计算第二个IMAGE_BASE_RELOCATION内的重定位个数是(0x24-8)/2=0xe,所以最终分析出来这个重定位表包括了两个IMAGE_BASE_RELOCATION,此程序一共有0x88+0xe个地方需要进行重定位修复。