16.基址重定位表

Pasted image 20250321124945
PE文件在重定位过程中会用到基址重定位表

1. PE重定位

向进程的虚拟内存加载PE文件(EXE/DLL/SYS)时,文件会被加载到PE头的ImageBase所指的地址处。若加载的是DLL(SYS)文件,且在ImageBase位置处已经加载了其他DLL(SYS)文件,那么PE装载器就会将其加载到其他未被占用的空间。这就涉及PE文件重定位的问题,PE重定位是指PE文件无法加载到ImageBase所指位置,而是被加载到其他地址时发生的一系列的处理行为。

Tip

使用SDK(Software Development Kit,软件开发工具包)或Visual C++创建PE文件时,EXE默认的ImageBase为00400000,DLL默认的ImageBase为10000000。此外,
使用DDK(DriverDevelopment Kit,驱动开发工具包)创建的SYS文件默认的ImageBase为10000。

1.1. DLL/SYS

如图:A.DLL被加载到TEST.EXE进程的10000000地址处。此后,B.DLL试图加载到相同地址(10000000)时,PE装载器将B.DLL加载到另一个尚未被占用的地址(3C000000)处
Pasted image 20250320125021

1.2. EXE

创建好进程后,EXE文件会首先加载到内存,所以在EXE中无须考虑重定位的问题。但是
WindowsVista之后的版本引入了ASLR安全机制,每次运行EXE文件都会被加载到随机地址,这样大大增强了系统安全性。

Tip

ASLR机制也适用于DLL/SYS文件。对于各OS的主要系统DLL,微软会根据不同版本分别赋予不同的ImageBase地址。同一系统的kernel32.dll、user32.dll等会被加载到自身固有的ImageBase,所以,系统的DLL实际不会发生重定位问题。

2. PE重定位时执行的操作

notepad.exe 为例,看看PE重定位时发生了什么,使用010查看可以发现其基址 ImageBase01000000h
Pasted image 20250320125628
然后用OD运行
ASLR 的机制下,发现程序被加载到了 00b60000 位置处
Pasted image 20250320125912
从图中指令可以看到,方框中进程的内存地址以硬编码形式存在。地址 B610FCb61100.text 节区的IAT区域,地址 b6C0A4.data节区的全局变量。每当在OllyDbg中重启notepad.exe(Restart(Ctrl+F2)),地址值就随加载地址的不同而改变。像这样,使硬编码在程序中的内存地址随当前加载地址变化而改变的处理过程就是PE重定位。
Pasted image 20250320130447

无法加载到 ImageBase 地址时,若未进行过PE重定位处理,应用程序就不能正常运行(因发生“内存地址引用错误”,程序异常终止)

3. PE重定位操作原理

原理很简单

  • 在应用程序中查找硬编码的地址位置。
  • 读取值后,减去ImageBase(VA→RVA)。
  • 加上实际加载地址(RVA→VA)。
    其中最关键的是查找硬编码地址的位置。查找过程中会用到PE文件内部RelocationTable
    (重定位表),它是记录硬编码地址偏移(位置)的列表(重定位表是在PE文件构建过程(编译/链接)中提供的)。通过重定位表查找,其实就是指根据PE头的“基址重定位表”项进行的查找。

3.1. 基址重定位表

基址重定位表地址位于PE头的DataDirectory数组的第六个元素(数组索引I为5)
Pasted image 20250320135728
可以看到基址重定位表的地址为 RVA 2F000

3.2. IMAGE_BASE_RELOCATION结构体

上图的基址重定位表中罗列了硬编码地址的偏移(位置)。读取这张表就能获得准确的硬编码地址偏移。基址重定位表是 IMAGE_BASE_RELOCATION 结构体数组
Pasted image 20250320140019

//
// Based relocation format.
//
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD VirtualAddress;
    DWORD SizeOfBlock;
    // WORD  TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

//
// Based relocation types.
//
#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_MIPS_JMPADDR  5
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define IMAGE_REL_BASED_IA64_IMM64    9
#define IMAGE_REL_BASED_DIR64         10

