PE文件格式学习(八):基址重定位表

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tutucoo/article/details/83828713

1.简介

基址重定位表位于数据目录表中的第六个,它位于安全表的后面。

这个表的作用是用来索引那些需要重定位的数据的。当系统发现DLL的真实加载基址跟PE文件中的ImageBase中的值不一样时,就会启用基址重定位表修复一些数据的地址。我们知道一个程序中可能包含多个DLL,因此有可能多个DLL之间的ImageBase是重合的,一旦某个加载基址被占用了,系统就会随机分配一个基址给将要加载的DLL,比如说本文中的DLL的ImageBase是0x10000000,在它要加载进某个程序的时候发现0x10000000已经被占用了,系统就会给它分配一个新的加载地址0x72ab2210,因为加载到了一个新的地址,所以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大小分页内的重定位信息,换句话说PE文件中需要重定位的部分每隔0x1000字节就有一个IMAGE_BASE_RELOCATION负责。也因此结构中的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位

因此:

0x10001006 = 0x10000000 + 0x1000 + 0x006

这里计算得到的最终需要重定位的数据地址是0x10001006,也就是push后面的地址0x10002210需要进行重定位。现在应该明白了重定位表的作用就是用来索引并定位到需要重定位数据的位置处,需要重定位的并不是重定位表中索引的值本身,而是指向的值,简单地说就是需要修改的是0x10002210这个值,而不是0x10001006这个值。如果不明白也没关系,后面会继续讲解,看完后面的再回来看就都明白了。继续讲解下一个字段。

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:上面介绍过,它跟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

最后,对重定位表进行总结性分析。由数据目录表中的RVA:5000h转换得到0x1A00,在0x1a00处是重定位表的起始位置,从这里开始可能有多个IMAGE_BASE_RELOCATION结构,每个结构负责4KB大小的页内需要重定位的信息。第一个IMAGE_BASE_RELOCATION的VirtualAddress是0x1000,SizeOfBlock是0x118,TypeOffset是0x3006,我们知道Typeoffset的高4位是Type,低12位是Offset,Type是指重定位信息的类型,Offset是指偏移,通过第一个Offset的值我们可以确定,0x10000000+0x1000+0x006是需要被重定位修正的位置,第二个TypeOffset是0x300B,因为分析方式相同就不再赘述,后面还有若干个TypeOffset,最后以0x0000结尾。至于重定位表中记录的重定位信息的个数可以通过下面的公式进行计算:

重定位个数 = (SizeOfBlock - 8(IMAGE_BASE_RELOCATION的大小)) / 2(每个TypeOffset是2个字节)
总重定位个数 = 重定位个数 * IMAGE_BASE_RELOCATION的个数

本文使用的DLL的第一个IMAGE_BASE_RELOCATION的SizeOfBlock的值是0x118,套用公式得到以下结果:

重定位个数 = (0x118 - 8) / 2 
          = 0x88

因为数据目录表中记录的重定位表的大小是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个地方需要进行重定位修复。

我们查看第一个TypeOffset0x3006,它的低12位是偏移,也就是0x006,我们知道DLL的ImageBase是0x10000000,但是实际加载基地址是0x72ab0000,因此系统检测到这两个值不同时会进行重定位操作,它会去TypeOffset处找到需要重定位的位置,第一个需要重定位的具体位置是:0x10000000+0x1000+0x006 = 0x10001006,上面已经说过0x10001006在call后面,也就是地址0x10002210处:

Offset           HEX         Assembly
10001005      68 10220010  push 0x10002210

系统会将它进行修复,修复方法是:

修复后地址 = (真实加载基址-默认加载基址) + 需要进行重定位的地址

修复后地址 = (0x72ab0000 - 0x10000000) + 0x10002210
                    = 0x72ab2210

系统接下来会修复每处TypeOffset记录的需要重定位的位置。

猜你喜欢

转载自blog.csdn.net/tutucoo/article/details/83828713