大家好,我是ConardLi,之前给大家分析过浏览器的攻略:《??HTTP 缓存别再乱用了!推荐一个缓存设置的最佳姿势!??》很多同学看了这篇文章都表示一头雾水。存在一个漏洞:Spectre漏洞。这个漏洞到底有什么魔力,让浏览器频繁的为它更新策略呢?今天我就给大家解释一下。如果Spectre中的一个漏洞很难构造,即使它能造成很大的危害,也未必会引起浏览器那么大的关注。那么我们今天的主角幽灵很容易构造,而且危害很大。使用Spectre,你可以:几行JavaScript,你可以读取你电脑/手机上的所有数据,浏览器中的网页可以读取你所有的密码,并且知道其他程序在做什么,这甚至都不知道要求你写出来的程序是易受攻击的,因为它是计算机硬件级别的漏洞。要理解Spectre,我们需要以下三个方面的知识:理解什么是旁道攻击,理解内存是如何工作的,理解计算机预测执行,其实是一些很基础的计算机知识,你可能在学校里学过。那么Spectre就巧妙的利用了以上三个原理,下面我们一一了解一下。您完全不必担心。我将以最简单的方式向您解释。先把这些知识拆解一下,最后拼起来,其实还是很容易理解的。内存的工作原理首先,我们的电脑是由很多部分组成的:存储:内存、硬盘等CPU输入输出设备:键盘、鼠标等我们的电脑运行时,程序是从存储设备中加载的进入CPU,CPU负责处理需要多次从内存中读取数据的繁重计算。然后将结果输出到我们的显示器等输出设备,这大概就是一个简单的计算机工作原理。接下来,让我们关注CPU和内存。内存中存放着很多你正在运行的程序,包括系统、用户数据等,同时也存放着CPU运行的中间结果。要存储如此多的信息,就需要一种标准化的存储方式。我们可以把内存想象成一堆排列的小内存块,每个内存块存储一位信息。另外,内存有很多层。CPU读取一个数据在里面是很慢的,所以我们在CPU和内存中建了好几级缓存。当我们拿到一个缓存的数据时,速度会更快。.那么当访问一个没有被缓存的数据时,该数据会在缓存内存中创建一个副本,下次访问时会非常快。这就是内存的一般工作原理。当然,这个过程简化了很多。我们这里只需要了解一下。侧信道攻击那么什么是侧信道(side-channel)呢?我们可以简单这样理解:如果在你的程序的正常通信通道之外还有另外一个特征,这些特征反映了你不想产生的信息,如果这个信息被别人获取了,你就会泄露。这种边缘特征产生的信息通道称为旁路。比如你的记忆在进行计算的时候,会产生一个电波,反映出记忆中的内容,有人用特定的方法收集这个电波,就形成了旁路。基于侧信道的攻击称为侧信道攻击。常见的绕过包括:延迟、异常、能耗、电磁、噪声、可见光、错误信息、频率等。反正你的操作总是有边缘特征的,一不小心,这个边缘特征就会成为泄漏的机会。下面举一个基于延迟的侧信道攻击的例子:假设我们想让计算机验证密码,比如我们的密码是ConardLi。我们从攻击者的角度来猜测一下,密码是多少?我们从一个字母来猜:密码是A,电脑告诉我:不是!密码是B,电脑告诉我:不行!1ms后密码为C。1.1ms后电脑告诉我:不行!你有没有发现任何问题?我们猜对了第一个字母,但是计算机告诉我们密码错误的时间增加了0.1ms?因为这一次,计算机发现第一个数字匹配后,需要验证第二个比特是否匹配,所以会多花一点时间。是不是很聪明!我们可以用同样的方法继续验证Ca,Cb,……Co,最终猜出我们的密码。这时候我们的猜测时间和密码的长度是线性关系,我们可以在O(n)的时间复杂度内猜出密码。如果直接爆破的话,至少需要计算52的8次方!这是旁路攻击,这该死的魅惑!CPU的预测执行正如我们上面提到的,当CPU运行时,它会频繁地从内存中调用信息。但是读内存很慢,CPU为此闲置了很长时间,只是在等待内存中的数据。这显然不是一个很好的解决方案。那么,人们会想,是不是CPU可以推测出需要执行的命令呢?假设我们有如下代码,根据内存中的某个数据判断执行不同的语句:if(Menory===0){//进行第一步计算//进行第二步计算//做第三步计算}这里有两种可能,Menory为0或者不为0。这时候CPU就会预测到什么时候等待内存数据了。假设读内存返回0,CPU不用等内存返回就可以跑路:跳过if判断,直接执行里面的计算命令。那么如果内存真的返回了0,说明CPU已经成功提前运行,CPU可以继续执行后面的命令。但是如果内存不返回0,CPU就会回滚上一次执行的结果。所以CPU执行需要非常小心,不要直接覆盖寄存器的值,这样才能真正改变程序的状态,一旦发现预测失败,立即回滚改变。攻击原理。我们已经掌握了该漏洞利用的所有因素。现在让我们来看看发生了什么。比方说下面是我们的缓存,读取它很慢。系统内核将其屏蔽,分配给不同的程序,如果考虑云计算,可能会分配给不同的虚拟机。不同程序可能分配的内存块是相邻的。我们继续沿用上一篇文章《??HTTP 缓存别再乱用了!推荐一个缓存设置的最佳姿势!??》的例子:红色的内存块存放的是我们受害者的数据,比如受害者的密码:操作系统会尽量保证一个程序不能访问属于其他程序的内存块,不同程序的内存块是隔离的。因此,其他程序无法直接读取“受害者”(红色区域)的数据:如果我们试图直接访问红色区域,肯定是读取不到的,但缓存中可能已经有一些数据了。接下来,我们可以尝试使用缓存来为它做点什么。我们在紫色的内存块中放了一个数组A。这块内存属于我们的程序,可以合法访问,但是很小,只有两位。但是我们并不满足于读取数组A中的两个元素,我们尝试越过A的范围(下标越界),访问A数组的第X个位置。但是X可能远远超出A数组的长度。通常情况下,CPU会阻塞这个操作,抛出一个错误:“非法操作”,然后强制结束这个操作,但是我们可以试着再观察一下这个过程,看看它是怎么做到的。我们在允许访问的内存范围内新建一个区域,可以称之为工具箱。我们特别要求CPU不要将这段数据复制到缓存中,而只保留在内存中,内存是一块连续的内存区域。假设我们执行的指令长这样,首先有一个if判断语句:if(name==='codesecretgarden'){//...}一般来说CPU执行会先忽略这个判断,因为需要等待内存返回的name的值是否等于codesecretgarden?因为有预测执行这样的技术,if语句里面的东西会被提前执行。if(name==='codesecretgarden'){accessTools[A[x]]}我们尝试读取Tools的第(XthelementofA)th元素。假设我们读到的victimmemory中有3:这是我们不应该读的,但是我们可以通过推测执行来做如下:CPU执行完不应该执行的命令后,CPU认为需要看A的值[X]。此时CPU不检查A[X]的下标是否越界,因为CPU认为内核会一直验证下标是否越界,如果越界,程序将被强制终止。所以预测执行直接查询A[X]的值,然后发现A[X]=3,即:Tools[A[x]]=Tools[3],这是Tools中存储的第四个值在我们的实际记忆中。有一个元素a,重点来了:CPU访问到a后,将a(也就是Tools[3])放入缓存!最后一步是遍历Tool中的每一个元素,我们发现访问前几个元素有点慢,直到访问第三个突然变快了!因为第三个元素a在缓存中存储了一份!当推测执行发现错误时,会回滚寄存器的变化,但不会回滚高速Cache!信息因此泄露,因为访问第三个元素比其他元素花费的时间更少!这是一种基于时间的旁路。所以,我们知道“受害者”在内存中的这个位置有一个3。后面我们可以把Tools区域做大一些,其他的数据你可以猜的更多!当然,这是在实际攻击中需要考虑的。上面已经分析了对Web的影响。显然,这种攻击实际上很容易使用JavaScript实现,几乎所有边界检查都可以绕过,允许任意内存边界读取。我们可以看下面这段代码:if(index
