逆核系列No.2--PE重定位

本节内容

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

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

程序未载入内存前:

程序文件中的资源是以RVA形式进行索引,以PE文件中的ImageBase作为基准

程序载入内存后

程序资源需要以实际载入内存的基址进行定位,需要将原先以PE文件中的ImageBase为基准进行定位的资源先进行偏移恢复,在结合实际载入的内存基址进行资源的重定位。(实际上就是由于偏移相对的基准发生变化,需要进行更新)

重定位基本原理

Windows的PE装载器进行PE重定位处理时的基本操作原理入下:

  • 在应用程序中查找硬编码的地址位置
  • 读取硬编码的值,这个值是根据PE文件中的ImageBase为基准定位的,进行重定位时只需要计算得到偏移位置
  • 通过第二步骤中得到的各个硬编码的偏移位置,对每个值以实际文件载入的地址为新的基准即可完成重定位。
Relocation Table

不难看出,PE装载器进行重定位的关键在于获得应用程序中所有的硬编码地址。PE文件中的Relocation Table(重定位表)记录了应用程序中硬编码地址所在的偏移位置(重定位表是在PE文件构建过程【编译/链接】中提供的)。通过重定位表查找硬编码地址偏移,其实指的是根据PE头中的“基址重定位表”的表项进行的查找。

IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER.DataDirectory[5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DataDirectory[0] = EXPORT Directory
DataDirectory[1] = IMPORT Directory
DataDirectory[2] = RESOURCE Directory
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory //重定位表
DataDirectory[6l = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory
DataDirectory[A] = LOAD CONFIG Directory
DataDirectory[B] = BOUND IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY IMPORT Directory
DataDirectoryIE] = COM DESCRIPTOR Directory
DataDirectory[F] = Reserved Directory

DataDirectory [ 5 ] 那一项。该项在StudyPE中的值:

可以看到在当前的notepad.exe(开启了ASRL)重定位表的RVA地址 = 0x0002F000,FOA地址 = 0x00002AE00(文件偏移),HxD中查看0x2AE00(截取部分):

IMAGE_BASE_RELOCATION

为了解读这些16进制数据,需要结合着IMAGE_BASE_RELOCATION的结构体:

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
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

#define IMAGE_SIZEOF_BASE_RELOCATION 8

//
// 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_SECTION 6
#define IMAGE_REL_BASED_REL32 7

#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define IMAGE_REL_BASED_IA64_IMM64 9
#define IMAGE_REL_BASED_DIR64 10
#define IMAGE_REL_BASED_HIGH3ADJ 11

将十六进制数据根据结构体的成员逐字段读取出来,这些数据在后续的例子中会使用到

成员 备注
VirtualAddress 0x00001000 基准地址RVA
SizeOfBlock 0x00000150 重定位块的大小
TypeOffset 0 3420 第一个TypeOffset
TypeOffset 1 342D 第二个TypeOffset
TypeOffset …
_IMAGE_BASE_RELOCATION.TypeOffset

读取出来的每一个TypeOffset的内容对应一个硬编码地址的偏移内容。

TypeOffset包含两个部分:高4位内容为类型(Type),剩下的12位指示偏移(Offset)的内容。

高4位的Type:

Base Relocation Types
Constant Value Description
IMAGE_REL_BASED_ABSOLUTE 0 The base relocation is skipped. This type can be used to pad a block.
IMAGE_REL_BASED_HIGH 1 The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the high value of a 32-bit word.
IMAGE_REL_BASED_LOW 2 The base relocation adds the low 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the low half of a 32-bit word.
IMAGE_REL_BASED_HIGHLOW 3 The base relocation applies all 32 bits of the difference to the 32-bit field at offset.
IMAGE_REL_BASED_HIGHADJ 4 The base relocation adds the high 16 bits of the difference to the 16-bit field at offset. The 16-bit field represents the high value of a 32-bit word. The low 16 bits of the 32-bit value are stored in the 16-bit word that follows this base relocation. This means that this base relocation occupies two slots.
IMAGE_REL_BASED_MIPS_JMPADDR 5 The relocation interpretation is dependent on the machine type. When the machine type is MIPS, the base relocation applies to a MIPS jump instruction.
IMAGE_REL_BASED_ARM_MOV32 5 This relocation is meaningful only when the machine type is ARM or Thumb. The base relocation applies the 32-bit address of a symbol across a consecutive MOVW/MOVT instruction pair.
IMAGE_REL_BASED_RISCV_HIGH20 5 This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the high 20 bits of a 32-bit absolute address.
6 Reserved, must be zero.
IMAGE_REL_BASED_THUMB_MOV32 7 This relocation is meaningful only when the machine type is Thumb. The base relocation applies the 32-bit address of a symbol to a consecutive MOVW/MOVT instruction pair.
IMAGE_REL_BASED_RISCV_LOW12I 7 This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the low 12 bits of a 32-bit absolute address formed in RISC-V I-type instruction format.
IMAGE_REL_BASED_RISCV_LOW12S 8 This relocation is only meaningful when the machine type is RISC-V. The base relocation applies to the low 12 bits of a 32-bit absolute address formed in RISC-V S-type instruction format.
IMAGE_REL_BASED_LOONGARCH32_MARK_LA 8 This relocation is only meaningful when the machine type is LoongArch 32-bit. The base relocation applies to a 32-bit absolute address formed in two consecutive instructions.
IMAGE_REL_BASED_LOONGARCH64_MARK_LA 8 This relocation is only meaningful when the machine type is LoongArch 64-bit. The base relocation applies to a 64-bit absolute address formed in four consecutive instructions.
IMAGE_REL_BASED_MIPS_JMPADDR16 9 The relocation is only meaningful when the machine type is MIPS. The base relocation applies to a MIPS16 jump instruction.
IMAGE_REL_BASED_DIR64 10 The base relocation applies the difference to the 64-bit field at offset.

PE32中Type常见的值 = 3(IMAGE_REL_BASED_HIGHLOW ),PE32+Type常见值 = A(IMAGE_REL_BASED_DIR64)

恶意代码中的Type

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


低12位的Offset:其中指示的是偏移内容。

TypeOffset例子

这里以TypeOffset 0 3420为例子进行解读。

高4位内容 = 3(IMAGE_REL_BASED_HIGHLOW )解释:The base relocation applies all 32 bits of the difference to the 32-bit field at offset.

低12位的偏移内容 = 420(基于_IMAGE_BASE_RELOCATION.VirtualAddress),将这个值结合_IMAGE_BASE_RELOCATION.VirtualAddress组合成32位长度的地址(依据Type)

上文读取出来的_IMAGE_BASE_RELOCATION结构体中VirtualAddress = 1000,因此偏移420重定位后地址 = 1420(RVA)

OD下本次程序载入的位置观察:

可以看到,本次载入内存时,载入的Base Address = 0x00B10000 上述硬编码重定位后的结果 = 0x00B11420

查看 0x00B11420 位置的内容:

内容 = 0x00B110C4,跟随即可查看到重定位的资源内容,这里是为kernel.dll导入的GetCommandLineW API地址

Author: Victory+
Link: https://cvjark.github.io/2022/04/27/逆核系列No.2--PE重定位/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.