一、重定位表的结构
重定位表是数据目录中第6项,它的结构如图示:
重定位表由多个块(block)组成,每个块内部由三部分组成——VirtualAddress、SizeOfBlock 和若干个2字节偏移。SizeOfBlock 表示当前块的大小,当 SizeOfBlock 与 VirtualAddress 同时为0,表示重定位表结束。
VirtualAddress 表示一个页面的基址的RVA,2字节偏移的数量 = (SizeOfBlock - 8) / 2,VirtualAddress+2字节偏移表示一个32位地址。其中,2字节偏移又分为高4位和低12位,高四位是属性,当它等于0011时,表示这个地址需要“重定位”;低12位刚好足够表示一个页面的所有地址,因为32位页面大小 = 2^12 = 4KB.
有了上述概念以后,就可以写出解析重定位表的程序了。但是对于初学者来说,重定位表是个啥,里面存的东西到底怎么使用,还是不清楚的。在讲解操作系统如何使用重定位表之前,不妨先了解一下为什么需要重定位表。
二、为什么要设计重定位表
每个PE文件(exe,dll)都有一个 ImageBase 表示它在4GB虚拟地址空间中加载的位置,一个运行的程序由一个exe和多个dll构成。对于exe来说,它总是最先加载到内存中,因此没有其他模块会和它抢内存,它总是可以加载到它希望去的位置,也就是exe指定的ImageBase;但是dll就不是这样了,也许程序用到了3个dll,它们的ImageBase都是1000000H,那就一定会发生冲突,后加载的dll只能往高地址加载了。
在代码中会用到一些写死的地址,例如:
100012CD |. E8 5EFEFFFF CALL 10001130
这个地址 10001130 是 ImageBase + RVA 算出来的,对于DLL2来说,由于无法抢占到期望的 ImageBase ,所以写死的地址就无效了,要改成 20001130 才对,因此就需要用一个表来记录下所有需要修正的地方,重定位表就是为了解决这个问题而设计的。
三、重定位表里存的是什么
重定位表存储的地址由 VirtualAddress + 2字节偏移 得到,计算出来的是RVA,加上 ImageBase 就是在内存中的地址。假设DLL2期望的ImageBase是 10000000,实际分配到的 ImageBase 是 20000000,程序里有一条指令:
100012CD |. E8 5EFEFFFF CALL 10001130
那么重定位表里一定在某个地方存储了 12CD 这个地址(VA+2字节偏移得到),类似的地址还有很多,每个2字节偏移就对应一个这样的地址。那么操作系统在加载DLL时,就会用DLL指定的 ImageBase + 12CD 得到一个地址,然后去修改里面写死的地址,也就是将 10001130 改成 20001130,修改当然就是加上实际ImageBase和期望ImageBase的差值了。
这就是操作系统使用重定位表的时候做的事情。
四、解析重定位表的代码
// 打印重定位表
VOID PrintRelocationTable(LPVOID pFileBuffer)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
//PIMAGE_SECTION_HEADER pSectionHeader = \
// (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_BASE_RELOCATION pBaseRelocation = \
(PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[5].VirtualAddress));
while (pBaseRelocation->VirtualAddress || pBaseRelocation->SizeOfBlock)
{
puts("-------------------------------------");
printf("VirtualAddress = %08x\n", pBaseRelocation->VirtualAddress);
printf("SizeOfBlock = %08x\n", pBaseRelocation->SizeOfBlock);
PWORD pwAddr = (PWORD)((DWORD)pBaseRelocation + 8);
int n = (pBaseRelocation->SizeOfBlock - 8) / 2;
printf("要修改的地址个数 = %d\n", n);
for (int i = 0; i < n ; i++)
{
WORD wProp = (0xF000 & pwAddr[i]) >> 12;
WORD wAddr = 0x0FFF & pwAddr[i];
printf("[%d]:RVA = %08x\t属性 = %d\n", i+1, pBaseRelocation->VirtualAddress + wAddr, wProp);
}
pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseRelocation + pBaseRelocation->SizeOfBlock);
}
}
运行结果
用LordPE验证了运行结果的正确性:
五、模拟操作系统使用重定位表修正地址
编写程序,修改ImageBase的值,然后模拟DLL加载时操作系统根据重定位表做的地址修正工作。
代码在我的机器上测试过,修改后的DLL能够正常使用。
// 修改 ImageBase 并修复重定位表
VOID SetImageBase(LPVOID pFileBuffer, DWORD dwNewImageBase)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)(pDosHeader->e_lfanew + (DWORD)pDosHeader + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(IMAGE_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = \
(PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_BASE_RELOCATION pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + \
RvaToFoa(pFileBuffer, pOptionHeader->DataDirectory[5].VirtualAddress));
DWORD dwImageBaseDelta = dwNewImageBase - pOptionHeader->ImageBase; // 新旧ImageBase 的差值
// 重定位表的 VirtualAddress + 低12位偏移 = RVA
// RVA + ImageBase 这个内存里存储了一个“指针”
// 要修改的是这个“指针”的值,要让这个“指针”加上两个ImageBase的差值
while (pRelocationTable->VirtualAddress || pRelocationTable->SizeOfBlock)
{
size_t n = (pRelocationTable->SizeOfBlock - 8) / 2; // 可能需要修改的地址数量(高4位==0011才要修改)
PWORD pOffset = (PWORD)((DWORD)pRelocationTable + 8); // 2字节偏移的数组
for (size_t i = 0; i < n; i++)
{
// 高4位等于0011才需要重定位
if ((pOffset[i] & 0xF000) == 0x3000)
{
// 计算需要重定位的数据的RVA地址
DWORD dwRva = pRelocationTable->VirtualAddress + (pOffset[i] & 0x0FFF);
// 计算在文件中的偏移
DWORD dwFoa = RvaToFoa(pFileBuffer, dwRva);
// 计算在文件中的地址
PDWORD pData = (PDWORD)((DWORD)pFileBuffer + dwFoa);
// 重定位,即修正写死的地址
*pData += dwImageBaseDelta;
}
}
pRelocationTable = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTable + pRelocationTable->SizeOfBlock);
}
// 修改 ImageBase
pOptionHeader->ImageBase = dwNewImageBase;
}