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記錄的需要重定位的位置。