PE格式第七講,重定位表
作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文連結進行轉載:)
一丶何為重定位(注意,不是重定位表格)
首先,我們先看一段程式碼,比如呼叫Printf函式,使用OD檢視.
那麼大家有沒有想過這麼一個問題,函式的字串偏移是00407030位置,函式Call的地址是00401020的位置
但是如果模組首地址申請不到了,變為了00100000的位置,那麼此時的偏移是不是都是錯的了?
首先說下,一般重定位表格都是DLL中的,因為滿足不了模組首地址的需求,所以會遇到函式的重定位問題.
那麼如果磨壞地址變為了00100000的位置,那麼對應的字串位置是不是也要變為00107030的位置,而Call的地址,是不是也要變為00101020的位置
那麼這個就叫做重定位,我們要把偏移,以及各種需要修正的位置,變為正確的.
二丶重定位表格如何設計?
首先我們自己先想一下,重定位的表格要如何設計?
我猜想,你要儲存模組的地址 ,修改地址,偏移, 以及大小.
新的模組 ImageBase
舊的模組 iMageBase
修改的地址
偏移
修改的大小
那麼如果這樣設計會不會出現問題?
會出現很多問題,比如佔得位元組太多了,如果是Kerner32.dll裡面都是這樣設計,那麼得要多少記憶體.
那麼進一步的優化
可不可以一個分頁,儲存修改的偏移,以及長度
分頁: page (DWORD) 佔4個位元組
大小: size (DWORD) 偏移:offset(DWORD)
表格設計為上面的,
感覺這樣可以了.但是感覺還可以進一步的優化,大小,以及偏移都佔4個位元組,是不是浪費了
而且如果記錄一個分頁中的重定位的資料,那麼偏移是不會超過12位的(二進位制12位,轉為10進位制是1024), 那麼如果一個DWORD儲存檔案偏移,那麼高4位是沒有用的.
而且我們發現,大小也是很佔位置的.大小一個位元組就可以表示了,比如0 做對齊使用,1修改高16位的偏移,2修改低16位的偏移,3修改4個位元組大小
那麼是不是可以合併了
page (DWORD)
sizeofoffset
0x3005 代表的意思就是看高位,3代表我要修改4個位元組,005代表修改的當前頁的偏移位置.
三丶真正的重定位表格
看下重定位表格的真正的結構體吧.
程式碼:
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; 頁儲存的RVA DWORD SizeOfBlock; word型別陣列的個數,也就是下面註釋的 // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
那麼看看是不是和我們猜想的一樣,我們隨便找個DLL,在資料目錄中定位重定位表格
1.尋找資料目錄RVA偏移
我們首先要找到資料目錄中重定位表格的RVA偏移然後判斷屬於哪個節,通過公式轉化,得到在檔案中的實際偏移位置.
得出RVA = 6000h
2.判斷屬於哪個節
我們發現,新增加了一個節,這個節就是重定位的節然後虛擬地址是6000位置,而且在檔案偏移的位置也是6000h
那麼我們就得出 FA = RVA了,那麼就不用算了,可以確定,檔案偏移位置就是6000就是重定位表的位置
3.定位檔案偏移處,檢視排列
然後可以看出 前八個位元組分別儲存頁的RVA偏移,以及大小,.我們使用計算器計算一下,看看有多大
計算的出 160h,這個大小,儲存的是陣列大小+上我們八個位元組的總大小,也就是說160 - 8 = 陣列的大小了.
可以看出確實是怎麼大,然後就到記錄下一個分頁了.
四丶陣列解析檢視
那麼按照我們的想法看上面重定位表中的陣列的第一個,按照小尾方式讀取則是
0x3005 那麼高位是3那麼就是要修改大小是4個位元組,005則是代表偏移.
至於高位怎麼檢視,VC++6.0中的巨集已經定義了.
程式碼:
#define IMAGE_REL_I386_ABSOLUTE 0x0000 // Reference is absolute, no relocation is necessary #define IMAGE_REL_I386_DIR16 0x0001 // Direct 16-bit reference to the symbols virtual address #define IMAGE_REL_I386_REL16 0x0002 // PC-relative 16-bit reference to the symbols virtual address #define IMAGE_REL_I386_DIR32 0x0006 // Direct 32-bit reference to the symbols virtual address #define IMAGE_REL_I386_DIR32NB 0x0007 // Direct 32-bit reference to the symbols virtual address, base not included #define IMAGE_REL_I386_SEG12 0x0009 // Direct 16-bit reference to the segment-selector bits of a 32-bit virtual address #define IMAGE_REL_I386_SECTION 0x000A #define IMAGE_REL_I386_SECREL 0x000B #define IMAGE_REL_I386_REL32 0x0014 // PC-relative 32-bit reference to the symbols virtual address
這裡只需要知道0 1 2 代表的意思即可,因為0x3005的高位是 用位運算 | 上去的,所以3代表的是1 和 2的組合
0 對齊使用
1.修改高16位
2.修改低16位
1和2 使用位運算|起來就是修改4個位元組.
1.定位修改位置
那麼怎麼定位要修改的位置那?
公式:
現在的ImageBase(模組地址) + 當前分頁大小的虛擬地址 +5的位置等於要修改的位置:
比如假設我們的現在的模組地址是00400000位置,而DLL以前的位置是10000000 而它以前的字串的偏移是 10003045
首先定位修改地址:
00400000 + 1000 + 005 = 401005 那麼在401005的位置就是你要修改的位置
比如我們在寫一個
0x3096 = 400000 + 1000 + 96 = 401096 那麼定位的位置就是401096是你要修改的偏移,大小是4個位元組,高位為3 為什麼是4個位元組,一會看下內部儲存
2.修改的偏移計算
比如我們呼叫一個printf
push 10003096 "HelloWorld"
call 10004086
那麼我們要修正他的偏移
我們現在得知,以前的DLL偏移是 10000000 以前的字串偏移是 10003096 ,不過因為DLL的模組地址沒有滿足,那麼現在的模組地址變為了00400000的位置
那麼我們要修正偏移
公式:
現在的ImageBase (00400000) - 以前的ImageBase(10000000) + 以前的偏移(10003096)
這樣寫彙編程式碼好寫,如果便於理解的話,可以寫成下面那樣,只不過你需要知道的是彙編程式碼就是上面這種寫法就行
以前的偏移(10003096) - 以前的ImageBase(10000000) + 現在的ImageBase(00400000)
= 3096 + 400000
= 403096 (計算出來的偏移)
那麼push的位置就成了 403096了,已經重定位了.
五丶實戰演練檢視
因為DLL中的重定位表中的項太多,所以這裡使用一個EXE(沒有匯出函式的EXE),然後注入這個DLL,那麼這個EXE就有重定位表格了.
首先我們先看DLL, 3005的位置要重定位
按照公式我們得出,要修改的位置是
現在模組地址 + 當前表中記錄分頁 + 陣列中後三位的偏移(上面說過,如果按照分頁儲存,那麼3位就可以表達一個分頁需要記錄的了)
那麼現在 我們的EXE的模組地址是00500000 + 1000(重定位表中第一項成員) + 005 (這是一個數組,第一個成員是0x3005 取出後三位則是005)
得出修改的位置是 00501005的位置,我們OD中CTRL+ G看看這個位置是不是要修正.
程式碼亂了,那麼我們可能斷掉指令了,那麼此時CTRL + A重新分析一下.
可以看出,我們修正的位置是501005的位置,不過彙編程式碼在501004才能顯示出來,501005後面正好是要修正的地址,那麼只需要計算偏移填進去就可以了,大小是按照高4個位元組, 現在0x3005 高位是3那麼代表了要修正4個位元組的偏移.
算出偏移位置:
偏移位置我們要進行反推了
因為OD已經幫我們重定位好了.
503000 = 現在的ImageBase - 以前的ImageBase + 以前的偏移 = 現在的偏移(503000)
那麼現在計算以前的偏移
以前的偏移 = 現在的偏移 - 現在的ImageBase + 以前的ImageBase
= 503000 - 50000 + 60000000
= 3000 + 60000000
= 60003000 (以前的偏移)
那麼算出了以前的偏移,我們就計算這4個位元組要填寫的偏移,也就是503000怎麼得出來的
公式上面說了:
Cur (縮寫,代表當前的意思) Old(代表舊的意思) offset(代表偏移的意思)
CurImageBase - OldImageBase + OldOffset = 要填入的偏移
代入公式:
00500000 - 60000000 + 60030000 = 00503000 (要填寫的檔案偏移)
看下我們當前的模組地址:
Inject是我們當前的EXE的名稱,模組地址在00500000的位置
DLL的模組地址是60000000 這個地址是我們通過修改DLL中的選項頭中的ImageBase得到的.
六丶總結
上面講的很細緻
今天主要就是結構體會看,偏移會算即可.
總結一下公式
1.定位重定位的地址 (也就是在哪裡修改)
首先從陣列取出一項,(2個位元組大小)
比如0x3005
公式:
定位修改地址 = 現在模組 + 當前結構記錄分頁的RVA + 取出陣列的2個位元組的低3位
例子: 00401000 + 1000 + 005 = 世紀你要修改的地址,修改大小和取出word自己的第一位有關
2.計算出偏移地址,填寫到定位地址的位置,使其偏移正確
現在的模組地址 - DLL模組地址 + 以前的偏移 = 實際修改的偏移
便於理解的公式:
以前的偏移 - DLL模組地址 + 現在模組地址
3.計算出以前偏移
要計算出以前的偏移,你首先要得出現在的偏移,好在OD已經寫好了,其實檔案中也有儲存的.(自己找吧)
以前的偏移 = 現在的偏移 - 現在模組地址 + DLL模組地址
作者:IBinary
出處:http://www.cnblogs.com/iBinary/
版權所有,歡迎保留原文連結進行轉載:)
堅持兩字,簡單,輕便,但是真正的執行起來確實需要很長很長時間.當你把堅持兩字當做你要走的路,那麼你總會成功.
分類: PE檔案格式