CVE-2022-22063 是某些较旧的高通芯片组中 hypervisor 固件的一个安全问题。未受保护的硬件组件("启动重映射器")可被滥用,从修改后的操作系统获得对 hypervisor 的完全读/写访问权限(权限提升)。在受影响的平台上利用该问题非常简单,因为不需要知道特定固件版本(例如地址或变量)的知识。
注意: 尽管高通已向客户提供了修复(有足够时间发布更新),但许多受影响的设备已经相当老旧,可能无法收到供应商的修复。该问题只能从修改过或已受损的操作系统(利用另一个安全问题)中利用。即使固件存在漏洞,保持操作系统更新和安全也可能足够。
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H该问题也已发布在[高通 2022 年 12 月安全公告]中。
该问题取决于受影响目标上的硬件和软件组合:
hyp 分区内的 ELF 映像)。APCS_BOOT_START_ADDR_NSEC 的硬件寄存器进行配置),该重映射器未受 hypervisor 保护,因此可由权限较低的操作系统内核(例如 Linux)访问。还有几个芯片组可能具有受影响的硬件(例如 MSM8909 和 MSM8953),但它们没有独立的 hypervisor 固件可被攻陷。
权限提升: 给定一个已被入侵的操作系统内核(例如 Linux),该问题允许轻松将权限提升到 hypervisor 级别(ARM 上是 EL1 -> EL2)。可以读取或写入由 hypervisor 管理的所有内存。这打破了由 hypervisor 管理的不同安全域或虚拟机(如果有的话,取决于配置)之间的隔离。
(另请参阅:[Snapdragon 平台上的访问控制简介])
安全启动: 生产环境中可用的多数高通设备都使用安全启动来防止固件的未授权修改。固件由启动链进行加密签名和验证。该问题允许在运行时从已修改的操作系统(通过官方支持的“引导加载程序解锁”或另一个漏洞)修改甚至完全替换已加载的 hypervisor 固件。
(另请参阅:[高通安全启动与镜像认证技术概述 (v1.0)] 和 (v2.0))
注意: 该问题最初是在高通 Snapdragon 410 (MSM8916) 平台上发现的。以下某些解释可能特定于 MSM8916,例如:
然而,总体概念类似地适用于所有受影响平台。
ARMv8-A 64 位架构定义了 4 个特权级别(“异常级别”,EL)。通常用于应用程序、操作系统内核和 hypervisor 的级别是分开的:
CPU 在异常期间(例如由于传入中断)在这些级别之间切换。也可以使用特殊指令(例如 Hypervisor Call (hvc))在部分级别之间切换。
(另请参阅:[AArch64 异常模型])
Hypervisor 可以托管一个或多个具有独立操作系统内核的虚拟机。每个虚拟机可以通过第 2 阶段转换获得自身的内存视图。来自虚拟机的所有内存访问都经过两个转换阶段:第一阶段由(虚拟)操作系统管理,而第二阶段由 hypervisor 管理。hypervisor 或其他虚拟机的内存可以通过在转换表中省略来隐藏。
(另请参阅:[AArch64 虚拟化],[AArch64 内存管理])
高通的 hypervisor 固件运行在 EL2,并使用第 2 阶段转换禁止运行在 EL1 的主操作系统内核(通常是 Linux)访问 hypervisor 内存。注意,在此设置中,第 2 阶段转换主要用于内存保护,而不进行地址转换。主操作系统可以直接访问内存映射输入/输出 (MMIO) 空间中的大多数硬件组件,例如 SD 控制器或相机子系统。访问属于 hypervisor/EL2 (hyp) 和安全监控器/EL3(tz 的一部分)的内存受到限制:
启动重映射器与虚拟化无关:它在 CPU 内核的早期启动期间是必需的。在此硬件平台上,CPU 内核始终从地址 0x0 开始执行。启动重映射器是一个围绕 CPU 构建的额外硬件组件,将前 64 或 128 KiB (0x00000 - 0x20000) 重映射到可配置的内存区域。
默认情况下,启动重映射器指向引导 ROM(设备启动时首先运行的代码)。稍后映射会被更改,以便其他 CPU 内核立即在已加载到 RAM 中的 EL3 固件(tz 的一部分)中开始执行:
注意 CPU 访问的地址(在 tz 内)如何通过两个不同的物理地址访问:RAM 中的真实地址 (0x8650xxxx) 和使用启动重映射器的重映射地址 (0x0000xxxx)。
实际上有两个独立的启动重映射器实例:
APCS_BOOT_START_ADDR_SEC (= 0x0b010004) 进行配置,但仅在安全状态下。APCS_BOOT_START_ADDR_NSEC (= 0x0b010008) 进行配置,即使在非安全状态下。两个启动重映射器实例都可通过一个内存寄存器进行配置,该寄存器包含重映射区域的基本地址和两个配置位:启用重映射的 REMAP_EN 和将前 128 KiB 而不是仅 64 KiB 进行重映射的 BOOT_128KB_EN。
(另请参阅:[高通 Snapdragon 410E 技术参考手册 rev. D],第 85 和 116 页)
利用前两节的知识,基本思想很简单:使用启动重映射器绕过 hypervisor 的内存保护(第 2 阶段转换)。
启动重映射器不仅在 CPU 启动期间有效。它可以在任何时候使用,并允许对重映射区域进行完全读/写/执行访问。此外,高通的 hypervisor 似乎没有阻止操作系统在受影响的设备上配置和访问非安全实例的启动重映射器(它未受第 2 阶段转换保护)。因此,该问题很容易通过以下方式利用:
hyp),然后重映射区域可以动态移动(逐块移动),以访问通过启动重映射器可用的 64/128 KiB 更大的内存区域。也可以使用此方法完全禁用 hypervisor 内存保护(参见概念验证)。
注意: 相同的利用方法不适用于安全世界固件 (tz)。虽然启动重映射器允许绕过第 2 阶段转换,但 DRAM 中的 tz 内存区域似乎受到额外的硬件组件(CPU 外部)的保护,该组件在访问通过启动重映射器后将其阻止:
tz 内存区域可能仅在安全状态下才可访问。该利用方法仅允许绕过 hypervisor 的内存保护,其他硬件安全机制仍然有效。
无需了解固件版本,即可在运行时使用启动重映射器完全禁用并替换原始 hypervisor 固件。特别地,不需要使用逆向工程来获取可能被修改变量和函数的内存地址。只需知道 hypervisor 固件的大致内存区域即可,例如来自开源 Linux 代码中的内存预留,或通过读取 hypervisor 固件二进制文件(在内部存储的 hyp 分区中可用)的 ELF 头。
总体思路是:
hvc),从操作系统切换到 hypervisor(从 EL1 到 EL2)。实现此功能的代码并不长,但涉及一些低级 AArch64 汇编和与 CPU 缓存的仔细交互。然而,主要问题仍然悬而未决:如何在不使代码特定于某个 hypervisor 固件版本的情况下确定 shell 代码应写入的位置?
在 Hypervisor Call(或任何异常通常)期间,CPU 执行被强制到一个特殊的内存地址:异常向量。异常向量是更大的向量表的一部分,该表包含处理来自当前或较低异常级别的不同类型异常的代码:
每个框代表一个异常向量,具有 32 条汇编指令的空间。这空间不够,因此它们通常包含分支指令,跳转到有更多空间用于额外代码的其他地方。
偏移量相对于定义每个异常级别向量表基地址的向量基地址寄存器 (VBAR)。hypervisor 将基地址写入 VBAR_EL2 CPU 寄存器。
Hypervisor Call 是一个从较低异常级别(运行在 EL1 的操作系统内核到 EL2 的 hypervisor)发出的同步异常。如果操作系统内核以 32 位模式运行,CPU 将跳转到 VBAR_EL2+0x600;如果以 64 位模式运行,则跳转到 VBAR_EL2+0x400。在通过启动重映射器将自定义代码写入此地址并发出 Hypervisor Call 后,CPU 将开始执行 shell 代码。
不幸的是,VBAR_EL2 对运行在 EL1 的操作系统内核不可读。它只能由 hypervisor (EL2) 本身或更高级别读取。尽管如此,这个知识使得通过暴力猜测入口地址变得更容易:向量表的基地址必须对齐到其大小的倍数(0x800 = 2 KiB)。这意味着在 128 KiB 区域内只有 64 个可能的位置,在 1 MiB 区域内有 512 个:
红色框显示了在 hypervisor 调用期间 CPU 可能跳转到的所有可能位置。将 shell 代码写入所有这些位置足以保持该方法独立于某个特定的固件版本(实际版本会在一个特定地址拥有向量表)。
这可以进一步改进:启动重映射器允许读写访问,因此可以根据从内存位置读取的现有代码/数据添加一些启发式规则。它应该包含有效的 AArch64 (A64) 指令以及一些重复的填充字节,如 NOP 或分支指令。(每个异常向量的 32 条指令空间通常仅部分使用,因为分支到一个有更多空间的适当函数更容易。)
此存储库中包含的概念验证代码是对适用于 Snapdragon 410 (MSM8916/APQ8016) 平台的高通开源 Little Kernel (LK) 引导加载程序的修改,最初旨在与 DragonBoard 410c 开发板一起测试。选择所有这些是为了简单起见,该问题也可以从其他操作系统(例如 Linux)、其他受影响平台甚至具有安全启动的设备中利用——只要有一种在操作系统内核内执行自定义代码的方法。