1. 项目概述:从EXPRACE看漏洞利用的范式转移
在漏洞利用领域,我们常常听到“竞争条件”这个词。传统的竞争漏洞利用,比如经典的TOCTOU(检查时间与使用时间),通常围绕着单一变量的状态在极短的时间窗口内被恶意篡改。攻击者需要精确地“赛跑”,在程序检查某个条件(如文件权限)之后、使用该条件之前,插入自己的恶意操作。这种利用方式对时序的要求极为苛刻,成功率往往依赖于系统负载和调度器的“心情”。
但2021年USENIX安全顶会上发表的这篇EXPRACE论文,提出了一种颠覆性的思路: 为什么不主动去“制造”和“控制”这个竞争窗口,而非被动地等待和“赛跑”呢? 论文的核心创新在于,它系统性地提出并验证了利用 硬件中断机制 来主动触发和扩大 多变量竞争漏洞 的攻击方法。这里的“多变量”是关键,它意味着漏洞的触发不再依赖于单一资源状态的翻转,而是涉及多个内存位置、寄存器或内核对象状态的交错变化,攻击者通过中断,能够以一种近乎确定性的方式,让这些变量进入一个非预期的、可被利用的中间状态。
简单来说,EXPRACE将漏洞利用从一个“百米冲刺”的偶然性事件,变成了一个可以“按下暂停键”进行精密操控的实验。这对于安全研究者和防御者而言,意义重大。它不仅揭示了操作系统和硬件交互深层中一类新的、更强大的攻击面,也迫使我们在设计并发安全机制时,必须将中断这个“不确定性的确定性来源”纳入核心考量。对于从事二进制安全、内核漏洞挖掘和利用的同行来说,理解EXPRACE的原理,相当于掌握了一把打开新世界大门的钥匙,能让我们以全新的视角去审视那些看似坚固的代码逻辑。
2. 核心原理:中断如何成为精密的漏洞利用扳机
要理解EXPRACE,我们必须先抛开对中断的常规认知。在教科书里,中断是硬件或软件发出的信号,要求CPU暂停当前任务,转去处理更紧急的事件,处理完后恢复原状。这听起来很“无害”,甚至是有益的。然而,从并发程序的状态机视角看, 中断点就是一个强制的、非确定性的上下文切换点 。EXPRACE的巧妙之处,正是将这种非确定性,转化为对多变量竞争漏洞的确定性利用工具。
2.1 多变量竞争漏洞的本质
传统的单变量竞争(如经典的
if (access(file)) then open(file)
)漏洞,其脆弱性在于一个变量的值在原子操作(或被认为原子的操作)之间被改变。而多变量竞争漏洞的模型则复杂得多。考虑内核中的一段代码,它需要按顺序操作两个或多个关联的数据结构(例如,一个进程描述符
task_struct
和其对应的文件描述符表
files_struct
)。正确的逻辑可能是:
- 锁定资源A。
- 修改资源A的状态。
- 根据资源A的新状态,更新关联的资源B。
- 锁定资源B(或使用其他同步机制)。
- 修改资源B。
- 释放所有锁。
在多核环境下,如果锁的粒度不够细,或者同步逻辑存在瑕疵,攻击者控制的另一个线程就有可能在这多个步骤之间介入,使得资源A和资源B的状态组合处于一种开发者未预料到的“中间态”。例如,资源A已被标记为“正在关闭”,但资源B还未被清理。这种不一致的状态,就是漏洞的根源。
2.2 中断机制的介入与“状态冻结”
在没有中断介入的情况下,利用这类漏洞需要攻击线程与受害者线程进行极其精密的“舞蹈”,成功率低。中断机制的引入改变了游戏规则。攻击者可以 通过编程方式触发一个高优先级的硬件中断(例如,利用性能监控单元PMU、调试中断或外部设备中断) ,在目标代码执行到某个特定的、脆弱的指令序列中间时,强行中断CPU。
此时,发生了一件关键的事情: 被中断的进程或内核线程的完整上下文(包括所有寄存器、程序计数器、栈指针以及它正在操作但尚未完成写入的变量)被“冻结”并保存起来 。CPU转而执行中断服务例程。而攻击者的核心工作,就是在中断服务例程中,或者在中断返回后、被冻结的上下文恢复前的那个瞬间,去篡改那些处于“中间态”的变量。
注意 :这里的中断利用,与通过
signal或alarm发送软件信号有本质区别。硬件中断的优先级更高,可以打断内核态的关键区域(除非被显式禁用),且其触发时机可以通过缓存侧信道、性能计数器等进行相对精确的定位,这为“瞄准”特定的脆弱指令序列提供了可能。
2.3 EXPRACE的攻击模型
论文中构建的攻击模型可以概括为以下步骤:
- 侦查与定位 :攻击者首先通过分析或模糊测试,找到一个潜在的多变量竞争漏洞点。这个点通常位于两个或多个共享变量的操作之间,且缺乏足够的原子性或内存屏障保护。
- 触发与同步 :攻击者运行恶意用户态程序,通过特定的系统调用或IO操作,使内核执行到目标漏洞代码路径。同时,攻击者准备好中断触发机制(例如,配置PMU在某个内存地址被访问特定次数后触发中断)。
- 精确中断 :当内核执行到目标指令序列(例如,刚更新完变量A,正准备去获取变量B的锁时),攻击者触发硬件中断。CPU保存当前受害上下文,跳转到中断处理程序。
- 状态篡改 :在中断处理上下文(或中断返回后、但在受害上下文恢复前,通过另一个高优先级的内核线程)中,攻击者篡改那些处于不一致状态的变量。例如,将变量B指向一个攻击者控制的内存对象。
- 恢复与利用 :中断返回,受害上下文被恢复,它“无知地”继续执行,基于已被篡改的变量B进行操作,从而可能实现权限提升、内存泄露或任意代码执行。
这个过程的核心是,中断为攻击者提供了一个 全局的、高优先级的“暂停”按钮 ,使得原本转瞬即逝的竞争窗口被极大地拉长和稳定化,攻击者可以从容地在“暂停”期间完成状态篡改。
3. 关键技术实现深度解析
理解了原理,我们来看EXPRACE是如何具体实现这套攻击链的。这涉及到对现代CPU架构和操作系统内核的深入理解。
3.1 中断源的选取与操控
并非所有中断都适合用于EXPRACE攻击。理想的中断源需要满足:
- 可触发性 :能从用户态间接或直接触发。
- 可预测性 :其触发能与目标代码执行达到一定程度的同步。
- 低干扰性 :不会导致系统崩溃或目标进程被杀死。
论文中重点探讨了几种可行的中断源:
-
性能监控中断
:这是最优雅的方式之一。现代CPU的PMU可以配置为在发生特定微架构事件(如缓存未命中、分支预测错误、执行了特定指令)时触发PMI。攻击者可以
mmap一个硬件性能计数器到用户空间,通过精心构造的负载,使目标内核代码路径产生特定的事件模式,从而“召唤”出中断。这种方式隐蔽且精准。 -
调试中断
:如果系统开启了内核调试支持(如
kdump或kgdb),调试寄存器(如DR0-DR3)可以设置硬件断点。触发断点会产生调试异常。虽然权限要求较高,但在某些场景下(如攻击者已获得部分权限)是可行的。 - 外部设备中断 :通过用户态驱动或与恶意硬件结合,可以触发如网络、USB设备的中断。这种方式同步精度较低,但更通用。
实操心得
:在真实环境中,PMI是最具研究价值的方向。你需要熟悉
perf_event_open
系统调用,并理解如何配置
event
、
config
等参数来监控如
mem_load_retired.l1_miss
这样的事件。关键在于,要让目标内核路径产生独特的事件“指纹”,从而让你的攻击程序能可靠地区分“何时”触发中断。
3.2 竞争窗口的识别与“夹点”定位
找到合适的中断源后,下一个挑战是: 到底应该在目标代码的哪一条指令处触发中断? 这个点被称为“夹点”。EXPRACE方法要求这个点位于多变量操作的中间,且此时内存状态处于不一致但可被外部干预的时机。
这项工作通常结合静态分析和动态分析:
-
静态分析
:通过对内核源码或二进制文件进行数据流分析,找出那些对多个共享变量进行非原子序列操作的函数。重点关注锁操作(
spin_lock,mutex_lock)、引用计数操作(atomic_inc,refcount_inc)和链表操作之间的代码区域。 -
动态插桩与追踪
:使用如
kprobe、SystemTap或基于eBPF的工具,在可疑函数入口和出口处放置探针,记录变量的地址和值的变化序列。通过运行正常负载和攻击负载,观察变量状态的变化轨迹。 - 模式识别 :分析轨迹,寻找这样的模式:变量A已改变,变量B尚未改变,且两者之间存在一个足够长的指令序列(足以插入中断处理)。这个指令序列的起点,就是潜在的“夹点”。
一个简化的示例模型 : 假设内核有如下代码片段(仅为示意):
// 假设 old_obj 和 new_obj 是全局指针
spin_lock(&list_lock);
old_obj = global_list->obj; // 步骤1:读取旧对象
global_list->obj = new_obj; // 步骤2:更新为新对象
// <--- 理想的“夹点”就在此处!
spin_unlock(&list_lock);
kfree(old_obj); // 步骤3:释放旧对象
在步骤2之后、步骤3之前,
global_list->obj
已经指向
new_obj
,但
old_obj
还未被释放。如果在这个“夹点”触发中断,并在中断处理中想办法让
old_obj
被其他内核路径重新引用或篡改,那么当步骤3执行
kfree
时,就可能导致释放后使用或双重释放。
3.3 中断上下文中的状态篡改艺术
在中断服务例程中直接进行复杂的漏洞利用操作通常是困难的,因为ISR运行在中断上下文中,有诸多限制(不能睡眠、不能调用可能阻塞的函数等)。EXPRACE论文提出了更巧妙的两种方式:
- 中断处理程序内嵌利用代码 :对于简单的状态翻转,可以直接在ISR中完成。例如,仅仅是将一个指针从指向合法对象改为指向攻击者控制的内存页。这要求ISR代码本身简洁高效。
-
触发异步利用线程
:这是更通用和强大的方法。在ISR中,并不直接执行利用代码,而是通过设置一个标志位、发送一个IPI(处理器间中断)或唤醒一个预先部署在内核中的高优先级攻击线程(例如,通过
kthread创建)。当中断返回后,调度器可能会优先运行这个被唤醒的攻击线程,让它在被中断的受害者线程恢复之前,完成对共享变量的篡改。这种方式赋予了攻击者更大的灵活性和能力。
注意事项
:在中断上下文中操作需要极度小心内存屏障和CPU缓存一致性。你修改的数据可能还在当前CPU的缓存中,而受害者线程可能运行在另一个CPU上。需要使用如
smp_wmb()
等内存屏障指令,确保修改对其他CPU可见。否则,可能会出现“明明改了值,对方却看不到”的诡异问题,导致利用失败。
4. 实战推演:构建一个简化的EXPRACE攻击实验
为了将理论落到实处,我们设计一个在Linux内核模块中模拟的、高度简化的实验环境,来演示EXPRACE的核心思想。 警告:此实验仅应在完全隔离的测试环境或虚拟机中进行,切勿在生产系统尝试。
4.1 实验环境搭建
我们假设一个最简单的“多变量”场景:一个内核模块维护两个全局变量
data_valid
(int) 和
data_ptr
(void *)。其设计逻辑是:只有当
data_valid == 1
时,
data_ptr
才指向一个有效的内存区域。一个“脆弱”的清理函数如下:
static int data_valid = 0;
static void *data_ptr = NULL;
static void vulnerable_cleanup(void) {
if (data_valid) { // 步骤A:检查有效性
// <--- 理想夹点:此时 data_valid==1, data_ptr 有效
kfree(data_ptr); // 步骤B:释放指针
data_ptr = NULL; // 步骤C:置空指针
data_valid = 0; // 步骤D:标记无效
}
}
正确的逻辑是A->B->C->D原子完成。但这里没有锁,假设我们允许它在中断中被调用。
4.2 攻击者模块设计
攻击者模块需要做三件事:
- 准备恶意数据 :分配一块受控内存,填充特定内容(如提权shellcode的地址)。
-
触发漏洞路径
:通过IOCTL等接口,设置
data_valid=1并让data_ptr指向一个合法内核对象。 -
部署中断处理器
:注册一个中断处理函数(例如,利用一个可用的软件中断或模拟的硬件中断)。在该处理函数中,篡改
data_ptr,使其指向步骤1中准备的恶意数据。
4.3 攻击流程模拟
- 加载受害者模块和攻击者模块。
-
攻击者通过IOCTL初始化数据,使系统进入
data_valid=1, data_ptr=合法地址的状态。 -
攻击者触发一个对
vulnerable_cleanup函数的调用(例如,通过另一个IOCTL)。 -
在
vulnerable_cleanup执行到步骤A之后、步骤B之前(即判断为真后,即将执行kfree时),攻击者触发中断。 -
CPU跳转到攻击者注册的中断处理函数。在该函数中,执行:
data_ptr = &malicious_data;(篡改指针)。 -
中断返回,
vulnerable_cleanup继续执行步骤B。此时,它本应释放原来的合法地址,但现在实际释放的是malicious_data的地址(这本身可能造成问题)。更重要的是,如果后续有其他内核代码相信data_valid为1时data_ptr有效,并去读取data_ptr的内容,它们读到的将是攻击者控制的恶意数据。
这个实验极大地简化了真实内核的复杂性,但它清晰地展示了EXPRACE的核心流程: 中断冻结状态 -> 在冻结期间篡改多变量中的关键变量 -> 恢复执行导致非预期行为 。
4.4 实验难点与技巧
-
中断同步
:在真实攻击中,让中断恰好发生在步骤A和步骤B之间是最大的挑战。在实验中,我们可以采用“笨办法”:在
vulnerable_cleanup的步骤A后加入一个小的延迟循环(如udelay(100)),人为扩大竞争窗口,方便中断触发。在真实研究中,则需要借助PMU等精密工具。 -
内存屏障
:在中断处理函数中修改全局变量后,应使用
smp_wmb()确保写入立即对其它CPU可见。 -
稳定性
:这种攻击可能会因为中断处理时机稍有偏差而导致内核崩溃(Oops)。在测试时,需要结合
printk输出大量调试信息,并做好系统随时崩溃的准备。
5. 防御思路与缓解措施探讨
EXPRACE揭示的攻击面是深刻且危险的,因为它利用了硬件机制本身,绕过了许多基于软件逻辑的同步假设。防御需要从多个层面进行:
5.1 内核编程范式改进
-
真正的原子操作
:对于关键的多变量状态更新,应尽可能使用真正的原子操作,或者使用能够保护多个变量的锁。例如,将上面的
data_valid和data_ptr封装在一个结构体中,并用一个自旋锁保护整个结构体的读写。 - 顺序锁与RCU :对于读多写少的场景,考虑使用顺序锁或RCU(读-复制-更新)机制。RCU尤其适用于EXPRACE这类攻击,因为它的更新机制(发布新指针)本身是原子的,读者总能看到一个一致的数据视图。
-
禁止中断的临界区
:在极其关键、短小的代码路径上,可以考虑使用
local_irq_save/local_irq_restore来完全禁止中断。但这会损害系统实时性,需谨慎使用,且范围必须尽可能小。
5.2 硬件与架构支持
- 事务内存 :硬件事务内存允许将一段代码块声明为原子执行。如果这段代码执行期间被中断或其他冲突导致事务中止,所有修改都会回滚。这可以从根本上消除竞争窗口。但目前其在通用操作系统内核中的应用还处于早期阶段。
- 更精细的中断控制 :未来的CPU或许可以提供一种“延迟中断”机制,对于某些非紧急中断,如果CPU正处于内核中标记为“高度敏感”的代码区域,可以将其排队,稍后处理。但这需要硬件、操作系统和驱动程序的协同设计。
5.3 检测与监控
-
静态分析工具增强
:现有的数据竞争检测工具(如Linux内核的
KCSAN)需要加强对“中断安全”的分析。工具应能标记出那些在访问共享变量时未考虑中断上下文可能性的代码区域。 - 动态模糊测试 :在模糊测试框架中,可以集成“模拟中断”的变异策略。在测试内核代码时,随机地在代码执行过程中注入模拟的中断事件,观察内核状态是否会出现不一致,从而发现潜在的EXPRACE类漏洞。
- 运行时监控 :可以设计内核监控模块,对关键的数据结构进行“影子”跟踪,记录其状态变迁。如果检测到在单次操作过程中,数据结构的状态序列违反了预定义的一致性规则(例如,先标记无效再释放),则发出警告。
EXPRACE论文的价值不仅在于提出了一种新的攻击方法,更在于它迫使整个社区重新审视“并发安全”的边界。在中断面前,很多基于多线程同步的假设都变得脆弱。作为安全研究者或开发者,我们需要建立一种更全面的“并发心智模型”,将中断、异常、NMI等异步事件都纳入考量,才能设计出真正坚固的系统。这无疑是一条漫长且充满挑战的道路,但也是提升软件系统安全性的必经之路。
475

被折叠的 条评论
为什么被折叠?



