作者:白小龍,蒸米

0x00 序

1月4號的早上,突然就被Meltdown和Spectre這兩個芯片漏洞刷屏了,但基本上都是一些新聞報道,對漏洞的分析和利用的信息基本為0。作為安全研究者,不能只浮在表面,還是要深入了解一下漏洞才行,于是開始研究這方面的資料。

研究后,發現其實這個硬件漏洞的影響非常廣,不光是Intel, ARM和AMD也受影響,因此基本上所有的操作系統(Windows,macOS,Linux,iOS, Android等)都有被攻擊的風險。雖然蘋果已經發布了公告說全系產品(包括使用ARM的iOS設備)都會受到影響[1],不過發現者聲稱還沒有看到針對AMD和ARM的攻擊。大家猜測原因是AMD和ARM對預測執行實現的效率略差,導致很難通過時間差來進行側信道攻擊(真是傻人有傻福,有時候笨一點反而能躲過一劫,哈哈)。

另外,根據我們分析,在操作系統層面只能夠修復Meltdown攻擊(在用戶態調用syscall的時候對頁表的進行限制),但因為Spectre是跑在內核態的,所以除了硬件上升級,暫時沒法修復。那么這篇文章就來講一下什么是Meltdown攻擊,以及實戰一下利用Meltdown獲取Linux內核的數據。

0x01 漏洞分析

Meltdown攻擊的雛形其實在2017年7月就已經出現了,由Anders Fogh發表于cyber.wtf [5]。直到最近爆出完整的利用攻擊,一石激起千層浪。該攻擊之所以被稱之為Meltdown(熔毀),其主要原因在于它可以破壞用戶態應用程序和內核態操作系統之間最根本的防護隔離措施,使得攻擊者在用戶態就可以任意讀取系統內核中的數據,造成內核信息泄露。這種攻擊方式借助于硬件特性,無視現代操作系統KASLR等保護機制,甚至無視SMAP、SMEP、NX和PXN等CPU硬件保護機制,能夠像熔漿一樣將用戶態和內核態之間的隔離墻熔掉,故而得名。

如下所示,Meltdown攻擊代碼的基本形式非常簡單。需要注意的是,與Spectre攻擊不同,Meltdown攻擊的代碼只需要在用戶態運行即可,不需要任何特權,也不需要在內核里執行任何代碼。

這段代碼初看上去不會產生什么問題。因為在第一句的時候,用戶態應用程序就會因訪問內核態地址空間([rcx])而產生異常,使其既不能讀取到rcx所指向的內核態地址空間,后面兩條指令也不能得到執行。

但實際上,現代CPU處理器的特性(例如Intel的microarchitectures)使得這段代碼的執行所產生的影響遠超我們的想象,尤其是亂序執行(out-of-order execution)和推測執行(Speculative execution)這兩個特性。簡單說來,在亂序執行和推測執行當中,指令的執行先后順序可能被打亂,一條指令的后續指令可以在該指令沒有完全產生效果之前就被執行。值得一提的是,現代CPU處理器所使用的亂序執行和推測執行算法思想在1967年就由Tomasulo[2]提出了。

而在上述Meltdown攻擊代碼當中,第一條MOV指令除了將rcx內核態地址內的數據放到al之中外,還會進行權限檢查,檢查進程是否有權限訪問該地址。但是這種權限檢查是一個相對耗費資源的操作,而亂序執行和推測執行使得CPU在al取到[rcx]內核數據后不會等待權限檢查結束就繼續執行第二條和第三條指令。在第二條指令計算了rax=al*4096之后,第三條指令會以al*4096作為索引來獲取rbx用戶態數組中的某一項數據。在第三條指令執行時,rbx[al*4096]會被加載到cache當中。

理論上講,第一條指令的權限檢查最終會失敗而產生異常,導致寄存器狀態回滾到第一條指令,使得第二、三條指令即使被亂序執行了也不會真正產生效果(即改變寄存器數據)。但實際上,正如前面所述的,第三條指令的執行使得用戶態數組中的rbx[al*4096]被加載到了cache當中,影響了cache的狀態,這就給了攻擊者一個可以利用CPU cache隱通道(covert channel)進行信息竊取的途徑。

所謂的CPU cache隱通道是指攻擊者可以利用CPU加載某塊地址空間的時間來推斷這塊內存最近是否被加載過,進而推測更多的數據。例如在上述攻擊中,如果al的值是2,則rbx[2*4096]會被加載到cache當中。當攻擊者以n(0<=n<=255)遍歷加載rbx[n*4096]時,由于rbx[2*4096]被cache過了,則加載的時間會遠小于加載其他rbx[n*4096]。由此可以推斷出al是2,亦即最開始rcx所指向內核數據(byte [rcx])是2。

整個攻擊流程如下圖所示。經測試[4],Meltdown中所使用到的這種隱通道其讀取速率可以達到503KB/s。

除了亂序執行和推測執行,Meltdown攻擊使攻擊者在用戶態即可讀取內核態數據的另外一個先決條件在于,在現代操作系統中,用戶態應用程序和內核共享著相同的虛擬地址空間。因此,已有的針對Meltdown的防御手段(例如Linux的KPTI/KAISER[4])將用戶態和內核態的映射分開,使用戶態的虛擬地址空間中沒有內核內存的映射。

0x02 芯片漏洞實戰之利用Meltdown獲取Linux內核數據

這個POC是今天早上在github上公布的[6]。POC可以在用戶態獲取到linux_proc_banner這個內核地址上的數據。不過,因為KASLR的原因,這個地址是不確定的,這里POC cheat了一下,用“sudo cat /proc/kallsyms”獲取到了linux_proc_banner在內核內存中的位置。其實用我們上一篇講到的破解KASLR的方法,也可以猜出來kslide。

接下來POC會去打開/proc/version這個文件。/proc/version并不是一個文本文件,如果我們的程序嘗試去讀這個文件,內核會從linux_proc_banner這個地址動態地讀取系統的信息并返回給程序。這就意味著這個地址上的數據會被加載到cache里。

因此POC會打開/proc/version這個文件,并每次在進行Meltdown攻擊前,都調用pread()一次,保證內核會將這個地址上的數據讀到cache中。接下來POC就開始嘗試讀取linux_proc_banner這個內核地址上的數據了。因為一個地址的數據是由8個bit組成的,POC里每次都是只猜一個bit,因此一個地址要猜8次才行。

猜數據的攻擊也就是Meltdown攻擊。首先POC將用戶態target_array的偏移地址傳給rbx。然后進行一段rept循環操作,保證數據已經讀入cache。接著,將addr上的數據讀到al中,隨后根據bit的值(也就是第幾位)對rax進行位移和AND 1操作,從而得到目標地址上第bit位的值(0或1)。最后根據是rax的值0還是1,對target_array進行寫入操作。雖然在實際情況下,執行到"movb (%[addr]), %%al\n\t"這一行的時候,CPU就會報出異常了,但因為推測執行的原因,隨后的代碼已經被執行了,雖然推測執行的結果會回滾,但是對cache的操作卻是不可逆的。

因為讀cache的速度要比讀內存快很多,因此我們可以通過訪問target_array里數據的速度來判斷哪個數據被放到了cache里,從而就能知道目標地址上某一位的數據是什么了。

那么程序最終的執行結果如下,POC成功的讀到了內核在linux_proc_banner地址上的數據:

0x03 總結

本篇文章分析了Meltdown漏洞的原理,并介紹了利用Meltdown獲取Linux內核的數據方法。我們隨后還會有更多關于芯片漏洞的分析和利用實戰,歡迎繼續關注本系列的文章,謝謝。

參考文獻:

[1] https://support.apple.com/en-us/HT208394

[2] TOMASULO, R. M. An efficient algorithm for exploiting multi- ple arithmetic units. IBM Journal of research and Development 11, 1 (1967), 25_33.

[3] LIPP, M., SCHWARZ, M., GRUSS, D., PRESCHER, T., HAAS, W., MANGARD, S., KOCHER, P., GENKIN, D., YAROM, Y., AND HAMBURG, M. Meltdown. Unpublished, 2018.

[4] https://lwn.net/Articles/738975/

[5] https://cyber.wtf/2017/07/28/negative-result-reading-kernel-memory-from-user-mode/

[6] https://github.com/paboldin/meltdown-exploit/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/499/