谈谈幽灵和熔断两兄弟

Editor 2018-01-12 18:24

最近爆出的CPU漏洞引起业内外人士一片哗然,其影响之广,危害之大相信各位都已了解。我就谈一谈自己对这两个漏洞的理解吧。

先来补补基础知识(参考维基百科)

概念:

0x00 乱序执行

在计算机工程领域,乱序执行(错序执行,英语:out-of-order execution,简称OoOE或OOE)是一种应用在高性能微处理器中来利用指令周期以避免特定类型的延迟消耗的范式。在这种范式中,处理器在一个由输入数据可用性所决定的顺序中执行指令,而不是由程序的原始数据所决定。在这种方式下,可以避免因为获取下一条程序指令所引起的处理器等待,取而代之的处理下一条可以立即执行的指令。 

0x01 分支预测

分支预测器(英语:Branch predictor)是一种数字电路,在分支指令执行结束之前猜测哪一路分支将会被运行,以提高处理器的指令流水线的性能。使用分支预测器的目的,在于改善指令管线化的流程。现代使用指令管线化处理器的性能能够提高,分支预测器对于现今的指令流水线微处理器获得高性能是非常关键的技术。

0x02 数据缓存

CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

幽灵(spectre)

上边提到cpu在执行指令的时候会进行分支预测,简单来说,就是cpu在执行一个逻辑判断的时候会进行提前预测改走哪个逻辑,然后选择其中它正确的逻辑,并将其所需要的数据加入缓存中。

这样,我们看一个例子。

一般来说,如果data[untrusted_opffset_from_caller]越界,程序就会失败。但是cpu有一个预测执行的特性,如果arr1->length不在缓存之中,并且之前这个逻辑多次进入的情况下,cpu就判断if语句是真的,然后先将下一步指令所需要的内存存储到缓存之中(此时不对访问数据是否允许进行判断)。假如发现预测错误,然后回滚指令就可以了。但是这时,cpu缓存并没有清空,而spectre正是利用了这一点来获取非法内存的内容的。

1)spectre构造一个训练集,使cpu不断地进入以下的逻辑。此时这是一个很正常的逻辑,x为一个小于array1_size的值。多次训练之后,cpu自作聪明的认为,哦,这就是一个正常的逻辑,那我为了加快执行速度就在判断的时候我将下条指令的所需的内存填入缓存吧。但是,此时CPU不对这个内存的地址是否合法进行判断!只有在执行到它的时候才判断对错,并且,上层预测执行如果预测失败,cpu会回滚指令,但是不去清除这里的缓存。

if (x < array1_size)

{

  temp &= array2[array1[x] * 512];

}

2)突然,攻击者传入了一个非法的x值,x值是目标地址相当于array1的偏移(注意,在这之前攻击者已经清除掉array1_size的缓存,cpu并不知道array1_size的大小)。

但是cpu还是傻傻的认为我可以进入if逻辑,那我就先将大菜准备好,等待客人到来吧。此时,cpu不知道自己已经陷入了攻击者的陷阱了(这时看来,spectre是精心构造了一个结构,其中,array1是一个普通的数组,而array2就比较神奇了,array2[256

* 512]是一个256 * 512的二维数组。为什么是256 * 512呢?这是因为单个字节最大为255。

我们来看spectre构造的这个结构,x为目标地址到array1的偏移,这样当这条指令被cpu解析装入缓存之后,array1[x]处便是我们要访问的地址内容,因为是一个字节,肯定小于256。这样array2[array1[x]*512]就是一个正常的数组)。

欸,cpu忽然发现这个时候x不再小于array1_size了,那我就不用等客人了,先跑去其他地方玩了,但是,它却忘了将准备好的菜倒掉(嘻嘻,这个cpu和大部分人一样懒呢,都不知道马上洗盘子洗碗)。

3)重头戏来了,我这时就要偷偷的从盘子中取菜了。这时,array2[目标内容*512]已经在缓存之中。因为读取缓存和读取内存所需的时间是不相同的,我们只需要去循环读取array2的内容,判断读取的时间就可以推测出目标内存的内容。

/* Time reads. Order is lightly mixed up to prevent stride prediction */

for (i = 0; i < 256; i++)

{

  mix_i = ((i * 167) + 13) & 255;

  addr = &array2[mix_i * 512];

  time1 = __rdtscp(&junk); /* READ TIMER */

  junk = *addr; /* MEMORY ACCESS TO TIME */

  time2 = __rdtscp(&junk) - time1; /* READ TIMER & COMPUTE ELAPSED TIME */

  if (time2 <= CACHE_HIT_THRESHOLD && mix_i != array1[tries % array1_size])

      results[mix_i]++; /* cache hit - add +1 to score for this value */

}

熔断(meltdown)

Meltdown利用的原理和spectre相似,只不过实现方式不同。

来看一个简化版的meltdown

mov rax byte[x]            // x为目标地址,非法操作

shl rax 0xC                // rax * 4096, page alignment

mov rbx qword [rbx + rax]  // [rbx] 为用户空间的一个array,合法操作

本身,cpu看到第一条指令非法之后,会将rax的值清零。一般cpu为了更好的性能,会将2,3条指令部分执行,直到rax,rbx被清零。但是关键在于第三条指令,如果rbx+rax不在缓存中,则会首先将目标内存加载到缓存之中。这时就和spectre的处理方式相似了,不断地访问用户空间的数组,通过访问时间来推出目标内容。

相关链接:

https://meltdownattack.com/

https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html

http://www.freebuf.com/vuls/159269.html

https://zhuanlan.zhihu.com/p/32757727

https://github.com/Eugnis/spectre-attack

https://github.com/feruxmax/meltdown



本文由看雪论坛 ixiaohuo 原创转载请注明来自看雪社区


最新评论 (0)
登录后即可评论
返回