當前位置:
首頁 > 新聞 > 通過DARPA的CFAR保護軟體免受漏洞利用

通過DARPA的CFAR保護軟體免受漏洞利用

今天,我們(Trail of Bits)將要討論我們正在努力解決的一個問題,這個問題是DARPA網路容錯攻擊恢復(CFAR)計劃的一個組成部分:自動保護軟體免受零時差攻擊、內存損壞還有許多當前未知的bug。你可能在想為什麼要這麼麻煩,難道不能只使用堆棧保護,CFG或CFI等漏洞利用緩解來編譯代碼嗎?當然,這些緩解措施是很棒的,但需要源代碼和對源代碼修改後才能構建過程。在許多情況下,更改構建過程或更改程序源代碼是不切實際的。這就是為什麼我們的CFAR解決方案可以保護源在不可用/不可編輯狀態下的二進位安裝。

CFAR看似非常直觀簡單。系統並行運行軟體的多個版本或「變體」,並通過比較這些變體來識別某些個版本在行為上何時與其他版本不同。這個想法類似於入侵檢測系統,該系統將程序行為與在相同輸入結果上運行的自身變體進行比較,而不是與過去行為的模型進行比較。當系統檢測到行為差異時,它可以推斷出發生了異常且可能是惡意的事情。

像所有DARPA計劃一樣,CFAR是一個龐大而棘手的研究問題。我們只處理它的一小部分。我們和我們的隊友——Galois,Immunant和UCI共同創作了這篇文章,他們每個人都對CFAR項目貢獻了很多細節。

我們非常樂意談論CFAR,不僅因為它是一個棘手的相關問題,也是由於我們的一個工具——McSema,我們團隊基於LLVM的多功能解決方案的一份子。在本文的部分篇幅中,我們將展示McSema一些鮮為人知的特徵,並解釋開發緣由,也許最讓大家感興趣的是,如何使用McSema和UCI多編譯器來強化現成的二進位文件以防止被利用。

我們的CFAR團隊

CFAR的總體目標是在不影響核心功能的情況下檢測現有軟體中的故障並從中恢復。我們團隊的職責是生成一組最佳變體,以檢測和減少引發故障的輸入。其他團隊負責專門的執行環境、紅隊,等等。Galois在其關於CFAR的博客文章有更詳細的描述。

這些變體的行為必須與原始版本完全相同,並能證明對於所有有效輸入都能保持不變。我們的隊友已經開發了轉換,並為具有可用源代碼的程序提供了等效保證。團隊使用Clang / LLVM工具鏈設計了一個基於多編譯器的變體生成解決方案。

McSema的角色

考慮到源代碼可能不適用於專有或較舊的應用程序,我們一直致力於生成二進位軟體的程序變體。我們團隊的基於源代碼的工具鏈在LLVM中間表示(IR)級別工作。IR級別的轉換和加固程序允許我們在不改變程序源代碼的情況下操作程序結構。使用McSema,我們可以將二進位程序轉換為LLVM IR,並能重新利用相同的組件讓源級別的二進位變體生成。

為了能夠準確的翻譯CFAR程序,我們需要彌合機器級語義和程序級語義之間的差距。機器級語義是由單個指令引起的處理器和內存狀態的更改。程序級語義(例如,函數,變數,異常和try / catch塊)是表示程序行為的更抽象的概念。McSema被設計成機器級語義的翻譯器(名稱「McSema」源自「機器代碼語義」)。但是,要準確轉換CFAR所需的變體,McSema也必須恢復程序語義。

我們正在積極努力恢復越來越多的程序語義,並且已經支持許多常見的用例。在下一節中,我們將討論如何處理兩個特別重要的語義:堆棧變數和全局變數。

堆棧變數

編譯器可以將數據支持函數變數放在任意幾個位置中。程序變數里最常見的位置是堆棧,這是一個專門用於存儲臨時信息並且易於被調用函數訪問的內存區域。編譯器存儲在堆棧中的變數稱為堆棧變數。

int sum_of_squares(int a, int b) {

int a2 = a * a;

int b2 = b * b;

return a2+b2;

}

圖1:在源代碼級別和二進位級別顯示的簡單函數的堆棧變數。在二進位級別,沒有單個變數的概念,只有大塊內存中的位元組。

