文/武成崗 李建軍
?
控制流完整性的發展歷程
文/武成崗李建軍

武成崗中國科學院計算技術研究所正高級工程師、博士生導師,主要研究領域為動態編譯、軟件安全、程序分析等。

李建軍中國科學院計算技術研究所副研究員,主要研究領域為動態程序分析、軟件安全保障等。
控制流劫持是一種危害性極大的攻擊方式,攻擊者能夠通過它來獲取目標機器的控制權,甚至進行提權操作,對目標機器進行全面控制。當攻擊者掌握了被攻擊程序的內存錯誤漏洞后,一般會考慮發起控制流劫持攻擊。早期的攻擊通常采用代碼注入的方式,通過上載一段代碼,將控制轉向這段代碼執行。為了阻止這類攻擊,后來的計算機系統中都基本上都部署了DEP(Data Execution Prevention)機制,通過限定內存頁不能同時具備寫權限和執行權限,來阻止攻擊者所上載的代碼的執行。為了突破DEP的防御,攻擊者又探索出了代碼重用攻擊方式,他們利用被攻擊程序中的代碼片段,進行拼接以形成攻擊邏輯。代碼重用攻擊包括Return-tolibc、ROP(Return Oriented Programming)、JOP(Jump Oriented Programming)等。研究表明,當被攻擊程序的代碼量達到一定規模后,一般能夠從被攻擊程序中找到圖靈完備的代碼片段。
為了抵御控制流劫持攻擊,加州大學和微軟公司于2005年提出了控制流完整性(Control Flow Integrity, CFI)的防御機制。其核心思想是限制程序運行中的控制轉移,使之始終處于原有的控制流圖所限定的范圍內。具體做法是通過分析程序的控制流圖,獲取間接轉移指令(包括間接跳轉、間接調用和函數返回指令)目標的白名單,并在運行過程中,核對間接轉移指令的目標是否在白名單中。控制流劫持攻擊往往會違背原有的控制流圖,CFI使得這種攻擊行為難以實現,從而保障軟件系統的安全。
CFI從實現角度上,被分為細粒度和粗粒度兩種。細粒度CFI嚴格控制每一個間接轉移指令的轉移目標,這種精細的檢查,在現有的系統環境中,通常會引入很大的開銷。而粗粒度CFI則是將一組類似或相近類型的目標歸到一起進行檢查,以降低開銷,但這種方法會導致安全性的下降。
CFI已經被提出十多年的今天,依然有許多研究者在探索新的CFI技術,使其在可接受的開銷下能獲得高安全性,圖1給出了CFI的發展歷程,本文將介紹在該歷程中的重要的技術創新和相關研究成果。
為了有效防御控制流劫持攻擊,2005 年Martín Abadi和Mihai Budiu,等人提出了控制流完整性(Control-Flow Integrity,CFI)的思路[1]。程序在其執行過程中,應當遵循預先定義好的控制流圖(Control Flow Graph, CFG),以確保程序控制流不被劫持或非法篡改,背離程序編制時所設計的控制流轉移關系。CFI在運行時檢測程序的控制轉移是否在控制流圖中,以識別是否遭遇了攻擊。具體作法是在控制流轉移指令前插入檢驗代碼,來判斷目標地址的合法性。這種做法能夠對控制流劫持攻擊起到防御作用,但是也存在一些問題。
嚴格意義上的CFI,需要做到對每條間接控制轉移都做檢查,并確保每條轉移指令只能轉移到它自身的目標集合。如果要達到更好的防御效果,則要做到上下文敏感的檢查。然而,這種檢查機制雖然能夠最大程度地提升系統的安全性,但其開銷過大,難以得到實際部署。
Chao Zhang 等人指出,CFI未被廣泛應用的原因是插樁引入的開銷過大,需要額外的信息(比如間接控制轉移可能的目標集合)的支持,且不能進行增量式的部署。為此,他們提出了CCFIR[2](Compact Control Flow Integrity and Randomization)。其策略是對間接調用指令和函數返回指令的目標進行區分,阻止未經驗證的返回指令跳轉到敏感函數的行為。這些限制囊括了CFI保護的主要思想,且不需要別名分析。出于效率和兼容性的考慮,他們通過一個專用的Springboard section來實現間接分支轉移,并限定代碼對齊,對跳轉目標進行限制。經SPECint2000進行測試,其平均/最高性能開銷分別為3.6%和8.6%(平均/最高)。為了進一步提升安全性,Springboard section在程序啟動時隨機變換允許的跳轉目標,進一步增加了控制流劫持的難度。CCFIR是一個純粹的二進制轉換程序,不依賴源碼或調試信息,所依賴的僅是重定位表中的信息。受保護的代碼可以被獨立地驗證,也可以進行增量式的部署。

