比如return-to-libc攻擊作為ROP的一類特例,就需要攻擊者首先在自己的機器上使用GDB一類工具從本地libc庫中尋找所需指令的返回地址,然後構造為數據包後準備提交給受害者的機器上的某個有棧溢出漏洞的進程,這個方法成立的前提應當是攻擊者自己的機器和受害者的機器的進程內存空間中同樣的返回地址下有著同樣指令吧?而為了便於猜測返回地址,很多實驗還會關閉地址隨機化設置。

實際上,假如不是libc這樣的基本庫函數已經公開而廣為通用了,ROP攻擊者是否其實不一定知道受害者的機器上一個進程的內存空間內的各類指令分別放在哪些地址下呢?

那麼假如使用某種不影響程序正常運行的花指令技術,對每一台機器內哪怕最基本的標準庫文件都進行相應處理,使得不同設備上的庫文件內部的具體實現不完全相同,從而使各個指令的返回地址也不完全相同或者無法預估,這樣能否防禦住ROP攻擊呢?


攻擊者本機上的libc跟目標機器上本來就不一定一樣。就算你不加混淆,libc也有不同的版本啊。不同版本的libc的不同函數偏移本來就有差別。

而且還有個問題是,『花指令』其實是逆向的對抗技術,讓基於靜態掃描的反彙編無法得到正確的結果。你設想的東西我不知道學界有沒有專用名詞,讓我自己稱呼的話,我會叫它『地址混淆』。

然後說問題的關鍵:一個攻擊可以很複雜,ROP往往只是其中的一環。在ROP之前,攻擊者完全可以先通過某個任意讀的漏洞掃一圈地址空間。如果他能這樣做,那你對libc的函數做地址混淆並沒有什麼意義,反正他還是能獲得libc。

而由於ASLR的存在,真實攻擊中的ROP通常都是要以某個內存泄露的漏洞為前提的……不然連基地址都拿不到,根本沒法做ROP。

綜上所述,你設想的東西不是說沒有意義,但對防護能力的提升也非常有限,這大概就是它沒有被廣泛應用的原因吧。


操作系統防ROP不是靠花指令,而是靠庫載入地址的隨機性。另外如果有些固定相對偏移的表內可能包含地址的情況下,比如_ _exit__list裡面的清理回調函數,都使用一個叫做PTR_ MANGLE的宏進行編碼,使用時需要使用PTR_ DEMANGLE宏解碼,這個宏原理如下:

# define PTR_MANGLE(var) asm ("xor %%fs:%c2, %0
"
"rol $2*" LP_SIZE "+1, %0"
: "=r" (var)
: "0" (var),
"i" (offsetof (tcbhead_t,
# define PTR_DEMANGLE(var) asm ("ror $2*" LP_SIZE "+1, %0
"
"xor %%fs:%c2, %0"
: "=r" (var)
: "0" (var),
"i" (offsetof (tcbhead_t,
pointer_guard)))

而tcbhead_t結構如下:

typedef struct
{
dtv_t *dtv;
uintptr_t pointer_guard;
} tcbhead_t;

可以看出來,這兩個宏實際上就是把指針跟tcbhead裡面的pointer_guard異或了以下,所以如果在非特權態即使取到了地址,但是拿不到pointer_ guard的話,也是沒辦法知道指針地址的。這樣攻擊者就沒辦法了。

但是這裡面是有脆弱性的,如果已經知道一個libc公共函數的地址,然後又知道了mangle過的地址,是可以反推pointer _guard的,這樣就可以解密指針了。這個脆弱性直到現在最新的libc(2020-6-15)裡面也有,已經有大神寫出來過相關的腳本進行反推了,自己可以google一下。


不行。從題干描述來看,你的目的是讓目標機器與攻擊者的機器上的 libc 文件內容不同。但問題在於,目標機器上每次進程啟動所載入的 libc 都是相同的。只要攻擊可以重複(比如服務崩潰後自動重啟),攻擊者只需要知道一個大致範圍就可以了,大不了上下 1K 位元組範圍挨個試,這個防禦手段就變成了擺設。

除非可以做到進程每次載入的 libc 加的花都不一樣。這樣的話就把動態鏈接庫最大的一個優點弄沒了:我的機器上一共有 90 個進程(這就是我現在 Windows 10 剛開機的狀態),但內存里只需要一份 libc。如果每個進程的 libc 都加不同的花,就變成了大家的 libc 都是獨一份,2M 的 libc 乘以 90 就變成了 180M 的內存佔用。這還只是 libc 一個文件,那麼多基礎的動態鏈接庫全搞一遍,進程再多一點,10 個 G 都不一定夠用?。

地址空間隨機化沒有上面的兩個問題,好用 ?


(當然更有可能的情況是,你還沒溢出完,就被 Security Cookie 給逮住了。Security Cookie,好用 ?


答案是不能。

ROP是掃碼代碼空間找出合適代碼,這個代碼不一定是原代碼。

比如

L1:

mov $0x12345678, %rax

這條指令從L1開始執行,是條mov指令,但是從L1+1,開始執行呢,就不是mov指令了。所以加花指令是沒有用的,攻擊者掃碼的返回指令之前的代碼。而返回指令就一個位元組c3,加了花指令代碼空間也會有很多0xc3。只要c3之前能拼湊出有用的代碼,就能被利用。因為棧是被攻擊者控制的,找出需要的代碼還是很簡單。尤其是x86,指令長度不固定的,因此沒法逆序反彙編,或者說逆序反彙編有很多種可能。在0xc3之前的代碼進行反彙編,如選地址-1,-2,-3,-4,-5,-6,-7,-8等進行反彙編,會得到各種不同的指令。其實代碼加密,很多用的也是這個方法。反彙編看到的代碼是雜亂無章的,就是執行的時候改了執行地址。

比如:

jmp L1+1:

L1:

.db 0x48

...

以上代碼執行的時候沒有問題,但是反彙編出來,jmp L1+1之後會接著反彙編,會反彙編到0x48(mov指令),而實際上0x48是跳過的,達到了反彙編代碼亂碼的效果。

操作系統為了保護代碼,代碼段是只讀的,ROP利用棧跟現有代碼片段組合,達到了改寫代碼段的效果。有人已經證明ROP是圖靈完備的,即可以利用現有代碼片段+控制的棧內容可以構造出任意邏輯代碼。


可以啊,有一些論文就是這麼乾的。思想還是隨機化,ASLR是地址隨機化。你說的就是局部指令隨機化,還有的更激進的有指令集隨機化。只是實用的目前只有ASLR。


推薦閱讀:
相关文章