當攻擊者將bug轉化為漏洞時,他們通常依賴於特定順序的堆棧變數。多編譯器可以通過生成程序變體來減少此類漏洞,其中沒有哪兩個變體會具有相同順序的堆棧變數。我們希望為二進位文件啟用堆棧變數重排,但是存在一個問題:在機器代碼級別沒有堆棧變數的概念(圖1)。相反,堆棧只是一個大的連續內存塊。McSema如實的模擬了這種行為,並將程序堆棧視為不可分割的一塊。當然,這會使得堆棧變數無法進行重排。

堆棧可變恢復

將表示堆棧的內存塊轉換為單個變數的過程稱為堆棧變數恢復。McSema將堆棧變數恢復分為三個步驟實現。

首先,McSema通過反彙編程序(例如IDA Pro)的啟發式演算法以及基於DWARF(如果存在的話)的調試信息,在反彙編期間識別堆棧變數邊界。以前的研究是在沒有這些提示的情況下識別堆棧變數邊界,但我們計劃在將來使用這些提示。其次,McSema嘗試識別程序中,哪些指令在引用哪個堆棧變數。必須準確識別每個參考,否則生成的程序將無法運行。最後,McSema為每個恢復的堆棧變數創建一個LLVM級變數,並重寫指令以引用這些LLVM級變數,而不是先前的單片堆棧塊。

堆棧變數恢復適用於許多功能,但它並不完美。當遇到具有以下特徵的函數時,McSema將默認採用將堆棧視為整體塊的經典行為:

·Varargs功能。使用可變數量參數的函數(如常見的printf函數系列),具有可變大小的堆棧幀。這種差異會導致很難確定哪個指令引用哪個堆棧變數。

·間接堆棧引用。編譯器還依賴於堆棧變數的預定布局,並將生成通過不相關變數的地址訪問變數的代碼。

·沒有堆棧幀指針。作為優化,堆棧幀指針可以用作通用寄存器。這種優化使我們很難檢測到可能的間接堆棧引用。

堆棧變數恢復是CFG恢復過程的一部分,目前在IDAPython CFG恢復代碼(在collect_variable.py中)中實現。它可以通過--recover-stack-vars參數調用mcsema-disass。有關示例,請參閱此篇文章隨附的代碼「升級和多樣化二進位」部分對此進行了詳細介紹。

全局變數

程序中的所有函數都可以訪問全局變數。由於這些變數與特定函數無關,因此通常將它們放在程序二進位文件的特殊部分中(圖2)。與堆棧變數一樣,攻擊者可以利用全局變數的特定順序。

bool is_admin = false;

int set_admin(int uid) {

is_admin = 0 == uid;

}

圖2:從源代碼級別和機器代碼級別看到的全局變數。全局變數通常放在程序的特殊部分(在本例中為.bss)

與堆棧一樣,McSema將每個數據部分視為一大塊內存。堆棧和全局變數之間的一個主要區別是McSema知道全局變數的起始位置,因為它們直接從多個位置引用。不過,這並不足以使全局變數布局混亂。McSema還需要知道每個變數的結束位置,這更難。目前,我們依靠DWARF調試信息來識別全局變數大小,但期待實現可用於沒有DWARF信息的二進位文件的方法。

目前,全局變數恢復與正常的CFG恢復(在var_recovery.py中)分開實現。該腳本創建一個「空」CFG,僅填充全局變數定義。正常的CFG恢復過程將使用實際控制流圖進一步填充文件,引用預先填充的全局變數。我們稍後將展示使用全局變數恢復的示例。

提升和多樣化二進位

在本文的其餘部分,我們將通過多編譯器引用生成新程序變體的過程稱為「多樣化」。對於此特定示例,我們將提升和多樣化使用異常處理的簡單C ++應用程序(包括捕獲-all子句)和全局變數。雖然這只是一個簡單的例子,程序語義恢復意味著可以處理大型實際應用程序:我們的標準測試程序是Apache2 Web伺服器。

首先,讓我們熟悉標準的McSema工作流程(即沒有任何多樣化),即將示例二進位文件提升到LLVM IR,然後將該IR編譯回可運行的程序。要開始使用,請構建並安裝McSema。我們在官方McSema README中提供詳細說明。