IMAGE_BASE_RELOCATION 结构体的第一个成员为 VirtualAddress,它是一个基准地址(BaseAddress),实际是RVA值。第二个成员为SizeOfBlock,指重定位块的大小。
最后一项TypeOffset数组不是结构体成员,而是以注释形式存在的,表示在该结构体之下会出现WORD类型的数组,并且该数组元素的值就是硬编码在程序中的地址偏移。

3.3. 基址重定位表的分析方法

Pasted image 20250320150721

010 Editor 显示的是文件中的原始数据,需要将 RVA 转换为文件偏移地址(FOA),所以要查看 RVA 21000h 就看 FOA 2AE00h
Pasted image 20250320150935

PEview可以直接看RVA
Pasted image 20250320151850

FOA(文件偏移地址) 数据 注释
2AE00 00001000 VirtualAddress
2AE04 00000150 SizeOfBlock
2AE08 3420 TypeOffset
2AE0A 342D TypeOffset
2AE0C 3436 TypeOffset

IMAGE_BASE_RELOCATION 结构体的定义可知,VirtualAddress 成员(基准地址的值为 1000h,SizeOfBlock成员的值为 150h。也就是说,表中显示的TypeOffest数组的基准地址(起始地址)为RVA 1000,块的总大小为150(这些块按照基准地址分类,以数组形式存在)。块的末端显示为0。TypeOffset值为2个字节(16位)大小,是由4位的Type与12位的Offset合成的。比如,TypeOffset值为3420,解析如表所示。

虽然010里面没有显示出 type 但是元数据里面是有的
Pasted image 20250320151743

类型(4位) 偏移(12位)
3 420

高4位用作Type,PE文件中常见的值为3(IMAGE_REL_BASED_HIGHLOW),64位的PE+文件中常见值为A(IMAGE_REL_BASED_DIR64)。

Tip

在恶意代码中正常修改文件代码后,有时要修改指向相应区域的重定位表(为了略去PE装载器的重定位过程,常常把Type值修改为0(IMAGEREL_BASED_ABSOLUTE))。

TypeOffset的低12位是真正的位移,该位移值基于VirtualAddress的偏移。所以程序中硬编码地址的偏移使用下面等式换算。

查看一下 RVA 1420 处是否实际存在要执行PE重定位操作的硬编码地址
Pasted image 20250320144435
上图中 notepad.exe 被加载到 b60000 地址处。故 RVA 1420 即为 VA b61420,该地址处存储着IAT地址(VA,b610C4)。并且该值经过PE重定位而发生了变化。使用相同原理,b6142D、b61436 地址的内容也都是硬编码到程序中的地址值,该偏移可以在表中求得。

Tip

TypeOffset项中指向位移的低12位拥有的最大地址值为1000。为了表示更大的地址,要添加1个与其对应的块,由于这些块以数组形式罗列,故称为重定位表。

3.4. 练习

运行 Notepad.exe 时,假设它被加载到 00b60000,而不是 ImageBase 地址(01000000)中。那此时PE重定位是如何进行的呢?

3.4.1. 查找程序中硬编码地址的位置

程序中使用的硬编码地址的偏移(位置)可以通过基址重定位表查找到(此处使用上面求得的 RVA 1420。使用PEView查看RVA 1420 地址中的内容,如图所示。
Pasted image 20250320152146
这里存在着程序硬编码地址值 010010c4 与下图中的 00b610c4 进行比较
Pasted image 20250320152418

3.4.2. 读取值后,减去ImageBase值(VA->RVA)

3.4.3. 加上实际地址(RVA->VA)

对于程序内硬编码的地址(010010C4),PE装载器都做如上处理,根据实际加载的内存地址修正后,将得到的值(00b610C4)覆盖到同一位置。对一个 IMAGE_BASE_RELOCATION 结构体的所有 TypeOffset 都重复上述过程,且对与 RVA 1000~2000 地址区域对应的所有硬编码地址都要进行PE重定位处理。若 TypeOffset 值为0,则表明一个 IMAGE_BASE_RELOCATION 结构体结束。
Pasted image 20250320153010
对重定位表中出现的所有IMAGEBASE_RELOCATION结构体都重复上述处理后,就完成了对进程内存区域相应的所有硬编码地址的PE重定位。重定位表以NULL结构体结束(即IMAGE_BASE_RELOCATION结构体成员的值全部为NULL)。
以上就是PE重定位的操作原理与重定位表结构体的相关内容。