ELF架構

ELF Header
ELF Header 位於檔案的最開頭,是整個檔案的「目錄頁」, 記錄了三類關鍵資訊:檔案類型、架構、進入點。
magic number
類別(Class)、資料(Data)、Version、 OS/ABI

類型(Type)
對應 e_type。常見值包含:
- REL(1)— 可重定位目標檔(.o 檔)
- EXEC(2)— 傳統固定位址可執行檔
- DYN(3)— 共享物件 / 位址無關可執行檔(PIE)
- CORE(4)— Core dump
你的檔案是 DYN,代表它是 PIE(Position-Independent Executable)。現代 Linux 發行版編譯的執行檔幾乎都是 PIE,以支援 ASLR(位址空間佈局隨機化)來增強安全性。

系統架構(Machine)
對應 e_machine。表示目標處理器架構為 x86-64(AMD64)。其他常見值如 AArch64(ARM 64 位元)、RISC-V 等。

進入點位址(Entry point address)
這是_start 的位址,對應 e_entry。程式被載入後,CPU 開始執行的第一個指令的虛擬位址。這通常不是 main(),而是 C runtime 的啟動程式碼(如 _start),它負責初始化環境後再呼叫 main()。因為是 PIE,0x1080 是相對偏移,實際執行時會加上隨機基底位址。
程式標頭起點(Start of program headers):
對應 e_phoff。e_phoff 標記的是 Program Header Table 在檔案中的起始位置。Program Header Table 在檔案中的偏移量為 64 bytes,恰好緊接在 ELF Header 之後(ELF64 Header 本身大小正好是 64 bytes)。
Size of this header:
對應 e_ehsize。ELF Header 本身的大小。ELF64 固定為 64 bytes,ELF32 為 52 bytes。
Size of program headers
對應 e_phentsize。每一個 Program Header 條目的大小。ELF64 固定為 56 bytes。
Number of program headers
對應 e_phnum。共有 13 個 Program Header 條目,每個描述一個 segment(如 LOAD、INTERP、DYNAMIC 等)。
Size of section headers
對應 e_shentsize。每一個 Section Header 條目的大小。ELF64 固定為 64 bytes。
Number of section headers
對應 e_shnum。共有 31 個 Section Header 條目,描述 .text、.data、.bss、.rodata、.symtab 等各個節。
Section header string table index:
對應 e_shstrndx。值為 30,表示第 30 個 section(從 0 開始算)是 .shstrtab,也就是存放所有 section 名稱字串的表。載入器要知道某個 section 叫什麼名字,就去查這個字串表。

Program Header
Program Header 是一個條目,描述一個 segment(記憶體段),告訴作業系統在載入程式時要怎麼處理這塊資料。
補充: Segment 與 Section 的差別
Section 是給連結器看的,是編譯和連結時的視角。它把檔案切得很細,每個 section 有明確的單一用途,例如 .text 放程式碼、.data 放已初始化資料、.bss 放未初始化資料、.rodata 放唯讀常數等。這樣連結器才能精確地合併多個 .o 檔中相同類型的內容。Segment 是給載入器看的,是執行時的視角。它不在乎內容的邏輯分類,只在乎權限是否相同。權限相同的多個 section 會被合併成一個 segment,一次映射到記憶體中。
example:
一個 segment 通常包含多個 section:
- 可讀+可執行的 segment ← 包含 .text、.rodata、.init 等
- 可讀+可寫的 segment ← 包含 .data、.bss、.got 等
權限管理

Program Header類型介紹

