PE檔案學習筆記(四):重定位表(Relocation Table)解析
1、重定位表的作用
重定位表(Relocation Table)用於在程式載入到記憶體中時,進行記憶體地址的修正。為什麼要進行記憶體地址的修正?我們舉個例子來說:test.exe可執行程式需要三個動態連結庫dll(a.dll,b.dll,c.dll),假設test.exe的ImageBase為400000H,而a.dll、b.dll、c.dll的基址ImageBase均為1000000H。
那麼作業系統的載入程式在將test.exe載入進記憶體時,直接複製其程式到400000H開始的虛擬記憶體中,接著一一載入a.dll、b.dll、c.dll:假設先載入a.dll,如果test.exe的ImageBase + SizeOfImage + 1000H不大於1000000H,則a.dll直接複製到1000000H開始的記憶體中;當b.dll載入時,雖然其基址也為1000000H,但是由於1000000H已經被a.dll佔用,則b.dll需要重新分配基址,比如載入程式經過計算將其分配到1200000H的地址,c.dll同樣經過計算將其載入到150000H的地址。如下圖所示:
但是b.dll和c.dll中有些地址是根據ImageBase固定的,被寫死了的,而且是絕對地址不是相對偏移地址。比如b.dll中存在一個call 0X01034560,這是一個絕對地址,其相對於ImageBase的地址為δ = 0X01034560 - 0X01000000 = 0X34560H;而此時的記憶體中b.dll存在的地址是1200000H開始的記憶體,載入器分配的ImageBase和b.dll中原來預設的ImageBase(1000000H)相差了200000H,因此該call的值也應該加上這個差值,被修正為0X01234560H,那麼δ = 0X01234560H - 0X01200000H = 0X34560H則相對不變。否則call的地址不修正會導致call指令跳轉的地址不是實際要跳轉的地址,獲取不到正確的函式指令,程式則不能正常執行。
由於一個dll中的需要修正的地址不止一兩個,可能有很多,所以用一張表記錄那些“寫死”的地址,將來載入進記憶體時,可能需要一一修正,這張表稱作為重定位表,一般每一個PE檔案都有一個重定位表。當載入器載入程式時,如果載入器為某PE(.exe、.dll)分配的基址與其自身預設記錄的ImageBase不相同,那麼該程式檔案載入完畢後就需要修正重定位表中的所有需要修正的地址。如果載入器分配的基址和該程式檔案中記錄預設的ImageBase相同,則不需要修正,重定位表對於該dll也是沒有效用的。比如test.exe和a.dll的重定位表都是不起作用的(由於一般情況.exe執行時被第一個載入,所以exe檔案一般沒有重定位表,但是不代表所有exe都沒有重定位表
2、重定位表的結構與解析:
知道了重定位表的作用,現在我們來分析一個重定位表在PE檔案中是如何存在的,首先來看看描述重定位表的結構:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//RVA
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION,* PIMAGE_BASE_RELOCATION;
#define IMAGE_SIZEOF_BASE_RELOCATION 8
該結構體有兩個成員:一個是地址,一個是大小。如下圖所示:一個重定位表由多個大小SizeOfBlock的Block組成,(不同塊的SizeOfBlock大小不一)。每一個塊記錄了1000H即4KB大小的記憶體中需要重定位資訊的地址(一頁大小),這些地址以VirtualAdress為該頁的基址,偏移地址佔兩個位元組(1000H最多需要12bit即可:0~FFFH)。所以兩個位元組的低12位為偏移地址,而高4位就是一個標記,當此標記為0011(3)時低12為才有效,否則該2個位元組可能是為了對齊而產生的,並且為對齊而產生的位元組其值全為0。
由於重定位表的SizeOfBlock大小不確定,新的Block的重定位資訊的結構體接著上一個Block4位元組對齊後開始,而當出現一個_IMAGE_BASE_RELOCATION結構體的值全為0時,表明重定位表結束。
3、程式碼解析PE的Relocation Table :
//將解析出來的重定位表資訊寫入檔案
void PETool::print_BaseRelocation()
{
fprintf(fp_peMess, "重定位表(Relocation):\n");
if(dataDir[5].VirtualAddress == 0){
fprintf(fp_peMess, "\t不存在重定位表!\n");
return;
}
BYTE secName[9] = {0};
//rec指向重定位表第一個IMAGE_BASE_RELOCATION結構體
IMAGE_BASE_RELOCATION * rec = (IMAGE_BASE_RELOCATION *)(pFileBuffer + RVAToFOA(dataDir[5].VirtualAddress));
for(int i = 1; rec->SizeOfBlock && rec->VirtualAddress; i++){
DWORD foa = RVAToFOA(rec->VirtualAddress);
DWORD size = (rec->SizeOfBlock - 8) / 2;
//確定該結構體所處節,並獲取節名稱
IMAGE_SECTION_HEADER * section = section_header;
for(int t = 0; t < sectionNum; t++){
DWORD lower = RVAToFOA(section[t].VirtualAddress);
DWORD upper = RVAToFOA(section[t].VirtualAddress) + section[t].Misc.VirtualSize;
if(foa >= lower && foa <= upper){
memcpy(secName, section[t].Name, 8);
break;
}
}
//列印該頁主要資訊
fprintf(fp_peMess, "\tIndex[%d]\tsection[%s]\tOffset[%08X]\tItems[%dD:%XH]\t【Block】\n", i, secName, foa, size, size);
//列印一個頁中所有重定位地址與資訊
WORD * recAddr = (WORD *)((BYTE *)rec + 8);//recAddr指向重定位表結構體後的首位元組
fprintf(fp_peMess, "\t\tindex\toffset\t\ttype\t【Block Items】\n");
for(DWORD j = 0; j < size; j++){
DWORD offset = (recAddr[j] & 0X0FFF) + foa;//低四位是偏移地址
WORD type = recAddr[j] >> 12;//高四位是有效判斷位
if(type == 0){
fprintf(fp_peMess, "\t\t[%d] \t[--------]\tABSOLUTE[%d]\n", j+1, type);
continue;
}
fprintf(fp_peMess, "\t\t[%d] \t[%08X]\tHIGHLOW[%d]\n", j+1, offset, type);
}
memset(secName, 0, 9);
rec = (IMAGE_BASE_RELOCATION *)((BYTE *)rec + rec->SizeOfBlock);//進行下一頁的判斷
}
}
由於列印的資訊過長(一個只包含加減乘除四個簡單函式的庫,其重定位資訊的地址就有2500多條),這裡只提出一部分比較短的Block資訊,可以很明顯地看到當需要重定位資訊的記錄長度是4Byte的倍數時,不存在高四位為0000的情況,當其不為4的倍數時,就有一個因對齊而產生的資料:
//...
Index[21] section[.text] Offset[00015000] Items[16D:10H] 【Block】
index offset type 【Block Items】
[1] [00015002] HIGHLOW[3]
[2] [00015008] HIGHLOW[3]
[3] [0001500E] HIGHLOW[3]
[4] [00015014] HIGHLOW[3]
[5] [0001501A] HIGHLOW[3]
[6] [00015020] HIGHLOW[3]
[7] [00015026] HIGHLOW[3]
[8] [0001502C] HIGHLOW[3]
[9] [00015032] HIGHLOW[3]
[10] [00015038] HIGHLOW[3]
[11] [0001503E] HIGHLOW[3]
[12] [00015044] HIGHLOW[3]
[13] [0001504A] HIGHLOW[3]
[14] [00015050] HIGHLOW[3]
[15] [00015056] HIGHLOW[3]
[16] [--------] ABSOLUTE[0]
//...
Index[23] section[.rdata] Offset[0002D000] Items[12D:CH] 【Block】
index offset type 【Block Items】
[1] [0002D804] HIGHLOW[3]
[2] [0002D808] HIGHLOW[3]
[3] [0002D810] HIGHLOW[3]
[4] [0002D814] HIGHLOW[3]
[5] [0002D874] HIGHLOW[3]
[6] [0002D878] HIGHLOW[3]
[7] [0002D884] HIGHLOW[3]
[8] [0002D888] HIGHLOW[3]
[9] [0002D8E4] HIGHLOW[3]
[10] [0002D8E8] HIGHLOW[3]
[11] [0002D8F0] HIGHLOW[3]
[12] [0002D8F4] HIGHLOW[3]
//...
Index[25] section[.data] Offset[00031000] Items[4D:4H] 【Block】
index offset type 【Block Items】
[1] [00031CC0] HIGHLOW[3]
[2] [00031CC8] HIGHLOW[3]
[3] [00031CCC] HIGHLOW[3]
[4] [--------] ABSOLUTE[0]
//...