返回

文章详情

利用系统管理模式进行非常长的中断

Hacker News2026年8月10日 16:03

利用系统管理模式进行非常非常非常非常非常非常非常长的中断。概述事实证明,只需一个极长运行的机器指令,就可以破坏SMM——即每个x86 CPU后台隐形运行的安全、超特权执行环境。SMM要求所有核心同时处于SMM或不处于SMM。没有这个,安全模型无法正常工作——当一个线程进入SMM时,会使其他所有线程也进入。要打破这一点,我们只需找到一个太忙而无法注意到它们应该加入SMM的线程。这个过程大致如下:核心0 - 开始一个长指令 ||| 核心1 - 邀请核心0加入SMM ||| 核心1 - 进入SMM ||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 等待核心0 ||||||||||||||||||||||||||||||||| 核心1 - 放弃 核心1 - 做秘密的SMM工作 核心1 - 完成SMM ||| 核心0 - 加入SMM此时,核心1处于SMM外,而核心0处于SMM中,让核心1攻击核心0。问题是:为了使其生效,我们需要一个非常、非常、非常长的指令——比任何指令预期的执行时间都要长。现代CPU上的大多数机器指令都很快:加法只需1个周期。为了让核心1放弃等待核心0,我们需要一个在核心0上执行的指令,持续约4000000000个周期——超过1秒的实际时间。 一秒超时 x86固件在CPU核心进入SMM时运行以下代码:for ( Timer = StartSyncTimer (); ! IsSyncTimerTimeout ( Timer , mTimeoutTicker ) && SyncNeeded ; ) { mSmmMpSyncData -> AllApArrivedWithException = AllCpusInSmmExceptBlockedDisabled (); if ( mSmmMpSyncData -> AllApArrivedWithException ) { break ; } CpuPause (); } 该代码等待所有核心进入SMM,或最多等待1秒,以先发生者为准。为了让一个核心在另一个核心继续执行SMM代码的同时,保持在SMM外,我们需要那个外部核心在整整一秒内保持不可中断——SMI是在指令边界上进行的,因此两个指令之间的任何间隙都是暂挂SMI进入的门。因此,延迟必须是单个指令:一个可以跨越一秒约定的不可中断操作。 概念验证 有多种方法可以达到被禁止的1秒指令,具体方法会因平台而异。但是,大致上:找到一个高延迟的MMIO地址,然后说服CPU尽可能慢地读取它——利用一个以缓慢速度响应读取的未记录区域,然后利用指令集架构提供的最宽载入在单个指令中跨越一大堆字节,理想情况下在其他核心试图用户同一个总线上时,让整个过程在争用中威嚣而行。一次读取,一条指令,然后CPU被困住得持有它近一秒。提供的概念验证是针对Zen 3 Ryzen 7 5800H优化的,其中一个来自慢MMIO的宽xmm加载在0xfcc68860上停顿足够长的时间以破坏所有核心的约定:mov $ 0xfcc68860 , % rsi ; 目标MMIO地址 vmovdqu (% rsi ) , % xmm0 ; 非常,非常长的加载 概念证明通过让两个核心相互对抗来利用这一点。一个核心被长指令保持在SMM外——一个对非常慢加载的严格循环:/* 受害核心:旋转在 ~1秒加载上,忙于回答SMI */ for (;;) asm volatile ( "vmovdqu (%0), %%xmm0" :: "r" ( mmio ) : "xmm0" ); 同时另一个核心启动每个核心的SMI计数器:#define MSR_PERF_CTL0 0xc0010200 /* AMD核心性能事件选择MSR */ #define MSR_PERF_CTR0 0xc0010201 /* 配对的48位计数器 */ for ( int cpu = 0 ; cpu < ACTIVE_CPUS ; cpu ++ ) { msr_write ( cpu , MSR_PERF_CTL0 , 0x43002b ); /* EN | OS | USR | 事件0x2b */ msr_write ( cpu , MSR_PERF_CTR0 , 0 ); /* 将计数清零 */ } 然后发起一场SMI风暴:asm volatile ( "outb %%al, $0xb2" :: "a" ( 0 )); /* 启动端口0xb2 -> #SMI */ 并读取每个核心的计数器:/* ...发起风暴,然后读取每个核心的计数... */ uint64_t delta = smi_max - smi_min ; if ( delta ) puts ( " !!! 一个核心在SMM外运行 " ); 计数器讲述了故事。如果它们不同,则意味着一个核心在SMM外继续运行,而其他核心被拉入——它在SMI休眠期间存活下来,而其他核心在处理SMI时没有它。

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