我阅读了文章about the Meltdown/Spectre exploit,它允许使用CPU中的硬件错误从内核读取特权数据。它说:
诀窍是在正常的用户进程中排列指令 使处理器推测性地从受保护的内核中获取数据 执行任何安全检查之前的内存。至关重要 熔化 - 利用x86-64代码可以像......一样简单。
; rcx = kernel address ; rbx = probe array retry: mov al, byte [rcx] shl rax, 0xc jz retry mov rbx, qword [rbx + rax]
尝试从内核地址获取一个字节作为用户进程 触发异常 - 但后续指令已经存在 被推测性地按顺序执行,并触摸基于缓存行 关于该获取字节的内容。
引发异常,并在其他地方进行非致命处理 无序指令已经对内容有所影响 字节。在缓存上执行一些Flush + Reload魔术会显示哪个缓存 触摸了行,从而触及了内核内存字节的内容。 反复重复一遍,最后你转储的内容 内核记忆。
有人可以解释这个 Flush + Reload magic 是如何完成的,它如何揭示触摸的缓存行?
答案 0 :(得分:9)
//再往下,C#中有伪代码显示完整的过程。
我们有一个内核地址rcx
,它是我们想要泄漏的内核内存空间中一个字节的地址(让我们调用那个字节“X”的值)。当前正在运行的用户进程不允许访问此地址。这样做会引发异常。
我们在用户空间中有一个大小为256 * 4096字节的探测器阵列,我们可以自由访问。所以,这只是一些正常的数组,正好是256页长。一页的大小是4096字节。
首先,执行刷新操作(“Flush + Reload”的第一部分)。这告诉处理器完全清除L1缓存。因此,L1缓存中没有缓存内存页面。 (我们在OP中的代码中没有看到)
然后我们执行OP中提到的代码。
mov al, byte [rcx]
我们读取了要泄漏的内核地址的字节值X并将其存储在rax寄存器中。该指令将触发异常,因为我们不允许从用户级代码访问此内存地址。
但是,因为测试我们是否允许访问此地址需要一些时间,处理器将已经开始执行以下语句。因此,我们希望知道存储在rax寄存器中的字节值X用于这些语句。
shl rax, 0xc
我们将此秘密值X乘以4096(页面大小)。
mov rbx, qword [rbx + rax]
现在我们将rax寄存器中的计算值添加到探测器数组的开头,并获取一个指向构成探测器阵列的内存空间中第X页的地址。
然后我们访问该地址的数据,这意味着探测器阵列的第X页被加载到L1缓存中。
现在,L1缓存为空(因为我们已经在显式之前清除它),除了缓存中的两个页面:
现在,“Flush + Reload”的第二部分开始了。我们一个接一个地读取探针阵列中的每个页面,测量所需的时间。所以,我们总共加载了256页。这些页面加载中的255个将相当慢(因为相关的内存尚未在L1缓存中),但是一个加载(第X页的加载)将非常快(因为它之前在L1缓存中)。
现在,因为我们发现加载Xth页面的速度最快,我们知道X是我们想要泄漏的内核地址的值。
从meltdown paper,这是显示在探测器阵列中加载页面的时间测量的图形:
在这种情况下,X是84。
C#中的伪代码显示完整的过程:
public unsafe byte LeakByte(IntPtr kernelAddress)
{
const int PAGE_SIZE = 4096;
// Make probe array
byte[] probeArray = new byte[256 * PAGE_SIZE];
// Clear cash
Processor.ClearL1Cache();
try
{
// mov al, byte [rcx]
// This will throw an exception because we access illegal memory
byte secret = *((byte*)kernelAddress.ToPointer());
// Note that although the previous line logically
// throws an exception,
// the following code is still executed internally
// in the processor before the exception is
// actually triggered
// Although the following lines are executed, any assignments
// to variables are discarded by the processor at the time the
// exception is then actually thrown.
// shl rax, 0xc
int pageOffset = secret * PAGE_SIZE;
// mov rbx, qword [rbx + rax]
// This moves the page with number secret into the L1 cache.
int temp = probeArray[pageOffset];
}
catch
{
// Ignore Exception
}
// Now meassure time for accessing pages
int bestTime = int.MaxValue;
byte bestPage = 0;
for(int i=0; i<= 255, i++)
{
int startTime = DateTime.NowInNanoSeconds;
int temp = probeArray[i * PAGE_SIZE];
int endTime = DateTime.NowInNanoSeconds;
int timeTaken = endTime - startTime;
if(timeTaken < bestTime)
{
bestTime = timeTaken;
bestPage = (byte)i;
}
}
// Fastest page was loaded from Cache and is the leaked secret
return bestPage;
}