系統安全與漏洞利用

返回 libc 攻擊(return-to-libc)

/ ret-to-LIB-see /

返回 libc 攻擊是 ROP 的直系祖先,也是程式碼重用攻擊最簡單的形式。攻擊者不注入新程式碼,而是改寫被儲存的返回位址,讓函式「返回」時直接進入某個既有的函式庫函式——最著名的就是 C 標準函式庫(libc)裡的 system()——並安排好引數,使它做出對攻擊者有用的事,例如 system("/bin/sh") 來開一個 shell。

在經典的 32 位元堆疊上,其機制特別乾淨。那裡的函式引數透過堆疊傳遞,就在返回位址的上方。所以當攻擊者溢出緩衝區時,他們布置成:在返回位址的位置放上 system() 的位址,接著一個假的返回位址(常是 exit() 的位址),再接著字串 "/bin/sh" 的位址。當有漏洞的函式執行 ret 時,控制轉移到 system();system() 接著正好從攻擊者放置 "/bin/sh" 指標的地方讀取它的引數,並執行那個 shell。沒有任何攻擊者的程式碼被執行——只有 libc 自己的程式碼被執行——這正是它能滑過 DEP/NX 的原因。在 64 位元系統上引數放在暫存器(rdi 為首),所以純粹的 ret-to-libc 通常需要一個小小的「pop rdi; ret」gadget 來載入引數,這正是通往完整 ROP 的概念橋樑。

它在歷史上之所以重要,是因為它是最早證明「不可執行的堆疊還不夠」的技術,促成了 ROP,以及如今把 libc 所在藏起來的位址隨機化(ASLR)。誠實的說法:返回 libc 攻擊其實只是 ROP 的一個用上一兩個 gadget 的特例,只不過它落在一個完整、方便的函式庫函式上;ASLR 是它的主要障礙,因為攻擊者必須知道 system() 與那個字串的執行期位址,而這通常由一次記憶體洩漏提供。

// 32 位元 ret-to-libc 佈局,改寫返回位址: [ &system ][ &exit ][ &"/bin/sh" ] // ^ ret 落在這 ^ 假返回位址 ^ system() 的引數 // 結果:system("/bin/sh") 執行,只用 libc 自己的程式碼

返回時「返回」進 system(),並在攻擊者放置之處正好找到 /bin/sh——沒有注入任何程式碼。

返回 libc 攻擊早於 ROP 並促成了 ROP:它重用一整個函式庫函式,而 ROP 重用細小的 gadget;兩者都依賴得知位址,而那正是 ASLR 旨在拒絕的。

又稱
ret2libcreturn-into-libc返回 libc