文章目录
- [1. 引言](#1. 引言)
-
- [1.1 云环境中的侧信道威胁](#1.1 云环境中的侧信道威胁)
- [1.2 LLC 可作为攻击向量](#1.2 LLC 可作为攻击向量)
- [1.3 本文的突破](#1.3 本文的突破)
- [2. 背景(预备知识)](#2. 背景(预备知识))
-
- [2.1 虚拟地址空间与大页](#2.1 虚拟地址空间与大页)
- [2.2 系统模型与缓存架构](#2.2 系统模型与缓存架构)
- [3. 攻击末级缓存的挑战](#3. 攻击末级缓存的挑战)
-
- [3.1 攻击模型](#3.1 攻击模型)
- [3.2 PRIME+PROBE 简述](#3.2 PRIME+PROBE 简述)
- [3.3 对LLC进行高效PRIME+PROBE攻击的5大挑战概述](#3.3 对LLC进行高效PRIME+PROBE攻击的5大挑战概述)
-
- [挑战1:LLC 对核心内存访问的可见性](#挑战1:LLC 对核心内存访问的可见性)
- [挑战2:探测整个 LLC 不可行](#挑战2:探测整个 LLC 不可行)
- 挑战3:识别与安全关键代码/数据相关的缓存集
- 挑战4:构造精确的冲突集
- 挑战5:探测分辨率
- [4. 构建冲突集并实现 Prime+Probe 攻击](#4. 构建冲突集并实现 Prime+Probe 攻击)
-
- [4.1 核心目标](#4.1 核心目标)
- [4.2 挑战回顾](#4.2 挑战回顾)
- [4.3 解决方法:基于大页的冲突集构造算法](#4.3 解决方法:基于大页的冲突集构造算法)
- [4.4 实现 Prime+Probe 攻击](#4.4 实现 Prime+Probe 攻击)
- [5. 通过信道容量测量评估探测分辨率](#5. 通过信道容量测量评估探测分辨率)
-
- [5.1 本章目的](#5.1 本章目的)
- [5.2 为什么用隐蔽信道而不是直接测攻击?](#5.2 为什么用隐蔽信道而不是直接测攻击?)
- [5.3 本章结论](#5.3 本章结论)
- [6. 攻击平方-乘指数算法](#6. 攻击平方-乘指数算法)
-
- [6.1 平方-乘算法回顾](#6.1 平方-乘算法回顾)
- [6.2 攻击思路](#6.2 攻击思路)
- [6.3 如何定位平方代码对应的缓存集?](#6.3 如何定位平方代码对应的缓存集?)
- [6.4 优化:利用页内偏移缩小范围](#6.4 优化:利用页内偏移缩小范围)
- [6.5 解码密钥](#6.5 解码密钥)
- [7. 攻击滑动窗口指数算法](#7. 攻击滑动窗口指数算法)
-
- [7.1 滑动窗口幂运算](#7.1 滑动窗口幂运算)
- [7.2 攻击挑战(相比平方-乘)](#7.2 攻击挑战(相比平方-乘))
- [7.3 攻击核心思路](#7.3 攻击核心思路)
- [7.4 具体步骤](#7.4 具体步骤)
-
- 找到乘法代码所在的缓存集(作为"时钟")
- [找出每个 gi 对应哪个缓存集](#找出每个 g[i] 对应哪个缓存集)
- 正式攻击
- [8. 相关研究](#8. 相关研究)
-
- [8.1 PRIME+PROBE 攻击的历史](#8.1 PRIME+PROBE 攻击的历史)
- [8.2 基于 LLC 的隐蔽信道](#8.2 基于 LLC 的隐蔽信道)
- [8.3 基于 LLC 的侧信道攻击](#8.3 基于 LLC 的侧信道攻击)
- [9. 缓解措施](#9. 缓解措施)
- [10. 总结](#10. 总结)
这篇 2015 年 IEEE S&P 顶会论文,是缓存侧信道领域的里程碑:它第一次实现了无需共享内存、无需同核心、无需漏洞的云环境跨 VM L3 缓存攻击,补全了 FLUSH+RELOAD 的最大短板。
1. 引言
1.1 云环境中的侧信道威胁
IaaS(基础设施即服务) 云服务通过 虚拟机隔离 来保证多租户安全。
尽管虚拟化营造出严格隔离与独占资源访问的假象,但实际上虚拟资源映射至共享的物理资源 ,这使得共置的虚拟机之间存在产生干扰的可能性。恶意虚拟机可能获取受害虚拟机处理的数据信息,甚至针对密码学实现实施侧信道攻击。
之前已有针对 L1 缓存 的高分辨率攻击 ,但 L1 是每个核心私有的。
要让攻击者和受害者运行在同一核心上,在真实云环境中不太现实(VMM 通常不会把不同租户的 VM 调度到同一核心)。
1.2 LLC 可作为攻击向量
什么是攻击向量?
在安全领域,"攻击向量"指的是攻击者用来进入目标系统或获取敏感信息的具体路径、方法或手段。可以理解为"攻击的切入点"或"攻击的通道"。
末级缓存(LLC,通常是 L3)被同一物理处理器上的所有核心共享。这构成了一个现实得多的攻击向量。
但是,L3 的容量比 L1 大得多、访问慢,导致攻击时间分辨率极低、带宽极低,使得大多数已公开的末级缓存攻击不适用于密码分析;
唯一好用的 FLUSH+RELOAD,必须依赖内存共享(比如共享库、共享文件),但所有云厂商都明确禁止 VM 间内存共享,所以 FLUSH+RELOAD 在实际云场景中也无法奏效。
1.3 本文的突破
论文证明了:改进版的 PRIME+PROBE攻击,无需共享内存,可以实现实用化的 L3 攻击。
作者们利用了两个硬件特性:
Intel 的包含式缓存;
VMM 默认开启的大页映射(为了性能,云厂商不会关)。
2. 背景(预备知识)
2.1 虚拟地址空间与大页
基本概念:
虚拟地址空间:每个进程运行在自己的私有虚拟地址空间中,这个空间被划分为页(Page)。典型页大小是4 KiB。
物理内存:也被划分为同样大小的帧(Frame)。
操作系统维护页表,将虚拟页映射到物理帧。
TLB(Translation Lookaside Buffer)------快表:CPU内部的一个硬件缓存(存放页表的部分副本数据),用于加速虚拟地址到物理地址的转换。
TLB条目数有限,如果程序的工作集很大且使用小页,TLB会频繁缺失,性能下降
大页:
x86-64处理器支持更大的页:2 MB 和 1 GB。
大页的优点:
减少TLB缺失(快表命中率更高 ):一个TLB条目可以覆盖2 MB连续地址(相当于512个4 KB小页)。
提高内存访问性能,尤其对内存占用大的应用(如数据库、高性能计算)。
在虚拟化环境中,VMM(如VMware ESX、Xen HVM)也倾向于使用大帧(large frames) 来映射客户虚拟机的物理内存,同样是为了性能。
2.2 系统模型与缓存架构
系统模型:
云服务器通常配备多核处理器,即芯片上集成了多个处理器核心,这些核心共享末级缓存(LLC)和内存,如图1所示。
多核处理器(multi-core processor)在一个芯片上包含多个核心。
每个核心有自己的私有L1缓存(指令和数据分离)以及私有的L2缓存。
所有核心共享一个统一的最后一级缓存(LLC,通常是L3)。
攻击者VM和受害者VM被调度到同一物理处理器的不同核心上时,它们会共享LLC。
缓存架构
缓存层级:
越靠近核心的缓存,体积越小,速度越快。
内存访问速度:缓存(L1>L2>L3) ≫ 主存
Intel 的 L3 是 Inclusive(包含式)缓存
即L3缓存中包含了L1、L2中的所有数据。
因此,从L3驱逐数据会同时从所有核心的L1/L2中移除该数据。(因为如果一个数据在 L1 或 L2 里,那么它 必须 同时在 L3 里)
缓存组织和访问
组织方式:组相联
缓存被划分为S个组,每个组包含W个缓存行(W称为路数)。也称W路组相联.
组间直接映射(即每个主存块对应一个指定的cache组,组号=主存块号%cache组数)
组内全相联映射(即确定了组号后,在组内可以放到任意一行)
组相联映射的地址结构为:
标记、组号、块内地址(偏移量)
访问时,先根据组号找到对应的cache组,在组内,将每行的标记与主存地址中的标记一一对比,如果相等且有效位为1,则命中。
替换算法:
Cache的容量有限,那如果cache满了怎么办?
当Cache的容量用完后,而又有新的内容需要添加进来时, 就需要挑选并舍弃原有的部分内容,从而腾出空间来放新内容。
那应该选取那一部分的内容和新内容进行替换呢?这就涉及到cache的替换算法,而LRU Cache就是cache替换算法中的一种!
LRU Cache 的替换原则就是将最近最少使用的内容替换掉。其实,LRU译成最久未使用会更形象, 因为该算法每次替换掉的就是一段时间内最久没有使用过的内容。
ntel的切片LLC
从Sandy Bridge微架构开始,Intel将LLC分成多个切片(slice),每个核心对应一个切片。
切片之间通过环形总线(ring bus) 连接,各个切片可以并行访问。
本质上是相互独立的缓存,不过环形总线可确保每个核心都能访问整个最后一级缓存(访问远程切片时延迟更高)。
内存地址映射到哪个切片,由一个未公开的哈希函数决定。
在切片内部,再按传统的组索引方式定位缓存行。
虚拟化环境中的地址映射
在虚拟化环境中,有两级地址转换:
客户虚拟地址 → 客户物理地址
客户物理地址 → 主机物理地址
3. 攻击末级缓存的挑战
3.1 攻击模型
攻击场景:
IaaS 云虚拟化环境,攻击者 VM 和 受害者 VM 同居在同一个物理 CPU上。
不做任何苛刻假设:
不需要同核心
不需要进程 / VM 之间共享内存
不假定 VMM(虚拟机监视器)存在任何漏洞
不需要和受害者程序同步
仅知道:
受害者运行的密码软件。
3.2 PRIME+PROBE 简述
经典 PRIME+PROBE 攻击流程:
PRIME:攻击者将自己的数据填满一个或多个缓存集。
IDLE:等待受害者执行(受害者会使用缓存,可能驱逐攻击者的部分数据)。
PROBE:攻击者重新访问自己的数据,测量时间。如果某行访问变慢,说明被受害者驱逐了 → 推断受害者访问了该缓存集。
ps:PROBE 之后,这些数据又被加载回缓存,所以也相当于下一次的 PRIME。
和 FLUSH+RELOAD 关键区别:
FLUSH+RELOAD:靠 clflush + 共享内存
PRIME+PROBE:靠缓存竞争,完全不用共享内存
3.3 对LLC进行高效PRIME+PROBE攻击的5大挑战概述
挑战1:LLC 对核心内存访问的可见性
L1/L2 会满足大多数访问,LLC 看不到很多内存活动。如果攻击者只控制 LLC 状态,但受害者数据从 L1/L2 命中,就不会到达 LLC → 攻击者看不到。
解决方案:
利用 包容性缓存 。
即L3缓存中包含了L1、L2中的所有数据。
因此,从L3驱逐数据会同时从所有核心的L1/L2中移除该数据。(因为如果一个数据在 L1 或 L2 里,那么它 必须 同时在 L3 里)
挑战2:探测整个 LLC 不可行
L1 攻击通常 prime+probe 整个 L1(几十 KB),然后通过机器学习分析整个足迹。
但这对L3通常是不可行的。
而LLC 有 几 MB(甚至几十 MB),全扫描时间太长,无法获得细粒度信息。
解决方案:
先精确定位与安全关键操作相关的少数几个缓存集,然后只监控这些集。
但如何定位,这本身也是一个挑战
挑战3:识别与安全关键代码/数据相关的缓存集
攻击者不知道受害者地址空间中那些安全关键型访问的虚拟地址,也无法控制这些虚拟地址如何映射到物理地址。
如何找到那些"有价值的"缓存集?
解决方案:
扫描整个 LLC,一次监控一个缓存集,寻找与算法特征匹配的时间访问模式。例如平方-乘算法中平方操作的周期性脉冲。详见第六、七章。
挑战4:构造精确的冲突集
什么是冲突集?
在 Prime+Probe 攻击中,攻击者想监控受害者是否访问了某个 LLC 缓存集。怎么做呢?
攻击者先把自己的数据填满这个缓存集(这叫 Prime)。
然后让受害者运行。
如果受害者访问了同一缓存集里的数据(即两者访问的地址映射到同一个 LLC 缓存集),就会把攻击者的某些数据"挤出去"(Evict) 。
最后攻击者去读自己的数据,测量时间:如果某条数据读得慢了,说明它被挤出去了 → 受害者访问过这个缓存集。
攻击者不需要知道缓存集里的数据内容。他只需要知道"受害者访问了哪个缓存集",因为这个信息与秘密数据/代码之间存在确定的映射关系(通过预先分析受害者二进制获得)。通过监控哪些缓存集被访问,就能推断出密钥相关的操作和数据。
所以,攻击者需要有一批自己的内存地址,它们全都映射到同一个 LLC 缓存集。这样攻击者才能用它们"填满"那个集。这一批地址就叫该缓存集的 冲突集(Eviction Set)
挑战:
对于 L1(虚拟索引),攻击者可以轻易选择虚拟地址相同的索引位。但 LLC 是物理索引,且现代 Intel 还有切片哈希(每核心一个切片,用哈希函数将地址分布到各切片)。攻击者不知道物理地址,也不知道哈希函数。
攻击者无法直接选虚拟地址来命中同一个集。
解决方案:
利用 大页 的物理地址连续性,以及一种算法(Algorithm 1),在不逆向哈希的情况下构造冲突集。详见第四章。
挑战5:探测分辨率
为了提取密钥,需要区分短时间内的不同操作(如平方 vs 乘法)。攻击者可以异步运行,不需要抢占受害者,但探测周期必须足够短。
探测 LLC 本身就比 L1 慢,如何保证分辨率?
见第五章。
4. 构建冲突集并实现 Prime+Probe 攻击
4.1 核心目标
攻击者需要实现:
找到一组自己的内存地址,它们都映射到同一个 LLC 缓存集(称为冲突集,eviction set)。
用这个冲突集执行 Prime+Probe:先 Prime(访问冲突集中所有地址,填满该缓存集),等待受害者,再 Probe(重新遍历冲突集并计时,检测哪些行被受害者驱逐)。
4.2 挑战回顾
LLC 是物理索引 + 分片(slice),使用未公开的哈希函数将物理地址分布到各核心的切片中。
攻击者不知道虚拟地址到物理地址的映射,也不知道哈希函数。
因此,无法直接计算哪些虚拟地址会冲突到同一个物理缓存集。
4.3 解决方法:基于大页的冲突集构造算法
论文利用了 大页(2 MiB) 的两个特性:
- 大页内的物理地址是连续的(例如从 0x0000 到 0x200000)。
- 连续物理地址经过哈希和索引后,会周期性地命中同一个 LLC 缓存集(周期取决于 LLC 的大小和分片方式)。
第二点怎么理解?
连续物理地址经过 LLC 的切片哈希和集合索引后,由于地址位被截断和取模,会周期性地命中同一个缓存集 。周期由缓存集数量、缓存行大小和切片哈希函数决定。
因为连续物理地址映射到 LLC 缓存集的索引是周期性的,攻击者只需在单个大页内以固定步长(周期)选地址,就能保证它们落在同一个缓存集,从而无需知道物理地址或哈希函数即可构造冲突集。
攻击者通过以下试探性算法来构造冲突集:
步骤1:分配一个大页
攻击者分配一个 2 MiB 的大页。这个大页内的物理地址连续。
步骤2:选取候选地址
从大页中选取一系列地址,间隔固定(例如 4 KiB 或 2 MiB 的整数倍)。由于物理连续,这些地址的物理地址也间隔相同的值
步骤3:冲突检测
攻击者需要判断两个地址是否映射到同一个 LLC 缓存集。
方法是用它们互相驱逐:
先访问地址 A 多次,确保它被缓存。
然后大量访问地址 B,看是否导致 A 的访问时间变长(即 A 被驱逐)。如果变长,说明 A 和 B 冲突(在同一个缓存集)。
通过两两测试,可以找出所有与某个参考地址冲突的其他地址。
步骤4:利用大页的周期性简化
因为大页内物理地址连续,冲突模式是周期性的。攻击者只需要找到一个冲突集(比如与地址 0 冲突的所有偏移),就可以通过偏移量生成更多冲突地址 。
解释:
"通过周期性生成更多冲突地址"是指:因为连续物理地址映射到同一个缓存集的间隔是固定的(周期 T),攻击者只要找到一个冲突地址,加上 T 的整数倍,就可以从大页中批量得到大量冲突地址,无需重复探测。
论文具体实现了一个算法:
分配一个大页,将其分成多个"槽"(slot),每个槽是一个缓存行(64 字节)。
通过测量两个槽之间的驱逐关系,构建一个图,同一个连通分量的槽就是冲突集(即联通分量中的所有槽是互相驱逐的关系,映射到同一个缓存集里面 )。
由于大页足够大(2 MiB),可以包含多个完整的冲突集。
步骤5:处理切片哈希
Intel 的哈希函数将地址分布到不同切片。
但论文发现:
当 CPU 核心数为 2 的幂时,LLC 切片哈希只依赖地址的高位,不依赖缓存集索引位 。
因此,攻击者只需选择一批地址,让它们高位的切片选择位(同一切片)相同且中间索引位相同(同一缓存集),就能保证它们全部映射到同一个切片内的同一个缓存集,从而直接构造冲突集,无需试探性驱逐检测。
当核心数不是 2 的幂时,哈希函数会用到更多地址位,但通过大页的周期性仍然可以构造冲突集(算法需要为每个缓存集重复执行)。
4.4 实现 Prime+Probe 攻击
一旦冲突集构造完成,攻击者就可以实现 Prime+Probe:
将每个驱逐集中的所有内存行按随机顺序组织为一个链表。随机排列可防止硬件预取驱逐集中的内存行。
步骤:
Prime:遍历冲突集中的所有内存行,确保它们都被加载到 LLC 中(记录开始时间),填满该缓存集。
Wait:等待固定时间,让受害者执行。
Probe:再次遍历冲突集(记录结束时间),测量访问的总时间 (两次时间的差值)。
如果某次访问时间显著变长,说明该行被受害者驱逐了 → 受害者访问过该缓存集。
用于探测12路缓存组的代码:

论文中还做了两个优化:
- 使用双向链表:在 Probe 时以相反顺序遍历,避免攻击者自己的访问自相驱逐(每次 probe 都以与上次 prime 相反的顺序访问,使得缓存行的 LRU 年龄分布均匀化,避免某些行一直处于最旧状态而被自己逐出 )。
- 减少 L1/L2 干扰:因为攻击者的数据也会被 L1/L2 缓存,导致测量时间波动。
论文建议可以测量每次 load 的时间,即单独测量每个地址的访问时间,而不是总时间,或者通过多次采样取平均。
论文中不是说了是Inclusive(包含式)缓存嘛。
即L3缓存中包含了L1、L2中的所有数据。
因此,从L3驱逐数据会同时从所有核心的L1/L2中移除该数据。(因为如果一个数据在 L1 或 L2 里,那么它 必须 同时在 L3 里)
为什么L1/L2 还会干扰?
因为攻击者在 Prime+Probe 中并不直接使用 clflush 来主动驱逐,而是依靠受害者的访问来"被动"驱逐 。
而且,攻击者自己的数据在 L1/L2 中的状态,在探测时刻可能已经与 L3 不同步了。
因为攻击者自己的数据也可能因为 L1/L2 容量小、被自己的其他代码替换掉而提前离开 L1/L2,但仍在 LLC 中。这种"不同步"不是驱逐导致的,而是正常的自身 L1/L2 替换导致的(Inclusive 缓存只保证"如果数据在 L1/L2,则一定在 L3",但反过来不成立------数据可以在 L3 中,却不在 L1/L2 中)。
不再一次遍历 12 个地址然后取总时间,而是单独测量每个地址的访问时间
为什么这样就避免干扰?
单个测量:
cache命中和未命中时间区分非常明显。
累加测量:
界限模糊,不好区分。
5. 通过信道容量测量评估探测分辨率
5.1 本章目的
在前面的章节中,作者已经解决了构造冲突集、实现 PRIME+PROBE 探测等关键技术问题。但是还有一个核心问题没有回答:
攻击者的探测操作需要多长时间?这个时间分辨率有多高?能否用来构建实用的隐蔽信道或侧信道?
为了量化评估探测的时间分辨率和可靠性,作者构建了一个隐蔽信道(covert channel):两个合谋的虚拟机(一个发送者,一个接收者)利用 LLC 缓存集来传递秘密信息,然后测量信道的容量(带宽)和错误率。通过这个实验,来间接评估 PRIME+PROBE 探测的精度和稳定性。
5.2 为什么用隐蔽信道而不是直接测攻击?
隐蔽信道是攻击者主动控制的两个实体之间的通信,可以精确控制发送的比特序列,便于统计错误率和带宽。
通过测量隐蔽信道的性能,可以推断出在真实侧信道攻击中(攻击者不知道受害者具体何时访问),探测分辨率的上限
5.3 本章结论
LLC 作为隐蔽信道可以达到 Mb/s 级别的带宽,远高于前人工作(190 Kb/s)。这说明了 PRIME+PROBE 探测具有足够高的时间分辨率,可用于实用的侧信道攻击 。
探测每个缓存集的时间开销虽然比 L1 缓存高,但由于发送者和接收者可以异步并行执行,整体带宽(隐蔽信道的信息传输速率)仍然很高 。
错误率可以通过增加 T pause降低,但会牺牲带宽。在实际攻击中,攻击者可以接受一定的错误率,然后通过合并多次观测或纠错码来恢复信息。
6. 攻击平方-乘指数算法
利用前面构造的冲突集和探测方法,实际攻击一个执行路径依赖于秘密指数的密码算法:平方-乘指数算法。
目标是从 GnuPG 1.4.13 的 ElGamal 解密中恢复私钥指数。
6.1 平方-乘算法回顾
逐位扫描指数 e 的二进制表示(从最高位到最低位)。
每一位 :
总是执行平方 + 模约减。
如果该位为 1,额外执行乘法 + 模约减。
因此,平方操作总是执行,而乘法只在比特为 1 时执行。
攻击者可以通过观察平方操作之间的间隔来推断比特:
比特 0:平方 → 约减 → 下一个平方(间隔短)
比特 1:平方 → 约减 → 乘法 → 约减 → 下一个平方(间隔长)
攻击者只要盯着平方函数什么时候被执行,看间隔长短,就能直接猜出每一位密钥。
6.2 攻击思路
-
定位平方代码所在的 LLC 缓存集(因为平方操作总是执行,其活动模式有规律)。
-
监控该缓存集,记录每次平方操作的时间间隔。
-
根据间隔长短解码指数比特。
6.3 如何定位平方代码对应的缓存集?
攻击者不知道平方代码的物理地址,但可以通过扫描所有缓存集,寻找具有预期活动模式的缓存集(少数几个承载了敏感代码/数据的集)。
-
攻击者分配大页,为每个缓存集(即末级缓存中是所有cache组)构造冲突集(或至少能探测每个集的方法)
-
划分时间槽
把时间切成固定 5000 周期一个小时间片。
每个时间槽里:Prime+Probe 探测目标缓存集。
- 遍历扫描所有缓存集
挨个监听,找有固定脉冲规律的那个缓存集。
只有平方函数的缓存集,会出现非常规整的周期性活动。
- 找到目标缓存集(图 6)
图中展示了所有缓存集的活动轨迹,大部分缓存集乱跳,只有 第 43 号缓存集 有标准脉冲节奏 → 这就是平方代码所在的缓存集。
6.4 优化:利用页内偏移缩小范围
-
分析 GnuPG 的代码,找到平方函数的虚拟地址,从而知道其页内偏移
-
因为 LLC 缓存集索引由物理地址的中间位(组号)决定,而大页内 页内偏移与物理偏移一致(虚拟地址转换到物理地址,页内偏移是不变的),所以平方代码必然落在少数几个缓存集中
因为大页内虚拟地址的页内偏移等于物理地址的页内偏移,而 LLC 缓存集索引由物理地址的偏移中的中间位决定
offset 直接决定了 set index。也就是说,平方代码的虚拟地址偏移量 X 会对应到一个唯一的 set index
所以平方函数代码的固定偏移范围决定了它只会映射到少数几个缓存集。攻击者利用这一点,可以将扫描范围从全部缓存集缩小到只与平方代码偏移匹配的少量缓存集,从而加速定位。
普通页面(4 KiB)的页内偏移太短(只有 12 位),而 LLC 的 set index 通常需要更多位(例如 13--16 位),这些高位会因页号变化而改变,攻击者无法直接控制。大页(2 MiB)提供 21 位页内偏移,足够覆盖 set index 的全部位数,因此攻击者可以通过虚拟地址偏移直接固定物理缓存集,从而构造冲突集。
6.5 解码密钥
一旦定位到平方缓存集,攻击者持续监控该集,记录每次脉冲之间的时间槽数。
攻击者监控平方代码所在的缓存集,通过 Prime+Probe 在每个时间槽判断是否有活动,形成活动时间序列。
然后识别出连续的脉冲(平方操作),测量相邻脉冲之间的时间槽间隔。间隔短(约 6 槽)对应比特 0,间隔长(约 16-17 槽)对应比特 1。
7. 攻击滑动窗口指数算法
第七章展示了如何攻击 GnuPG 最新版(1.4.18)的滑动窗口指数算法。与第六章不同,这里攻击的是数据访问模式,而不是代码执行路径。
7.1 滑动窗口幂运算
平方-乘算法虽然简单,但效率不高。
滑动窗口通过预计算奇数次幂并批量处理指数中的连续 1 比特,减少乘法次数,提高性能 。GnuPG 从 1.4.14 开始默认使用滑动窗口(窗口大小 S=4)。
滑动窗口幂运算


滑动窗口是一种加速模幂运算的方法,相比于平方-乘算法,它减少了乘法的次数。代价是需要预计算一些值。
第一步:预计算
提前算出所有可能的奇数次幂
第二步:指数运算
指数 e 被分成若干个窗口(每个窗口是一段连续的二进制位,且非零窗口 最左边必须是 1,最右边也必须是 1)。
例如:指数 101101011 可能被分成 1, 0110, 1011 等(具体规则略复杂,但概念是"从高位往低位,尽量取最长的窗口")
对每个窗口的处理(从高位到低位):
对窗口内的每一位执行一次平方(总次数等于窗口长度)。
如果窗口非零 (即窗口值 w 为奇数),则执行一次乘法 ,乘数是预计算表 g(w−1)/2。
如果窗口值为 0 ,就跳过这一步(相当于只做平方)。
平方的次数等于窗口长度,乘法只出现在非零窗口。
攻击者无法通过平方和乘法之间的时间间隔来区分每一位,因为窗口长度不固定,间隔也变化。
漏洞:
乘法时访问的乘数 gi 依赖于当前窗口的值(即依赖指数比特序列)。攻击者如果能监控到 使用了哪个乘数,就能恢复窗口值,进而得到指数比特。
7.2 攻击挑战(相比平方-乘)
- 乘数表位置不固定:
每次解密,乘数表存储在堆内存中(动态分配),地址每次都可能变化。而平方-乘中的平方代码地址是固定的。
- 乘数访问模式稀疏且不规则:
一个乘数可能在一次指数运算中只被使用几次(平均约 10 次),而不是像平方那样每轮都出现。
- 需要对齐到乘法操作的时间点:
攻击者不知道乘法何时发生,必须依赖乘法代码本身的缓存集活动来作为"时钟"。
7.3 攻击核心思路
既然时间间隔没用了,就换思路:
不看执行快慢,改看数据访问模式
密钥窗口值不同,会访问不同的预计算表 g i
每个表项占用不同缓存集,只要测出哪个缓存集被访问,就能反推密钥。
因此:
如果攻击者能监控到"这次乘法访问了哪个缓存集",他就知道用了哪个 gi,从而知道窗口值。知道了窗口值(例如 5)和窗口长度(例如 3),就能反推出那几位指数比特(例如 101)。
7.4 具体步骤
找到乘法代码所在的缓存集(作为"时钟")
攻击者需要知道什么时候发生了乘法,才能去检查是哪个缓存集被访问。他通过类似第六章的方法,找到乘法函数(mpi_mul)代码所在的缓存集。这个缓存集在每次乘法时都会出现活动脉冲,就像一个时钟,告诉攻击者"现在发生了乘法"。
找出每个 gi 对应哪个缓存集
攻击者不知道 gi 存储在哪些缓存集,但可以通过预计算阶段的固定模式来标定。
滑动窗口算法在正式计算指数前,会先预计算表 gi。预计算阶段会计算 8 个乘数:g0, g1, ..., g7(分别代表 b^1, b^3, b^5, ..., b^15)。这些计算过程中,会反复用到已有的乘数 。具体的乘法操作顺序是固定的(不依赖密钥):
第 1 个乘法:访问 g0
第 2 个乘法:又访问 g0
第 3 个乘法:访问 g1
第 4 个乘法:访问 g2
......
让受害者执行一次解密(例如解密一个已知的密文)。攻击者同时做两件事:
-
监控"乘法代码时钟"缓存集(前面已定位),确定每次乘法发生的精确时间窗口。
-
使用 Prime+Probe 扫描所有可能的缓存集(或者利用页内偏移缩小范围后的候选集),记录每个缓存集在每次乘法窗口内是否有活动。
-
对齐到预计算阶段:
一次完整的解密包含预计算阶段和指数阶段。攻击者如何知道前几次乘法属于预计算阶段?可以通过乘法次数来区分。因为预计算阶段的乘法次数是固定的(S=4 时,预计算阶段共有 8 次乘法)。攻击者只需从解密开始时数乘法次数,前 8 次就是预计算阶段。
- 观察前8次乘法中每个缓存集的活动:
假设某个缓存集 A 在第一次和第二次乘法中都有活动,那么它一定对应 g0(因为只有 g0 在前两次乘法中被访问)。
缓存集 B 只在第三次乘法中有活动 → 对应 g1。
缓存集 C 只在第四次乘法中有活动 → 对应 g2。
依此类推。
注意,一个乘数可能占用多个缓存集(因为乘数数据可能跨多个缓存行),所以可能多个缓存集表现出完全相同的活动模式。它们会归为一组,代表同一个乘数。
- 这样就完成了标定:
攻击者建立了一张映射表,记录了每个乘数索引 i 对应的一个或多个缓存集。
正式攻击
一旦知道了每个 gi 对应的缓存集,攻击者再让受害者执行一次真正的解密(比如解密一封加密邮件)。他一边监听乘法代码时钟,一边监控所有 gi 对应的缓存集。每次乘法时钟响起,他就检查:这次是哪个缓存集被访问了? → 就知道用了哪个 gi → 就知道窗口值 → 结合窗口长度(通过两次乘法之间的平方次数算出)→ 还原指数比特。
8. 相关研究
8.1 PRIME+PROBE 攻击的历史
PRIME+PROBE 技术已被用于攻击多种处理器缓存,包括 L1 数据缓存、L1 指令缓存以及分支预测缓存。所有这些缓存都是核心私有的,因此这些攻击利用超线程或核心时间共享来实施。
引用 47(Zhang et al.):他们使用 PRIME+PROBE 实现了跨 VM 攻击,针对 GnuPG 1.4.13 的平方-乘实现。该攻击依赖于Xen 调度器的一个弱点,并且要求攻击者和受害者有非零的概率时间共享同一个核心。攻击需要六小时的持续解密才能收集足够的数据来破解密钥。
本文的不同:
本文使用 LLC 作为攻击向量,LLC 是所有核心共享的,因此攻击者不需要欺骗调度器来共享核心,攻击速度快得多。
8.2 基于 LLC 的隐蔽信道
通过精确映射缓存集(即本文构造冲突集的方法),实现了高达 1.2 Mb/s 的带宽,远高于先前工作。
8.3 基于 LLC 的侧信道攻击
本文的这种攻击移除了共享内存的要求,并且足够强大,可以攻破最新版 GnuPG 中更高级的滑动窗口模幂实现,而 FLUSH+RELOAD 无法做到这一点。
9. 缓解措施
攻击依赖三个必要条件,破坏任意一个就能防御:
- 攻击者能构造精准驱逐集(依赖大页)
- 攻击者能监听受害者的缓存竞争(依赖 L3 共享)
- 受害者代码有秘密依赖的执行 / 数据访问模式
9.1 修复 GnuPG 本身(软件层防御)
法一:指数盲化
把指数(私钥)拆成两个随机部分,分别做模幂运算,最后合并结果。
让每次运算的执行轨迹都不一样,攻击者无法通过多次采样聚类还原密钥。
法二:常数时间实现
常数时间(Constant Time)是一种编程和密码学防御技术,它的核心意思是:算法执行的总时间(或指令序列)不依赖于秘密数据(如私钥、指数、密码)。
让代码的指令序列、内存访问模式、执行时间完全不依赖秘密比特值。
效果:从根源上消除侧信道泄露,是密码软件的终极防御。
法三:论文作者提供的补丁
作者给 GnuPG 提交了一个补丁:
把滑动窗口改成固定窗口算法
确保预计算表的访问模式不依赖密钥比特
虽然不是完全常量时间,但能挡住本文的攻击
9.2 避免资源竞争(系统 / 硬件层防御)
这是防御无共享 L3 攻击最有效的方向,因为攻击本质上就是利用 L3 缓存的资源竞争。
禁止跨租户共驻
原理:不让不同租户的 VM 运行在同一个物理 CPU 上,彻底隔离 L3 缓存。
效果:100% 防御所有 L3 侧信道攻击。
缺点:完全违背云计算的核心逻辑,资源利用率暴跌,成本飙升,云厂商不可能接受。
缓存分区
细粒度方案,将 LLC 分区,使不同租户的 VM 使用互不重叠的缓存部分。
a. 基于页着色
将不同颜色的物理帧映射到不同的缓存集。VMM 为不同租户分配不同颜色的帧,实现隔离。
缺点:复杂化 VMM 资源管理,因碎片导致内存浪费,且与大页不兼容(会失去大页的性能优势)。
b. STEALTHMEM
只预留少数几种颜色作为"隐形页",用于存放每个物理核心的敏感代码和数据,确保这些敏感内容不会与其他内容发生缓存冲突。
但这种方法不能消除 LLC 隐蔽信道(因为非敏感数据仍可共享缓存)。
c. Intel 缓存分配技术
原理:硬件层面把 L3 缓存按路(Way)划分,给不同 VM 分配不同的路,互不干扰。
优点:硬件原生支持,性能开销小,和大页兼容。
缺点:目前最多支持 4 个分区,云环境租户多的时候不够用。
d. 分区锁定缓存
使用特殊 load/store 指令,可以将安全关键的缓存行锁定在缓存中,防止被驱逐。
限制:只能锁定数据,不清楚如何扩展到指令。
运行时多样化
随机化内存到缓存的映射,使攻击者无法获得可靠的信息。已应用于 L1 数据缓存而不影响性能,但尚未研究用于 LLC。
模糊时间
通过添加噪声或降低时钟精度来破坏时间测量
缺点:可能影响需要高精度计时器的正常应用。
10. 总结
攻击有效:基于 LLC 的 Prime+Probe 可实现 1.2 Mb/s 隐蔽信道,并攻破 GnuPG 的平方-乘和滑动窗口算法。
假设极弱:只需包容性缓存 + 大页映射,无需共享内存、同核心、VMM 漏洞。
防御方向:禁用大页(影响性能)、缓存分区(如 Intel CAT)、随机化映射、模糊时间。
未来工作:需在真实云环境(有噪声)中测试攻击的鲁棒性。
利用硬件对最后一级缓存(LLC)分区的支持可能是最有前景的防御手段,但这些机制在实际中是否奏效仍有待观察。











