starrycan的pwn隨筆——ELF檔案和延遲繫結機制

Starrycan星灿發表於2024-11-16

一.ELF檔案結構

0x01什麼是ELF檔案

1.linux環境中,二進位制可持性檔案的型別是ELF(Executable and LinkableFormat)檔案。類似windows下的exe

2.elf檔案的格式比較簡單,我們需要了解的就是elf檔案中的各個節、段等概念

3.程式elf的基本資訊存在於elf的頭部資訊中,這些資訊包括指令的執行架構入口等等內容,我們可以透過 readelf -h來檢視頭部資訊

0x02ELF檔案組成

1.elf檔案中包含許多個節(section)各個節中存放不同的資料,:這些節的資訊存放在節頭表中,readelf-S 檢視,這些節主要包括:

我們可以在ida中對節點進行檢視

0x04elf檔案的儲存

1.elf檔案平時是放在磁碟上 執行時放至在記憶體中

2.elf檔案進入記憶體時結構 對映

elf檔案在載入進入記憶體時:
elf檔案的節(section)會被對映進記憶體中的段(segment),而這一對映過程遵循的機制是根據各個節的許可權來進行對映的,
換句話說,可讀可寫的節被對映入一個段,只讀的節被對映入一個段。

二.延遲繫結機制

0x01動態連結庫

1.我們再寫程式的過程中會用到系統函式,比如read write open 等等函式

2.這些函式是前輩開發人員已經在系統中定義好的,雖然我們在使用中僅僅只是輸入了read 或者 open這樣一個字元,但是其實際上是對系統已經定義好的函式進行呼叫,存放這些函式的庫檔案就是動態連結庫 比如我們寫c語言時的 main math 等等庫

3.我們對於pwn題接觸到的動態連結庫就是libc.so檔案

0x02靜態編譯與動態編譯

1.什麼是編譯?

編譯按照我的理解就是將程式轉換成可執行檔案的過程,大家常用的編譯器就是這個意思,大家在寫程式實際會出現一個框框就是我們所說的可執行檔案,系統也有自己的大型編譯器,只是我們以前沒有感知罷了

2.什麼是靜態編譯和動態編譯

類似於c語言中執行程式需要寫出標頭檔案和main函式一樣,系統在進行編譯時,也需要呼叫系統中封裝好的函式,根據pwn1筆記中的內容,我們可以知道系統的主函式和呼叫有關的內容都分配在開闢好的堆疊空間中.

下面我給大家舉一個我自己學到例子,來理解什麼是靜態編譯和動態編譯

小明要開一個餐館(program)餐館的選單上有幾百種菜餚(函式),小明的餐館每天都會來很多顧客,每個顧客點的菜都可能不一樣。我們知道,每道菜所需要的食材(系統函式)都不一樣,這些食材都存放於倉庫(動態連結庫中。

那麼現在問題來了,小明如何保證每個顧客點的菜都能被滿足呢?

A.第一種方式:小明把倉庫中所有的食材都搬進廚房(靜態編譯)
這時,小明不需要挪地方(靜態)只需要在廚房中就可以工作,但是這會帶來冗餘,可能廚房中的食材很多都用不上。

B.第二種方式:小明每次遇到新的所需要的食材,才去倉庫取(動態編譯)
這時,小明可能挪動的比較頻繁(動態),但是可以保證廚房裡面沒那麼多可能用不到的東西。

3.實踐上的靜態和動態編譯

A.靜態編譯的思路就是將所有可能執行到的庫函式一同編譯到可執行檔案中

這一方式的優點就在於在程式執行中不需要依賴動態連結庫。適用的場合就是比如你本地編譯的程式需要的動態連結庫版本比較特殊,如果在別的機器上執行可能對方動態連結庫版本和你不一樣會出bug,這時候用靜態編譯。

缺點就是編譯過後程式體積很大,編譯速度也很慢,

B.動態編譯的思路就是逢山開路,遇水架橋,直到遇到需要呼叫庫函式的時候再去動態連結庫中尋找。

所以其優點一方面是縮小了執行檔案本身的體積,另一方面是加快了編譯速度,

缺點是哪怕是很簡單的程式,只用到了連結庫中的一兩條命令,也需要附帶一個相對龐大的連結庫;二是如果其他計算機上沒有安裝相應的執行庫,則用動態編譯的可執行檔案就不能運動

0x03延遲繫結機制

1.什麼是延遲繫結機制

在學習到這裡時我產生了一個疑問,在實際的作業系統底層中採用的是靜態還是動態編譯,以下是chatgpt的回答

在實際的動態編譯過程中,由於每一次編譯都要呼叫系統函式,也就是每一次都要重新定址,這樣就太麻煩了也大大的拖延了計算機的編譯效率,回到剛才的例子中

我們可以舉出這樣一個例子:我們再回去看看小明:小明說我選擇第二種方式(動態編譯)
但是小明餐館開業後發現搞不贏,每次都要去倉庫找,太麻煩了。於是乎,小明想到:每次我遇到新的食材,我就去倉庫找,但是每次找完,我就在小本子(got表)上記錄這個食材的地址,這樣下一次找就快很多了!

實際上在計算機內部我們採用的也是這樣一種方式我們稱之為got表

2.什麼是got表

這就是linux的延遲繫結機制,而存放這個地址的小本子就是got表。got表全稱是Global Offset Table,也就是全域性偏移量表。
在程式執行時,got表初始並不儲存庫函式的地址,只有在第一次呼叫過後程式才將這一地址儲存在got表中。

3.GOT與PLT表

GOT(Global Offset Table,?全域性偏移表)
GOT 是資料段用於地址無關程式碼的 Linux ELF 檔案中確定全域性變數和外部函式地址的表。ELF 中有 .got 和 .plt.got 兩個 GOT 表,.got 表用於全域性變數的引用地址,.got.plt 用於儲存函式引用的地址,
PLT(Procedure Linkage Table,程式連結表),PLT 是 Linux ELF 檔案中用於延遲繫結的表

4.兩個表之間的關係以及呼叫流程

1.got和plt的關係就像兄弟一樣,其聯絡非常緊密 作用大致相同 在正式的瞭解其的關係之前,我從定義中看到got 是全域性變數 plt是函式地址 依據c語言的邏輯 寫程式碼都是先寫主函式也就是大家寫的 main()猜測應該是先呼叫plt表 在2中我講詳細說明真正的呼叫流程

2.真正的呼叫流程如下圖

在開始一次呼叫之前 PLT表會call 以下動態連結的函式

A.在第一次呼叫外部函式時,!plt表首先會跳到對應的got表項中。由於並沒有被呼叫過,此時的got表儲存的不是目標函式地址,此時的got表中儲存的地址是plt表中的一段指令,其作用就是準備一些引數,進行動態解析。

B.跳轉回plt表

C.跳轉回plt表後,plt表又會跳轉回plt的表頭,表頭內容就是呼叫動態解析函式,將目標函式地址存放入got表中。

在之後第二次以上的呼叫後程式已經完成了延遲繫結,got表中已經儲存了目標函式地址,直接跳轉即可

相關文章