與位置無關的可執行檔(a position-independent executable)
/ PIE, said pie /
數十年來,一個普通程式每次執行都被載入到相同的固定基底位址——譬如 0x400000——所以知道版面配置的攻擊者總能預測程式碼與資料住在哪。與位置無關的可執行檔是一個像共享函式庫那樣建構的主程式:它不含絕對位址,所以載入器每次都能自由地把它放在隨機選定的基底。那份隨機性正是重點——它讓定址空間版面配置隨機化(ASLR)真正也涵蓋到主程式。
技術上,PIE 由與位置無關的程式碼建構(用 -fPIE / -pie),並被產生為型別 ET_DYN 的 ELF 物件——與共享函式庫相同的型別——而非舊的 ET_EXEC 固定位址型別。因為它是 ET_DYN,核心與動態連結器把它當成共享物件:它們挑一個載入基底(常是隨機化的),並套用安頓其 GOT 與任何絕對參照所需的那一小組載入期重定位。每個內部跳躍與資料存取早已寫成 PC 相對,所以程式主體不在意它落在哪。從程式設計師那一側什麼都沒變;從載入器那一側,可執行檔只是變成可重定位的了。
它主要為了安全而重要。在固定位址下,像返回導向程式設計(ROP)這類攻擊技術可以寫死有用程式碼片段的位址;在 PIE 下,那些位址每次執行都被隨機化,所以攻擊者必須先洩漏一個位址才能瞄準,這大幅提高了門檻。大多數現代 Linux 發行版正是基於這個原因,預設把使用者程式建構成 PIE。誠實的告誡是 PIE 並非免費:間接帶來一點啟動與程式碼大小的成本,而且它是縱深防禦措施、不是保證——能洩漏一個位址的堅定攻擊者往往仍能擊敗該隨機化。
$ gcc -fPIE -pie main.c -o app $ readelf -h app | grep Type Type: DYN (Position-Independent Executable) # 不是 EXEC $ ./app; ./app # 每次執行基底位址不同(ASLR)
PIE 是 ET_DYN 檔,所以載入器每次執行都能把它放在隨機化的基底,讓 ASLR 也保護主程式,而不只是它的函式庫。
PIE 啟用隨機化但不保證安全:它是縱深防禦。若攻擊者能洩漏單一執行期位址,其餘版面配置往往又變得可計算。它也因額外間接而帶有少量效能與大小負擔。