- PHDR
偏移量 0x40, 大小 0x2d8, 權限 R, 對齊 0x8
Program Header Table 自身的位置和大小。讓載入器知道這張表本身也要被映射到記憶體中。偏移量 0x40(64)正好在 ELF Header 之後。
- INTERP
偏移量 0x318, 大小 0x1c, 權限 R, 對齊 0x1
存放動態連結器的路徑:/lib64/ld-linux-x86-64.so.2。Kernel 看到這個 segment 就知道這不是靜態連結的程式,要先把動態連結器載入,由它負責載入所有依賴的 .so 共享函式庫。
- 第一個 LOAD
偏移量 0x0, 大小 0x6e0, 權限 R, 對齊 0x1000
載入檔案最前面的 0x6e0 bytes 到記憶體,權限為唯讀。這段包含 ELF Header、Program Header Table、以及一些唯讀的 metadata(如 .note、.gnu.hash 等)。
- 第二個 LOAD
偏移量 0x1000, 大小 0x1ed, 權限 R E, 對齊 0x1000
這是程式碼段,權限為可讀+可執行。包含 .text(程式碼)、.init(初始化程式碼)、.fini(結束程式碼)、.plt(動態連結跳板)等 section。你的進入點 0x1080 就落在這個 segment 裡。
- 第三個 LOAD
偏移量 0x2000, 大小 0x18c, 權限 R, 對齊 0x1000
唯讀資料段,包含 .rodata(字串常數等唯讀資料)和 .eh_frame(例外處理框架資訊)。
- 第四個 LOAD
偏移量 0x2dd0, 虛擬位址 0x3dd0, 大小 0x260/0x268, 權限 RW, 對齊 0x1000
可讀寫資料段。包含 .data(已初始化全域變數)、.bss(未初始化全域變數)、.got(Global Offset Table)等。注意記憶體大小(0x268)比檔案大小(0x260)大,多出來的就是 .bss,因為 .bss 不佔檔案空間,載入時在記憶體中補零即可。
- DYNAMIC
偏移量 0x2de0, 大小 0x1e0, 權限 RW, 對齊 0x8
指向 .dynamic section,存放動態連結所需的所有資訊,例如依賴哪些共享函式庫、符號表在哪裡、重定位表在哪裡等。動態連結器 ld-linux 主要就是靠這個 segment 來完成連結工作。
- 第一個 NOTE
偏移量 0x338, 大小 0x20, 權限 R, 對齊 0x8
通常是 GNU ABI 標記(.note.gnu.build-id 或 .note.ABI-tag),記錄這個 ELF 是為哪個 OS/ABI 版本編譯的。
- 第二個 NOTE
偏移量 0x358, 大小 0x44, 權限 R, 對齊 0x4
通常是 Build ID 或其他附加資訊,用來唯一識別這個二進位檔,debug 時很有用。
- GNU_PROPERTY
偏移量 0x338, 大小 0x20, 權限 R, 對齊 0x8
記錄 GNU 特定的屬性,例如這個程式是否支援 CET(Control-flow Enforcement Technology)等 CPU 安全特性。
- GNU_EH_FRAME
偏移量 0x20b4, 大小 0x2c, 權限 R, 對齊 0x4
指向 .eh_frame_hdr section,這是例外處理(exception handling)框架的索引表。當程式拋出例外或需要 stack unwinding(展開呼叫堆疊)時,runtime 透過這個表快速找到對應的處理資訊。
- GNU_STACK
偏移量 0x0, 大小 0x0, 權限 RW, 對齊 0x10
標記 stack 的權限。這裡權限是 RW(可讀+可寫,不可執行),表示 stack 上不能執行程式碼。這是一個重要的安全機制,防止攻擊者在 stack 上注入 shellcode 並執行(NX bit 保護)。注意大小為 0,它不載入任何資料,只是一個權限標記。
- GNU_RELRO
偏移量 0x2dd0, 大小 0x230, 權限 R, 對齊 0x1
RELRO(Relocation Read-Only) 安全機制。動態連結器完成重定位後,會把這個範圍的記憶體改為唯讀,保護 .got(Global Offset Table)等敏感區域不被覆寫。這可以防禦 GOT overwrite 攻擊。
Virtual Address

ELF呼叫函數參數順序
在 x86-64 Linux(System V ABI)下,前 6 個整數或指標參數依序放入暫存器:
| 順序 | 暫存器 |
|---|---|
| 第 1 個參數 | RDI |
| 第 2 個參數 | RSI |
| 第 3 個參數 | RDX |
| 第 4 個參數 | RCX |
| 第 5 個參數 | R8 |
| 第 6 個參數 | R9 |
超過 6 個的參數由右至左推入堆疊(stack)。
呼叫者保存(Caller-Saved)暫存器
RAX, RCX, RDX, RSI, RDI, R8–R11, XMM0–XMM15
函數呼叫後這些暫存器的值不保證被保留。
被呼叫者保存(Callee-Saved)暫存器
RBX, RBP, R12–R15
被呼叫的函數若使用這些暫存器,必須在返回前恢復原值。
Example:
long add(long a, long b, long c, long d, long e, long f, long g);
結果
a → RDI
b → RSI
c → RDX
d → RCX
e → R8
f → R9
g → [RSP] ← 第 7 個參數推入堆疊
與 Windows x64 的差異
| 順序 | 暫存器 |
|---|---|
| Syscall 編號 | RAX |
| 第 1 個參數 | RDI |
| 第 2 個參數 | RSI |
| 第 3 個參數 | RDX |
| 第 4 個參數 | R10(注意:不是 RCX) |
| 第 5 個參數 | R8 |
| 第 6 個參數 | R9 |
回傳值放在 RAX。


說些什麼吧!