本节内容
当程序文件向进程的虚拟内存加载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 | DataDirectory[0] = EXPORT Directory |
DataDirectory [ 5 ] 那一项。该项在StudyPE中的值:
可以看到在当前的notepad.exe(开启了ASRL)重定位表的RVA地址 = 0x0002F000,FOA地址 = 0x00002AE00(文件偏移),HxD中查看0x2AE00(截取部分):
IMAGE_BASE_RELOCATION
为了解读这些16进制数据,需要结合着IMAGE_BASE_RELOCATION的结构体:
1 | typedef struct _IMAGE_BASE_RELOCATION { |
将十六进制数据根据结构体的成员逐字段读取出来,这些数据在后续的例子中会使用到
成员 | 值 | 备注 |
---|---|---|
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地址