现代 CPU 的信任模型建立在一条公理上:ring 3 的代码不可能直接碰 ring 0 的内存,除非先找到一个漏洞。project:rosenbridge 做的事情是绕开这条公理,不利用漏洞,直接在部分 x86 处理器上执行一条指令,把内核内存当自己的内存来读写。

作者 Christopher Domas(@xoreaxeaxeax)把这套工具和研究开源在 github.com/xoreaxeaxeax/rosenbridge。这不是新发布,但它完整示范了"硬件后门长什么样、怎么被找出来、怎么关掉"这三件事,值得逐层拆开看。

后门本身长什么样

rosenbridge 不是软件漏洞,而是主 x86 核旁边多出来的一个非 x86 小核。访问路径分三步:

  1. 一个 model-specific register(MSR)上的控制位负责使能;
  2. 一条 launch instruction 把执行切进那个小核;
  3. 之后命令被包在特殊格式的 x86 指令里送过去,小核执行所谓的 DEIS(Deeply Embedded Instruction Set)。

它能绕过所有内存保护和特权检查。README 里有一句话值得单独拎出来:这个东西比 Management Engine 和 Platform Security Processor 嵌得更深,因为它不只能访问 CPU 的全部内存,还能访问寄存器堆和执行流水线。

"应该需要 ring 0 才能打开"是设计意图,但实测发现部分系统上这个后门是默认打开的。也就是说,任何非特权代码都能改内核。

触发路径与 PoC

lock/lock.c 里能看到关键常量:

#define BACKDOOR_MSR     0x00001107
#define BACKDOOR_TOGGLE  0x00000001
#define MSR_DEV "/dev/cpu/0/msr"

控制位是 MSR 0x1107 的 bit 0,清掉它就把后门锁上。esc/escalate.c 是提权的骨架:

int main(void)
{
    __asm__ ("movl $payload, %eax");
    __asm__ (".byte 0x0f, 0x3f");
    __asm__ ("payload:");
    #include "bin/payload.h"
    system("/bin/bash");
}

EAX 指向 DEIS 程序,0F 3F 是那条 launch instruction,后面的字节交给隐藏核心执行。esc/payload.asm 用自定义的 DEIS 汇编写了一条完整提权链,注释说明是针对 Debian 6.0.10(i386)写的:

  • lgd eax 取 GDT 基址;
  • 读 GDT 偏移 0x78 处的内核段描述符,拆出 fs_base;
  • 从 fs_base 读出 task_struct 指针;
  • 从 task_struct 偏移 0x208 读出 cred;
  • 把 cred 偏移 0x4、0x8、0xc、0x10 上的 uid、gid、euid、egid 四个字段全部写 0。

这段逻辑和常规内核提权 exploit 一模一样,区别在于它不靠任何内存破坏,每一步都是隐藏核心代劳。DEIS 的助记符也自成一套:lgd、izx、ada、ad2、ld4、st4、zl3、la8、ra8、cmb。

研究方法比结论更值得借

这套研究的方法论有参考价值:

  • sandsifter 用来系统性扫未知指令,launch instruction 和 bridge instruction 就是这么找出来的。
  • fuzz/ 按阶段拆成三个工具:deis 探索隐藏核心的能力,wrap 定位 x86 侧的 bridge instruction,exit 是早期以为需要退出序列时写的,后来找到的处理器不需要,该目录直接废弃。这种把死路留在仓库里的做法很少见,但很有用。
  • kern/ 是一组内核模块和工具,用来观察被模糊测试的 DEIS 指令到底改了哪块内核内存。kern/deis_kernel.c 注册了一个字符设备,暴露 GET_BUFFER_ADDRESS、GET_BUFFER_SIZE、READ_BUFFER、RESET_BUFFER 四个 ioctl,配一个 32 字节的哨兵 buffer:发一条 DEIS 指令,再看 buffer 变没变,就能确认读写是否穿透到了内核。
  • proc/extract.py 从模糊测试日志里归纳指令行为类别。
  • asm/deis_asm.py 是 DEIS 的汇编器,把自定义汇编翻译成 x86 字节。

这里有个概念要分清:launch instruction 和 bridge instruction 是两个不同的东西。前者负责把执行从 x86 核切到隐藏核,后者负责把后续命令送进去。项目早期以为还需要一条 exit 序列切回来,后来发现找到的那颗处理器不需要,fuzz/exit/ 就整体废弃了。

fuzz/manager/ 是配套的分布式模糊测试调度器,也是整个仓库里工程量最容易被低估的部分。它把任务分到一组 worker 上执行,power/relay_ftdi.py 和 power/relay_serial.py 通过 FTDI 或串口继电器给被测机器物理断电重启,watch_sessions.py 盯着会话状态,util/indent.py 负责把日志缩进成可读的树。硬件模糊测试必然挂机,能远程断电是刚需而不是优化。

lock 和 unlock 两个工具直接操作那个 MSR 位;fix/ 则把锁定动作包装成开机早期脚本,fix/lock_deis.sh 只有两行——modprobe msr 加一句执行 lock 二进制。

影响面,以及别夸大

README 写得很克制:只有 VIA C3 受影响,C3 之后的代次不再包含这个特性。C 系列的市场定位是工业自动化、POS、ATM、医疗设备,以及一部分消费级笔记本和台式机。所以这不是"所有 x86 都有后门",而是特定一代嵌入式处理器的历史遗留。

作者给的定性也值得引用:他们认为这个功能当初是作为嵌入式市场的有用特性善意实现的,在某些早期代次上被无意地留在了默认开启状态,研究没有暗示恶意意图。

检测与关闭

检测工具必须跑在裸机上,不能进虚拟机,而且处于 alpha 状态,在不受影响的系统上可能崩溃、panic 或挂住:

git clone https://github.com/xoreaxeaxeax/rosenbridge
cd rosenbridge/util
make
sudo modprobe msr
sudo ./bin/check

如果确认中招,fix/ 提供开机早期关闭的方案:

cd fix
make
sudo make install
reboot

一个必须说清楚的局限:如果攻击者已经拿到内核权限,它仍然可以把后门重新打开。这个脚本是"在启动早期纠正问题"的范例,不同系统需要自行适配。

我的做法

  1. 怀疑硬件之前先量化范围。这套工具是针对特定处理器家族和核心写的,稍微改过的实现就会漏检。所以 check 报告"没检测到"不等于安全,只等于"没匹配上已知形态"。
  2. 把这套方法论借过来。sandsifter 扫未知指令、自定义内核哨兵 buffer 观察内存变化、从模糊测试日志里归纳行为模式,这三段式可以套到任何"想验证某个不可见执行单元到底做了什么"的场景,不一定非得是 CPU。
  3. 该关的早关。如果设备清单里有 C3 工控机,别指望常规漏洞管理流程能覆盖到它。modprobe msr 加上开机早期 lock 是成本最低的一步,而且它只需要做一次。

硬件后门研究里最实用的一句话大概是:你不是在找一个 bug,你是在找一块不在图纸上的硅。找得到的前提是,你先假设它存在。

⚠️ 网络安全免责声明

本文内容仅供技术研究与安全学习之用。文中描述的漏洞分析、攻击链拆解和代码示例均基于公开披露的安全研究,旨在帮助开发者理解攻击原理、提升安全意识和防御能力。

请勿将本文所述技术用于任何未经授权的安全测试、渗透攻击或非法用途。任何因滥用本文信息而造成的法律后果,由使用者自行承担,与本文作者无关。

如发现系统安全漏洞,请通过合法渠道向相关厂商或平台报告,共同维护网络安全生态。