連結器、載入器與目的檔格式

RELRO(重定位唯讀,RELocation Read-Only)

/ REL-roh /

執行中程式裡有些持有指標的部分——最重要的是全域偏移表——在啟動期間必須可寫,好讓動態連結器用解析後的位址填入它們。但啟動一旦完成,那些指標表就再也不該改變。讓它們保持可寫等於遞給攻擊者一個絕佳目標:覆寫一個 GOT 項目,你就能把一次函式呼叫改往你選定的程式碼。RELRO 是關上這扇窗的強化功能,做法是在動態連結器處理完之後,讓那些與重定位相關的區域變唯讀。

它有兩種強度。部分 RELRO 重新排列二進位檔,把連結器必須重定位的變數聚在一起,並在重定位後把用於資料的 GOT 部分(.got)標為唯讀——但它讓 .got.plt(用於延遲函式繫結的部分)保持可寫,因為延遲繫結需要在每個函式第一次呼叫時持續修補它。完整 RELRO 走得更遠:它完全關閉延遲繫結(在啟動時積極解析每個符號,彷彿設了 LD_BIND_NOW),於是整個 GOT 之後可被標為唯讀。你在連結時要求這個,例如部分用 -Wl,-z,relro、完整再加 -Wl,-z,now。完整 RELRO 之後,載入器套用一次記憶體保護變更(類似 mprotect 的呼叫),撤掉那些記憶體頁的寫入權限。

它重要,是因為 GOT 覆寫攻擊是經典的利用原語,而完整 RELRO 對那個二進位檔來說就是把它從桌面上拿掉。取捨誠實而微小:完整 RELRO 使啟動稍慢(一切預先解析)並停用延遲繫結最佳化,換來顯著縮小的攻擊面。大多數注重安全的發行版預設啟用完整 RELRO。你可以用 checksec 之類的工具檢查二進位檔的狀態,它會報告 'No RELRO'、'Partial RELRO' 或 'Full RELRO'。

$ gcc -Wl,-z,relro -Wl,-z,now main.c -o app # 完整 RELRO $ checksec --file=app RELRO: Full RELRO # 啟動後 GOT 變唯讀 # 只要部分:省略 '-z now' -> .got.plt 為延遲繫結保持可寫

完整 RELRO(-z relro -z now)在啟動時解析所有符號,然後讓整個 GOT 變唯讀,使攻擊者無法覆寫 GOT 項目來改寫呼叫方向。

部分 RELRO 只保護 .got,並讓 .got.plt 保持可寫以允許延遲繫結;完整 RELRO 停用延遲繫結以保護整個 GOT,代價是少量啟動成本。RELRO 強化 GOT 但只是眾多層次之一——它本身並非完整的防禦。

又稱
relocation read-onlyGOT hardening重定位唯讀