1. 程式人生 > >PE檔案格式學習(八):基址重定位表

PE檔案格式學習(八):基址重定位表

1.簡介

基址重定位表位於資料目錄表中的第六個,它位於安全表的後面。

這個表的作用是用來索引那些需要重定位的資料的。當系統發現DLL的真實載入基址跟PE檔案中的ImageBase中的值不一樣時,就會啟用基址重定位表修復一些資料的地址。我們知道一個程式中可能包含多個DLL,因此有可能多個DLL之間的ImageBase是重合的,一旦某個載入基址被佔用了,系統就會隨機分配一個基址給將要載入的DLL,比如說本文中的DLL的ImageBase是0x10000000,在它要載入進某個程式的時候發現0x10000000已經被佔用了,系統就會給它分配一個新的載入地址0x72ab2210,因為載入到了一個新的地址,所以DLL中的一些資料就索引不到了,因此就需要重定位表來索引到需要重定位的資料處,通過一個公式計算出修復後的地址,資料就可以正確被訪問了。

2.重定位表的解析

通過資料目錄表檢視到重定位表的RVA是5000h,轉換成offset為0x1a00,我們找到0x1a00處:

我們對比重定位表的結構體來分析:

typedef struct _IMAGE_BASE_RELOCATION
{
    DWORD VirtualAddress;
    DWORD SizeOfBlock;
    WORD TypeOffset[1];
}IMAGE_BASE_RELOCATION;

需要說明的是,資料目錄表中重定位表的RVA指向的位置是一個數組,裡面的元素都是IMAGE_BASE_RELOCATION,之所以是一個數組是因為每個IMAGE_BASE_RELOCATION只負責4KB大小分頁內的重定位資訊,換句話說PE檔案中需要重定位的部分每隔0x1000位元組就有一個IMAGE_BASE_RELOCATION負責。也因此結構中的VirtualAddress總是0x1000的倍數。

VirtualAddress:需要重定位的資料的RVA,這個值需要加上後面的TypeOffset的低12位就是需要重定位資料的RVA,這麼說可能不好理解,我們用本文使用到的程式作為例子,這是它的一部分彙編程式碼:

Offset        HEX          Assembly
10001000      55           push ebp
10001001      8BEC         mov ebp,esp
10001003      6AFE         push 0xFFFFFFFF
10001005      68 10220010  push 0x10002210

程式的ImageBase是0x10000000,VirtualAddress是0x1000,後面要提到的TypeOffset的低12位是0x006,有如下公式成立:

需要重定位的資料位置 = ImageBase + VirtualAddress + TypeOffset低12位

因此:

0x10001006 = 0x10000000 + 0x1000 + 0x006

這裡計算得到的最終需要重定位的資料地址是0x10001006,也就是push後面的地址0x10002210需要進行重定位。現在應該明白了重定位表的作用就是用來索引並定位到需要重定位資料的位置處,需要重定位的並不是重定位表中索引的值本身,而是指向的值,簡單地說就是需要修改的是0x10002210這個值,而不是0x10001006這個值。如果不明白也沒關係,後面會繼續講解,看完後面的再回來看就都明白了。繼續講解下一個欄位。

SizeOfBlock:整個重定位表的大小,IMAGE_BASE_RELOCATION結構的總大小,對應上圖中的0x118,實際上本文用到的程式有兩個IMAGE_BASE_RELOCATION結構,它們的大小分別是0x118和0x24,合起來是0x13c,正好是資料目錄表中標記的值。

TypeOffset:重定位的偏移,這個值加上IMAGE_BASE_RELOCATION中的VirtualAddress就是完整的RVA了,這個欄位實際上並不屬於IMAGE_BASE_RELOCATION結構體,但SizeOfBlock欄位記錄的大小包含了這個結構體,這個結構體通常有多個,按順序進行排列,最後以0x0000作為結尾,結構體本身佔2個位元組,結構體如下:

struct
{
    WORD Offset:12; 
    WORD Type:4; 
}TypeOffset;

Offset:上面介紹過,它跟VirtualAddress相加就是完整的重定位RVA

Type:重定位資訊的型別,有如下型別

