Spectre

幽灵攻击:利用“推测执行”

摘要

当代处理器使用分支预测和推测执行来提高性能。例如,如果某个分支的目标取决于将要读取的某个内存中的值,处理器会猜测这个值,尝试提前执行运算。内存中的实际值达到以后,处理器再决定是直接提交推测后执行的结果,还是把这个结果丢弃。推测逻辑,不为执行方式负责,因此可以被利用,来访问受害者的内存和寄存器,执行一些具有可掌控的副作用的操作。

幽灵攻击,就是诱导受害者的处理器,推测性地执行正确的程序执行期间不会执行的操作,通过侧信道将受害者的机密信息泄露给攻击者。

本文描述的实际攻击,结合了侧信道攻击、故障攻击和面向返回编程的攻击方法,从受害者的进程中读取任意内存。

该论文表明,推测执行,严重违反了众多软件安全体制的安全假设,比如操作系统进程分离、容器化、即时编程、针对缓存---计时攻击的对策。

合理的解决方案:

Intro

关于侧信道攻击,进行一些介绍。

关于处理器的推测执行,进行一些介绍。

A. 本论文的成果

transient 指令:瞬态指令,暂时执行,但之后会被恢复,也就是根据实际结果再处理。

分析了一下“推测执行”的安全性,提出一种“幽灵攻击”。通过影响推测执行的那些transient指令,攻击者能够从受害者的内存空间泄露信息。作者通过下面的两个例子,对幽灵攻击的危害,进行描述。

使用本地非特权代码,攻击进程间的隔离边界

POC中,作者准备了一个victim程序,包含一个secret数据在它的内存地址空间内。然后,在victim程序编译后的二进制序列和引用的共享库中,寻找一些满足特殊条件的指令。作者准备一个attacker程序,利用处理器“预测执行”的特征,将前面找到的特殊指令作为transient指令执行。具体如何利用这些特殊指令,实现对victim的非法内存访问,之后会讲解。

使用JavaScript和eBPF来攻击沙箱技术

通过可安装的JavaScript代码可以实现幽灵攻击。

实验证明,我们提供的JavaScript程序能够读取运行它的浏览器进程地址空间的数据。同时,attack能够充分利用Linux的eBPF和JIT。

B. 具体技术

为了发起 Spectre 攻击,

  1. 攻击者首先在进程地址空间中定位或引入一系列指令,这些指令在执行时充当秘密通道发送器,泄漏受害者的内存或寄存器内容。

  2. 然后,攻击者诱使 CPU 推测性地错误执行该指令序列,从而通过隐蔽通道泄露受害者的信息。

  3. 最后,攻击者通过隐蔽通道接收到受害者的信息。

虽然由于这种错误的推测性执行而导致的标称 CPU 状态的更改最终会被恢复,但之前泄露的信息或对 CPU 的其他微体系结构状态(例如缓存内容)的更改可以在标称状态恢复后幸存下来。

上面是,一些general的描述,具体来说,我们需要找到一种诱导错误推测执行的方法、一种微体系结构下的隐蔽通道。

关于隐蔽通道:

本文使用的是基于缓存的方式,Flush+Reload 和 Evict+Reload。

关于诱导错误执行的方法

在这里介绍两种:

v1: 利用条件分支

在这种幽灵攻击中,攻击者对CPU的分支预测单元进行误导训练,使得它错误预测分支的方向,从而执行原本不会执行的代码,导致CPU暂时违背程序的原始语义。在之后的展示中,幽灵攻击使得攻击者能够读取存储在程序地址空间中的秘密信息。

1
2
if (x < array1_size)
    y = array2[array1[x] * 4096];

攻击者提供x的值,这里有一个 if 语句,判定 x 是否在数组array1的合法下标范围内。下面展示,攻击者怎么绕过这个 if 语句。

首先,攻击者使用合法的x值,调用该指令,一次又一次地训练CPU的分支预测单元,让它倾向于认为这个 if 分支始终为真。

然后,攻击者用一个超出合理范围的 x,调用该指令,CPU的分支预测单元猜测它为真,根据 x 获取 array1[x]的值,然后访问array2的对应位置的数据,将其保存在cache。

边界检查的真正结果出来之后,CPU发现预测结果出错,将CPU的微架构状态回退到之前的状态,却不会进行cache的清除。

那么,attacker就可以分析cache,得到 array1[x] 的值。

v2: 利用间接分支

基于ROP,攻击者选取victim地址空间内的一个gadget,影响victim去推测性地执行这个gadget。与纯粹的ROP不同,攻击者不依赖victim程序中的软件漏洞,而是对Branch Target Buffer(BTB)进行误导训练,使其对某一个间接分支指令错误地预测为gadget的地址,使得victim程序执行gadget。

然后,CPU发现预测结果出错,将CPU的微架构状态回退到之前的状态,却不会进行cache的清除。那么,gadget就可以通过缓存侧信道泄露信息。

除此以外,我们会展示如何通过精确的gadget选择,读取victim进程的任意内存。

关于如何误导训练BTB

当然,还有一些其他方法。

C. 目标硬件和漏洞的现状

目标硬件,范围很广

