對PLT表和GOT表的學習

我們在做pwn題目的時候,經常會用到這兩個表,如果對這兩個表瞭解的不是特別深刻的話,會對你的泄露造成一定影響,故我們在這裏着重介紹一下這兩個表。

例子

我們通過例子來了解一下這兩個表會出現在什麼地方。
在這裏插入圖片描述
我們可以觀察到read@plt這個函數,爲什麼後面加了個@plt?因爲這個爲PLT表中的數據的地址。 那爲什麼反編譯中的代碼地址爲PLT表中的地址呢?我們帶着疑問接下去看。

能讓計算機執行的都是二進制文件,是ELF可執行文件,就如我們做pwn題的pwn文件都是可執行文件。
**ELF可以生成一種特殊的代碼——與位置無關的代碼(PIC)。**用戶對gcc使用-fPIC指示GNU編譯系統生成PIC代碼。它是實現共享庫或共享可執行代碼的基礎。這種代碼的特殊性在於它可以加載到內存地址空間的任何地址執行。這也使加載器可以很方便的在進程中動態鏈接共享庫。

PIC的實現運用了一個事實,就是代碼段中任何指令和數據段中的任何變量之間的距離都是一個與代碼段和數據段的絕對存儲器位置無關的常量。 因此,編譯器在數據段開始的地方創建了一個表,叫做全局偏移量表(global offset table,GOT)。GOT包含每個被這個 目標模塊 引用的 全局數據目標 的表目。編譯器還爲GOT中每個表目生成一個重定位記錄。在加載時,動態鏈接器會重定位GOT中的每個表目,使得它包含正確的絕對地址。 PIC代碼在代碼中實現通過GOT表間接地引用每個全局變量,這樣,代碼中本來簡單的數據引用就變得複雜,必須加入得到GOT適當表目內容的指令。對只讀數據的引用也根據同樣的道理,所以,加上PIC編譯成的代碼比一般的代碼開銷大。

如果一個ELF可執行文件需要調用定義在共享庫中的任何函數,那麼它就有自己的GOT和PLT (過程鏈接表)。這兩個節之間的交互可以實現延遲綁定(lazy binging),這種方法將過程地址的綁定推遲到第一次調用該函數。 爲了實現延遲綁定,GOT的前三條表目是特殊的:GOT[0]包含.dynamic段的地址,這個段裏保存了動態鏈接器需要的最基本的信息,比如符號的位置和重定位信息;GOT[1]包含動態鏈接器的標識;GOT[2]包含動態鏈接器的延遲綁定代碼的入口點。GOT的其他表目爲本模塊要調用的一個全局變量或函數的真實地址。

PLT是一個以16字節(32位平臺中)表目的數組形式出現的代碼序列。其中PLT[0]是一個特殊的表目,它跳轉到動態鏈接器中執行;每個定義在共享庫中並被本模塊調用的函數在PLT中都有一個表目,從PLT[1]開始,模塊對函數的調用會轉到相應PLT表目中執行,這些表目由三條指令構成。第一條指令是跳轉到相應的GOT存儲的地址值中;第二條指令把函數相應的ID壓入棧中;第三條指令跳轉到PLT[O]中調用動態鏈接器解析函數地址,並把函數真正地址存入相應的GOT表目中。被調用函數GOT相應表目中存儲的最初址爲相應PLT表目中第二條指令的地址值,函數第一次被調用後,GOT表目中的值就爲函數的真正地址。 因此,第一次調用函數時開銷比較大,但是其後的每次調用都只會花費一條指令和一個間接的存儲器引用。

結論

當程序編譯時會採用兩種表進行輔助,一個爲PLT表,一個爲GOT表,PLT表可以稱爲內部函數表,GOT表爲全局函數表,這兩個表是相對應的。
在這裏插入圖片描述
PLT表中的每一項的數據內容都是對應的GOT表中一項的地址,這個是固定不變的,PLT表中的數據根本不是函數的真實地址,而是GOT表項的地址。

在進入帶有@plt標誌的函數時,這個函數其實就是個過渡作用,因爲GOT表項中的數據纔是函數最終的地址,而PLT表中的數據又是GOT表項的地址,我們就可以通過PLT表跳轉到GOT表來得到函數真正的地址。
@plt函數是編譯系統自己加的。
在這裏插入圖片描述
這個函數只有三行代碼:第一行跳轉,它的作用是通過PLT表跳轉到GOT表,而在第一次運行某一個函數之前,這個函數PLT表對應的GOT表中的數據爲@plt函數中第二行指令的地址,針對圖中來說步驟如下:

1、jmp指令跳轉到GOT表
2、此時GOT表中的數據爲0x400486
3、跳轉到指令地址爲0x400486
4、執行push 0x3 # 這個爲在GOT中的下標序號
5、在執行jmp 0x400440
6、而0x400440爲PLT[0]的地址
7、PLT[0]的指令會進入動態鏈接器的入口
8、執行一個函數並將真正的函數地址覆蓋到GOT表中
在這裏插入圖片描述
第一步由函數調用跳入到PLT表中,然後第二步PLT表跳到GOT表中,可以看到第三步由GOT表回跳到PLT表中,這時候進行壓棧,把代表函數的ID壓棧,接着第四步跳轉到公共的PLT表項PLT[0]中,第5步進入到GOT表中,然後_dl_runtime_resolve對動態函數進行地址解析和重定位,第七步把動態函數真實的地址寫入到GOT表項中,然後執行函數並返回。
在這裏插入圖片描述
已經調用過一次函數之後被調用的過程:第一步還是由函數調用跳入到PLT表,但是第二步跳入到GOT表中時,由於這個時候該表項已經是動態函數的真實地址了,所以可以直接執行然後返回。
對於動態函數的調用,第一次要經過地址解析和回寫到GOT表項中,第二次直接調用即可。

參考:https://blog.csdn.net/weixin_44681716/article/details/89877497

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