首先,先來說一下爲什麼需要重定位表,先來觀察一段程序
00401000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |. 68 1D204000 push 0040201D ; |Title = "PE"
00401007 |. 68 10204000 push 00402010 ; |Text = "Hello World!"
0040100C |. 6A 00 push 0 ; |hOwner = NULL
0040100E |. E8 07000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
00401013 |. 6A 00 push 0 ; /ExitCode = 0
00401015 \. E8 06000000 call <jmp.&KERNEL32.ExitProcess> ; \ExitProcess
0040101A $- FF25 08204000 jmp dword ptr [<&USER32.MessageBoxA>>; user32.MessageBoxA
00401020 .- FF25 00204000 jmp dword ptr [<&KERNEL32.ExitProces>; kernel32.ExitProcess
假設上述程序的建議裝載地址爲0x400000,然而當操作系統裝載時,該地址被佔用,所以實際地址裝載地址爲0x500000。
好了,那麼你會發現,上述的代碼片段中,是不是有些地址需要被修正呢,如第二行代碼,其中的40201D肯定就不是原來的值了,50201D才爲正確的值,OK,那麼下面我們來列出上述程序所有需要被修正的地址
相對虛擬地址 需修正的地址 修正後地址
1003 40201D 50201D
1008 402010 502010
101C 402008 502008
1022 402000 502000
好了,觀察這些地址,我們其實可以明白爲什麼這些地址需要被修正了,因爲這些地址在代碼中是寫死的,或者說在二進制層面是固定的值,而且是虛擬地址,所以一旦基址發生變化後,這些地址都需要修正。
其實重定位表中記錄的其實就是上面的相對虛擬地址,我們可以根據這個地址找到需要修正的地址並對其進行修正,那麼如何修正呢?這裏應該也比較明顯,應該爲原地址+(實際裝載地址-原裝載地址),後面後面括號部分其實就是個差值,把這部分差值加上就可以修正了,下面來模擬一下上面修正過程,就那1003來說吧,然後原裝載地址爲0x400000,實際地址裝載地址爲0x500000
假設已從重定位表中取出1003
1.計算修正地址VA
1003 + 500000 = 501003
2.取四字節內容,因爲上面地址指向的內容是需要修正的
*(PDWORD)501003 = 40201D
3.修正上面取出的內容(加上實際裝載和原裝載的偏移)
40201D + (500000 - 400000) = 50201D
4.寫回修正後的地址
*(PDWORD)501003 = 50201D
我們先來手工修改一下ImageBase和地址,來試一試是否可以修正成功
OD載入再次觀察代碼
00501000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00501002 |. 68 1D205000 push 0050201D ; |Title = "PE"
00501007 |. 68 10205000 push 00502010 ; |Text = "Hello World!"
0050100C |. 6A 00 push 0 ; |hOwner = NULL
0050100E |. E8 07000000 call <jmp.&USER32.MessageBoxA> ; \MessageBoxA
00501013 |. 6A 00 push 0 ; /ExitCode = 0
00501015 \. E8 06000000 call <jmp.&KERNEL32.ExitProcess> ; \ExitProcess
0050101A $- FF25 08205000 jmp dword ptr [<&USER32.MessageBoxA>>; user32.MessageBoxA
00501020 .- FF25 00205000 jmp dword ptr [<&KERNEL32.ExitProces>; kernel32.ExitProcess
可以將這段代碼與上面代碼進行對比,對比後就會更清楚了。
好了明白了重定位的意義後,我們就可以來理解一下這個重定位表的結構了,也就是如何記錄上述的那些地址
上面有提到過重定位表中記錄的是VA,不過這個VA需要用多少字節來存儲呢,這裏的話我們想想,2字節,貌似不夠,比如其真正地址爲5FFFFF,那麼其VA爲FFFFF,明顯已經超過2字節了,所以應該需要四字節,那麼我們算一下總共大小
4 * sizeof DWORD = 16
好了,看着很小,只有16字節,不過是因爲我們這個程序需要重定位的值比較少而已,一般都是大量的。那麼如何可以優化呢,我們可以按頁來劃分,一頁爲0x1000,此時2字節就能存下了,那麼頁基址還是需要使用四字節來記錄的
頁基址: 1000
偏移: 3 8 1C 22
因爲偏移肯定在頁的範圍內,所以只需要使用2字節記錄即可
sizeof DWORD + 4 * sizeof WORD = 12
這裏上下兩差異是大不太,因爲這裏基數太小的原故,你可以把上面的4改成一個大一點的基數,那麼差異就畢竟明顯了,幾乎可以說是折半了。
這裏的這個結構就比較像重定位表的結構了,不過我們先再來想一個問題,一頁的範圍爲[0,0xFFF],那麼其實使用12位記錄即可,2字節佔16字節,所以是不是會多出來4位呢,這裏的話操作系統使用高四位另做他用,起到狀態位的效果
#define IMAGE_REL_BASED_ABSOLUTE 0 //無效重定位項,通常用於對齊
#define IMAGE_REL_BASED_HIGHLOW 3 //高低位修正,也就是四字節,windows 32位情況
#define IMAGE_REL_BASED_DIR64 10 //64位情況
這裏我們只需要關注上幾種即可,其餘的是微軟考慮其他操作系統兒準備的,比如可能有些操作系統只需要修正高位或者低位等等情況。
所以當我們在取這個VA地址的時候,切記需要驗證狀態位,然後取低12位當做頁內偏移進行處理。
好了,重點其實差不多都羅列出來了,下面可以來看這個結構了
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; //頁基址 4字節
DWORD SizeOfBlock; //總共塊大小
// WORD TypeOffset[1]; //偏移數組
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
首先對於頁基址和偏移數組都好理解,那麼這個塊大小是什麼呢?其實因爲這裏的偏移數組是個不確定大小的(雖然結構體裏面把這個字段註釋掉了,但是在內存構造中這個偏移數組就跟在後面),因爲每一頁中有多少需要修正並不知道,所以纔有了塊大小,這裏的塊大小也包含VirtualAddress和SizeOfBlock兩個字段的大小
(SizeOfBlock - sizeof IMAGE_BASE_RELOCATION) / sizeof WORD
(SizeOfBlock - 8) / 2
所以這個塊大小減去這兩個字段的大小,然後除以2就是需要修正的個數了。
這裏的SizeOfBlock作用二就是定位下一塊的修正塊,也就是該塊的地址加上該塊的大小,這樣子就定位到了下一塊的數據。
那麼何時結束呢,這裏就用找數據目錄表了,重定位表位於數據目錄的第五項,所以上面的結構我們需要從數據目錄的偏移過去
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //重定位塊的首地址
DWORD Size; //總共重定位塊的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
OK,下面說說如何結束,這裏數據目錄項的Size是有意義的,也就是全部重定位塊佔用的總大小,我們需要在遍歷每一塊重定位塊的時候減去SizeOfBlock,這樣子減到零說明遍歷就結束了
下面來分析一個簡單的DLL模塊,首先,來看一下數據目錄表的第五項
做一下地址轉換得出,重定位塊位於0xA00,總大小爲0xC,這裏我們可以先預測一下,一個重定位塊最少需要8字節(頁內無偏移修正,不過話說回來,要是沒修正的話也不會存在該塊..),所以可以確定重定位塊數只有一塊,偏移有2項((0xC-0x8)/2)
下面來看一下數據
預測還是正確的,這裏的頁基址爲0x1000,塊大小爲0xC,然後計算偏移項數爲2,下面來分析下這兩項,基址爲0x10000000
0x3036
1.取狀態 (0x3036&0xF000) >> 12
結果爲3,說明是個32位的四字節修正
2.計算修正地址
計算頁內偏移 0x3036 & 0x0FFF = 0x36
加上頁基址 0x1000 + 0x36 = 0x1036
加上實際基址 0x10000000 + 0x1036 = 0x10001036
3.對該地址處的四字節內容進行修正
*(PDWORD)0x10001036 += (實際裝載地址-建議裝載地址)
0x0000
1. 同上取狀態
結果爲0,說明無效重定位項,直接略過即可
供2項,遍歷結束
數據目錄項中的Size 減去 SizeOfBlock,結果爲0,遍歷修正結束。
OK,下面我們使用OD打開來驗證一下0x10001036項的地址是否需要修正
看結果是正確的,在OD中,需要修正的地址都會有下劃線的標識(也是根據重定位識別的)。