目前已经在下列 intel 处理器上验证了幽灵攻击:Ivy Bridge、Haswell、Broadwell、Skylake、Kaby Lake

以及AMD Ryzen 系列处理器,可以实现幽灵攻击

关于ARM架构的Samsung处理器和Qualcomm处理器,也存在。

目前幽灵攻击涉及的漏洞编号为:CVE-2017-5753 和 CVE-2017-5715

D. Meltdown

Meltdown是基于 out-of-order 执行的微架构攻击,可以用案例泄露 kernel 内存。

Meltdown 和 Spectre 有两个主要的不同:

  1. Meltdown没有利用“分支预测”,而是一个 observation:当一条指令造成 trap时,接下来的指令会乱序执行,而不是立即停止整个程序。
  2. Meltdown利用许多Intel处理器和部分ARM处理器的一个特定的弱点,它们推测性地执行指令,从而绕过内存保护。

Spectre 攻击适用于范围更广的处理器,包括大多数 AMD 和 ARM 处理器。此外,KAISER 机制已被广泛应用于缓解 Meltdown 攻击,但不能防止 Spectre

背景

A. 乱序执行

对于当前指令后,那些不需要依赖前面运算结果的指令,会先被执行,然后保存运算结果到某个寄存器,或缓存。

B. 推测执行

乱序执行时,如果某个指令取决于尚未完成的前面的指令,处理器会先保存当前的寄存器状态,然后推测该指令的结果,按照推测结果进行指令运算。

C. 分支预测

现代处理有很多针对直接分支、间接分支的预测机制。其中,间接分支指令,能够跳转到任意的目标地址,比如,x86指令能够跳转到寄存器、内存地址、栈。

相比于直接分支,间接跳转和调用采用了两种不同的预测机制来优化。

Intel处理器对以下三种情况进行预测:

  1. 直接调用和跳转。
  2. 间接调用和跳转。
  3. 条件分支。

D.内存的层次结构

E.微架构侧信道攻击

Flush+Reload 和 Evict+Reload

攻击者首先从与受害者共享的缓存中逐出缓存行。

在受害者执行一段时间后,攻击者测量在与被逐出的缓存行对应的地址处执行内存读取所需的时间。

如果受害者访问被监控的缓存行,数据会在缓存中,访问速度很快。否则,如果受害者没有访问该行,则读取会很慢。

因此,通过测量访问时间,攻击者可以了解受害者是否在逐出和探测步骤之间访问了受监控的缓存行。

F. 面向“返回”编程---ROP

攻击概述

  1. 设置阶段,攻击者执行一些误导处理器的操作,以便它啥后做出可利用的错误预测。

  2. 攻击

  3. 恢复敏感数据。

v1: 利用条件分支的预测失误

In this section, we demonstrate how conditional branch misprediction can be exploited by an attacker to read arbitrary memory from another context, e.g., another process.

C语言示例,在https://gist.github.com/anonymous/99a72c9c1003f8ae0707b4927ec1bd8a

x 是攻击者控制的,外界传入的(比如另一个进程)

1
2
if (x < array1_size)
    y = array2[array1[x] * 4096];

如果没有第1行的边界检查,随意构造的 x 可能引发异常;也可能泄露特定信息,比如构造 x = (secret的地址) - (array1数组的基地址)

因为,边界检查与推测执行的四种结果。所以,为了效率,处理器采用推测执行;同时也带来了漏洞。

真正边界检查结果出来以前,处理器会推测性地执行之后的指令。

边界检查的结果可能无法立即获知的原因有很多,比如:

  1. 之前的缓存,未命中
  2. 边界检查期间,必需的执行单元拥塞
  3. 复杂的算术依赖
  4. 嵌套的预测执行

然后,代码2 可能先尝试性地执行

A. 实验结果

B. C代码实现

C. JavaScript实现

D. 利用eBPF的实现

E. 恢复数据的准确度

v2: 对间接分支下毒

本节主要展示,间接分支是如何被攻击者下毒的,间接分支的错误预测结果是如何被另一个 context(比如另一个进程)利用来读取任意内存的。

在几乎所有架构的程序中,间接分支都广泛存在。如果间接分支的目标地址没有及时取得(比如因为缓存未命中),处理器会根据之前执行的代码,推测一个位置,继续执行程序。

变种2里,攻击者使用一个恶意目标地址,错误训练分支预测器,使得程序在一个攻击者选定的位置继续预测执行。如图2所示,分支预测器在一个context被错误训练,并基于此在另一个context进行预测。具体来说,攻击者能够误导程序在合法执行以外的位置预测执行。预测执行会留下一些可以度量的副产物,这对于攻击者是很有利的,本节介绍的就是一种不同于变种1的内存泄漏方法。

这里,我们假定攻击者控制了涉及间接分支的两个寄存器,目的是读取 victim 的内存。这个条件在真实的二进制程序中很容易满足。

其它变种

缓解方案

避免推测执行

避免机密数据的访问

避免数据进入隐蔽信道

限制从隐蔽信道提取的数据

避免分支“中毒”

结论

附录A:对Intel Haswell分支预测结构的逆向工程

附录B:Windows下对间接分支下毒的PoC

附录C:幽灵攻击的示例

updatedupdated2023-01-292023-01-29