接下來,使用提供的腳本(lift.sh)構建並提升程序。需要編輯腳本以匹配您的McSema安裝。

運行lift.sh後,你應該有兩個程序:example和example-lift,以及一些中間文件。

示常式序將兩個數字對齊並將結果傳遞給set_admin函數。如果兩個數字都是5,那麼程序將拋出std :: runtime_error異常。如果數字為0,則全局變數is_admin設置為true。最後,如果沒有向程序提供兩個數字,那麼它會拋出std :: out_of_range。

可以通過以下程序調用來演示四種不同的情況:

$ ./example

Starting example program

Index out of range: Supply two arguments, please

$ ./example 0 0

Starting example program

You are now admin.

$ ./example 1 2

Starting example program

You are not admin.

$ ./example 5 5

Starting example program

Runtime error: Lucky number 5

我們可以看到,示例的提升程序,與McSema提升並重新創建的程序完全相同:

$ ./example-lifted

Starting example program

Index out of range: Supply two arguments, please

$ ./example-lifted 0 0

Starting example program

You are now admin.

$ ./example-lifted 1 2

Starting example program

You are not admin.

$ ./example-lifted 5 5

Starting example program

Runtime error: Lucky number 5

現在,讓我們對提升的示常式序進行多樣化。首先,安裝多編譯器。接著,編輯lift.sh腳本以指定多編譯器安裝的路徑。

現在是構建多樣化版本的時候了。使用diversify參數(./lift.sh diversify)運行腳本以生成多樣化的二進位文件。 多樣化的示例在二進位級別上看起來與原始級別不同(圖3),但具有相同的功能:

$ ./example-diverse

Starting example program

Index out of range: Supply two arguments, please

$ ./example-diverse 0 0

Starting example program

You are now admin.

$ ./example-diverse 1 2

Starting example program

You are not admin.

$ ./example-diverse 5 5

Starting example program

Runtime error: Lucky number 5

圖3:正常提升二進位(左)及其多樣化等價物(右)。兩個二進位文件在功能上是相同的,但在二進位級別上看起來不同。二進位多樣化通過防止某些類型的bug變成漏洞來保護軟體

在您最喜愛的反彙編程序中打開示例- 提升和示例- 多樣化。您的二進位文件可能與屏幕截圖中的二進位文件不同,但它們應該彼此不同。

讓我們回顧一下我們做了什麼。這真的很神奇。我們首先構建一個使用異常和全局變數的簡單C ++程序。然後我們將程序翻譯成LLVM bitcode,識別堆棧和全局變數,並保留基於異常的控制流程。然後我們使用多編譯器對其進行了轉換,並創建了一個新的,多樣化的二進位文件,其功能與原始程序相同。

雖然這只是一個小例子,但這種方法可以擴展到更大的應用程序,並且提供了一種快速創建多樣化程序的方法,無論是從源代碼還是以前的程序二進位文件開始。

結論

我們首先要感謝DARPA,為CFAR和其他偉大的研究項目提供持續的資金,沒有他們這項工作是不可能的完成的。我們還要感謝我們的隊友--Galois,Immunant和UCI,為創建多編譯器,轉換,為變體提供等效保證以及使所有內容協同工作所做的辛勤工作。

我們正積極致力於改善McSema的堆棧和全局變數恢復。這些更高級別的語義不僅可以創造更多樣化和轉換機會,而且還可以實現更小、更精簡的bitcode,更快的重新編譯二進位文件以及更徹底的分析。

我們相信CFAR和類似技術有著光明的未來:每台機器的可用內核數量不斷增加,安全計算需求也在不斷增加。許多軟體包無法利用這些內核來提高性能,因此很自然的將備用內核用於保障安全性。McSema,多編譯器和其他CFAR技術展示了我們如何將這些額外的內核用於更強大的安全保障。

如果您認為其中一些技術可以應用於您的軟體,請與我們聯繫。我們很樂意聽取您的意見。要了解有關CFAR,多編譯器以及在此計划下開發的其他技術的更多信息,請閱讀Galois博客和Immunant博客上的隊友博客文章。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

RtPOS惡意軟體分析
三種身份訪問管理部署模式:哪種最適合您的組織?

TAG:嘶吼RoarTalk |