當前位置:
首頁 > 新聞 > 從理論層面談Meltdown與Spectre攻擊 | 核武按鈕終被劫持?

從理論層面談Meltdown與Spectre攻擊 | 核武按鈕終被劫持?

地球上有沒有一種安全漏洞,廣泛存在於各種計算設備之中,並且難以被檢測,難以被修復?在2018年的第一個月里,Meltdown 與 Spectre 兩大處理器安全漏洞給出了這個問題的部分答案。


邊信道攻擊進入熱核時代


信息安全行業有一類很高端的攻擊方法,叫做邊信道攻擊(side channel,也有譯作側信道攻擊,或旁路攻擊的),聽著就感覺很高級有沒有。這種攻擊出現於上世紀 90 年代,以色列的高等學府似乎經常研製邊信道攻擊的奇技淫巧——所謂的邊信道,就是不從正面進攻,而是從側面竊取或傳遞信息的方式,比如利用設備運行時產生的電磁輻射,或者電流導致的機械波動雜訊,來分析破解出密鑰,甚至還能利用散熱風扇產生的雜訊、機箱上的 LED 燈閃爍變化來將信息傳出。


許多邊信道攻擊條件都非常苛刻,而且攻擊雜訊比較大,情報獲取的正確率有時很堪憂,所以邊信道攻擊在此之前只有 2016 年精妙的 Linux TCP 漏洞(CVE-2016-5696)造成廣泛的影響[1]。

如果將各種漏洞比作攻擊武器,那 Linux TCP 漏洞之前的各種邊信道攻擊類似冷兵器——致命但難以造成廣泛破壞。Linux TCP 漏洞攻擊思路則將邊信道攻擊水平提升到了熱兵器時代,1 年之後,Meltdown 和 Spectre 漏洞正式開啟了熱核時代(即便邊信道攻擊本身嚴格意義上並不需要漏洞的介入)。



核武 Meltdown(熔斷)和 Spectre(幽靈)儘管只有兩個名字,但卻包含了 3 個漏洞分別是 CVE-2017-5753/5715/5754 [2])。理論上將影響一切現代處理器,藉助這三個漏洞,黑客能竊取信息,比如你的密碼、個人敏感數據,甚至拿下整個內核地址空間;運行個極為隱蔽的惡意程序,或者點個網址鏈接,就能讓你的信息泄露。由於漏洞存在於硬體層面,因此殺毒軟體和個人防火牆很難對攻擊進行檢測,遑論防護。


截至 2018 年 1 月 10 日,包括 Intel、高通、Apple、NVIDIA、ARM 均承認旗下處理器受 Meltdown 和 Spectre 兩個漏洞影響。Intel 受傷最深,從 25 年前 Pentium 75MHz 開始全部中招,就連體系架構完全不同的 Itanium、Xeon Phi 都無法倖免。高通和 ARM 則確認所有帶有 OoO(不是表情符號,Out of Order 亂序執行)的 ARM 架構都受到影響。相比之下,NVIDIA 則幸運得多,旗下的 GPU 只受到 Spectre 漏洞影響,而對 Meltdown 免疫。至於 AMD 處理器和其他處理器指令集結構(ISA)如 MIPS64、ALPHA 等,我們認為其有可能對 Meltdown 免疫,但大概率存在 Spectre 漏洞——大量企業級、電信級路由器防火牆都使用 Cavium 基於 MIPS64 ISA 的 SoC,不少超算包括太湖之光都使用了 ALPHA ISA。


熱核攻擊,從緩存開始


早在 2013 年,就有人根據 Intel 處理器的三級緩存架構提出了專門的緩存攻擊思路,並將此攻擊思路命名為 FLUSH+RELOAD。這種思路利用了現代處理器線程之間共享緩存的設計,通過沖刷和重載來實現目標線程的劫持和攻擊。簡單地說,現代處理器內執行的 N 個線程就相當於一幢樓中每個使用自來水的家庭,緩存就像大家共用一個自來水水箱。如果其中一個家庭在水箱中投毒,將會導致所有家庭都中毒。現代處理器的 Cache 本質上是 SRAM,也需要持續的沖刷,這給此類攻擊製造了大量機會。



這次有關 Meltdown 攻擊和 Spectre 攻擊,美國幾家學府和安全公司的研究人員共同發布了兩篇技術 paper,這兩篇論文均提到了上述 FLUSH + RELOAD 的攻擊方案 [3]。


當代操作系統有個特性叫 page sharing,就是不同的進程可以進行主內存的共享。Page sharing 的價值主要在於兩個聯合運行的進程通訊更方便,還能減少內存佔用。本質上這是一種內存復用的思路,這種基於內容的 page sharing 用於在進程執行和使用共享庫時,可執行文件正文段的共享。而且不止是操作系統,如今的主流虛擬化 Hypervisor 其實也用這套方案。比如雲計算技術中普遍採用的 de-duplication,就是不同虛擬主機共享一個內存模塊的技術,也是同理——像 VMware ESX、PowerVM 之類的 hypervisor 都是支持的。


系統對於共享 page 的映射,採用 copy-on-write(COW) 的方案——就是對改動的數據延後寫入到內存中,這也是 FLUSH+RELOAD 攻擊可導致信息泄露的一個基礎。

兩個進程共享內存頁,到了線程層面也有很大概率共享處理器上的緩存 Cache 部分。Cache 是連接主內存和處理器前端的緩衝地帶,其讀寫速度比內存要快得多。處理器在進行數據處理時,會首先從 cache 中提取數據和指令,唯有在發現 cache 中沒有所需數據時才向主內存發出請求,要知道從主內存中提個數據,可能需要浪費多達 200 個時鐘周期,這對處理器這種高速設備而言絕對是不太能忍的,所以你看當代 CPU 的 cache 命中率有多高。



當代處理器都採用多層級 cache 方案, Cache 中的存儲基本單位是 line,每條 line 包含固定位元組數(如下圖)。比如 i5-3470 的 cache line 尺寸為 64 位元組。另外作為共享 cache,所有 cache 數據的副本在此處都有一份(至少 Intel 處理器是這樣)。所以如果我們嘗試從 L3 擦除一些數據,則其他各級 cache 的數據都也可以被移除——這是 FLUSH+RELOAD 攻擊的基礎。


比如上面這張圖是 Intel 酷睿 i5-3470 處理器的 cache 架構。其 cache 分三級,分別是 L1、L2 和 L3 緩存,L1 離處理器核心最近,速度最快,容量也最小,L3 則離處理器最遠,容量相對也更大——此例中是 6MB。從 2008 年 Intel Nehalem 架構開始,Intel 的所有多核酷睿處理器就有了共享 L3 緩存,無論這個處理器有多少內核均能共享 L3 至 LLC(Last Level Cache,末級緩存)中的所有數據,這也間接降低了攻擊實施的難度。



假設有這樣一個目標進程 1,為了讓它和我們構造的惡意進程 2 進行 page sharing,就將內存映射函數 mmaps 應用到目標可執行程序,令其進入惡意進程 2 的虛擬地址空間,完成映射文件的內存鏡像共享(需保證兩者在同一物理處理器上運行,可令其位於處理器的不同核心之上)。


攻擊第一步,讓惡意進程 2 監控某個特定的 cache line,並將其內容從 cache 中擦除;第二步,惡意進程 2 等待一段時間;第三步,惡意進程 2 重新載入剛才的那條 line。如果說載入這條 line 的時間比較久,就說明應該是從主內存中載入的(因為主內存很慢),最主要的是說明在等待過程中目標進程 1 並沒有嘗試載入這條 line;但如果在這一步中,惡意進程 2 載入這條 line 的時間很短,就說明目標進程 1 剛才也嘗試過載入這行 line(因為目標進程 1 載入過之後,這部分數據就已經寫到了 cache 中,而 cache 是很快的,所以惡意進城 2 載入這條 line 的時間就很短了)——撞上了!



如此循環往複,如上圖所示,這樣一來就能得知哪些數據被存取過。這就是所謂的 FLUSH+RELOAD L3 cache 攻擊。當然了,這種攻擊還是存在精度問題的,比如說上圖 C 的情況,惡意進程請求載入某條 line,在極短時間內目標進程也恰巧請求載入這條 line,這樣觀測到的結果就會有問題;另外還需要考慮一些處理器的性能優化方案的雜訊干擾,比如說現在的處理器的空間局部性實現的數據預取(prefetch),還有像是分支預測,這要求在設計攻擊程序(建議採用循環體之類以內存訪問相對頻繁的為目標,如上圖 E)和對結果進行分析的時候有過濾策略。


比如像上面這樣,其中第 14 行 cflush 就是從所有 cache 中擦除某行 line;另外,如 mfence、lfence 這些指令實現指令流的串列序列化,避免並行和亂序執行的出現。這篇 paper 還特別舉了攻擊的實例,以這種攻擊方法從 RSA 實施方案 GnuPG 中獲取私鑰。按照這篇 paper 所說,平均而言,這種攻擊方案通過觀察單獨的一個簽名或加密輪,平均就能恢復密鑰 96.7% 的 bits。


至此我們不難看出,現代處理器特別是多內核處理器為了提升性能,大部分都採用了多級緩存設計,而各大處理器廠商更是投入巨資去研究和提升緩存命中率,然而這樣的設計恰恰存在漏洞。那麼,現代處理器如果沒有緩存或者不共享緩存,是不是就會幸免於難?在熱核時代,答案當然是否定的——還記得我們開篇所說么?Meltdown 和 Spectre 包含 3 個漏洞,緩存只是其一,亂序執行接踵而來。


Meltdown, 亂序執行惹的禍


上述 FLUSH+RELOAD 攻擊實際上就是這次 Meltdown 和 Spectre 攻擊最終環節的組成部分,也是這兩個攻擊被稱為邊信道攻擊的原因:敏感信息的獲取,靠的是檢查讀取某條 cache line 的時間,這已經是比較典型的邊信道攻擊了,雖然好像感覺不夠神奇。不過在 Meltdown 和 Spectre 的攻擊鏈條中,FLUSH+RELOAD 並非唯一實施方案,還有各種變體,比如 Prime+Probe 也是可行的,這裡就不做展開了。但這部分構建了攻擊鏈中的 convert channel,也就是最後獲取到敏感數據的一個秘密通道。


因為 ARM 雖然也有擦除 cache line 的指令,但這些指令僅可用於高許可權模式,ARM 架構不允許用戶進程選擇性地擦除 line;對 AMD 處理器攻擊也無效,作者推測 AMD 處理器的緩存結構可能是非包含式的,即 L1 的數據不需要存在於 L2 或 L3 中,所以擦除 L3 cache 的某行 line,並不對 L1 構成影響。


在 Spectre 攻擊的 paper 中[4],研究人員提到他們不僅採用 FLUSH+RELOAD 攻擊方案,另外還融合了 EVICT+RELOAD 方案——這本質上算是前者的一個變體,只不過後者對 line 的擦除方法更複雜,這可能是 Meltdown 和 Spectre 得以在不同處理器平台上實現的一部分原因,有興趣的同學可以去深挖後一種方案[5]。



就 Meltdown 而言,這種攻擊方式主要利用的是當代處理器的亂序執行特性,可以在不需要進行系統提權的情況下,就讀取任意內核內存位置,包括敏感數據和密碼,甚至拿下整個內核地址空間。不過它對 AMD 和 ARM 的處理器也是無效的,但原因似乎並不像上面這樣。研究人員在 paper 中提到[6],

Meltdown 在 AMD 和 ARM CPU 上的攻擊復現並不成功但可能只需要對攻擊進行一定優化,深入挖掘依然可能會成功,比如對競爭條件進行一些調整,所以 Meldown 攻擊或許也並不僅限於 Intel。


「亂序執行」本身不是個生詞了,當代的高性能處理器都有亂序執行特性:早些年 CPU 性能每年翻番都並不稀罕,通過增加核心數、時鐘頻率,加寬管線外加工藝迭代就能實現。但是在順序執行架構達到瓶頸之後,架構的優化就成為一個重要方向:順序執行架構中,指令完全按照一個不變的順序執行,就算 CPU 運算單元的執行速度很快,CPU 卻浪費大量時間在等待,許多單元處在閑置狀態,所以亂序執行成為提升效率的重要解決方案。好比你攢台 PC,如果顯卡還沒到貨,肯定不會守在門口傻等,而是把其餘部分先組裝好。

1967 年,Tomasulo 最早開發出了可用於動態規劃指令亂序執行的演算法,當時他就提出了一個叫 

Unified Reservation Station 的東西。CPU 在不需要將某個值存儲到寄存器並讀取的情況下,就可以在這裡使用值。此外,Unified Reservation Station 還通過一個 CDB(共用數據匯流排)把所有執行單元連接起來。如果某個操作數尚未準備就緒,URS 單元可以監聽 CDB,獲取裡面的數據,然後就可以直接執行指令了。

(請注意,這一段對於理解後面的 Meltdown 攻擊流程很重要)



在 Intel 的處理器架構中,其整條管線包含了前端、執行引擎(後端)和存儲子系統。前端會從存儲系統中(包括 cache 和主內存)讀取 x86 指令,隨後分解成為 μOP(所謂的「微指令」,有些類似於將 CISC 轉為 RISC 的過程),μOP 再發往執行引擎。亂序執行操作就是在執行引擎中發生的,如上圖所示。


其中有個 Reorder buffer(重新排序緩衝器),負責寄存器分配、寄存器重命名和 retire。在經過此處之後,μOP 就轉發到了上面提到的 Unified Reservation Station,此處對操作進行排序,排隊完了就可以發往執行單元了;執行單元就能進行 ALU 加減乘除、AES、AGU 或者載入存儲執行之類的操作了。AGU(地址生成單元)以及載入與存儲執行單元直接與存儲子系統相連,可以直接處理請求。


現在的處理器一般都已經不再直線型執行指令,比如分支預測單元可以猜測接下來該執行什麼指令——分支預測器在某個 if then 語句中的條件語句還沒有判斷之前,就已經開始預測其中的分支運行結果了。在這條線路上,那些沒有依賴關係的指令可以先執行,如果預測正確,運算結果就可以馬上使用了。如果預測錯誤,Reorder buffer 清理回滾,並重新初始化 Unified Reservation Station。



選讀:有關地址空間(可略過)


為了讓進程彼此間隔離,CPU 支持虛擬地址空間,在此虛擬地址會轉為物理地址。一個虛擬地址空間會被分成幾個 page,這些 page 通過多層級頁轉換表(multi-level page translation)分別映射到物理內存。這裡的轉換表,定義了虛擬和物理間的映射轉換,另外還定義了用於進行許可權檢測(可讀屬性、可寫屬性、可執行屬性、用戶可訪問屬性)的保護屬性。現如今的轉換表放在某個特定的 CPU 寄存器裡面。


在每次進程上下文切換的時候,操作系統都會用另一個進程的轉換表地址去更新該寄存器,所以每個進程都有個虛擬地址空間,每個進程只能參照其自有虛擬地址空間的數據。每個虛擬地址空間又切分成用戶(User)和內核(Kernel)兩部分。運行中的應用可以訪問用戶地址空間,但內核地址空間僅當 CPU 運行在特權模式下才可以訪問。這一步是由操作系統決定的,操作系統在相應的轉換表中禁用用戶可訪問屬性即可。


實際上內核地址空間不僅包含內核自己用的部分,也需要在用戶 page 執行操作,比如往裡面灌數據。因此,整個物理內存在內核中都有映射。在 Linux 和 OS X 系統中,這種映射比較直接,比如整個物理內存直接映射到預定義的虛擬地址;Windows 的情況則比較特殊,Windows 系統維護分頁池(paged pool)、非分頁池(non-paged pool)以及系統緩存(system cache)。這些「池」就是內核地址空間中的虛擬存儲區域,將物理頁映射到虛擬地址,其中非分頁池要求地址位於內存中,分頁池由於已經存儲在了磁碟上,所以可以從內存中移除。而系統緩存部分則包含所有文件備份頁的映射。