圖1 CFI主要技術發展歷程
Mingwei Zhang 等人提出了一種針對COTS(Commercial-Off-The-Shelf)程序的CFI機制——binCFI[3],目標是使CFI直接應用于已有的商用應用上。他們將間接轉移指令的操作數分為代碼指針、異常處理程序入口、導出符號地址和返回地址等類型,通過精細的靜態分析,對不同類型的間接控制轉移,收集它們的合法目標集合,如返回指令的合法目標集合就是所有函數調用指令的下一條指令的地址,導出符號地址集合就是ELF文件中.dynamic段中存儲的地址。利用這種方法,binCFI可以得到與已有CFI方案相當的安全性。通過新增代碼段的方式,不改變原有的代碼,達到完全透明的目的。實驗表明,論文中提出的方案可以應用于大規模的二進制程序中,擁有不錯的安全性。
相對于嚴格意義上的CFI,CCFIR[2]和binCFI[3]都屬于粗粒度CFI機制。所謂粗粒度CFI,就是放寬檢查的條件,減少比較目標集合。原來的CFI是一個間接轉移對應一個目標集合。放寬后,間接轉移目標進行合并,比如把所有的間接調用指令和間接跳轉指令的目標集合合成一個目標,所有的函數返回指令的目標集合合成一個。這種方式能夠有效地降低CFI引入的開銷,但也存在安全性問題。
Enes G?ktas在Overcoming CFI[4]中,利用兩種特殊的gadget——entry point(EP)gadget和call site(CS)gadget,來繞開粗粒度CFI機制的防御。這兩種gadget滿足檢查的條件,但是其實是并不符合真實的控制流。Enes G?ktas對這兩種gadget的有效性進行了論述,并利用其構造ROP鏈、調用庫函數,形成實際的攻擊。
為了對抗Overcoming CFI對粗粒度CFI的攻擊,Ali Jose Mashtizadeh 等人提出了一種通過對代碼指針加密,來增強CFI的方法,稱為CCFI[5]。粗粒度CFI的問題本質就在于歸類使得攻擊者有了可乘之機。CCFI就是要對代碼指針做更細致地劃分,還不能回歸到CFI原本定義的那種細粒度的分類。他們將代碼指針細分成四類,分別是函數指針類、return指針類(函數返回地址)、方法指針類、虛表指針類(后兩種都是針對C++語言特有的類)。每次在保存一個代碼指針時,將其運行時信息加密保存。每類指針對應一些具有標識作用的運行信息。在這些指針解引用(間接轉移)時,解密信息并比對。另外為了加快加解密的速度,CCFI使用了處理器中獨特的指令“AES-IN”對AES加解密算法加速,同時為了保證密鑰的安全,又將密鑰保存在處理器中獨特的寄存器中。他們修改了開源編譯器LLVM,定制了一個編譯器,并用其編譯了一些程序,在實際的攻擊環境中檢測其有效性。實驗證明,他們的方法在安全性和性能方面取得了很好的折衷。
Lucas Davi 等人在Stitching the Gadgets[6]中詳細分析了不同的CFI解決方案包括kBouncer、ROPecker、binCFI、ROPGuard 和Microsoft EMET4.1,并指出它們的安全性不容樂觀。他們首先重新思考了不同的粗粒度的CFI的假設,對kBouncer、ROPecker、binCFI、ROPGuard和Microsoft EMET4.1進行了詳盡的安全性分析,特別地,整合了這些現有粗粒度CFI,形成了一個安全策略更為嚴格的CFI系統。在這個更為嚴格的條件下,完成了圖靈完整性的驗證。為了更好地展示攻擊能力,他們在Adobe Reader和mPlayer上進行了實驗,證明了粗粒度的CFI無法達到預期的安全效果。論文[7]也探索對CFI進行攻擊的手段。多數防御人員認為,通過影子棧(Shadow Stack)來檢測函數返回目標,再加上DEP和ASLR的保護,棧應該會變得非常安全,從而將重心轉向堆數據的安全保護,然而,作者發現,棧還是會受到攻擊的。他們提出了三種攻擊方法:一是利用堆上的漏洞來破壞棧上的calleesaved寄存器保存區域,使得calleesaved寄存器被劫持;二是利用用戶空間和內核之間進行上下文切換的問題,來劫持sysenter指令,使控制跳轉到攻擊者想跳轉的位置;三是通過泄露主棧的地址來泄露出shadow stack的地址,進而進行攻擊。
以往CFI方案的問題是只實施了控制流不敏感策略。上下文信息的缺少不可避免地降低了CFI的防御能力,從而給了攻擊者利用空間。上下文敏感的CFI(Context sensitive CFI)是有望解決這一問題的方法,它依賴于上下文敏感的靜態分析,將CFI不變量和CFG中的控制流路徑聯系到一起,運行時在執行路徑上強制執行這些不變量。CCFI早在CFI提出之時已被認可,但因其在現實應用中不實際而被迅速廢棄。Victor van der Veen 等人提出的Practical Context-Sensitive CFI[8],展示了一個可用于現實應用程序的高效、可靠、實用的上下文敏感CFI方案:PathArmor。PathArmor依賴于商用硬件的支持,高效可靠地監控通往敏感函數(可用于實施控制流轉移攻擊)的執行路徑;使用仔細優化的二進制插樁設計,在被監控路徑上強制執行上下文敏感CFI的不變量。PathArmor的路徑不變量是按需在CFG上,通過一定范圍內的上下文敏感靜態分析得到的,而且路徑驗證步驟使用了caching來提高驗證效率。路徑驗證本身也是非常高效的,因為所有的CFI檢查都在敏感函數點處集中完成。
John Criswell將CFI做到操作系統內核中,使之免受經典的控制流劫持、return2user和代碼段修改攻擊,該系統稱為KCoFI[9]。他們在基于標簽的控制流間接轉移保護的基礎上,加入一個運行時監控的軟件層,負責保護一些關鍵的操作系統數據結構和監控操作系統進行的所有底層狀態操作。并且還通過形式化方法,證明了一個子集的正確性。證明涵蓋了頁表管理、異常處理、上下文切換和信號分派等操作。實驗表明,KCoFI阻止了攻擊者將控制流向FreeBSD Kernel中的任何gadget的轉移。與未修改的內核相比,KCoFI在運行server測試集時具有較小的開銷,但運行文件系統操作密集型的測試集時,開銷較大。
為了能夠在性能和防御方面取得更好的效果,一些研究著手于利用現有的硬件機制,來降低CFI的開銷。
Vasilis Pappas提出利用硬件性能計數器,在運行時觀察執行流的思路,該方法被稱為kBouncer[10]。他們利用LBR(Last Branch Register)來捕獲最近的16次跳轉信息。具體做法是在敏感系統調用處,對捕獲的16次跳轉進行安全性判斷,即return指令需要跳轉到調用點的后繼位置,indirect-call指令的目標是函數入口,其余跳轉指令目標基本塊長度不能全部少于20條指令。為了避免攻擊者利用庫函數調用來完成攻擊,文章在所有的庫函數調用點,進行上述合法性檢查。為了驗證kBouncer的防御效果,作者對IE瀏覽器、Adobe Flash Player和Adobe Reader進行了實驗(利用已知安全漏洞,組織ROP payload攻擊這三種應用),實驗結果表明該方法能夠有效緩解ROP攻擊。同時,該方法的性能開銷低于4%。
Yueqiang Chen等人設計了一種與kBouncer類似的方法,稱作ROPecker[11],也是利用LBR捕獲程序控制流的方式進行ROP攻擊監測。但不同之處在于判斷是否遭受ROP攻擊的邏輯和觸發監測的時機。
1.判斷邏輯:在運行時檢測過去(利用LBR)和未來的執行流(模擬執行)中是否存在長gadget鏈(5個比較短的gadget),若存在,則認為這是一次ROP攻擊。Gadget信息是通過靜態分析二進制程序和共享庫得到的。
2.運行時監測是事件驅動的,具體時機是敏感系統調用和執行流跳出滑動窗口觸發異常。ROPecker設計了一個滑動窗口,因為代碼本身具有時間和空間的局部性,但是gadget鏈卻是散列的,利用這一特性,系統保證該窗口內的gadget數目不足以構成一次ROP攻擊,窗口內的代碼設置可執行權限,窗口外的代碼不可執行,當執行流跳出滑動窗口時,便會觸發異常,進行運行時檢測。該方法利用代碼本身具有的時間和空間局部性,針對gadget鏈是散列的前提,提出了滑動窗口機制,使用事件驅動的檢測方法,具有較高的準確性和高效性。為了驗證該方法的安全性,ROPecker選取了有棧溢出的真實世界應用(Linux Hex-editer)進行攻防演練。實驗結果證明,ROPecker能夠有效的阻止ROP攻擊。同時,SPEC CPU2006 benchmark顯示了該方法的開銷非常低(2%)。
Yubin Xia等人設計的CFIMon[12],也是采用性能計數器來捕獲程序執行流,并進行合法性判斷。但他們采用的是BTB(Branch Trace Buffer),來捕獲受保護程序運行過程中所有跳轉指令的信息。BTB 與LBR不同之處在于,BTB可以把程序整個執行過程中所有的跳轉指令的歷史信息都記錄下來,LBR只能記錄16條。但是BTB需要CPU向指定的一個緩沖區內寫入跳轉信息,當緩沖區滿時,CPU會觸發異常交給操作系統處理(將緩沖區內容寫入文件中),LBR是循環的寄存器。使用BTB的程序性能明顯比LBR性能低。CFIMon檢查BTB的時機在兩個階段:一是當緩沖區滿時,操作系統將所有歷史信息寫入另一個進程,由另一個進程進行合法性判斷;二是當受保護進程執行敏感系統調用時,另一個進程也進行歷史信息的合法性判斷。合法性判斷主要檢查間接控制轉移的跳轉目標是合法目標集合內。如果所有間接控制轉移的歷史跳轉目標在合法目標集合中,認為當前受保護進程沒有收到攻擊;如果有至少一個間接控制轉移的歷史跳轉目標在合法目標集合中,那么認為受保護進程受到攻擊。合法目標的集合是在線下通過靜態分析獲得的,并且存儲在檢查進程中。
大多數的攻擊都依賴于某種形式的控制劫持來重定向程序執行,CFI(Control Flow Integrity)是一種運行時強制執行的技術,用以抵御代碼注入、代碼重用攻擊,也不易受信息泄露攻擊。CFI在運行時強制執行預期的控制流轉移,不允許執行應用程序控制流圖(Control Flow Graph,CFG)中未出現的轉移。精確的CFI會引入大量開銷,這一點激勵了更為實際的、粗粒度的CFI變體的發展;粗粒度CFI通過放寬限制條件來降低開銷,卻也給攻擊者提供足夠的利用空間。另外,CFI的作用也僅僅是用于阻止攻擊者對控制流的劫持,有研究表明(如Control-Flow Bending,CFB[13]),攻擊者可以僅僅通過控制函數調用的參數,完成攻擊者的目的,如執行任意代碼、執行受限制的代碼和信息泄露。
從2005年CFI技術被提出,CFI技術已經歷經了10多年的發展。近兩年涌現出的大量CFI相關的研究工作,極大推進了CFI技術的發展。但是,CFI技術未來仍有很大的發展空間,仍然需要研究人員繼續深入研究,實現一種高可靠、低開銷的CFI技術。
(作者單位為中國科學院計算技術研究所)
參考文獻
[1]. Martín Abadi,Mihai Budiu, úlfar Erlingsson, and Jay Ligatti. 2005. Control-flow integrity.InProceedings of the 12th ACM conference onComputer and communications security(CCS ’05). ACM, New York, NY, USA,340-353.
[2]. Chao Zhang, TaoWei, Zhaofeng Chen, Lei Duan, Laszlo Szekeres, Stephen McCamant, Dawn Song, and Wei Zou. 2013. Practical Control Flow Integrity and Randomization for Binary Executables. In Proceedings of the 2013 IEEE Symposium on Security andPrivacy(SP ’13).IEEE Computer Society, Washington,DC, USA, 559-573.DOI=http://dx.doi.org/10.1109/SP.2013.44
[3]. Mingwei Zhang and R. Sekar. 2013. Control flow integrity for COTS binaries. In Proceedingsof the 22nd USENIX conference on Security(SEC’13). USENIX Association,Berkeley, CA, USA, 337-352.
[4]. Enes G?ktas,Elias Athanasopoulos, Herbert Bos, and Georgios Portokalidis. 2014. Out of Control: Overcoming Control-Flow Integrity. In Proceedingsof the 2014 IEEE Symposium on Security and Privacy(SP ’14). IEEE Computer Society,Washington,DC, USA, 575-589.
[5]. Ali JoseMashtizadeh, Andrea Bittau, Dan Boneh, and David Mazières. 2015. CCFI:Cryptographically Enforced Control Flow Integrity. In Proceedingsof the 22nd ACM SIGSAC Conference on Computer and Communications Security(CCS ’15). ACM, New York, NY, USA,941-951.
[6]. Lucas Davi,Ahmad-Reza Sadeghi, Daniel Lehmann, and Fabian Monrose. 2014. Stitching thegadgets: on the ineffectiveness of coarse-grained control-flow integrityprotection. In Proceedings of the 23rd USENIX conference on Security Symposium(SEC’14). USENIX Association, Berkeley, CA, USA, 401-416.
[7]. Mauro Conti, Stephen Crane, Lucas Davi,Michael Franz, Per Larsen, Marco Negro, Christopher Liebchen, Mohaned Qunaibit,and Ahmad-Reza Sadeghi. 2015. Losing Control: On the Effectiveness of Control-FlowIntegrity under Stack Attacks. In Proceedings of the 22nd ACM SIGSAC Conferenceon Computer and Communications Security(CCS ’15). ACM, New York, NY,USA,952-963.
[8]. Victor van der Veen, Dennis Andriesse, EnesG?ktas, Ben Gras,Lionel Sambuc, Asia Slowinska, Herbert Bos, and CristianoGiuffrida. 2015. Practical Context-Sensitive CFI. In Proceedings of the 22ndACM SIGSAC Conference on Computer and Communications Security(CCS ’15). ACM,New York, NY, USA, 927-940.
[9]. John Criswell, NathanDautenhahn, and Vikram Adve. 2014. KCoFI: Complete Control-Flow Integrity forCommodity Operating System Kernels. In Proceedings of the 2014 IEEE Symposium on Security andPrivacy(SP ’14).IEEE Computer Society, Washington,DC, USA, 292-307.
[10]. Vasilis Pappas, MichalisPolychronakis, and Angelos D. Keromytis. 2013. Transparent ROP exploitmitigation using indirect branch tracing. In Proceedings of the 22nd USENIXconference on Security(SEC’13). USENIX Association, Berkeley, CA, USA, 447-462.
[11]. Yueqiang Cheng, Zongwei Zhou,Miao Yu, Xuhua Ding and Robert H. Deng. 2014. ROPecker: A Generic and PracticalApproach for Defending Against ROP Attacks. In Proceedingsof the 2014 Network and Distributed System Security Symposium(NDSS‘14).
[12]. Yubin Xia, Yutao Liu, HaiboChen, and Binyu Zang. 2012. CFIMon: Detecting violation of control flowintegrity using performance counters. In Proceedings of the 2012 42nd Annual IEEE/IFIPInternational Conference on Dependable Systems and Networks(DSN)(DSN ’12). IEEE Computer Society,Washington,DC, USA, 1-12.
[13]. Carlini N, Barresi A, Payer M, Wagner D andGross TR. 2015. Control-Flow Bending: On the Effectiveness of Control-FlowIntegrity. In Proceedings of the 24th USENIX Security Symposium(USENIX Security 15). USENIX Association, Washington,D.C., USA