返回導向程式設計(return-oriented programming)
/ R-O-P /
假設有人禁止你寫新的勒索信,但允許你從現有的報紙剪下字詞再拼貼起來。你仍能拼出任何訊息——你只是重用已經存在的文字。返回導向程式設計(ROP)就是把這個點子用到程式碼上:當攻擊者無法注入並執行自己的機器碼時(因為資料頁不可執行),他們改為把程式中「既有、已可執行」的程式碼的細小片段縫合起來,以執行任意運算。
它之所以可行,是因為 DEP/NX 讓注入的 shellcode 無法執行,於是攻擊者轉向程式碼重用。其建構單元是「gadget」:以 ret 結尾的短指令序列,例如「pop rdi; ret」(把一個值載入 rdi 後返回)或「pop rax; ret」。攻擊者在藉溢位劫持堆疊後,填入的不是單一返回位址,而是一整條 ROP 鏈:一連串 gadget 位址,與這些 gadget 所消耗的資料交錯。每個 gadget 結尾的 ret 會從攻擊者控制的堆疊上彈出「下一個」gadget 位址並跳過去。於是堆疊本身成了程式,而 ret 是「下一條指令」的引擎。藉由挑選正確的 gadget,攻擊者能載入暫存器、做算術,最終呼叫像 execve 這樣的真實函式、或把某頁標記為可執行——而這一切只用到二進位檔及其函式庫裡早已存在的位元組。
它之所以重要,是因為 ROP 正是讓「只靠 DEP」變得不足的技術,並且仍是現代記憶體利用的骨幹。誠實的說法:ROP 需要「知道」它的 gadget 位址,所以它受 ASLR 把關——這就是為何資訊洩漏(一次任意讀)通常是前置條件。正面瞄準 ROP 的防禦是控制流完整性(間接的 ret/call 必須落在被認可的目標上)與影子堆疊(一次返回必須對應真正的呼叫),它們即使在漏洞觸發時也能打斷鏈。
// 鋪在被劫持堆疊上的一條 ROP 鏈(低 -> 高位址): [ 'pop rdi; ret' 的位址 ][ 0x... "/bin/sh" ][ system() 的位址 ] // gadget 把字串指標彈進 rdi,接著 ret 跳進 system()
每個 ret 把控制交給堆疊上的下一個位址——堆疊變成了一段以 gadget 寫成的程式。
ROP 需要 gadget 位址,所以通常搭配資訊洩漏來擊敗 ASLR;跳躍導向程式設計(JOP)是它的近親,串接的是間接跳躍而非返回,在以 ret 為基礎的 gadget 受限時派上用場。