基址重定位
当链接器生成一个PE文件时,会假设这个文件在执行时被装载到默认的基地址处,并把 code 和 data 的相关地址都写入PE文件。如果载入时将默认的值作为基地址载入,则不需要重定位。但是,如果PE文件被装载到虚拟内存的另一个地址中,连接器登记的那个地址就是错误的,这时就需要用重定位表来调整。在PE文件中,重定位表往往单独作为一块,用“.reloc”表示。
PE格式不参考外部DLL或模块中的其他区块,而是把文件中所有可能需要修改的地址放到一个数组里(重定位表)。如果PE文件不在首选的地址载入,那么文件中的每一个定位都需要被修正。
对加载器来说,它不需要知道关于地址使用的任何细节,只要知道有一系列的数据需要以某种一致的方式来修正就可以了。
基址重定位表
基址重定位表结构:
基址重定位表(Base Relocation Table)位于一个 .reloc 区块内,其结构为:
IMAGE_BASE_RELOCATION STRUCT
VirtualAddress DWORD //重定位数据的开始RVA地址
SizeOfBlock DWORD //重定位块的长度
TypeOffset WORD //重定位项数组
IMAGE_BASE_RELOCATION ENDS
可以通过可选映像头的数据目录表的 IMAGE_DIRECTORY_ENTRY_BASERELOC 查找,位于数据目录表的第5项。
1)VirtualAddress : 这组重定位数据的开始RVA地址。各重定位项的地址加上这个值才是该重定位项完整的RVA地址。
2)SizeOfBlock:当前重定位结构的大小。因为 VirtualAddress 和 SizeOfBlock 的大小都是固定的4字节,所以这个值减8就是 TypeOffset 数组的大小。
3)TypeOffset:一个数组。数组每项大小为2字节,共16位。这16位分为高4位和低12位。高4位代表重定位类型,低12位是重定位地址,低12位地址加上 VirtualAddress 相加就是指向PE映像中需要修改的地址数据的指针。
基址重定位表数据存放方式:
基址重定位数据采用类似按页分割的方法组织,是由许多重定位块串拼接而成,每个块存放4KB(1000h)的重定向信息,每个重定位数据块的大小必须以4字节对齐。由一个 IMAGE_BASE_RELOCATION 结构开始。
也就是说:
1)基址重定位表是由连续的多个 IMAGE_BASE_RELOCATION 存储的
2)基址重定位表是根据页的大小来存放数据的,一个页大小为4KB,所以一个 IMAGE_BASE_RELOCATION 存储的数据为4K大小,因为 IMAGE_BASE_RELOCATION 结构是按照页的方式存储的,所以存在多少个 IMAGE_BASE_RELOCATION 结构就表示当前的PE文件使用了多少的物理页。
获取下一个 IMAGE_BASE_RELOCATION 结构:
IMAGE_BASE_RELOCATION 是连续存放的,上一个IMAGE_BASE_RELOCATION结束就是下一个 IMAGE_BASE_RELOCATION开始,所以:
当前IMAGE_BASE_RELOCATION结构开始地址 + 当前IMAGE_BASE_RELOCATION结构大小 = 下一个IMAGE_BASE_RELOCATION结构开始地址
当前IMAGE_BASE_RELOCATION结构大小 = IMAGE_BASE_RELOCATION.SizeOfBlock:
当前IMAGE_BASE_RELOCATION结构开始地址 + SizeOfBlock = 下一个IMAGE_BASE_RELOCATION结构开始地址
获取 TypeOffset 项数:
SizeOfBlock是当前重定位结构的大小。因为 VirtualAddress 和 SizeOfBlock 的大小都是固定的4字节,所以这个值减8就是 TypeOffset 数组的大小,并且 TypeOffset 的没一项的大小位 word所以项数为:
int iTypeOffsetCount = (int)(SizeOfBlock - 8) / 2;
重定位表地址
重定位表数据:
1)一个地址是4字节大小,当重定位地址多时会占用大量的空间,所以操作系统根据页的大小来存放数据,即一个 IMAGE_BASE_RELOCATION 结构只存放一个物理页的地址。
2)因为是同一个页,所以这个页的基址是相同的,用 VirtualAddress 来保存当前页的基址。
3)因为一个页的大小为4KB,这样我们只需要12位就可以表示出4KB大小页的所用位置。
4)TypeOffset数组的大小为2个字节,即16位,其中低12位为这个页的页内偏移,高4位为当前重定位地址的属性。
5)所以一个重定位地址的RVA地址为:
重定位地址的RVA = VirtualAddress + TypeOffset[i]的低12位
DWORD RelocationAddress = VirtualAddress + (DWORD)(TypeOffset[i] & 0x0FFF);
这样操作系统就不用没次保存4个字节大小的地址,从而节省空间。
6)重定位地址的RVA地址 + PE文件基址 = 要修复的地址
DWORD allRelocationAddress = RelocationAddress + pFileBuffer; //偏移地址 + 基址, 指向要修复的地址
DWORD dwModAddress = (DWORD)(*(PDWORD)allRelocationAddress); //修复的地址
7)TypeOffset数组保存16位数据,其中高4位为当前重定位地址的属性:
WORD TypeOffsetFlag = (TypeOffset[i] >> 12) & 0x000F; //获取高4位
if (((BYTE)TypeOffsetFlag) == IMAGE_REL_BASED_HIGHLOW || ((BYTE)TypeOffsetFlag) == IMAGE_REL_BASED_DIR64)
{
//计算要修正的地址
}
// IMAGE_REL_BASED_HIGHLOW = TypeOffsetFlag = 3 表示当前地址需要修正
修复重定位表
当我们自己手动将一个PE文件加入到内存需要是修正重定位表,步骤:
1)知道要修中成定位表的地址。
2)计算PE文件默认基址和加载后的基址的相差的offest。
3)将要修中的地址加上这个offest。
内核的程序:
//修复重定位表
NTSTATUS RepairRelocationTable(PVOID pBaseAddress) //函数接受一个PE文件基址
{
NTSTATUS status = STATUS_SUCCESS;
PIMAGE_NT_HEADERS pImageNT = (PIMAGE_NT_HEADERS)(((DWORD)pBaseAddress + ((PIMAGE_DOS_HEADER)pBaseAddress)->e_lfanew));
//获取重定位表的RVA,在PIMAGE_DATA_DIRECTORY的第5个,每个PIMAGE_DATA_DIRECTORY位8个字节
PIMAGE_DATA_DIRECTORY pRelocationTable = (PIMAGE_DATA_DIRECTORY)(pImageNT->OptionalHeader.DataDirectory);
pRelocationTable += 5;
if (pRelocationTable == NULL)
{
KdPrint(("获取DataDirectory数据失败!\n"));
status = STATUS_UNSUCCESSFUL;
return status;
}
int iRelocationTableCount = 0;
//获取第一个重定位表的结构体
PIMAGE_BASE_RELOCATION pRelocationTableInfoFirst = (PIMAGE_BASE_RELOCATION)((DWORD)pBaseAddress + pRelocationTable->VirtualAddress);
PIMAGE_BASE_RELOCATION pRelocationTableInfoNext = pRelocationTableInfoFirst;
//计算修改重定位地址的offest
DWORD dwBaseAddressoffest = ((DWORD)pBaseAddress) - (pImageNT->OptionalHeader.ImageBase);
while (TRUE)
{
PIMAGE_BASE_RELOCATION pRelocationTableInfo = pRelocationTableInfoNext;
if (pRelocationTableInfo->SizeOfBlock == 0 || pRelocationTableInfo->VirtualAddress == 0)
{
break;
}
DbgPrint("第%d块------------------ \n", iRelocationTableCount + 1);
//计算当前重定位表有多少个TypeOffset
int iTypeOffsetCount = (int)(pRelocationTableInfo->SizeOfBlock - 8) / 2;
//循环TypeOffset数组
DWORD wTypeOffsetAddress = (DWORD)pRelocationTableInfo + 8; //获取TypeOffset数组的首地址
for (int i = 0; i != iTypeOffsetCount; i++)
{
WORD TypeOffsetInfo = (WORD)(*((PDWORD)(wTypeOffsetAddress + (i * sizeof(WORD)))));
WORD TypeOffsetFlag = (TypeOffsetInfo >> 12) & 0x000F; //获取高4位
if (((BYTE)TypeOffsetFlag) == IMAGE_REL_BASED_HIGHLOW || ((BYTE)TypeOffsetFlag) == IMAGE_REL_BASED_DIR64)
{
//计算要修正的地址
DWORD RelocationAddress = pRelocationTableInfo->VirtualAddress + (DWORD)(TypeOffsetInfo & 0x0FFF);
DbgPrint("PE内偏移地址:%x \n", RelocationAddress);
DWORD allRelocationAddress = RelocationAddress + (DWORD)pBaseAddress; //偏移地址 + 基址, 指向要修复的地址
DbgPrint("要修正地址:%x \n", *(PDWORD)allRelocationAddress);
*(PDWORD)allRelocationAddress += dwBaseAddressoffest; //修正重定位地址
}
}
//计算下个重定位表的地址
pRelocationTableInfoNext = (PIMAGE_BASE_RELOCATION)((DWORD)pRelocationTableInfo + pRelocationTableInfo->SizeOfBlock); //当前结构开始地址加结构体大小
iRelocationTableCount++;
}
return status;
}
应用层: