Intel CET緩解措施深度研究
0x00 TL;DR
上?篇?章中已經簡單介紹過了CET的基本原理和實際應?的?些技術,站在防守?的視?下,CET確實是?個能
?較有效防御ROP攻擊技術的措施。那么在攻擊者的視?來看,研究清楚CET的技術細節,進?判斷CET是否是?
個完美的防御?案,還是存在?定的局限性,則是攻擊?的重中之重。
本?由淺?深地講述CET的實現細節,最后提出?個理論可?的繞過?案,供研究者參考。
0x01 Shadow Stack Overview
上?篇?章已經?概對CET做了個基本概念介紹,所以就不重復,直接說重點。
Shadow Stack PTE
Shadow Stack本質上是塊內存?,屬于新增的?類型,因此需要增加?個新的?屬性來標識Shadow Stack。PTE中的?些未有被CPU定義的,也有保留給操作系統使?的,例如第0位的Present就由CPU標識?是否分配。Linux
操作系統沒有將所有保留位都使?掉(?于別的?途),但是其他操作系統則沒有剩余可?的保留位了,因此從
Linux中取?個未使?的位,不太可取。
這?Linux采?了復?很少使?的?狀態(寫時復制的狀態):write=0, dirty=1。當Linux需要創建寫時復制
write=0, dirty=1的?時,?軟件定義的_PAGE_COW代替_PAGE_DIRTY,創建shadow stack時,則使?write=0,
dirty=1。這就將兩者區分開來了:

Shadow Stack
Management Instructions
為了保證shadow stack的獨特性,CET專?設計了獨有的匯編指令。普通的指令(MOV, XSAVE...)將不被允許操
作shadow stack。

這?重點說SAVEPREVSSP、RSTORSSP。Linux環境下,會存在棧切換的情況(系統調?、信號處理...),為了保
證shadow stack的正常運作,數據棧切換后shadow stack也需要相應切換,因此就會?到這兩個指令。
下圖為執?RSTORSSP指令前后的shadow stack狀態變化。執?的操作為先將SSP指針指向new shadow stack的
‘restore token’,即0x4000。然后?current(old) shadow stack的地址做‘new restore token’替換掉‘restore
token’,?于后續的SAVEPREVSSP指令使?。

下圖為執?SAVEPREVSSP指令前后的變化。執?的操作為將前?設置的‘new restore token’壓?previous shadow
stack中,并將標志位置0。然后將SSP指針加1。

?此,就完成了shadow stack切換的整個過程。
0x02 Shadow Stack Implementation
這?不提及Shadow Stack的普遍情況(?上?篇?章),只研究Shadow Stack在?些特殊場景下的實現,在這些
場景中光申請Shadow Stack?后做push/pop操作是不夠的,往往需要更復雜的實現。
Signal
?般?戶需要對某個信號做?定義的特殊處理時,就會?到信號。對應的函數為signal()、sigaction():

當捕獲信號到執?信號處理函數再到恢復正常執?的整個過程中,會經歷進程掛起、Ring0和Ring3間的切換、上下?切換等操作,這都需要shadow stack作出相應的變化,否則就會出現不可知的異常。下圖是信號處理期間進程的變化。

以signal函數舉例,在glibc中它的具體實現為下?所示,最終會調?rt_sigaction去注冊信號。

再看CET的實現,它在 __setup_rt_frame 函數中添加了shadow stack相關的操作函數, __setup_rt_frame 函
數會在信號處理過程中被調?,即上?信號處理期間進程變化的圖中②的期間:

上?新增的 setup_signal_shadow_stack 函數,參數restorer即為前? __libc_sigaction 函數中提到的
__NR_rt_sigreturn 系統調?,且該參數后續會被push到shadow stack中去作為新的函數返回地址。
相應地,再看 __NR_rt_sigreturn 系統調?的實現,該調?會在上?信號處理期間進程變化的圖中④執?,CET
也在該處做了相應的改動:


從上? rt_sigreturn 新增代碼結合 __setup_rt_frame 新增代碼可知,兩者是相互配合的:?個負責創建
restore token并在shadow stack設置返回地址,另?個則負責校驗restore token并設置新的ssp,以此來兼容在
信號處理過程中數據棧切換、上下?切換的場景。
?于為什么要在創建restore token后設置shadow stack返回地址,是因為在信號處理過程中執?完sa_handler?戶?定義函數后,緊接著就會執?sa_restorer所設置的函數,因此在CET場景下需要在shadow stack設置相應的返回地址。
Fork
調?fork后,存在兩種情況:
1. ?進程和?進程分別有??的?塊內存,不共享;
2. ?進程和?進程共享同?塊內存,為vfork。
因此,在shadow stack場景下,需要對fork系統調?做特殊處理。fork調?鏈如下:

CET在copy_thread函數中添加了相關代碼:


從上?新增的代碼可知,CET針對fork系統調?過程增加了創建新的shadow stack的部分,以兼容fork后??進程
不共享內存的情況。同時也對vfork后??進程共享內存的情況做了處理,使得不創建新的shadow stack以兼容相應場景。
Ucontext
ucontext涉及到協程相關的技術,該技術和系統調?在R3、R0間的切換?較類似。但是該技術作?于?戶態,?
的是給?戶態程序提供更快的切換效果,以及使得?戶態的代碼能夠更加靈活。在?戶態層?實現上下?切換。常?的函數為getcontext/setcontext:

