睿诚科技协会

Meltdown漏洞如何窃取处理器数据?

理解 Meltdown 的关键在于理解现代 CPU 的一个核心设计理念:并行处理,为了提升性能,CPU 采用了“乱序执行”(Out-of-Order Execution, OOOE)技术。

Meltdown漏洞如何窃取处理器数据?-图1
(图片来源网络,侵删)

我们可以把 CPU 想象成一个非常高效的厨师(执行单元),但他有一个奇怪的习惯:不等一道菜完全做完,他就会开始准备下一道菜的食材,因为他预判接下来你需要这些食材。


第一部分:背景知识 - 乱序执行

什么是乱序执行?

  • 顺序执行:CPU 严格按照程序代码的顺序一条一条地执行指令,代码是 A -> B -> C,CPU 就先做 A,再做 B,最后做 C,这种方式简单但效率低下,因为指令之间可能存在等待(比如指令 A 需要从内存读取数据,这时 CPU 就会空闲等待)。
  • 乱序执行:CPU 会分析指令流,找出没有先后依赖关系的指令,然后不按代码顺序,而是将那些可以立即执行的指令先拿出来处理,代码是 A -> B -> C,但 B 依赖于 A 的结果,而 C 不依赖任何东西,CPU 就可以先执行 C,然后再执行 A 和 B,这极大地提高了 CPU 的利用率,避免了等待,从而大幅提升了性能。

乱序执行的“副作用”

乱序执行虽然高效,但它带来一个关键问题:CPU 在执行“非法”指令时,它的工作状态(寄存器、缓存等)也会像执行合法指令一样被改变。

这个“非法”指令通常指的是“特权指令”(Privileged Instruction),这些指令只能由操作系统内核在最高权限级别(Ring 0)下执行,普通应用程序(在用户态,Ring 3)是无权执行的。

核心原则:当 CPU 试图执行一条特权指令时,硬件会检测到,并立即取消整个乱序执行的流程,丢弃所有已经执行但尚未提交的结果,并抛出一个异常(如 #GP),让操作系统来处理,这个过程叫做“管态切换”(Context Switch)或“陷阱”(Trap)。

Meltdown漏洞如何窃取处理器数据?-图2
(图片来源网络,侵删)

第二部分:Meltdown 漏洞的攻击三部曲

Meltdown 的攻击者就是利用了乱序执行的这个“副作用”,攻击者是一个普通应用程序,它无法直接读取内核内存,但 Meltdown 通过一个精巧的三步法,绕过了这个限制。

触发“非法”读取,启动乱序执行

攻击者在自己的程序中,故意执行一条读取内核内存地址的指令。

// 假设 0xdeadbeef 是一个内核空间的地址
// 这条指令在用户态下是非法的
value = *(char *)0xdeadbeef;

当 CPU 的指令解码器看到这条指令时,它会识别出这是一个非法操作。在抛出异常之前,CPU 的乱序执行引擎已经启动了。

它会开始“预执行”这条指令:

Meltdown漏洞如何窃取处理器数据?-图3
(图片来源网络,侵删)
  1. 它会尝试从地址 0xdeadbeef 获取数据。
  2. 由于地址在内核空间,CPU 的内存管理单元会阻止这次访问。
  3. 在 MMU 拦截之前,数据已经被加载到了 CPU 的缓存中!
  4. 随后,CPU 的异常检测单元发现了这个非法操作,于是它丢弃了 value 这个变量的结果,并准备触发一个异常。

关键点:虽然 value 的最终结果被丢弃了,但那个从内核地址读取的数据已经被悄悄地留在了 CPU 缓存里,这是一个“幽灵操作”,程序本身并不知道它发生过。

通过“侧信道”泄露缓存信息

攻击者知道了某个内核数据(我们称之为 Secret)已经被加载到了缓存中,他需要一种方法来“探测”这个数据是什么,这就是“侧信道攻击”的用武之地。

攻击者无法直接读取 Secret,但他可以观察 CPU 缓存的行为,他的方法是:

  1. 准备一个“探针”数组:在用户空间,创建一个很大的、可以放入 CPU 缓存的数组(大小为 256 字节)。

  2. 清空缓存:先访问这个数组的每一个元素,确保所有数据都在 L1 缓存中(这个过程叫“预热”)。

  3. 选择性访问:根据对 Secret 的猜测,访问探针数组中的特定位置。

    // 假设 Secret 是一个内核地址的值,攻击者猜测它可能是 0, 1, 2, ..., 255 中的一个
    // 攻击者会循环猜测,比如先猜 0
    if (Secret == 0) {
        // 如果猜对了,步骤一中,Secret 的数据(0)会被加载到缓存
        // 那么访问 probe_array[0] 就会很快,因为它在缓存里
        access_time = measure_access_time(probe_array[0]);
    } else {
        // 如果猜错了,Secret 的数据不会加载到缓存
        // 访问 probe_array[0] 就会慢,因为它需要从主内存读取
        access_time = measure_access_time(probe_array[0]);
    }
  4. 测量访问时间:通过高精度计时器,测量访问 probe_array[0] 所需的时间。

    • 访问时间很短 -> 说明 Secret 很可能是 0
    • 访问时间很长 -> 说明 Secret 不是 0

通过这个方法,攻击者可以像“二分查找”一样,逐位、逐字节地“猜”出 Secret 的值,这个过程非常快,可以在毫秒级别内窃取大量敏感数据。

绕过 KASLR(地址空间布局随机化)

现代操作系统为了缓解这类攻击,使用了 KASLR 技术,它会随机化内核代码和数据在内存中的地址,让攻击者不知道 0xdeadbeef 这种具体地址对应什么。

但 Meltdown 依然可以绕过,攻击者不需要知道具体的内核地址,他可以:

  1. 泄露内核的基址_stext 的地址)。
  2. 利用泄露的基址和公开的内核符号信息(可以从 /proc/kallsyms 或调试接口获取),计算出任何内核函数或全局变量的精确地址。

一旦获得了这些地址,步骤一中的非法读取就可以精确地指向任何他想窃取的数据,如用户密码、密钥文件、或其他进程的内存。


第三部分:Meltdown 的本质与影响

本质

Meltdown 的本质是打破了 CPU 的“权限隔离”机制。 它利用了硬件设计(乱序执行)与软件(操作系统)之间的一个时间差,在软件意识到权限错误并采取行动(抛出异常)之前,硬件已经“不小心”地把高权限数据泄露到了低权限可以观察到的区域(缓存)。

影响

  • 影响范围极广:几乎所有的 Intel CPU(2025年之前及之后的大部分型号)、部分 ARM 和 AMD CPU 都受到影响。
  • 影响所有操作系统:Windows, Linux, macOS, iOS, Android 等所有运行在这些 CPU 上的操作系统都无法幸免。
  • 后果严重:攻击者可以在同一台物理机上运行的任何用户程序(如浏览器、恶意网站脚本)中,窃取其他进程甚至整个内核的内存数据,导致敏感信息泄露。

缓解措施

Meltdown 的修复方案主要从软件层面入手,因为修改全球数以亿计的 CPU 硬件是不现实的。

  1. KPTI (Kernel Page Table Isolation) - 内核页表隔离

    • 原理:这是最核心的修复方案,它为每个进程(包括内核)维护两套独立的页表。
      • 用户态页表:只包含用户空间的内存地址。
      • 内核态页表:包含内核空间的内存地址。
    • 执行:当 CPU 在用户态运行时,硬件页表寄存器指向用户态页表,当发生系统调用需要切换到内核态时,操作系统会立即切换硬件页表寄存器,指向内核态页表。
    • 效果:这样,即使用户程序触发了乱序执行,试图访问内核地址,其地址翻译也会失败(因为用户态页表中没有这些映射),从而从源头上阻止了数据被加载到缓存。代价是每次系统调用都需要切换页表,带来了一定的性能开销(约 5%-30% 不等)。
  2. Retpoline (间接分支预测屏障)

    • 原理:Meltdown 的变种 Spectre 漏洞利用了 CPU 的分支预测,Retpoline 是一种软件技术,通过特殊的代码序列“欺骗”分支预测器,使其做出错误的预测,从而阻止攻击者利用分支预测来泄露信息。
    • 效果:主要用于防御 Spectre,但也是整个“幽灵”漏洞家族缓解方案的一部分。

| 特性 | 描述 |

分享:
扫描分享到社交APP
上一篇
下一篇