PE-基址重定位与重定位表

c++

Posted by YiMiTuMi on September 2, 2021

基址重定位

当链接器生成一个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;
}

应用层:

PE文件格式

蓝色妖姬 – 相守