#define IMAGE_REL_BASED_ABSOLUTE              0
#define IMAGE_REL_BASED_HIGH                  1
#define IMAGE_REL_BASED_LOW                   2
#define IMAGE_REL_BASED_HIGHLOW               3
#define IMAGE_REL_BASED_HIGHADJ               4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5    5
#define IMAGE_REL_BASED_RESERVED              6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7    7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8    8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9    9
#define IMAGE_REL_BASED_DIR64                 10
資訊 巨集定義
0 無重定位操作,用於4位元組對齊 IMAGE_REL_BASED_ABSOLUTE
1 重定位指向位置的高2個位元組需要被修正 IMAGE_REL_BASED_HIGH
2 重定位指向位置的低2個位元組需要被修正 IMAGE_REL_BASED_LOW
3 重定位指向位置的全部4個位元組需要被修正,絕大多數都是這種情況 IMAGE_REL_BASED_HIGHLOW
4 需要兩個TypeOffset配合完成索引 IMAGE_REL_BASED_HIGHADJ
5 IMAGE_REL_BASED_MACHINE_SPECIFIC_5
6 保留 IMAGE_REL_BASED_RESERVED
7 IMAGE_REL_BASED_MACHINE_SPECIFIC_7
8 IMAGE_REL_BASED_MACHINE_SPECIFIC_8
9 IMAGE_REL_BASED_MACHINE_SPECIFIC_9
10 重定位指向位置的8個位元組需要被修正 IMAGE_REL_BASED_DIR64

最後,對重定位表進行總結性分析。由資料目錄表中的RVA:5000h轉換得到0x1A00,在0x1a00處是重定位表的起始位置,從這裡開始可能有多個IMAGE_BASE_RELOCATION結構,每個結構負責4KB大小的頁內需要重定位的資訊。第一個IMAGE_BASE_RELOCATION的VirtualAddress是0x1000,SizeOfBlock是0x118,TypeOffset是0x3006,我們知道Typeoffset的高4位是Type,低12位是Offset,Type是指重定位資訊的型別,Offset是指偏移,通過第一個Offset的值我們可以確定,0x10000000+0x1000+0x006是需要被重定位修正的位置,第二個TypeOffset是0x300B,因為分析方式相同就不再贅述,後面還有若干個TypeOffset,最後以0x0000結尾。至於重定位表中記錄的重定位資訊的個數可以通過下面的公式進行計算:

重定位個數 = (SizeOfBlock - 8(IMAGE_BASE_RELOCATION的大小)) / 2(每個TypeOffset是2個位元組)
總重定位個數 = 重定位個數 * IMAGE_BASE_RELOCATION的個數

本文使用的DLL的第一個IMAGE_BASE_RELOCATION的SizeOfBlock的值是0x118,套用公式得到以下結果:

重定位個數 = (0x118 - 8) / 2 
          = 0x88

因為資料目錄表中記錄的重定位表的大小是0x13c,而第一個IMAGE_BASE_RELOCATION只佔用了0x118,所以應該還有其他的IMAGE_BASE_RELOCATION,通過0x1a00+0x118得到0x1b18,我們可以看到這個位置是另一個IMAGE_BASE_RELOCATION的起始,它的VirtualAddress是0x2000,它的SizeOfBlock是0x0024,通過將這兩個IMAGE_BASE_RELOCATION的SizeOfBlock相加正好等於0x13c,通過計算第二個IMAGE_BASE_RELOCATION內的重定位個數是(0x24-8)/2=0xe,所以最終分析出來這個重定位表包括了兩個IMAGE_BASE_RELOCATION,此程式一共有0x88+0xe個地方需要進行重定位修復。

我們檢視第一個TypeOffset0x3006,它的低12位是偏移,也就是0x006,我們知道DLL的ImageBase是0x10000000,但是實際載入基地址是0x72ab0000,因此係統檢測到這兩個值不同時會進行重定位操作,它會去TypeOffset處找到需要重定位的位置,第一個需要重定位的具體位置是:0x10000000+0x1000+0x006 = 0x10001006,上面已經說過0x10001006在call後面,也就是地址0x10002210處:

Offset           HEX         Assembly
10001005      68 10220010  push 0x10002210

系統會將它進行修復,修復方法是:

修復後地址 = (真實載入基址-預設載入基址) + 需要進行重定位的地址

修復後地址 = (0x72ab0000 - 0x10000000) + 0x10002210
                    = 0x72ab2210

系統接下來會修復每處TypeOffset記錄的需要重定位的位置。