Blind Return Oriented Programming (BROP) Attack - 攻击原理
0x00 寫在前面
第一次在WooYun發(fā)文章,不知道是否符合眾客官口味,望輕拍。
這篇文章翻譯至我的這篇博客,主要介紹了一種叫做BROP的攻擊,該文章主要介紹原理部分,對該攻擊的重現(xiàn)可以參看我的另外一篇博客。
BROP攻擊基于一篇發(fā)表在Oakland 2014的論文Hacking Blind,作者是來自Standford的Andrea Bittau,以下是相關paper和slide的鏈接:
paper
slide。
以及BROP的原網(wǎng)站地址:
Blind Return Oriented Programming (BROP) Website
可以說這篇論文是今年看過的最讓我感到興奮的論文(沒有之一),如果要用一個詞來形容它的話,那就只有“不能更帥”才能表達我對它的喜愛程度了!
這篇文章假設讀者已經(jīng)了解Return-Oriented Programming (ROP) 的基本概念,所以只是介紹BROP的實現(xiàn)原理,如果還不清楚什么是ROP,請先出門左轉,看看Wiki的相關介紹。
BROP的實現(xiàn)真的是讓人感到非常“cool”和“smart”,我希望能夠通過這篇文章把它講清楚。
0x01 BROP攻擊的目標和前提條件
目標:通過ROP的方法遠程攻擊某個應用程序,劫持該應用程序的控制流。我們可以不需要知道該應用程序的源代碼或者任何二進制代碼,該應用程序可以被現(xiàn)有的一些保護機制如NX, ASLR, PIE, 以及stack canaries等保護,應用程序所在的服務器可以是32位系統(tǒng)或者64位系統(tǒng)。
初看這個目標感覺實現(xiàn)起來特別困難。其實這個攻擊有兩個前提條件的:
- 必須先存在一個已知的stack overflow的漏洞,而且攻擊者知道如何觸發(fā)這個漏洞;
- 服務器進程在crash之后會重新復活,并且復活的進程不會被re-rand(意味著雖然有ASLR的保護,但是復活的進程和之前的進程的地址隨機化是一樣的)。這個需求其實是合理的,因為當前像nginx, MySQL, Apache, OpenSSH, Samba等服務器應用都是符合這種特性的。
0x10 BROP的攻擊流程 1 - 遠程dump內存
由于我們不知道被攻擊程序的內存布局,所以首先要做的事情就是通過某種方法從遠程服務器dump出該程序的內存到本地,為了做到這點我們需要調用一個系統(tǒng)調用write,傳入一個socket文件描述符,如下所示:
write(int sock, void *buf, int len)
將這條系統(tǒng)調用轉換成4條匯編指令,如圖所示:
所以從ROP攻擊的角度來看,我們只需要找到四個相應的gadget,然后在棧上構造好這4個gadget的內存地址,依次進行順序調用就可以了。
但是問題是我們現(xiàn)在連內存分布都不知道,該如何在內存中找到這4個gadgets呢?特別是當系統(tǒng)部署了ASLR和stack canaries等保護機制,似乎這件事就更難了。
所以我們先將這個問題放一放,在腦袋里記著這個目標,先來做一些準備工作。
攻破Stack Canaries防護
如果不知道什么是stack canaries可以先看這里,簡單來說就是在棧上的return address下面放一個隨機生成的數(shù)(成為canary),在函數(shù)返回時進行檢查,如果發(fā)現(xiàn)這個canary被修改了(可能是攻擊者通過buffer overflow等攻擊方法覆蓋了),那么就報錯。
那么如何攻破這層防護呢?一種方法是brute-force暴力破解,但這個很低效,這里作者提出了一種叫做“stack reading”的方法:
假設這是我們想要overflow的棧的布局:
我們可以嘗試任意多次來判斷出overflow的長度(直到進程由于canary被破壞crash了,在這里即為4096+8=4104個字節(jié)),之后我們將這4096個字節(jié)填上任意值,然后一個一個字節(jié)順序地進行嘗試來還原出真實的canary,比如說,我們將第4097個字節(jié)填為x,如果x和原來的canary中的第一個字節(jié)是一樣的話,那么進程不會crash,否則我們嘗試下一個x的可能性,在這里,由于一個字節(jié)只有256種可能,所以我們只要最多嘗試256次就可以找到canary的某個正確的字節(jié),直到我們得到8個完整的canary字節(jié),該流程如下圖所示:
我們同樣可以用這種方法來得到保存好的frame pointer和return address。
尋找stop gadget
到目前為止,我們已經(jīng)得到了合適的canary來繞開stack canary的保護, 接下來的目標就是找到之前提到的4個gadgets。
在尋找這些特定的gadgets之前,我們需要先來介紹一種特殊的gadget類型:stop gadget.
一般情況下,如果我們把棧上的return address覆蓋成某些我們隨意選取的內存地址的話,程序有很大可能性會掛掉(比如,該return address指向了一段代碼區(qū)域,里面會有一些對空指針的訪問造成程序crash,從而使得攻擊者的連接(connection)被關閉)。但是,存在另外一種情況,即該return address指向了一塊代碼區(qū)域,當程序的執(zhí)行流跳到那段區(qū)域之后,程序并不會crash,而是進入了無限循環(huán),這時程序僅僅是hang在了那里,攻擊者能夠一直保持連接狀態(tài)。于是,我們把這種類型的gadget,成為stop gadget,這種gadget對于尋找其他gadgets取到了至關重要的作用。
尋找可利用的(potentially useful)gadgets
假設現(xiàn)在我們找到了某個可以造成程序block住的stop gadget,比如一個無限循環(huán),或者某個blocking的系統(tǒng)調用(sleep),那么我們該如何找到其他 useful gadgets呢?(這里的“useful”是指有某些功能的gadget,而不是會造成crash的gadget)。
到目前為止我們還是只能對棧進行操作,而且只能通過覆蓋return address來進行后續(xù)的操作。假設現(xiàn)在我們猜到某個useful gadget,比如pop rdi; ret, 但是由于在執(zhí)行完這個gadget之后進程還會跳到棧上的下一個地址,如果該地址是一個非法地址,那么進程最后還是會crash,在這個過程中攻擊者其實并不知道這個useful gadget被執(zhí)行過了(因為在攻擊者看來最后的效果都是進程crash了),因此攻擊者就會認為在這個過程中并沒有執(zhí)行到任何的useful gadget,從而放棄它,這個步驟如下圖所示:
但是,如果我們有了stop gadget,那么整個過程將會很不一樣. 如果我們在需要嘗試的return address之后填上了足夠多的stop gadgets,如下圖所示:
那么任何會造成進程crash的gadget最后還是會造成進程crash,而那些useful gadget則會進入block狀態(tài)。盡管如此,還是有一種特殊情況,即那個我們需要嘗試的gadget也是一個stop gadget,那么如上所述,它也會被我們標識為useful gadget。不過這并沒有關系,因為之后我們還是需要檢查該useful gadget是否是我們想要的gadget.
最后一步:遠程dump內存
到目前為止,似乎準備工作都做好了,我們已經(jīng)可以繞過canary防護,并且得到很多不會造成進程crash的“potential useful gadget”了,那么接下來就是該如何找到我們之前所提到的那四個gadgets呢?
如上圖所示,為了找到前兩個gadgets:pop %rsi; ret和pop %rdi; ret,我們只需要找到一種所謂的BROP gadget就可以了,這種gadget很常見,它做的事情就是恢復那些callee saved registers. 而對它進行一個偏移就能夠生成pop %rdi和pop %rsi這兩個gadgets.
不幸的是pop %rdx; ret這個gadget并不容易找到,它很少出現(xiàn)在代碼里, 所以作者提出一種方法,相比于尋找pop %rdx指令,他認為可以利用strcmp這個函數(shù)調用,該函數(shù)調用會把字符串的長度賦值給%rdx,從而達到相同的效果。另外strcmp和write調用都可以在程序的Procedure Linking Table (PLT)里面找到.
所以接下來的任務就是:
- 找到所謂的BROP Gadget;
- 找到對應的PLT項。
尋找BROP Gadget
事實上BROP gadgets特別特殊,因為它需要順序地從棧上pop 6個值然后執(zhí)行ret。所以如果我們利用之前提到的stop gadget的方法就可以很容易找到這種特殊的gadget了,我們只需要在stop gadget之前填上6個會造成crash的地址:
如果任何useful gadget滿足這個條件且不會crash的話,那么它基本上就是BROP gadgets了。
尋找PLT項
PLT是一個跳轉表,它的位置一般在可執(zhí)行程序開始的地方,該機制主要被用來給應用程序調用外部函數(shù)(比如libc等),具體的細節(jié)可以看相關的Wiki。它有一個非常獨特的signature:每一個項都是16個字節(jié)對齊,其中第0個字節(jié)開始的地址指向改項對應函數(shù)的fast path,而第6個字節(jié)開始的地址指向了該項對應函數(shù)的slow path:
另外,大部分的PLT項都不會因為傳進來的參數(shù)的原因crash,因為它們很多都是系統(tǒng)調用,都會對參數(shù)進行檢查,如果有錯誤會返回EFAULT而已,并不會造成進程crash。所以攻擊者可以通過下面這個方法找到PLT:如果攻擊者發(fā)現(xiàn)好多條連續(xù)的16個字節(jié)對齊的地址都不會造成進程crash,而且這些地址加6得到的地址也不會造成進程crash,那么很有可能這就是某個PLT對應的項了。
那么當我們得到某個PLT項,我們該如何判斷它是否是strcmp或者write呢?
對于strcmp來說, 作者提出的方法是對其傳入不同的參數(shù)組合,通過該方法調用返回的結果來進行判斷。由于BROP gadget的存在,我們可以很方便地控制前兩個參數(shù),strcmp會發(fā)生如下的可能性:
arg1 | arg2 | result :--: | :--: | :--: readable | 0x0 | crash 0x0 | readable | crash 0x0 | 0x0 | crash readable | readable | nocrash根據(jù)這個signature, 我們能夠在很大可能性上找到strcmp對應的PLT項。
而對于write調用,雖然它沒有這種類似的signature,但是我們可以通過檢查所有的PLT項,然后觸發(fā)其向某個socket寫數(shù)據(jù)來檢查write是否被調用了,如果write被調用了,那么我們就可以在本地看到傳過來的內容了。
最后一步就是如何確定傳給write的socket文件描述符是多少了。這里有兩種辦法:1. 同時調用好幾次write,把它們串起來,然后傳入不同的文件描述符數(shù);2. 同時打開多個連接,然后使用一個相對較大的文件描述符數(shù)字,增加匹配的可能性。
到這一步為止,攻擊者就能夠將整個.text段從內存中通過socket寫到本地來了,然后就可以對其進行反編譯,找到其他更多的gadgets,同時,攻擊者還可以dump那些symbol table之類的信息,找到PLT中其它對應的函數(shù)項如dup2和execve等。
0x11 BROP的攻擊流程 2 - 實施攻擊
到目前為止,最具挑戰(zhàn)性的部分已經(jīng)被解決了,我們已經(jīng)可以得到被攻擊進程的整個內存空間了,接下來就是按部就班了(從論文中翻譯):
- 將socket重定向到標準輸入/輸出(standard input/output)。攻擊者可以使用dup2或close,跟上dup或者fcntl(F_DUPFD)。這些一般都能在PLT里面找到。
- 在內存中找到/bin/sh。其中一個有效的方法是從symbol table里面找到一個可寫區(qū)域(writable memory region),比如environ,然后通過socket將/bin/sh從攻擊者這里讀過去。
- execve shell. 如果execve不在PLT上, 那么攻擊者就需要通過更多次的嘗試來找到一個pop rax; ret和syscall的gadget.
歸納起來,BROP攻擊的整個步驟是這樣的:
- 通過一個已知的stack overflow的漏洞,并通過stack reading的方式繞過stack canary的防護,試出某個可用的return address;
- 尋找stop gadget:一般情況下這會是一個在PLT中的blocking系統(tǒng)調用的地址(sleep等),在這一步中,攻擊者也可以找到PLT的合法項;
- 尋找BROP gadget:這一步之后攻擊者就能夠控制write系統(tǒng)調用的前兩個參數(shù)了;
- 通過signature的方式尋找到PLT上的strcmp項,然后通過控制字符串的長度來給%rdx賦值,這一步之后攻擊者就能夠控制write系統(tǒng)調用的第三個參數(shù)了;
- 尋找PLT中的write項:這一步之后攻擊者就能夠將整個內存從遠端dump到本地,用于尋找更多的gadgets;
- 有了以上的信息之后,就可以創(chuàng)建一個shellcode來實施攻擊了。
0x100 后記
以上就是BROP攻擊的原理,在這篇博文中重現(xiàn)了這個攻擊,有興趣的可以去看看。
其實在整個攻擊過程中最酷的要數(shù)第一個步驟:如何dump內存,之后的步驟其實就是傳統(tǒng)的ROP攻擊了。明白了原理之后,其實最好的了解該攻擊的方法就是看源代碼了,這個對了解整個ROP會有非常大的幫助。
原文地址: http://drops.wooyun.org/tips/3071
總結
以上是生活随笔為你收集整理的Blind Return Oriented Programming (BROP) Attack - 攻击原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Head First FILE Stre
- 下一篇: Double Free浅析