幽灵攻击如何读取它欺骗CPU加载的缓存



我理解论文中他们欺骗CPU推测性地将受害者内存的一部分加载到CPU缓存中的部分。我不明白的部分是他们如何从缓存中检索它。

它们不会直接检索它(越界读取字节不会被 CPU "停用",攻击者在攻击中看不到)。

攻击的媒介是一次进行一点"检索"。在准备好 CPU 缓存(刷新缓存的位置)并"教导">if分支经过条件依赖于非缓存数据之后,CPU 推测性地执行if范围内的几行,包括越界访问(给出字节 B),然后立即访问某个授权的非缓存数组,索引依赖于在秘密B的一点上(B永远不会被攻击者直接看到)。最后,攻击者从用 B 位计算的索引(例如零)中检索相同的授权数据数组:如果检索该 ok 字节的速度很快,则数据仍在缓存中,这意味着 B 位为零。如果检索(相对)慢,则CPU必须在其缓存中加载确定数据,这意味着它没有更早,这意味着B位是一个。

例如,Cond,所有ValidArray都没有缓存,LargeEnough足够大,以确保CPU不会一次加载缓存中的ValidArray[ valid-index + 0 ]ValidArray[ valid-index + LargeEnough ]

if ( Cond ) {
// the next 2 lines are only speculatively executed
V = SomeArray[ out-of-bounds-attacked-index ]
Dummy = ValidArray [ valid-index + ( V & bit ) * LargeEnough ]
}
// the next code is always retired (executed, not only speculatively)
t1 = get_cpu_precise_time()
Dummy2 = ValidArray [ valid-index ]
diff = get_cpu_precise_time() - t1
if (diff > SOME_CALCULATED_VALUE) {
// bit was its value (1, or 2, or 4, or ... 128) 
}
else {
// bit was 0
}

其中bit被连续尝试先是0x01,然后是0x02......到0x80.通过测量"下一个"代码对每个位的"时间"(CPU 周期数),可以显示 V 的值:

  • 如果ValidArray[ valid-index + 0 ]在缓存中,则V & bit0
  • 否则V & bitbit

这需要时间,每个位都需要准备 CPU L1 缓存,尝试多次相同的位以尽量减少时序错误等......

然后必须确定正确的攻击"偏移"以读取有趣的区域。

聪明的攻击,但不是那么容易实现。

他们

如何从缓存中检索它

基本上,推测检索到的机密立即用作索引,以从另一个名为side_effects的数组中读取。我们所需要的只是side_effects数组中"触摸"一个索引,这样相应的元素就会从内存到CPU缓存:

secret = base_array[huge_index_to_a_secret];
tmp = side_effects[secret * PAGE_SIZE];

然后测量访问数组中每个元素side_effects延迟,并将其与内存访问时间进行比较:

for (i = 0; i < 256; i++) {
start = time();
tmp = side_effects[i * PAGE_SIZE];
latency = time() - start;
if (latency < MIN_MEMORY_ACCESS_TIME)
return i; // so, thas was the secret!
}

如果延迟低于最小内存访问时间,则该元素位于缓存中,因此密钥是当前索引。如果延迟很高,则元素不在缓存中,因此我们继续测量。

所以,基本上我们不直接检索任何信息,而是在推测执行过程中接触一些内存,然后观察副作用。

以下是 99 行代码中的基于 Specter 的 Meltdown 概念证明,您可能会发现比其他 PoC 更容易理解: https://github.com/berestovskyy/spectre-meltdown

一般来说,这种技术被称为侧信道攻击,更多信息可以在维基百科上找到:https://en.wikipedia.org/wiki/Side-channel_attack

我想为已经存在的答案提供一条信息,即攻击者如何在探测阶段实际探测受害者进程中的数组。这是一个问题,因为Spectre(与Meltdown不同)在受害者的进程中运行,即使通过缓存,攻击者也不能只查询来自其他进程的数组。

简而言之:使用Spectre时,FLUSH+RELOAD攻击需要KSM或其他共享内存的方法。这样,攻击者(据我所知)可以在自己的地址空间中复制受害者内存的相关部分,从而能够查询缓存以获取探测阵列上的访问时间。

详细解释:

Meltdown和Spectre之间的一个很大区别是,在Meltdown中,整个攻击都在攻击者的地址空间中运行。因此,很明显攻击者如何同时导致缓存更改和读取缓存。然而,对于Spectre,攻击本身在受害者的过程中运行。通过使用所谓的小工具,受害者将执行将秘密数据写入探针数组索引的代码,例如使用a = array2[array1[x] * 4096].

其他答案中链接的概念证明实现了 Spectre 的基本分支/推测概念,但所有代码似乎都在同一个过程中运行。因此,当然,让小工具代码写入array2然后读取array2进行探测是没有问题的。但是,在实际方案中,受害进程将写入也位于受害进程中的array2

现在,问题 - 在我看来论文没有很好地解释 - 是攻击者必须能够探测受害者的地址空间数组(array2)。从理论上讲,这可以再次从受害者内部或从攻击者地址空间完成。

原文只是含糊地描述了它,可能是因为作者很清楚:

在最后阶段,将恢复敏感数据。对于使用刷新+重新加载或逐出+重新加载的 Spectre 攻击,恢复过程包括对受监视的缓存行中的内存地址的访问进行计时。

为了完成攻击,攻击者会测量 array2 中的哪个位置被引入缓存,例如,通过刷新+重新加载或 Prime+Probe。

可以从受害者的地址空间内访问缓存以获取array2,但这需要另一个小工具,攻击者必须能够触发此小工具的执行。这对我来说似乎很不现实,尤其是在Spectre-PHT中。

在论文《通过使用机器学习识别缓存侧信道攻击来检测幽灵攻击》中,我发现了我缺失的解释:

为了使刷新+重新加载攻击在这种情况下起作用, 必须满足三个先决条件。[...]但大多数 重要的是,CPU必须具有内核同页合并(KSM)[4]或透明页面共享(TPS)[54]之类的机制。 已启用 [10]。

KSM 允许进程通过合并不同的虚拟来共享页面 地址到同一页面(如果它们引用相同的物理地址) 地址。因此,它增加了内存密度,允许 更高效的内存使用。KSM 最初是在 Linux 中实现的 2.6.32 并且默认启用 [33]。

KSM 解释了攻击者如何访问通常只能在受害者进程中可用的array2

最新更新