您好,登錄后才能下訂單哦!
本篇文章為大家展示了怎么深入理解GOT表和PLT表,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。
操作系統通常使用動態鏈接的方法來提高程序運行的效率。在動態鏈接的情況下,程序加載的時候并不會把鏈接庫中所有函數都一起加載進來,而是程序執行的時候按需加載,如果有函數并沒有被調用,那么它就不會在程序生命中被加載進來。這樣的設計就能提高程序運行的流暢度,也減少了內存空間。而且現代操作系統不允許修改代碼段,只能修改數據段,那么GOT表與PLT表就應運而生。
我們先簡單看一個例子
我們跟進一下scanf@plt
會發現,有三行代碼
jmp 一個地址 push 一個值到棧里面 jmp 一個地址
看函數的名字就可以知道這是scanf函數的plt表,先不著急去了解plt是做什么用的,我們繼續往下看我們先看一下第一個jmp是什么跳到哪里。
其實這是plt表對應函數的got表,而且我們會發現0x201020的值是壓棧命令的地址,其他地方為0,此時就想問:
一、got表與plt表有什么意義,為什么要跳來跳去?
二、got表與plt表有什么聯系,有木有什么對應關系?
那么帶著疑問先看答案,再去印證我們要明白操作系統通常使用動態鏈接的方法來提高程序運行的效率,而且不能回寫到代碼段上。
在上面例子中我們可以看到,call scanf —> scanf的plt表 —>scanf的got表,至于got表的值暫時先不管,我們此刻可以形成這樣一個思維,它能從got表中找到真實的scanf函數供程序加載運行。
我們這么認為后,那么這就變成了一個間接尋址的過程
我們就把獲取數據段存放函數地址的那一小段代碼稱為PLT(Procedure Linkage Table)過程鏈接表存放函數地址的數據段稱為GOT(Global Offset Table)全局偏移表。我們形成這么一個思維后,再去仔細理解里面的細節。
已經明白了這么一個大致過程后,我們來看一下這其中是怎么一步一步調用的上面有幾個疑點需要去解決:
一、got表怎么知道scanf函數的真實地址?
二、got表與plt表的結構是什么?我們先來看plt表剛才發現scanf@plt表第三行代碼是 jmp 一個地址 ,跟進看一下是什么
其實這是一個程序PLT表的開始(plt[0]),它做的事情是:
push got[1] jmp **got[2]
后面是每個函數的plt表。此時我們再看一下這個神秘的GOT表
除了這兩個(printf和scanf函數的push 0xn的地址,也就是對應的plt表的第二條代碼的地址),其它的got[1], got[2] 為0,那么plt表指向為0的got表干什么呢?因為我們落下了一個條件,現代操作系統不允許修改代碼段,只能修改數據段,也就是回寫,更專業的稱謂應該是運行時重定位。我們把程序運行起來,我們之前的地址和保存的內容就變了在這之前,我們先把鏈接時的內容保存一下,做一個對比
② 尋找printf的plt表 ③ jmp到plt[0] ④ jmp got[2] -> 0x00000 ⑤⑥ printf和scanf的got[3] got[4] -> plt[1] plt[2]的第二條代碼的地址 ⑦⑧ 證實上面一點
運行程序,在scanf處下斷點
可以發現,此時scanf@plt表變了,查看got[4]里內容
依然是push 0x1所在地址繼續調試,直到這里,got[4]地址被修改
此時想問了,這是哪里?
然后就是got[2]中call<_dl_fixup>從而修改got[3]中的地址;
那么問題就來了,剛才got[2]處不是0嗎,怎么現在又是這個(_dl_runtime_resolve)?這就是運行時重定位。
其實got表的前三項是:
got[0]:address of .dynamic section 也就是本ELF動態段(.dynamic段)的裝載地址 got[1]:address of link_map object( 編譯時填充0)也就是本ELF的link_map數據結構描述符地址,作用:link_map結構,結合.rel.plt段的偏移量,才能真正找到該elf的.rel.plt got[2]:address of _dl_runtime_resolve function (編譯時填充為0) 也就是_dl_runtime_resolve函數的地址,來得到真正的函數地址,回寫到對應的got表位置中。
那么此刻,got表怎么知道scanf函數的真實地址?
這個問題已經解決了。我們可以看一下其中的裝載過程:
說到這個,可以看到在_dl_runtimw_resolve之前和之后,會將真正的函數地址,也就是glibc運行庫中的函數的地址,回寫到代碼段,就是got[n](n>=3)中。也就是說在函數第一次調用時,才通過連接器動態解析并加載到.got.plt中,而這個過程稱之為延時加載或者惰性加載。
到這里,也要接近尾聲了,當第二次調用同一個函數的時候,就不會與第一次一樣那么麻煩了,因為got[n]中已經有了真實地址,直接jmp該地址即可。
上述內容就是怎么深入理解GOT表和PLT表,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。