PE結構-重定位表

首先,先來說一下爲什麼需要重定位表,先來觀察一段程序

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中,需要修正的地址都會有下劃線的標識(也是根據重定位識別的)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章