重定位的处理
2.重定位PE文件的重定位块和重定位项
当可执行映像加载到的地址与其首选基地址(IMAGE_OPTIONAL_HEADER.ImageBase
)不同时,重定位对于调整可执行映像中的硬编码地址是必要的。在大多数情况下,PE 文件会被映射到除 IMAGE_OPTIONAL_HEADER.ImageBase
之外的地址,因此需要对 PE 文件中的某些硬编码地址进行调整。
通过计算得出地址差值:
// The difference between the current PE image base address and its preferable base address.
ULONG_PTR uDeltaOffset = pPeBaseAddress - pPreferableAddress;
下面是微软SDK定义的重定位块的头部结构
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
并没有对BASE_RELOCATION_ENTRY做出定义,但描述为:
每个重定位条目占用2字节(WORD)
- 每个重定位条目占用2字节(WORD)
- 高4位是类型(Type)
- 低12位是偏移量(Offset)
代码具体实现可以是:
typedef struct _BASE_RELOCATION_ENTRY {
WORD Offset : 12; //前12字节
WORD Type : 4; //后4字节
//WORD总共占16位
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
IMAGE_BASE_RELOCATION和BASE_RELOCATION_ENTRY在PE文件中的位置关系是:
+——————————–+——————+——————+— | IMAGE_BASE_RELOCATION (8字节) | 重定位条目1 | 重定位条目2 | … | - VirtualAddress (4字节) | (2字节) | (2字节) | | - SizeOfBlock (4字节) | | | +—————————————-+—————-+———–+— ↑ ↑ pImgBaseRelocation pImgBaseRelocation + 1
于是可以通过以下代码访问BASE_RELOCATION_ENTRY:
pBaseRelocEntry = (PBASE_RELOCATION_ENTRY)(pImgBaseRelocation + 1);
并通过下列switch语句根据预定义Type改变重定位地址:
switch (pBaseRelocEntry->Type) {
case IMAGE_REL_BASED_DIR64:
// Adjust a 64-bit field by the delta offset.
*((ULONG_PTR*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += uDeltaOffset;
break;
case IMAGE_REL_BASED_HIGHLOW:
// Adjust a 32-bit field by the delta offset.
*((DWORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += (DWORD)uDeltaOffset;
break;
case IMAGE_REL_BASED_HIGH:
// Adjust the high 16 bits of a 32-bit field.
*((WORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += HIWORD(uDeltaOffset);
break;
case IMAGE_REL_BASED_LOW:
// Adjust the low 16 bits of a 32-bit field.
*((WORD*)(pPeBaseAddress + pImgBaseRelocation->VirtualAddress + pBaseRelocEntry->Offset)) += LOWORD(uDeltaOffset);
break;
case IMAGE_REL_BASED_ABSOLUTE:
// No relocation is required.
break;
default:
// Handle unknown relocation types.
printf("[!] Unknown relocation type: %d | Offset: 0x%08X \n", pBaseRelocEntry->Type, pBaseRelocEntry->Offset);
return FALSE;
}
总结就是:
PE(Portable Executable)文件的重定位块是为了解决PE文件在加载到内存时,实际加载地址与首选加载地址不一致的问题。当文件不能被加载到其首选基地址(IMAGE_OPTIONAL_HEADER.ImageBase
)时,就需要对文件中硬编码的地址进行调整,这些调整信息就存储在重定位块中。以下详细介绍重定位块的结构和包含的信息:
重定位表的整体布局
重定位信息存储在.reloc
节中,重定位表由多个重定位块(Base Relocation Block)组成。每个重定位块描述一个4KB(4096字节)的内存页面。
重定位块的结构
1. 重定位块头部(IMAGE_BASE_RELOCATION
结构)
每个重定位块以 IMAGE_BASE_RELOCATION
结构开头,该结构定义在 Windows 头文件中,其结构如下:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // RVA to the base address of the section this block describes.
DWORD SizeOfBlock; // The total size of the block, including the block header and all entries (discussed below).
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
VirtualAddress
:这是一个相对虚拟地址(RVA),表示该重定位块所描述的内存页面的起始地址。也就是说,这个 RVA 指向需要进行重定位操作的内存页面的基地址。SizeOfBlock
:表示整个重定位块的大小,包括块头部(IMAGE_BASE_RELOCATION
结构本身)和后续的所有重定位条目(BASE_RELOCATION_ENTRY
结构数组)。通过这个值可以确定该重定位块在内存中占用的字节数,从而可以正确遍历块内的所有重定位条目。
2. 重定位条目(BASE_RELOCATION_ENTRY
结构数组)
重定位块头部之后紧跟着一个 BASE_RELOCATION_ENTRY
结构数组,每个结构代表一个重定位条目。BASE_RELOCATION_ENTRY
结构定义如下:
typedef struct _BASE_RELOCATION_ENTRY {
WORD Offset : 12; // Specifies where the base relocation is to be applied.
WORD Type : 4; // Indicates the type of base relocation to be applied.
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
Offset
:占用 12 位,指定了从当前重定位块的VirtualAddress
开始的偏移量。通过这个偏移量,可以确定需要进行重定位操作的具体内存位置。例如,如果VirtualAddress
是某个页面的起始地址,Offset
则表示在这个页面内需要调整地址的具体偏移位置。Type
:占用 4 位,指示了要应用的基址重定位类型。不同的重定位类型决定了如何对相应的地址进行调整。常见的重定位类型如下:IMAGE_REL_BASED_DIR64
:用于 64 位地址的重定位,需要将整个 64 位的地址加上基地址的偏移量。IMAGE_REL_BASED_HIGHLOW
:用于 32 位地址的重定位,将 32 位地址加上基地址偏移量的低 32 位。IMAGE_REL_BASED_HIGH
:用于调整 32 位地址的高 16 位,只需要加上基地址偏移量的高 16 位。IMAGE_REL_BASED_LOW
:用于调整 32 位地址的低 16 位,只需要加上基地址偏移量的低 16 位。IMAGE_REL_BASED_ABSOLUTE
:表示该条目不需要进行重定位,通常用于占位或预留。
重定位块的遍历过程
在进行重定位操作时,需要遍历所有的重定位块和每个块内的重定位条目。大致的遍历过程如下:
- 从重定位表的起始位置开始,获取第一个重定位块的头部(
IMAGE_BASE_RELOCATION
结构)。 - 根据
VirtualAddress
和SizeOfBlock
信息,确定该重定位块所描述的页面和块的大小。 - 从块头部之后开始,依次读取每个
BASE_RELOCATION_ENTRY
结构,根据Type
字段确定重定位类型,根据Offset
字段确定要调整的具体内存位置,然后进行相应的地址调整。 - 当遍历完当前重定位块内的所有条目后,根据
SizeOfBlock
移动到下一个重定位块的头部,重复上述步骤,直到所有重定位块都被处理完毕。
通过这种方式,可以确保 PE 文件在加载到任意内存地址时,其内部的硬编码地址都能被正确调整,从而保证程序的正常运行。