重定位表(Relocation Table)是Windows PE可執行文件中的一部分,主要記錄了與地址相關的信息,它在程式載入和運行時被用來修改程式代碼中的地址的值,因為程式在不同的記憶體地址中載入時,程式中使用到的地址也會受到影響,因此需要重定位表這個數據結構來完成這些地址值的修正。當程式需要被加... ...
重定位表(Relocation Table)是Windows PE可執行文件中的一部分,主要記錄了與地址相關的信息,它在程式載入和運行時被用來修改程式代碼中的地址的值,因為程式在不同的記憶體地址中載入時,程式中使用到的地址也會受到影響,因此需要重定位表這個數據結構來完成這些地址值的修正。
當程式需要被載入到不同的記憶體地址時,相關的地址值需要進行修正,否則程式運行會出現異常。而重定位表就是記錄了在程式載入時需要修正的地址值的相關信息,包括修正地址的位置、需要修正的位元組數、需要修正的地址的類型等。重定位表中的每個記錄都稱為一項(entry),每個entry包含了需要修正的地址值的詳細信息,通常是以可變長度數據的形式存儲在一個或多個叫做重定位塊(relocation block)的數據結構中。
解析重定位表需要通過PIMAGE_BASE_RELOCATION
這個關鍵結構體來實現,PIMAGE_BASE_RELOCATION
是一個指向重定位表(Relocation Table)的指針類型,它是Windows PE可執行文件中用於支持動態基地址重定位(Dynamic Base Relocation)
的結構體類型。在2GB以上的虛擬地址下,Windows使用了Dynamic Base Relocation
技術來提高系統的安全性,PIMAGE_BASE_RELOCATION
就是在這種情況下使用的。
由於Windows系統中DLL文件並不能每次都能載入到預設的基址上,因此基址重定位主要應用於DLL文件中,通常涉及到直接定址的指令就需要重定位,重定位信息是在編譯時,由編譯器生成並被保存在可執行文件中的,在程式被執行前,由操作系統根據重定位信息修正代碼,這樣在開發程式的時候就不用了考慮重定位問題了,我們還是使用上面的這段彙編代碼。
00D21000 | 6A 00 | push 0x0 |
00D21002 | 68 0030D200 | push main.D23000 |
00D21007 | 68 0730D200 | push main.D23007 |
00D2100C | 6A 00 | push 0x0 |
00D2100E | E8 07000000 | call <JMP.0x00D2101A> | call MessageBox
00D21013 | 6A 00 | push 0x0 |
00D21015 | E8 06000000 | call <JMP.0x00D21020> | call ExitProcess
00801017 | CC | int3 |
00D2101A | FF25 0820D200 | jmp dword ptr ds:[<&0x00D22008>] | 導入函數地址
00D21020 | FF25 0020D200 | jmp dword ptr ds:[<&0x00D22000>] | 導入函數地址
如上jmp dword ptr ds:[<&0x00D22008>]
這段代碼就是一句需要重定位的代碼,當程式的基地址位於0x00D20000
時,這段代碼中的函數可以被正常調用,但有時程式會開啟基址隨機化,或DLL被動態裝載等問題,此時基地址可能會發生變化,那麼上面的彙編指令調用就會失效,這就意味著這些地址需要被修正。
此時我們假設程式基址變為了0x400000
,那麼jmp dword ptr ds:[<&0x00D22008>]
這條指令就需要被修正,修正演算法可以描述為,將直接定址指令中的地址加上模塊實際裝入地址與模塊建議裝入地址之差,為了進行運算需要3個數據,首先是需要修正機器碼地址,其次是模塊建議裝入地址,最後是模塊的實際裝入地址。
在這3個數據中,模塊的建議裝入地址已經在PE文件頭中定義了,而模塊的實際裝入地址時Windows裝載器在裝載文件時確定的,事實上PE文件重定位表中保存的僅僅只是,一大堆需要修正的代碼的地址。
重定位表IMAGE_BASE_RELOCATION解析
重定位表會被單獨存放在.reloc
命名的節中,重定位表的位置和大小可以從數據目錄中的第6個IMAGE_DATA_DIRECTORY
結構中獲取到,該表的組織方式時以0x1000
頁為一塊,每一塊負責一頁,從PE文件頭獲取到重定位表地址後,就可以順序讀取到所有表結構,每個重定位塊以一個IMAGE_BASE_RELOCATION
結構開頭,後面跟著在本頁中使用的所有重定位項,每個重定位項占用16位元組,最後一個節點是一個使用0填充的_IMAGE_BASE_RELOCATION
標誌表的結束,其結構如下所示:
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress; // 需重定位數據的起始RVA
DWORD SizeOfBlock; // 本結構與TypeOffset總大小
WORD TypeOffset[1]; // 原則上不屬於本結構
} IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;
TypeOffset的元素個數 = (SizeOfBlock - 8 )/ 2 TypeOffset的每個元素都是一個自定義類型結構
struct
{
WORD Offset:12; // 大小為12Bit的重定位偏移
WORD Type :4; // 大小為4Bit的重定位信息類型值
}TypeOffset; // 這個結構體是A1Pass總結的
PIMAGE_BASE_RELOCATION指針指向PE文件中的重定位表(Relocation Table)的起始地址,重定位表是一個可變長度的數據結構,其中包含了一組以4個位元組為單位的記錄,每個記錄表示一個需要修正的地址及其操作類型。
typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
每個重定位表項(Relocation Table Entry)包括兩部分:前16位表示需要修正的地址的偏移量(Offset),後16位則表示需要對該地址進行什麼樣的修正操作(Relocation Type)。普通的重定位項類型有如下幾種:
- IMAGE_REL_BASED_ABSOLUTE:表示不需要進行任何修正;
- IMAGE_REL_BASED_HIGHLOW:表示需要將地址中的低16位和高16位分別進行修正;
- IMAGE_REL_BASED_DIR64:表示需要對64位指針進行修正;
當讀者需要遍歷這個表時,首先可以通過NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress
獲取到重定位表的相對信息,並通過(PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA))
得到重定位表的FOA文件地址,在Reloc->SizeOfBlock
變數內獲取到重定位塊,並迴圈輸出則可實現枚舉所有重定位塊;
// --------------------------------------------------
// 重定位表解析結構體
// --------------------------------------------------
struct TypeOffset
{
WORD Offset : 12; // 低12位代表重定位地址
WORD Type : 4; // 高4位代表重定位類型
};
int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);
if (PE == TRUE)
{
// 1.拿到映像基地址
DWORD base = NtHeader->OptionalHeader.ImageBase;
// 2.獲取重定位表的RVA 相對偏移
DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;
// 3.獲取重定位表FOA
auto Reloc = (PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA));
printf("映像基址: %08X 虛擬偏移: %08X 重定位表基址: %08X \n", base, RelocRVA, Reloc);
// 4.遍歷重定位表中的重定位塊,以0結尾
while (Reloc->SizeOfBlock != 0)
{
// 計算出重定位項個數 \ 2 = 重定位項的個數,原因是重定位項的大小為2位元組
DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
// 輸出VirtualAddress分頁基址 與SizeOfBlock重定位塊長度
printf("起始RVA: %08X \t 塊長度: %04d \t 重定位個數: %04d \n", Reloc->VirtualAddress, Reloc->SizeOfBlock, Size);
// 找到下一個重定位塊
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
}
}
else
{
printf("非標準程式 \n");
}
system("pause");
return 0;
}
編譯並運行上述代碼片段,則讀者可以看到當前程式內所具備的重定位塊及該塊的記憶體地址,輸出效果圖如下所示;
上圖中我們得到了0x905a4d00
這個記憶體地址,該記憶體地址代表的則是重定位表中一個塊的基址,如果我們需要得到該基址內的其他重定位信息,則需要進一步遍歷,這個遍歷過程只需要更加細化將如上代碼片段進行更改,增加更加細緻的枚舉過程即可,更改後的代碼片段如下所示;
// --------------------------------------------------
// 傳入一個十六進位字元串,將其自動轉化為十進位格式:例如傳入40158b轉為4199819
// --------------------------------------------------
int HexStringToDec(char hexStr[])
{
int i, m, n, temp = 0;
// 迴圈讀入每一個十六進位數
m = strlen(hexStr);
for (i = 0; i < m; i++)
{
// 十六進位還要判斷他是不是在A-F或0-9之間的數
if (hexStr[i] >= 'A' && hexStr[i] <= 'F')
n = hexStr[i] - 'A' + 10;
else if (hexStr[i] >= 'a' && hexStr[i] <= 'f')
n = hexStr[i] - 'a' + 10;
else n = hexStr[i] - '0';
// 將數據加起來
temp = temp * 16 + n;
}
return temp;
}
int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);
char * GetRva = "0x905a4d00";
if (PE == TRUE)
{
DWORD base = NtHeader->OptionalHeader.ImageBase;
// 1. 獲取重定位表的 rva
DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;
// 2. 獲取重定位表
auto Reloc = (PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA));
printf("起始RVA \t 類型 \t 重定位RVA \t 重定位地址 \t 修正RVA \n");
// 起始RVA:% 08X-- > 類型:% d-- > 重定位RVA:% 08X-- > 重定位地址:% 08X 修正RVA : % 08X
// 3. 遍歷重定位表中的重定位塊,以0結尾
while (Reloc->SizeOfBlock != 0)
{
// 3.2 找到重定位項
auto Offset = (TypeOffset*)(Reloc + 1);
// 3.3 計算重定位項的個數
DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
// 3.4 遍歷所有的重定位項
for (DWORD i = 0; i < Size; ++i)
{
// 獲取重定位類型,只關心為3的類型
DWORD Type = Offset[i].Type;
// 獲取重定位的偏移值
DWORD pianyi = Offset[i].Offset;
// 獲取要重定位的地址所在的RVA: offset+virtualaddress
DWORD rva = pianyi + Reloc->VirtualAddress;
// 獲取要重定位的地址所在的FOA
DWORD foa = RVAtoFOA(rva);
// 獲取要重定位的地址所在的fa
DWORD fa = foa + GlobalFileBase;
// 獲取要重定位的地址
DWORD addr = *(DWORD*)fa;
// 計算重定位後的數據: addr - oldbase + newbase
DWORD new_addr = addr - base;
// 如果傳入了數值,則說明要遍歷特定的重定位表項
if (Reloc->VirtualAddress == HexStringToDec(GetRva))
{
printf("%08X \t %d \t %08X \t %08X \t%08X \n", Reloc->VirtualAddress, Type, rva, addr, new_addr);
}
// 否則如果不傳參數,則預設遍歷全部RVA
else if (strcmp(GetRva, "all") == 0)
{
printf("%08X \t %d \t %08X \t %08X \t%08X \n", Reloc->VirtualAddress, Type, rva, addr, new_addr);
}
}
// 找到下一個重定位塊
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
}
}
else
{
printf("非標準程式 \n");
}
system("pause");
return 0;
}
當讀者運行這段程式,則會輸出0x905a4d00
這段記憶體地址中所具有的所有重定位信息,輸出效果圖如下圖所示;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/72fc3188.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17684114.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!