PLT與GOT

0x01  什麼是PLT和GOT數據結構

名稱:函數

  • PLT : 程序連接表(PLT,Procedure Link Table)
  • GOT : 重局偏移表(GOT, Global Offset Table)

原因:spa

  這緣起於動態連接,動態連接須要考慮的各類因素,但實際總結起來講兩點:調試

  • 須要存放外部函數的數據段  —— PLT
  • 獲取數據段存放函數地址的一小段額外代碼 —— GOT

 

  若是可執行文件中調用多個動態庫函數,那每一個函數都須要這兩樣東西,這樣每樣東西就造成一個表,每一個函數使用中的一項。code

  存放函數地址的數據表,稱爲全局偏移表(GOT, Global Offset Table),而那個額外代碼段表,稱爲程序連接表(PLT,Procedure Link Table)。blog

 

 

內容:進程

  舉個例子,對於一個函數,這裏命名爲common,其plt以下:get

080482a0 <common@plt>:
80482a0: pushl 0x80496f0
80482a6: jmp *0x80496f4
      ...

 


第一句,pushl 0x80496f0,是將地址壓到棧上,也即向最終調用的函數傳遞參數。
第二句,jmp *0x80496f4,這是跳到最終的函數去執行,不過猜猜就能想到,這是跳到能解析動態庫函數地址的代碼裏面執行。it

 

0x80496f4屬於GOT表中的一項,進程尚未運行時它的值是0x00000000,當進程運行起來後,它的值變成了0xf7ff06a0。io

若是作更進一步的調試會發現這個地址位於動態連接器內,對應的函數是_dl_runtime_resolve。(相應的過程圖在下面貼出)

 

若是將PLT和GOT抽象起來描述,能夠寫成如下的僞代碼:

plt[0]:
   pushl got[1]
   jmp *got[2]

plt[n]:              // n >= 1
   jmp *got[n+2]           

      // GOT前3項爲公共項,第3項開始纔是函數項,plt[1]對應的GOT[3],依次類推
   push (n-1)*8
   jmp plt[0]

—————————————————————————————————————————

got[0] = address of .dynamic section                    

      //本ELF動態段(.dynamic段)的裝載地址 
got[1] = address of link_map object( 編譯時填充0)           

      //本ELF的link_map數據結構描述符地址 
got[2] = address of _dl_runtime_resolve function (編譯時填充爲0)     

      //_dl_runtime_resolve函數的地址
got[n+2] = plt[n] + 6 (即plt[n]代碼片斷的第二條指令)

 

 

特色:

PLT表結構有如下特色:

PLT表中的第一項爲公共表項,剩下的是每一個動態庫函數爲一項(固然每項是由多條指令組成的,jmp *0xXXXXXXXX這條指令是全部plt的開始指令)

每項PLT都從對應的GOT表項中讀取目標函數地址

GOT表結構有如下特色:

GOT表中前3個爲特殊項,分別用於保存 .dynamic段地址、本鏡像的link_map數據結構地址和_dl_runtime_resolve函數地址;

但在編譯時,沒法獲取知道link_map地址和_dl_runtime_resolve函數地址,因此編譯時填零地址,進程啓動時由動態連接器進行填充3個特殊項後面依次是每一個動態庫函數的GOT表項

 

注意點:

以printf函數爲例,三個問題:

  • _dl_runtime_resolve是怎麼知要查找printf函數的
  • _dl_runtime_resolve找到printf函數地址以後,它怎麼知道回填到哪一個GOT表項
  • 到底_dl_runtime_resolve是何時被寫到GOT表的
printf@plt>:
   jmp *0x80496f8
   push $0x00
   jmp common@plt

 

  每一個xxx@plt的第二條指令push的操做數都是不同的,它就至關於函數的id,動態連接器經過它就能夠知道是要解析哪一個函數了。

 

它倆的運行關係以下:

 

0x02  重定位

重定位分爲如下三種:

  • 連接重定位:將一個或多箇中間文件(.o文件)經過連接器將它們連接成一個可執行文件。其中分爲兩種狀況:
  1. 若是是在其餘中間文件中已經定義了的函數,連接階段能夠直接重定位到函數地址
  2. 若是是在動態庫中定義了的函數,連接階段沒法直接重定位到函數地址,只能生成額外的小片斷代碼,也就是PLT表,而後重定位到該代碼片斷
  • 運行重定位:運行後加載動態庫,把動態庫中的相應函數地址填入GOT表,因爲PLT表是跳轉到GOT表的,這就構成了運行時重定位
  • 延遲重定位:只有動態庫函數在被調用時,纔會進行地址解析和重定位工做,這時候動態庫函數的地址纔會被寫入到GOT表項中,過程以下圖:

 

 

  PLT屬於代碼段,在進程加載和運行過程都不會發生改變,PLT指向GOT表的關係在編譯時已徹底肯定,惟一能發生變化的是GOT表。

 

示例:

重定位時:

 

 重定位後:

  

 

 

0X03  在PWN中的應用 —— ret2libc

 應用場景:

  在一些提供單獨 libc(版本號).so的pwn題中,大部分狀況是要在這個so文件中尋找一些函數的偏移地址,並且大部分狀況下,爲了方便,只會使用已經在程序中出現的函數的got表中的實際地址,咱們能夠直接把這個so文件拖進IDA中進行尋找,

  經典的例題如Jarvis oj上面的pwn level3,這裏只給出較好的wp地址,能夠看到,用read函數寫入,而後return已經使用過一次的write函數的plt地址,從而調用這個函數(以後的那個padding是系統call用來跳轉執行的下一個地址,能夠deadbeef也能夠換成想要執行的函數地址),繼而是對ret的這個write函數的參數進行輸入,其中第二個參數就是要輸出在顯示屏上的內容,所以咱們填入write的got表地址,輸出write的真實地址,進而基地址就能夠由  基地址= 真實地址-偏移地址算出。

相關文章
相關標籤/搜索