基址重定位

使用工具

  • OllyDbg 1.10原版,简称OD
  • OD 汉化插件均来自互联网;
  • PE 查看工具为LoadPE,来自互联网;
  • WinHex来自互联网;
  • 文中特殊数字均是HEX,为了书写方便采用DEC

基址重定位的概念

基址重定位是指 PE 文件无法加载到 ImageBase 所指的位置,而是被加载到其它地址时所发生的一系列的处理行为;

当链接器生成 PE 文件时,会假设这个文件在执行时会被装载到默认的基地址处,即 ImageBase 所指的地址处;

如果载入时将 ImageBase 默认的值作为基地址写入,则不需要重定位;

如果载入时 PE 文件被装载到虚拟内存的另一个地址中,此时,链接器登记的地址就是错误的,这时就需要用重定位表来调整;

在 PE 文件中,重地位表往往单独作为一块,用.reloc表示;

对 EXE 文件来说,每个文件总是使用独立的虚拟地址空间,且创建好线程后,EXE 文件会首先加载到内存,所以 EXE 总是能够按照基址载入,这意味着 EXE 文件无需考虑重定位的问题(不需要重定位信息);

对 DLL 文件来说,因为多个 DLL 文件使用宿主 EXE 文件的地址空间,不能保证载入地址没有被其它 DLL 使用,所以 DLL 文件中必须包含重定位信息;

但是,Windows Vista 之后的版本引入了 ASLR 安全机制,每次运行 EXE 文件都会被加载到随机地址,这样大大增强了系统安全性;

ASLR 机制也适用于 DLL/SYS 文件,对于各 OS 的主要系统 DLL,根据不同版本会分别赋予不同的 ImageBase 地址;

ASLR

ASLR(Address Space Layout Randomization,地址空间布局随机化)是一种针对缓冲区溢出的安全保护技术,微软从 Windows Vista 开始采用该技术;

在以前的 OS 中,根据 OS 版本的不同,特定 DLL 总是会加载到固定地址,所以大部分安全漏洞(一般为内存缓冲区溢出)只出现在特定 OS、特定模块、特定版本中,漏洞利用代码(exploit code)会在特定内存地址中以硬编码形式编入,因此,微软采用 ASLR 技术来增加恶意用户编写漏洞利用代码的难度,从而降低利用 OS 安全漏洞破坏系统的风险;

借助 ASLR 技术,PE 文件每次加载到内存中的起始地址都会随机变化,并且每次运行程序时相应进程的栈以及堆的起始地址也会随机变化,也就是说,每次 EXE 文件运行时加载到进程内存的实际地址都不同,最初加载 DLL 文件时装载到内存中的实际地址也是不同的;

但是,并不是所有可执行文件都会自动应用 ASLR 技术,要应用 ASLR 技术,必须满足两个条件:

  • OS 的内核版本必须为 6 以上,即系统版本不低于 Windows Vista(Kernel Version 6);
  • 使用的编程工具要支持 /DYNAMICBASE 选项;

应用 ASLR 技术的 PE 文件会增加 1 个名为.reloc的节区,它是编译时由编译器生成并保留在可执行文件中的,PE 文件被加载到内存时,该节区被用作重定位的参考,它不是 EXE 文件运行时的必须部分,可以从 PE 文件中删除,但是,由于 DLL 文件总是需要重定位,所以在 DLL 文件中不可将其删除;

应用 ASLR 技术的 PE 文件,其 IMAGE_FILE_HEADER(映像文件头)结构中的 Characteristics 字段将缺少 IMAGE_FILE_RELOCS_STRIPPED 标志(特征值为:0001),但 NumberOfSections 字段会增加 1;

应用 ASLR 技术的 PE 文件,其 IMAGE_OPTIONAL_HEADER(可选映像头)结构中的 DllCharacteristics 字段会设置 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(特征值为:0040)标志;

若要删除 ASLR 功能,只需将 DllCharacteristics 字段设置的 IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(特征值为:0040)标志置 0 即可;

基址重定位的处理

PE 格式不参考外部 DLL 或模块中的其它区块,而是把文件中所有可能需要修正的地址放在一个数组中;

