操作系統是如何使用重定位表的

一、重定位表的結構

重定位表是數據目錄中第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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章