重定向是計算機技術中很是底層的概念和操做。它指的是將程序中涉及到的變量名與變量在計算機內存中的位置關聯起來。當在代碼中執行相似x=1;的語句時,編譯器須要經過重定向信息找到變量x對應的內存位置,而後將數值1寫入該內存,所以重定向既跟程序的加載連接有關,又與編譯原理有關,所以對計算機體系結構不瞭解,或只關注上層應用開發,對底層技術理解很少的同窗對它進行掌握就會有些困難。算法
爲了準確將變量對應到具體的內存位置,就必需要有相關信息來描述變量名與內存之間的關係,這些信息就叫重定向記錄(relocation records),程序中描述的「變量」不只僅指int,float類型的數據變量,還會涉及到函數的入口地址,而函數或者變量的入口地址經常在連接或動態裝載時纔會肯定。例以下面代碼:微信
void _start() {
foo()
}
若是boo實現放在一個obj1.c文件,函數foo實現放在boj2.c文件,那麼編譯後_start函數對應的二進制指令存儲在obj1.o中,foo對應的二進制指令存儲在obj2.o中,因而整個程序要順利執行,就必須將obj1.o和obj2.o整合在一塊兒,負責整合工做的就是鏈接器,它位於Linux系統的目錄/bin/ld中。問題是如何將他們整合在一塊兒,在執行boo函數時,內部調用foo函數時,IP寄存器能準確的指向foo函數第一條指令所在位置呢,這就須要編譯器在編譯代碼時所生成的重定向數據結構,內容以下:數據結構
typedef struct {
ELF64_Addr r_offset;
Uint64_t r_info;
} Elf64_Rel;
typedef struct {
ELF64_Addr r_offset;
Uint64_t r_info;
int64_t r_addend;
} Elf64_Rela;
r_offset指向.o文件中須要指定其在內存中位置的偏移,例如obj1.o中,foo()這條語句編譯成彙編指令後是e8 0 0 0 0,e8對應指令call, 那麼「0 0 0 0」字節的在obj1.o內的位置就是r_offset,而」0 0 0 0「其實應該是函數foo第一條指令所在地址。函數
若是elf文件對應32位系統,那麼重定向就使用第一個結構,若是對應64位系統就對應第二個結構。爲了更好的理解結構中各個字段的意義,咱們看一個具體例子,建立一個obj1.c文件,包含以下代碼:spa
_start() {
foo()
}
而後使用gcc -c obj1.c進行編譯就會獲得obj1.o文件,接着使用objdump -d obj1.o就能夠看到以下內容:.net
因爲咱們如今大多使用64位系統,所以咱們着重理解第二個結構,從上圖看到」0 0 0 0」這四個字節對於的偏移是0a,由於0x9對應指令e8佔據了一個字節,因此r_offset的值就是0a,r_addend對應要修改的數據長度,由於」0 0 0 0」對應4個字節,所以r_addend對應的值就是4,咱們可使用命令readelf -r obj1.o 來查看結構體的內容:code
能夠看到第一條信息對應的就是結構體Elf64_Rela各個字段的內容。其中R_X86_64_PC32對應重定向的類型,不一樣類型決定了如何從新修改」0 0 0 0「這4個字節的內容,咱們先將foo函數實如今ob2.c中,將其編譯成obj2.o,而後連接ob1.o,obj2.o,最後再分析字節」0 0 0 0「是如何被修改的,obj2.c內容以下:orm
int foo() {
return 1;
}
使用命令gcc -c boj2.c編譯後獲得obj2.o,而後使用命令gcc -nostdlib objj1.o obj2.o -o relocated 將兩個.o文件連接成relocated文件,而後使用命令objdump -d relocated查看連接後的內容:
blog
從上面能夠看到原來e8後面的」0 0 0 0」改爲了「02 00 00 00」,注意到第一個字節是低位字節,所以「02 00 00 00 「其實就是2,若是從」02 00 00 00「日後通過2個字節,對應語句5d, c3後進入地址400154,這剛好就是函數foo第一條指令所在地址,也就是」02 00 00 00」實際上是foo函數入口地址相對於call這條指令以後的偏移。進程
」02 00 00 00「是怎麼計算的呢。它首先用foo的起始地址也就是400154減去call指令通過一字節後的地址,也就是40014d+1=40014e,而後再減去addend對應數值,其實就是要修改的數據長度,根據前面使用readelf -r obj1.o指令顯示的內容,這個值就是4,因而連接後」00 00 00 00」被修改的值就是0x400154-0x40015e-4=2,也就是」02 00 00 00」。因而連接後地址的修改算法爲,被調用函數的地址-call指令所在地址-表示地址的字節長度。
這種地址修改其實給黑客劫持進程提供了入口,這種黑客技術也叫重定向代碼注入。本來計劃將該技術用代碼實現後再將文章發佈,但在實現過程當中發現技術難度較大,有很多問題消耗一週多的時間依然沒有解決,再加上最近其餘工做的壓力使得我騰不出手來,所以先將這部分理論發佈出來,後續在代碼實現完成後再針對該技術的具體實現原理進行進一步解析。
本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。