setjmp/longjmp的技術原理和實現和ucontext類似,就不提及了。getcontext/setcontext具體實現都在glibc中。ucontext協程技術涉及到上下?切換的場景,也會存在數據棧切換的情況,因此,shadow stack也需要做出相應
的動作。
先看shadow stack在getcontext中的改動,先? __NR_arch_prctl 系統調?獲取當前shadow stack的基地址,其
次將其保存在SSP_BASE_OFFSET寄存器中,隨后保存shadow stack基地址、ssp值在ucontext結構體中,供后續
setcontext使?:


再來看setcontext中的改動,校驗getcontext保存的ucontext中的shadow stack基地址和ssp,再恢復,達到切換
回上?狀態的?的:


上?getcontext/setcontext的場景,是在同?塊shadow stack中實現切換,因為進程并沒有創建新的數據棧。此外,makecontext會創建?個新的數據棧,開辟?個新的上下?,和上?的場景?有些許不同,makecontext和
setcontext也都做了相應的改動,由于篇幅原因不過多敘述,讀者??閱讀源碼即可,技術原理都是?樣的。
0x03 CET Bypass
CET在多場景下的實現還是相對復雜的,需要軟件層?做相應的配合,因此在復雜的設計實現層?,是否有可能存
在繞過CET的可能性呢?本?節提出?個理論可?的?案供研究者參考。
Overwrite Function
該?法?較簡單粗暴,篡改結構體中的函數指針來控制執?流。假設現有如下代碼:


調?結構體函數(1)處的匯編代碼如下:

此時有間接call,IBT機制會起作?,call rax后?條指令必須為ENDBR64。
如果此時擁有任意讀寫的能?,就可以篡改結構體str1的test函數指針為over_write(2)即可改變執?流。且此時
over_write函數的??點也是ENDBR64,即可繞過IBT的檢查:

IBT機制會給絕?部分函數體的??點添加ENDBR指令,因此這種?法還是可?的,實際測試:

擴展?下,還可以利?JOP去做。例如使?以下序列,也可以繞過CET:

但是這種JOP序列實際上是?較稀少的,難找到。
Migrate Shadow Stack by RSTORSSP
這種?案利?了CET新增的指令來做?章。前?已經介紹過了RSTORSSP,?于shadow stack的切換,那么如果切
換到的是攻擊者偽造的shadow stack呢?
整個過程?較簡單,步驟如下:
1. 構造?塊可控內存;
2. 在可控內存中事先構造好返回地址,后續作為shadow stack使?;
3. 將內存轉變為shadow stack;
4. 構造ROP;
5. ROP利?rstorssp將原shadow stack遷移到偽造的shadow stack中;
6. ROP執?system。
CET針對mmap和mprotect都做了相應的改動,在mmap中主要增加了?個VMA_FLAG為VM_SHADOW_STACK的
屬性,在mprotect中除了PROT_READ/PROT_WRITE外增加了PROT_SHADOW_STACK(有?點是PROT_WRITE和
PROT_SHADOW_STACK不能同時使?,即只讀),這兩者是互相對應的關系。簡單編寫了這種?案的demo:


調試效果如下,可?當前已經將shadow stack切換到事先偽造的內存?中,且返回地址也篡改得和數據棧返回地址
相同,為0x41414141:

最終,RIP也能成功執?到控制的執?流:

不過這種?法在實際場景中構造的要求?較?,局限性?較?。
當然了,還有更粗暴的?法,CET新增指令還有?個WRSS的指令,該指令可以直接在shadow stack中寫數據。但
是該指令需要在CPU上做使能操作,?前筆者閱讀的源碼暫時還沒有使能,就不贅述了。
0x04 Summary
CET與以往軟件實現的CFI不同,它從硬件側尋找解決?案,在底層就將ROP掐斷,對于軟件CFI來說從性能、緩解效果?度來說都有著極?的提升。有得必有失,底層的變動必然會撬動上層隨之變化,想要將這?緩解措施真正實
施落地,還有著很?的?段路要?。筆者略淺地研究了?番CET當前的實施進展,提出了部分攻防?向上的想法,
供后續研究者參考。我相信在不遠的將來,CET的落地會給攻防帶來很?的變化,到時候?將摩擦出怎樣的?花?讓我們?起期待吧。
0x05 Reference
- https://github.com/yyu168/linux_cet/commit/72367656271aba4d29a25b38232e680ab9231a26
- https://ty-chen.github.io/linux-kernel-signal/ https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/sigaction.c.html#__libc_sigaction
- https://man7.org/linux/man-pages/man2/signal.2.html
- https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/getcontext.S.html#137
- https://code.woboq.org/userspace/glibc/sysdeps/unix/sysv/linux/x86_64/setcontext.S.html#197
- https://man7.org/linux/man-pages/man3/getcontext.3.html
- https://lore.kernel.org/lkml/776fb081217145f4a488f7bca3e16eab@AcuMS.aculab.com/
- https://github.com/hjl-tools/linux/commit/280503098ea762b3100edb30d60489a030d4abca