第16-17章 - 基址重定位表&.reloc节区
@date: 2016/11/31
@author: dlive
0x01 PE重定位
若加载的是DLL、SYS文件,且在ImageBase位置处已经加载了其他DLL/SYS文件,那么PE装载器就会将其加载到其他未被占用的空间。这就涉及了PE文件重定位问题,PE重定位是指PE文件无法加载到ImageBase位置,而被加载到其他地址时发生的一系列处理行为。
开发工具 | PE | 地址 |
---|---|---|
SDK/Visual C++ | EXE | 00400000 |
SDK/Visual C++ | DLL | 10000000 |
DDK(Driver Development) | SYS | 00010000 |
以Win 7的notepad.exe为例,Win7的ASLR机制使得exe每次启动时加载到的地址不尽相同。
notepad.exe进程中存在一些内存地址以硬编码的形式存在,如IAT的地址,.data节区全局变量的地址。这些地址值随着exe加载地址的不同而改变。像这样,使硬编码在程序中的内存地址随当前加载地址的变化而变化的处理过程就是PE重定位。
0x02 基址重定位表
Windows PE装载器在进行PE重定位处理时,基本的操作原理很简单
1. 在应用程序中查找硬编码地址的位置(位置记录在基址重定位表里)
2. 读取值后,减去ImageBase
3. 加上实际加载的地址
其中关键的是查找硬编码地址的位置,查找过程中会用到PE文件内部的RelocationTable(重定位表),它是记录硬编码地址偏移的列表(重定位表是在PE文件构建过程(编译/链接)中提供的)。通过重定位表查找,其实就是根据PE头的基址重定位表进行查找。
基址重定位表位于PE头的IMAGE_NT_HEADERS/IMAGE_OPTION_HEADER/IMAGE_DATA_DIRECTORY[5]
(WinXP上notepad.exe是看不到基址重定位表的,我的理解是因为XP没有ASLR机制, exe加载到虚拟内存中的基址都是确定的,无需考虑重定位问题)
基址重定位表中罗列了硬编码地址的位置(偏移)。读取这张表就能获得准确的硬编码地址偏移。基址重定位表示IMAGE_BASE_RELOCATION结构体数组
#IMAGE_DATA_DIRECTORY[5]中存放的只是基址重定位表的指针,基址重定位表的数据存放在.reloc节区
#IMAGE_BASE_RELOCATION
DWORD VirtualAddress; //基准地址(Base Address)
DWORD SizeOfBlock; //重定位块大小(一个IMAGE_BASE_RELOCATION结构的大小)
//WORD TypeOffset[1]; //不是结构体成员,而是以注释形式存在,表示该结构体之下会出现WORD类型数组
//并且该数据元素的值就是硬编码在程序中的地址偏移
Win7 notepad.exe的BaseRelocationTable
TypeOffset中的内容1Byte表示类型(Type) 3Byte表示偏移(Offset),如上图Block[0]中0x3420
3表示类型IMAGE_REL_BASED_HIGTLOW
PE文件中常见的值是3(IMAGE_REL_BASED_HIGTLOW),PE+文件中常见值为A(IMAGE_REL_BASED_DIR64)
在恶意代码中正常修改文件代码后,有时候要修改指向相应区域的重定位表(为了略去PE装载器的重定位过程,常常把Type修改为0(IMAGE_REL_BASED_ABSOLUTE))
420表示偏移,该偏移基于IMAGE_BASE_RELOCATION.VirtualAddress
所以程序中硬编码地址的偏移使用下面的的等式换算:
VirtualAddress(0x1000) + offset(0x420) = 0x1420(RVA)
TypeOffset中指向位移的低3Byte可表示的最大地址为0x1000,为了表示更大的地址,要添加一个与其对应的块,由于这些块以数组形式罗列,故称为重定位表
若TypeOffset为0,则表明一个IMAGE_BASE_RELOCATION结构体结束
重定位表以NULL结构体结束,即IMAGE_BASE_RELOCATION结构体成员的值全部为NULL
0x03 .reloc节区
EXE形式的PE文件中,基址重定位表对运行没什么影响。将其删除后程序仍然正常运行(基址重定位表对DLL/SYS形式的文件来说几乎是必须的0)
VC++中生成的PE文件的重定位节区名为.reloc,删除该节区后文件照常运行,且文件大小将缩减。