一般的內存破壞漏洞利用,就需要某個數據的地址。為了阻止內存破壞一類攻擊,就有了 ASLR 等技術。為了保護內核,KASLR(內核地址空間布局隨機化)在啟動的時候會對內核地址進行隨機化,令攻擊難度更大。微軟和蘋果在Windows 7和Mac OS X 10.8 中引入了kALSR並強制開啟。Linux在2017年5 月於4.12 版本中默認開啟kALSR。


實施 Meltdown 攻擊,要克服 KASLR 就需要獲取到這種隨機偏移量。但要做到這一點也並不難。



這裡我們看一個亂序執行的簡單例子。這段代碼第一行就產生了異常(不用管是怎麼產生異常的),按照控制流來說,發生異常就該跳到操作系統的異常處理程序,應用終止,後面的代碼就不會繼續執行了。但

因為有亂序執行的存在,第三行指令可能已經部分被執行了,只不過沒有 retire,所以實際上並不會在架構層面產生可見的影響,即它對寄存器、內存都不會有影響。


這部分執行最終會被丟棄(恢復狀態),寄存器和內存的內容也不會執行 commit。但它對微架構層面有影響——這裡所謂的微架構也就是 cache 部分了。在亂序執行(第三行指令)過程中,引用的內存讀取到寄存器上,也存儲到了 cache 裡面,即便最終 CPU 發現異常以後清空整條管線,

cache 中的內容也依然會保留

。這樣一來,利用本文第一部分提到的 cache 邊信道攻擊就可以獲知其中的信息了(此處是要提取 data 的值):也就是不停探測某個內存位置是否進入 cache。


完整的 Meltdown 攻擊主要分成兩個組成部分,第一部分就是讓 CPU 執行某個永遠不會在路徑中被執行的指令,如上例中的第三行代碼——我們將這種指令稱作 

transient instruction

。真正要將這種 transient instruction 應用到實踐攻擊中,就要求指令序列中包含密鑰之類的東西——就是攻擊者想要竊取的數據。



Meltdown 的第二部分主要就是我們這篇文章第一部分提到的那種基於時間的邊信道攻擊了,把密鑰給恢復出來,或者說對 L3 cache 進行攻擊獲取數據的方法。這樣我們基本上已經把 Meltdown 的攻擊過程給說清楚了。但實際操作中可能會碰到很多複雜的情況,如攻擊者要獲取密鑰,那麼就意味著要訪問用戶不可訪問的 page,比如說內核頁——訪問這樣的位置,由於沒有許可權,所以會導致異常。攻擊者需要去想辦法處理這樣的異常,否則的話進程就要被終止了。處理方法可以是把攻擊程序分成不同部分,只在子進程中執行後面的 transient instruction 序列,而父進程通過後續的邊信道攻擊來恢復密鑰。


另外,在第二部分的邊信道攻擊,也就是上圖中所謂構建 covert channel 秘密通道,悄悄把已經存在於 cache 中的密鑰給竊取到了。在這部分里,我們可以把 transient instruction 序列看成是這個通道的發射端,而接收端可以是不同的進程(和第一部分的 transient instruction 可以是無關的),比如可以是前面提到的父進程。



如果你還是不明白,這裡舉個簡單例子:有家餐廳(CPU),這家餐廳有個收銀員,還有個廚師(執行引擎)。小明和小紅每天都去這家餐廳吃東西,小紅(內核)每次點東西的方法都是對收銀員說:我要和昨天一樣的東西——然後廚師把東西做出來,收銀員給小紅打包帶走。小明很想知道小紅吃的究竟是什麼,所以他有一天跟在小紅身後;小紅點完以後,小明跟收銀員說:我要和小紅一樣的東西。收銀員說:你這是侵犯人家的隱私,滾出我們餐廳(發生異常)!於是小明就被人收銀員一腳踢出了餐廳(管線清空)。


小明想了一個新的辦法,第二天他帶著小方一起去,他倆跟在小紅後面。小紅照常點餐(點的是漢堡),小明大聲喊:我要和小紅點一樣的東西。餐廳的廚師聽到了,於是就做了兩個漢堡出來(執行引擎進行亂序執行,第二個漢堡進入了 cache)。但小明再次因為侵犯隱私,被收銀員扔出了餐廳。小方(cache 邊信道攻擊)這個時候上前了,他和小明是串通一氣的,他對收銀員說:我要點漢堡、薯條、雞腿、土豆泥、紅豆派、可樂、雞翅……最終小方發現,最快送上前來的是漢堡(因為在 cache 裡面),於是就知道小紅點的其實是漢堡。


Spectre,處理器背後的幽靈


Spectre 的情況在這裡只略作介紹,下文也不再進行展開。筆者認為,Spectre 的利用難度要大很多(攻擊成本更高),但牽涉面更廣。Spectre 基本思路和 Meltdown 是差不多的,最終也是通過 FLUSH+RELOAD 這樣的 cache 邊信道攻擊來獲取內存裡面的東西,只不過切入方式利用的是分支預測——就是我們前面提到的,比如 If Then 語句出現,處理器在還沒有判斷 if 條件是否滿足的情況下,就會去預測後面的分支,如果預測正確就可以有效提升運算效率。對分支預測的利用,無論是分析成本,還是攻擊成本,都大了不少,只不過包括 AMD、ARM、Intel、等在內的處理器全部都中招。


Spectre 攻擊涉及到兩個漏洞,分別是 CVE-2017-5753 和 5715。針對 CVE-2017-5753,如上面這個語句,綠色部分那一行是個 if 條件語句。上述代碼,如果 arr1->length, arr2->data[0x200] 和 arr2->data[0x300] 都還沒有進入 cache,處理器都還沒有判斷條件語句,但其它數據都已經 cache,而且分支條件預測為真,那麼處理器在載入 arr1->length 之前就會載入 arr1->data[untrusted_offset_from_caller] 的值,並開始載入 arr2->data 數據依賴偏移,將相應 cache line 載入 L1 cache。


但最後處理器發現,預測錯誤,此刻包含 arr2->data[index2] 的 cache line 已經位於 L1 cache,那麼此時由惡意程序去請求 arr2->data[0x200] 和 arr2->data[0x300],測量請求載入的時間,就能判斷 index2 的值是 0x200 還是 0x300 了,也就可以知道 arr1->data[untrusted_offset_from_caller]&1 是 0 還是 1。


篇幅有限,我們無法再展開做更為具體的分析和講解,不過這個邏輯本質上和 Meltdown 對於亂序執行的利用有些相似,都建基於處理器率先執行了後面的指令,而且還把數據放在了 cache 中,但明顯更為複雜。最終也都通過惡意程序去檢查讀取載入指令所需時間,來推測某值。


要利用這樣的行為,攻擊者需要在目標環境中構造這樣的代碼模式執行。有兩種思路,要麼這種代碼模式在現有代碼中就有,要麼需要有個解釋器(interpreter)或者 JIT 引擎,最終可以生成這種代碼模式。第一種攻擊思路,對實際環境的要求太苛刻或者說很難達成;第二種是谷歌採用的方案,他們選擇了 eBPF 位元組碼解釋器和 JIT 引擎。



這種攻擊還涉及一些複雜的問題,比如說需要對分支預測機制首先進行訓練,令其足以產生錯誤的預測結果;然後操作目標進程,執行上述構造出來的代碼模式;最後用上咱們文章第一部分提到的 cache 邊信道攻擊。


而相關 CVE-2017-5715,Spectre 攻擊可利用 CPU 的間接分支預測器(Indirect branch predictor),執行特定代碼片段;條件間接分支可被用於攻擊,分支目標地址被攻擊者控制;通過控制間接跳轉目標位置,或其代碼,利用邊信道攻擊來推測敏感信息。為此,谷歌的研究人員還專門逆向分析了 Intel Haswell 的分支預測器。這一思路的實現難度也不小,莫說不同 CPU 在分支預測機制方面的差異需要攻擊者差別對待,應對超線程,Spectre 的攻擊還需要解決同一 CPU 核心之上兩個線程與間接分支預測器極為複雜的關係問題——AMD 方面甚至認為由於架構方面的差異,CVE-2017-5715 幾乎不可能對 AMD 處理器造成影響。


Meltdown 的 paper 中提到,Spectre 攻擊需要專門為目標進程的軟體環境做定製。許多安全專家也都認為,Spectre 攻擊是有目標明確指向性的,它很難造成大規模無差別攻擊。筆者意見:Spectre 可能會成為未來 APT(高級持續性威脅)攻擊中的一環,畢竟它適用於幾乎所有處理器,但攻擊難度略高;指望黑客專攻小網民,其成本收益可能並不對等。尤為值得一提的是,到目前為止,針對 Spectre 攻擊的補丁無法從根本上杜絕其影響,充其量只是增加攻擊難度的緩解方案。Spectre 的研究 paper 也提到,所有提出來的從硬體到軟體層面的緩解建議只是折中選擇,治標不治本。


谷歌倒是針對 Spectre 自己開發了一個名為 Retpoline 的補丁(該補丁也得到了 Intel 的支持)[7],這可能也是個緩解方案:可以避免內核中的「預測間接調用」,將預測執行與間接分支進行隔離,可應用於基於 Linux 的系統、系統程序、庫以及軟體程序,主要面向 x86 架構——而且按照谷歌的說法,從已經部署到 Linux 伺服器的實驗結果來看,這個補丁對性能的影響也比較小(這就叫自己挖洞,自己補)…


Mozilla 方面已經確認,Spectre 的又一厲害之處就在於,

它能夠使用 JavaScript 程序從 web 瀏覽器讀取敏感信息

[8, 9],不依賴於惡意程序,而是欺騙用戶點擊一個鏈接,通過 web 端來竊取信息——但筆者以為,通過瀏覽器進行攻擊依然存在很大限制,可能需要結合其他漏洞進行組合攻擊才能發揮比較大的成效。不過各瀏覽器廠商,包括 Chrome、IE/Edge、Firefox 都牟足勁兒開發新版本來緩解 Spectre 帶來的影響。


按下核按鈕,Meltdown 攻擊實務


這裡,我們對 Meltdown 再做更為細緻的理論探討。在上文 Meltdown 簡介部分的代碼中,攻擊者通過 probe_array 開闢了一段內存,而且是 256 個 page 大小的內存(乘以 4096 是假設一個 page 為 4KB),而且要保證這部分內存沒有 cache 過。接下來我們就徹底地理一理整個邏輯的過程究竟是什麼樣的:


第一步:讀取密鑰


從主內存中載入數據到寄存器,引用主內存中的數據需要使用虛擬地址。在將虛擬地址轉為物理地址的過程中,CPU 還檢查了該虛擬地址的訪問許可權,用戶可訪問,還是僅內核可訪問。所有內核地址導向有效的物理地址,CPU 也就能夠訪問這些地址的內容了。當前許可權不允許訪問某地址,CPU 就會生成異常,用戶空間無法簡單地讀取到這類地址的內容。



不過亂序執行上線了,CPU 會在非法內存訪問和產生異常這個很短的時間窗口內,亂序執行後面的指令。在上面的彙編代碼中,第 4 行,RCX 寄存器裡面有個內核地址,這一行載入了該地址中的位元組值(密鑰,實際上這個操作是不被允許的,所以理應產生異常);然後把它放到 RAX 寄存器(這也是 x86 架構中的一個 64 位寄存器)的最低有效位上。


如我們前面科普的那樣,mov 指令被處理器讀取,編碼成 μOP,分配後發往 reorder buffer。在這個 buffer 中,RAX 和 RCX 這樣的架構級寄存器,會映射到實際上的物理寄存器。


由於亂序執行的存在,代碼第 5-7 行也已經解碼分配成了 μOP,隨後這些 μOP 被發往 unified reservation station(還記得文章前面提到的內容嗎?翻回去看看)。如果後面的執行單元此時被占,或者執行指令所需的某個操作數還沒準備好,μOP 就會在這裡等著。實際上,後面這幾行指令的 μOP 可能已經在 unified reservation station 裡面等著前面第 4 行的內核地址抵達了。這個時候,如前文所述,

由於 unified reservation station 通過共用數據匯流排監聽執行單元,所以這部分 μOP 不需要等第 4 行指令 retire 就可以開始執行運算了

!!!是不是感覺亂序執行頓時就高級了?

當 μOP 執行完成之後,他們按照順序 retire,執行結果會 commit 到架構狀態中。在 retire 階段,CPU 才會處理,前面指令執行過程中產生的異常和中斷。這個時候,第 4 行的 mov 指令顯然要被斃掉了(因為沒許可權訪問內核地址),異常此時才被處理,整條管線清空,消除亂序執行指令計算的所有結果。


但不要忘記,cache 裡面還有後面亂序執行算出來的貨呢,這些貨沒有清掉。



第二步:傳輸密鑰


如果 transient instruction 序列在上面的 mov 指令 retire 之前執行,那麼第一步就成功了。這裡生成異常(即 mov 指令 retire),和 transient instruction 之間有個競爭狀態,就是 transient instruction 必須要先執行,否則異常如果先生成了,後面指令的亂序執行就不會進行——所以攻擊中需要減少 transient instruction 序列的運行時間,這才能夠提升攻擊的成功率。接下來就該是把密鑰傳出去了。


說穿了,transient instruction 所做的事情就是把前面的密鑰放進 cache 裡面。那麼在傳出密鑰階段,攻擊者用 probe_array 開闢一部分內存(還記得前面的代碼嗎?),並且確保這部分內容沒有被 cache。在上面彙編代碼的第 5 行,是把從第一步中取得的密鑰,乘以 page size,假定 page 大小是 4KB(對應於高級語言 access(probe_array[data * 4096]))。


這個乘法是為了保證對數組的訪問,相隔距離足夠大,阻止處理器的 prefetcher 去預取相鄰內存位置。這樣的話內存佔用 256 x 4096(256 個 page)。代碼的第 7 行,是將乘法運算過後的密鑰,和 probe array 的地址相加,組成最後 convert channel 的目標地址。這個地址被讀取,用以對相應 line 進行 cache。


第三步:接收密鑰


這部分其實就是本文的第一部分,Meltdown 攻擊描述也基本採用了 FLUSH+RELOAD 攻擊方案。上面第二步中提到的 transient instruction 序列執行後,probe array 的一個 line 被 cache。probe array 的 cache line 位置就取決於第一步中讀取的密鑰。那麼攻擊者對 probe array 的所有 256 個 page 進行迭代,測量 page 的每個首行 cache line。包含已寫入到緩存的 cache line 的那個頁數(也就是哪個 cache line 寫入時間最短),也就直接表示密鑰的值了。



對 256 個 page 進行迭代,觀察讀取時間,僅有一個 cache 命中,這個命中的 page 就是 data 值啦!


如果上文兩部分無法看懂,則只需要搞清楚,惡意進程通過一些簡單的方式試出敏感數據是什麼——根據文章第一部分 FLUSH+RELOAD 的思路,再用編程的一些技巧就可以搞定。


通過對前面三個步驟進行重複操作,對所有不同的地址進行迭代,攻擊者是可以還原出整個內存樣貌的。研究人員利用這套方案,在 Intel 酷睿 i7-6700K 平台,外加 Ubuntu 16.10(Linux 內核 4.80) 操作系統中進行攻擊,能夠從內存中獲取及其向 web 伺服器發出請求的 HTTP header。另外,還能獲取 Firefox 56 瀏覽器的內存部分,並找出存儲在其內部密碼管理器中的密碼。



Firefox 56 存儲的密碼一覽無遺啊


Meltdown 的這套攻擊方案,利用的是漏洞 CVE-2017-5754。實際上,這種攻擊技術的提出並不是近期才有的,去年 7 月份就有人寫過一篇警告文《Negative Result: Reading Kernel Memory From User Mode》[10],不過此人當時未能完美復現 Meltdown 造成的問題,但他自己說似乎是「開啟了潘多拉魔盒」。


這是個實實在在的硬體級漏洞,屬於 CPU 微架構實施層面的漏洞。鑒於硬體產品的特殊性,讓 Intel 召回這 20 年的產品是不現實的。不過封堵這種程度的漏洞,最佳方案難道不是禁用亂序執行嗎?但這對當代 CPU 性能影響將會是災難性的。



硬體層面實際上還可以對許可權檢查和寄存器讀取,實施一個序列化過程——如果許可權檢查失敗,則不讀取該內存地址。但這麼做的代價也會非常大,每次內存讀取都要進行許可權檢查。更加現實的方案應該是從硬體層面實現對用戶空間和內核空間的隔離:設立分隔比特位,內核必須位於地址空間上部,用戶空間則必須位於地址空間下半部。這樣一來,內存讀取即便通過虛擬地址都能發現讀取目標是否違反安全邊界。


熔斷易擋,幽靈難防


硬體層面的修復對我們來說都是空談。操作系統層面,各主流操作系統這兩天都已經陸續推了針對 Meltdown 的修復補丁。主要是一種研究人員推薦叫做 KAISER 的方案,在 Linux 內核更新中這項特性叫 KPTI(內核頁表隔離)——其實現方法是,讓內核不要映射到用戶空間中。這種方案其實早兩年就有了,當時是為了杜絕攻破 KASLR 的邊信道攻擊提出的。



國外網友最早發布的 FS-Mark 測試,顯示在打完 KAISER 補丁前後,Coffee Lake 的 I/O 性能差別可超 50%——當然了,這應該只是個案[12]


不過巧合的是,它也能預防 Meltdown 攻擊,因為內核空間或物理內存在用戶空間中已經沒有有效的映射了。提出相對完整的解決方案的 paper 就是去年發布的,研究人員在 paper 中將這套方案稱為用於內核地址隔離非常「高效的實踐系統」[11],號稱對性能影響僅有 0.28%。文中提到,實驗中發現禁用全局比特位(也就是標記許可權的比特位)對於性能的影響竟然是微乎其微的,而且當代 CPU 對於 TLB(可以看做是內存頁的映射)的優化對性能影響並不會太大。有興趣的可以去研究下這套方案,雖然 0.28% 這個數字很值得商榷。


毫無疑問,KAISER 對 I/O 性能會造成一定影響,因為 KAISER 的這套方案設計了個 Shadow 地址空間頁結構,去掉了標記許可權的全局比特位,需要頻繁切換 CR3 寄存器,並清除非全局的 TLB 項。根據現在的測試,應用KAISER補丁之後可以觀察到高性能NVMe設備、網路互聯設備有明顯的性能下降。



Intel 方面已經確認,針對八代酷睿晶元,補丁會造成 6% 的性能下滑——只不過大部分計算機用戶並不會造成太大的影響,主要在日常使用方面並不會有明顯差異[13]。Intel 甚至還主動公布了跑分測試結果[14],就近兩代酷睿產品,使用涉及複雜 JavaScript 操作的 web 應用的用戶可能受到最大影響,補丁前後的最大差別可達 10% ,遊戲等圖形密集型工作負載或財務分析等計算密集型工作負載受到的影響最低。第六代 Skylake-S 平台性能影響可能稍高,SYSMark 2014 SE 測試總體差距在 8% 左右。


微軟方面也已經發布聲明[15],提到:





  • 針對 Skylake、Kaby Lake 及更新的 CPU,Windows 10 系統在性能方面僅有百分比個位數的下滑,而且大部分用戶基本是察覺不出來的;



  • 針對 Haswell 及更早的 CPU,Windows 10 則在性能方面會有「更為顯著的下滑」,「部分用戶會察覺到系統性能的下降」;



  • Windows 7/8 的情況更早,Haswell 及更老的 CPU 用戶,絕大部分都會感知到系統性能的下降。


同時微軟還提到,對於運行 Windows Server 的任意 CPU,尤其是對 I/O 操作比較敏感的服務任務,「作為緩解方案,如果在 Windows Server 實例中隔離不受信任的代碼,性能方面會有更為明顯的影響」。所以微軟建議伺服器用戶,在安全和性能之間做出更為權衡的選擇——這也是我們能夠看到的,為數不多微軟針對 IT 管理員給出這樣的說辭。


值得一提的是,

KAISER 本身也存在一定的局限性

,鑒於 x86 架構的設計,某些高許可權內存位置必須映射到用戶空間。這仍然留下了一小撮攻擊面,這些內存位置仍然可從用戶空間讀取,只不過這些內存位置並沒有什麼敏感信息——但善加利用,比如這些位置仍然可以包含指針,打破 KASLR 也就成為可能了。


另外,

針對 Spectre,目前並沒有真正行之有效的根治方案,包括 KAISER 對 Spectre 也是沒用的。原 paper 提供的一些緩解思路並不能從根本上杜絕 Spectre 攻擊。Meltdown 與 Spectre 官網在 Q&A 中表態說[16],修復 Spectre 並不容易,未來可能會困擾我們很長一段時間;補丁會讓攻擊變得更困難。


