天天看点

《逆向工程核心原理》第16.17章——基址重定位

基址重定位

    • PE重定位
    • PE重定位时执行的操作
    • PE重定位操作原理
      • 基址重定位表
      • IMAGE_BASE_RELOCATION结构体
      • 基址重定位表的分析方法
      • 练习
    • 总结一下
    • 从可执行文件中删除.reloc节区
      • 01 删除.reloc节区头
      • 02 删除.reloc节区
      • 03 修改IMAGE_FILE_HEADER
      • 04 修改IMAGE_OPTION_HEADER
    • 增加一个节区

PE重定位

向进程的虚拟内存中加载PE文件时,文件会被加载到PE头的ImageBase所指的地址处,该位置存在被占用可能,此时PE装载器会将其加载到其他未被占用的空间。PE重定位指PE文件无法加载到ImageBase所指位置,而是加载到其他地址时发生的一系列行为。

《逆向工程核心原理》第16.17章——基址重定位

创建进城后,EXE文件会首先加载到内存,所以EXE文件无需考虑重定位问题。

Windows Vista之后的版本引入了ASLR安全机制,每次运行EXE文件都会被加载到随机地址。

下图可以看出每次运行添加了ASLR机制的程序,代码的地址,PE在内存中的地址,堆栈地址都会改变:

《逆向工程核心原理》第16.17章——基址重定位
《逆向工程核心原理》第16.17章——基址重定位

而打开没有添加ASLR机制的程序,每次加载后上述地址不变:

《逆向工程核心原理》第16.17章——基址重定位

PE重定位时执行的操作

以Windows 7的notepad.exe为例分析。

首先查看可执行文件的ImageBase为01000000。

《逆向工程核心原理》第16.17章——基址重定位

OD打开程序,可以看到程序被加载到00FD0000地址处。

《逆向工程核心原理》第16.17章——基址重定位

方框中进程的内存地址以硬编码形式存在,但每次重启程序上述地址就会随加载地址的不同而变化。这种使硬编码在程序中的内存地址随当前加载地址变化而改变的处理过程就是PE重定位。

PE重定位操作原理

  • 在应用程序中查找硬编码地址的位置
  • 读取值后,减去ImageBase(VA→RVA)
  • 加上实际加载地址(RVA→VA)

基址重定位表

查找硬编码地址的位置会用到PE文件内部的重定位表(Relocation Table),它是记录硬编码地址偏移的列表,在PE文件编译/链接时形成的。

基址重定位表地址位于PE头的DataDirectory数组的第6个元素。

《逆向工程核心原理》第16.17章——基址重定位

IMAGE_BASE_RELOCATION结构体

查看该地址RAV=2F000,对应磁盘中位置为2AE000。

基址重定位表是IMAGE_BASE_RELOCATION结构体数组。

《逆向工程核心原理》第16.17章——基址重定位

TypeOffset表示该结构体之下会出现WORD型数组,且该数组元素的值就是硬编码在程序中的地址偏移。

基址重定位表的分析方法

《逆向工程核心原理》第16.17章——基址重定位

上图显示TypeOffset数组的基准地址为RVA 1000,块的总大小为150。

TypeOffset由4位Type与12位Offset组成,PE中常见Type=3,PE+常见Type=A。

Offset是基于VA的偏移。

以3420为例,3表示IMAGE_REL_BASED_HIGHLOW,程序中硬编码的偏移RVA=1000+420=1420。

下面查看RVA 1420处是否存在要执行PE重定位操作的硬编码地址,换算为VA为FD1420。可以看到该处存储着IAT地址(VA,FD10C4)。并且该值经过PE重定位而发生了变化

《逆向工程核心原理》第16.17章——基址重定位

同样原理,FD142D、FD1436也发生了同样的重定位。

《逆向工程核心原理》第16.17章——基址重定位
《逆向工程核心原理》第16.17章——基址重定位

练习

运行notepad,查看RVA=1420地址中的内容。

《逆向工程核心原理》第16.17章——基址重定位

可以看到其中存在着程序的硬编码地址01010C4。

将其将去ImageBase=1000000,得到RVA=10C4。

再加上实际加载地址00FD0000,得到VA=FD10C4,即为前面OD中显示的地址。

对于程序内硬编码的地址,PE装载器都做如上处理,根据实际加载的内存地址修正后

将得到的值覆盖到同一位置。对一个IMAGE_BASE_RELOCATION结构体中的所有TypeOffset都重复上述过程(结构体以0结尾)。

对重定位表中出现的所有的IMAGE_BASE_RELOCATION结构体都重复上述处理后,就完成了对进程内存区域相应的所有硬编码地址的PE重定位。重定位表以NULL结构体结束。

总结一下

DataDirectory数组的第六个元素指向基址重定位结构体数组,对每个结构体数组中的每一个TypeOffset对应的RVA进行查找,将对应位置地址进行变换并覆盖同一位置。(修改的地址均位于.text节区,即直接修改的是汇编代码段)

从可执行文件中删除.reloc节区

.reloc节区是PE文件的基址重定位节区,删除后文件仍能正常运行。有以下四个步骤:

  • 删除.reloc节区头
  • 删除.reloc节区
  • 修改IMAGE_FILE_HEADER节区头中的节区数Number of Sections
  • 修改IMAGE_OPTIONAL_HEADER可选头中的size of ImagePE文件加载到内存中的大小

01 删除.reloc节区头

查看节区头位置:

《逆向工程核心原理》第16.17章——基址重定位

将其覆盖为0:

《逆向工程核心原理》第16.17章——基址重定位

02 删除.reloc节区

查看节区位置与大小:

《逆向工程核心原理》第16.17章——基址重定位

从2AE00开始删除到文件末端所有数据:

《逆向工程核心原理》第16.17章——基址重定位

03 修改IMAGE_FILE_HEADER

查看Nuber of Sections位置:

《逆向工程核心原理》第16.17章——基址重定位

修改文件中的节区数,将其减一:

《逆向工程核心原理》第16.17章——基址重定位

04 修改IMAGE_OPTION_HEADER

修改可选头中的Size of Image,即文件加载入内存的大小,查看其原本大小为30000:

《逆向工程核心原理》第16.17章——基址重定位

前面可知.reloc的Virtual Size为E34,又因为SectionAlignment=1000,因此将其扩展为1000。

《逆向工程核心原理》第16.17章——基址重定位

所以此时Size of Image应该减去1000,即29000,修改:

《逆向工程核心原理》第16.17章——基址重定位

完成。

但是很奇怪的是修改完成后程序运行出错了。不知道哪里出了问题。😦

之后又拿给的示例程序做了练习,修改后仍能正常运行:

《逆向工程核心原理》第16.17章——基址重定位

增加一个节区

《逆向工程核心原理》第16.17章——基址重定位