如果 PE 文件不在首选的地址载入,那么文件中的每个定位都需要被修正;

对加载器来说,它不需要知道关于地址使用的任何细节,只要知道有一系列的数据需要以某种一致的方式来修正就可以了;

基址重定位表

基址重定位表(Base Relocation Table)位于一个.reloc区块内,基址重定位表地址位于 PE 头的 DataDirectory 数组的第 6 个元素(索引为 5),即 IMAGE_DIRECTORY_ENTRY_BASERELOC(IMAGE_NT_HEADERS/IMAGE_OPTIONAL_HEADER/IMAGE_DATA_DIRECTORY[5])条目;

基址重定位数据采用类似按页分割的方法组织,是由许多重定位块串接而成的;

每个块存放 4KB(1000)的重定位信息,每个重定位数据块的大小必须以 DWORD(4 字节)对齐;

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

IMAGE_BASE_RELOCATION 结构如下:

1
2
3
4
5
IMAGE_BASE_RELOCATION STRUCT
VirtualAddress DWORD ? ;重定位数据的开始 RVA 地址
SizeOfBlock DWORD ? ;重定位数据块的尺寸
TypeOffset WORD ? ;重定位项数组
IMAGE_BASE_RELOCATION ENDS
  • VirtualAddress:双字,这组重定位数据的起始 RVA 地址,各重定位项的地址加上这个值才是该重定位项的完整 RVA 地址;
  • SizeOfBlock:双字,当前重定位结构的大小,因为 VirtualAddress 和 SizeOfBlock 都是固定的 4 字节,所以这个值减去 8(字节)就是 TypeOffset 数组的大小;
  • TypeOffset:重定位项数组,数组每项大小为 2 字节,共 16 位,分为高 4 位和低 12 位;
    高 4 位代表重定位类型,低 12 位是重定位地址,它与 VirtualAddress 相加就是指向 PE 映像中需要修改的地址数据的指针;

常见的重定位类型:

类型winnt.h 中的预定义值含义
0IMAGE_REL_BASED_ABSOLUTE没有具体含义,只是为了让每个段 4 字节对齐
3IMAGE_REL_BASED_HIGHLOW重定位指向的整个地址都需要修正,实际上大部分情况下都是这样的
10(A)IMAGE_REL_BASED_DIR64出现在 64 位 PE 文件中,对指向的整个地址进行修正

在一组重定位结束的地方会出现类型是 IMAGE_REL_BASED_ABSOLUTE 的重定位,这些重定位什么都不做,只是用于填充,以便下一个 IMAGE_BASE_RELOCATION 结构按 4 字节分界线对齐;
对于 PE 文件来说,所有的基址重定位类型都是 IMAGE_REL_BASED_HIGHLOW;
对于 64 位的 PE+ 文件来说,重定位类型似乎总是 IMAGE_REL_BASED_DIR64;
尽管 64 位的 PE+ 文件页大小是 8KB,但基址重定位仍是 4KB 的块;

删除.reloc节区

  1. 使用 WinHex 打开程序,定位到节区头:

    WinHex

    节区头从文件偏移 270 处开始,到 297 处结束,节区大小为 1000,磁盘偏移为 C000;

    用 00 填充

    选中节区头的全部数据,用 00 覆盖填充;

  2. 在 WinHex 中定位到节区起始偏移 C000,然后选中节区所有数据:

    选中节区所有数据

    删除.reloc节区:

    删除`.reloc`节区

    这样.reloc节区就被物理删除了,同时 PE 文件的体积也会变小;

  3. 由于删除了一个节区, IMAGE_FILE_HEADER 结构中表示节区数目的 NumberOfSections 字段就需要修正:

    NumberOfSections

  4. 删除.reloc节区后,整个映像体积随之减少了相应的大小,IMAGE_OPTIONAL_HEADER 结构中的 SizeOfImage 字段同样需要修正:

    SizeOfImage

  5. 保存所有修改后,将程序导入 OD,观察内存映射窗口:

    内存映射窗口

    同时,使用 LoadPE 查看程序的区段信息:

    LoadPE

    .reloc区段已经被剔除了;