性能與安全的割裂是否將永遠持續?


Meltdown 和 Spectre 兩者幾乎影響到當代所有的電子設備,包括筆記本、筆記本、智能手機,以及對企業而言悲劇的雲伺服器。但凡操作系統(微軟、蘋果、谷歌、Linux)、晶元製造商(蘋果、三星、Intel、AMD 等)似乎都在慌忙地對這次事故做響應,尤其 Intel 公關,以為去年忙完 Zen 的滅火之後今年得交好運了,沒想到碰上這麼個事兒。但從微軟去年 11 月的動作來看,大約 Meltdown 的修復從來不是最近才開始的。



不過對個人設備的 Meltdown 和 Spectre 攻擊,

初始攻擊向量要麼是惡意程序,要麼是 web 端的惡意鏈接:這仍然是需要用戶去交互的,比如引誘用戶下載惡意程序,或者點擊某個惡意鏈接。

但在雲平台就不同了,The Verge 認為,這次漏洞波及最大的應該是雲計算,畢竟雲平台是個更大的威脅空間。一台雲伺服器上,就會有好些租戶,所以亞馬遜、谷歌、微軟、阿里也都全面卷了進來。如 paper 中對虛擬環境的測試那樣,meltdown 要針對同一台雲主機上的其他租戶竊取數據,也是完全可行的。大量中小型企業的基建現如今就在雲上,這樣一來隱患就顯得相當之大。主要的這幾家雲服務提供商也已經很快打上了補丁,谷歌發言人說,其雲伺服器現在已經不受 Meltdown 和 Spectre 影響,但沒有透露究竟是怎麼保護 Spectre 的[17]。


硬體層面的某一種優化就會帶來微架構元素的狀態改變,並危及安全軟體的實施——這其實是 20 年前的一個共識[18]。比如說出現一個漏洞,是由於硬體優化導致微架構狀態的變化,如果某種加密演算法沒有及時針對可能出現的泄露做防護,就會產生這樣的 BUG。不過 Meltdown 的出現顛覆了現狀,因為攻擊者甚至可以讀取每個比特位,而無視精度問題,這是任何演算法改進都無法做出防護的。就我們而言,唯一可以冠冕堂皇對企業做出的建議就是:把安全融入到開發中去。


信息安全行業現如今正在給企業、互聯網產品灌輸一個理念,即將安全融入到開發環節中去,而不是在開發結束後再檢測安全問題——雖然這可能只是安全行業希望賺到錢的忽悠。這其實是相當理想化的一種開發理念,開發和安全一直以來都有不可逾越的鴻溝,光是開發人員和安全人員兩者技能傾向性上的不同,而且開發和安全的職能是完全不一樣的,安全在很多管理者眼中除了燒錢並沒有什麼卵用。就好像 Intel 的工程師們大部分都並非安全專家,何況 AMD 最近給的壓力這麼大,再說這次的攻擊還是用了邊信道這麼賴皮的方法,任誰也很難想到。所以這種「融合」會不會發生需要打個巨大的問號。


但 MIT 的一份研究顯示,去年一年召回的植入式醫療 IoT 設備達到 150 萬台,都是因為安全問題,其中還包括心臟起搏器——這個數字聽來似乎並沒有很龐大。如果說現如今的某些硬體遭遇安全問題,比如這次的處理器漏洞,還能在操作系統層面加以彌補,那麼醫療 IoT 設備出現安全問題就沒有這種可行性了。而且這種程度的召回已經不僅限於企業的經濟損失,更關乎到用戶本身的生命健康,還要考慮召回的難度問題。隨著工業 5.0 時代的到來,未來不光是醫療設備,植入式設備、VR/AR 設備的出現可能會越來越常見,比如可能出現植入人體的娛樂設備、感測器,那麼當他們出現嚴重的安全問題時,我們又該何去何從?或許開發和安全的融合會是為數不多的一條思路,至少能夠緩解安全問題。


參考資料:


[1] Linux 設備 TCP 連接的有趣漏洞:傳說中的 Off-Path 劫持:


[2]Reading privileged memory with a side-channel:


[3]FLUSH+RELOAD: a High Resolution, Low Noise, L3 Cache Side-Channel Attack


[4]Spectre Research Paper


[5]Cache Template Attacks: Automating Attacks on Inclusive Last-Level Caches


[6]Meltdown Research Pape


[7]Retpoline: a software construct for preventing branch-target-injection


[8]Mitigations landing for new class of timing attack


[9]Site Isolation - The Chromium Projects


[10]Negative Result: Reading Kernel Memory From User Mode


[11]KASLR is Dead: Long Live KASLR


[12]Initial Benchmarks Of The Performance Impact Resulting From Linux』s x86 Security Changes


[13]Intel Offers Security Issue Update


[14]Blog Benchmark Table - Intel


[15]Understanding the performance impact of Spectre and Meltdown mitigations on Windows Systems


[16]Meltdown and Spectre - Vulnerabilities in modern computers leak passwords and sensitive data


[17]The CPU catastrophe will hit hardest in the cloud


[18]Timing Attacks on Implementations of Diffe- Hellman, RSA, DSS, and Other Systems


*本文作者:歐陽洋蔥,來源:愛活網,轉載請註明來源和 FreeBuf.COM。


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

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


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

TAG:FreeBuf |