操作系統/虛擬化安全知識域:系統強化之控制流限制
控制流限制
正交防線是調節操作系統的控制流。通過確保攻擊者無法將控制權轉移到他們選擇的代碼上,即使我們不刪除內存錯誤,我們也使利用內存錯誤變得更加困難。最好的例子被稱為控制流完整性(CFI),現在許多編譯器工具鏈(如LLVM和微軟的VisualStudio),并于2017年以ControlFlowGuard的名義并入Windows內核-另見軟件安全CyBOK知識領域。
從概念上講,CFI非常簡單:我們確保代碼中的控制流始終遵循靜態控制流圖。例如,一個函數的返回指令應該只允許返回到它的調用站點,而使用C中的函數指針或C++中的虛函數的間接調用應該只能夠定位它應該能夠調用的合法函數的入口點。為了實現這種保護,我們可以標記間接控制轉移指令的所有合法目標(返回、間接調用和間接跳轉),并將這些標簽添加到特定于此指令的集合。在運行時,我們檢查指令即將進行的控制轉移是否是到集合中的目標。否則,CFI會發出警報和/或使程序崩潰。
與ASLR一樣,CFI有多種口味,從粗粒度到細粒度,從上下文敏感到上下文不敏感。就像在ASLR中一樣,今天的大多數實現只采用最簡單,最粗粒度的保護。粗粒度CFI意味著為了性能而稍微放寬規則。例如,它不是將函數的返回指令限制為可能調用此函數的僅目標合法調用站點,而是可以針對任何調用站點。雖然不如細粒度CFI安全,但它仍然極大地限制了攻擊者的回旋余地,并且具有更快的運行時檢查。
在現代機器上,某些形式的CFI甚至(或將要)得到硬件的支持。例如,英特爾控制流強制技術(CET)支持影子堆棧和間接分支跟蹤,以幫助分別強制實施退貨和前向控制傳輸的完整性(以非常粗粒度的方式)。ARM也不甘示弱,它提供了指針身份驗證,以防止對指針值進行非法修改—主要是通過使用指針的上位來存儲指針身份驗證代碼(PAC),其功能類似于加密指針值上的簽名(除非您獲得正確的PAC,否則您的指針無效)。
遺憾的是,CFI只能通過破壞返回地址、函數指針和跳轉目標等控制數據來幫助抵御改變控制流的攻擊,但對非控制數據攻擊卻無能為力。例如,它無法阻止覆蓋當前進程的權限級別并將其設置為“root”的內存損壞(例如,通過將有效用戶ID設置為root用戶的ID)。但是,如果對控制流的限制在實踐中如此成功,您可能想知道數據流是否也可以進行類似的限制。事實上,它們確實如此,這被稱為數據流完整性(DFI)。在DFI中,我們靜態地確定每個加載指令(即從內存中讀取的指令)存儲指令可能合法地產生了數據,并且我們標記這些指令并將這些標簽保存在一組中。在運行時,對于內存中的每個字節,我們記住該位置的最后一個存儲的標簽。當我們遇到加載指令時,我們會檢查該地址的最后一個存儲是否在合法存儲集中,如果不是,我們會發出警報。與CFI不同,DFI在實踐中并未被廣泛采用,可能是因為其顯著的性能開銷。