Meltdown Spectre

This is a community article written by Kelvin Chan, kernel researcher from the VXRL Community. Originally posted in Windows kernel and software security.

引言

現今操作系統(OS)設計一般分為應用層 (Ring 3) 與 內核層(Ring 0) ,應用層屬於普通應用程式級別,而內核層屬於OS的代碼,Intel CPU Spectre漏洞產生後,有大量的非技術性文章,但Google Project Zero的文章講得比較艱深,因此筆者在這做一個比較簡單的解釋與定義。

理論背景

Out-of-Order Execute(OoOE) - 非順序執行 即表示正常匯編語言 (Assembly Language) 不按正常的順序執行,這是因為處理器中的各個運算單元實際上是可以異步工作的,不需要像8086等老CPU,同步執行指令。常見的如Cache Load/Store Unit,ALU 等等。

Indirect Branch Prediction (分支預測) - CPU 在執行過程中,遇上了分支的話,會先進行分支預測,如常見的 if 則是分支指令之一,而他對應的匯編指令一般不會等到比較后才執行,而 CPU 發現比較需要更久的時間的話,那就會把 if 中的內容優先執行,而執行的內容不一定會影響到結果,但是執行的內容使用到的緩存則不會被修改 (L1/2 DCache)。

詳見: The Intel Optimization Reference Manual section 2.3.2.3 (“Branch Prediction”):

Spectre 漏洞的產生就是基於以上兩個處理器優化機制而出現的

見以下代碼,處理器在執行過程中,假如 arr1-length 不在 cache 中,CPU 則不會等到條件判斷完成才執行 if{..} 中的內容,如果條件不成立,才會退回對寄存器的影響,但是 arr1->data 則會一直在 L1 DCache 中

struct array {
  unsigned long length;
  unsigned char data[];
};
struct array *arr1 = ...;
unsigned long untrusted_offset_from_caller = ...;

if (untrusted_offset_from_caller < arr1->length) {
  unsigned char value = arr1->data[untrusted_offset_from_caller];   ...
}

把代碼擴展到如下,假如其中 arr1->lengtharr2->data 均不在 cache 中,則會觸發 CPU 優先把 IF 中的內容執行,看以下代碼可以發現 arr2->data[index2] 其中 index2 依賴 value,而 value 亦來自 arr1->data[untrusted_offset_from_caller] ,而這個會被加載到緩存裡,這段代碼執行以後,if 是沒有成功進入,但代碼卻是被 CPU 被執行了,而且結果也在 cache 了。

由於載入 cache 後,訪問內存的速度會變快,只要我們給出一個臨界值,便可以判斷出一個內存當前狀態是否存在於 cache,假如 arr2->data[0x200] 在 cache 那麼他時間一定比 arr->data[0x300] 少很多 (一倍以上)。

當我發們哪個更少的話,就能夠反向說明 arr1->data[untrusted_offset_from_caller] 中的值是1還是0。

把例子單位細化到 bit 我們可以知道一個字節每1個 bit 中的值是多少,只要遍歷8次就能知道一個字節,遍歷32次就能知道一個ULONG。

struct array {
  unsigned long length;
  unsigned char data[];
};
struct array *arr1 = ...; /* small array */
struct array *arr2 = ...; /* array of size 0x400 */
/* >0x400 (OUT OF BOUNDS!) */
unsigned long untrusted_offset_from_caller = ...;
if (untrusted_offset_from_caller < arr1->length) {
  unsigned char value = arr1->data[untrusted_offset_from_caller];
  unsigned long index2 = ((value&1)*0x100)+0x200;
  if (index2 < arr2->length) {
    unsigned char value2 = arr2->data[index2];
  }
}

假設我們需要知道 arr1->data[untrusted_offset_from_caller] 中的值,但沒辨法進入該 if,整個過程我們可以通過時間間隔逆向反推而得知 arr1->data 中裡的值,最核心原因也是因為 cache 的時間差比較大,通過估量 arr2->data[index2] 的內存訪問時間間隔得知哪一個才是真的,從而得知 arr1->data[untrusted_offset_from_caller] 是1或0。

由於是預取內存,沒有特權級的限制。

漏洞構造總結:

  1. 建立分支,使分支預測執行
  2. 在分支裡把需要知道的地址讀取一次(內核地址也可以,例子中是 arr1->data[..]
  3. 在分支裡把該值 & 1 ,然后當成數組的索引訪問 arr2->data 一次
  4. 測試 arr2->data[index2] 的執行時間
  5. 較小的那個 index2 反向推算后發現 value 為1或0
  6. 得知目標內存中第一個 bit 的值,反覆執行能知道整個內核中的所有值

About the Author

I’m Kelvin, currently working in Tencent Game Security Researcher and developement department, a kernel mode driver developer, and a experienced game (almost 10 years) security researcher, mainly responsible for providing a kernel-based solution for anti-game cheating, such as, game’s speed hack, keyboard/ mouse emulation, game data accessed,etc and I’m a reverse engieer also, responsible for cheat sampling and unpacking, I’m also familiar with kernel mode debugging and Virtualization-based security solution, such as, Memory Hiding.

Furthermore, I’m a kHypervisor developer, which is a open-source the only project which is provided a nested-virtualization feature for Windows x64 platform. which will be a virtualization-based security solution.for giving a whole control and monitor of system and hardware access.


Did you enjoy this post? Want to find out more about us? Contact us

Anthony Lai