Published on

利用 ARM 核间调试漏洞获得 SoC 硬件最高权限(上)

Authors
  • avatar
    Name
    wellsleep (Liu Zheng)
    Twitter

研究了三个星期的实验,第一篇被推到公司公众号的文章 LINK


曾经以为 拥有了 root 就拥有了全世界

直到遇见 TA 才明白 其实还有更美好的未来(大误)

前言

获得操作系统的 root 权限一般认为是渗透攻击的终点。然而在基于 TEE(Trusted Execution Environment) 的安全方案的加持下,即使是 root 权限,对于获取敏感信息也往往无能为力。其原因,是在基于 ARM 处理器的架构中,有比 root 权限等级更高的由硬件保证的特权分级(Privilege Level 或 Exception Level)。在最高的特权等级下,甚至可以在运行时修改虚拟 - 物理地址映射等重要参数。

在 2019 年 5 月,来自 Wayne Univerisy 的 NING Zhenyu, ZHANG Fengwei 披露了利用 ARM 核间调试的功能,从普通世界 EL1 权限提权到 EL3,以绕过 ARM 处理器上的特权隔离机制,访问安全世界数据的方法 [1]。他们给这种攻击方法取名为 Nailgun Attack。

同年 7 月,来自顶尖安全团队 Project Zero 的 Brandon Azad 发现用于 iPhone X 的 A11 芯片上,类似的核间调试机制也没有禁用,使得攻击者可以进行内核调试,并可能获取更多 SecureROM 中的信息 [2]。Brandon 把这个消息报告给了 Apple,但由于没有造成明显的危害,没有作为安全风险获得承认 [3]。

我们仔细研究了 Nailgun Attack 的形成原因和攻击路径,并认为该攻击足以对某些应用场景造成安全威胁。在此,我们将分享该攻击的原理,并给出实际攻击步骤。

前置知识

为了更好的理解 Nailgun Attack 的机制和危害,在前置知识中,我们将介绍该攻击中用到的一些基本概念。为了简化描述,在实施 Nailgun Attack 之前,有如下假设:

  • 目标设备为 ARMv8-A AArch64 系统
  • 攻击者拥有 root 权限

Linux 内核简述

Nailgun Attack 以 Linux 为基础,可以扩展到类 Linux 的系统,如 Android、iOS 等,这也是 ARMv8-A 的主要运行平台。

操作系统原理就不展开了,简单来说:

  • 内核(kernel)是操作系统中负责重要操作的,具有高权限的程序。内核作为硬件和应用软件的中间层,帮助应用程序有序地访问硬件资源。
  • Linux 内运行的用户程序都属于应用程序,处于用户态,没有直接访问硬件的权利。
  • 如果有程序需要直接访问硬件资源,必须进入高权限的状态,称为内核态。
  • 内核态执行需要 root 权限。

在 Nailgun Attack 中,我们需要编写的代码直接访问硬件寄存器,也就是在内核中运行。Linux 为此提供了一个功能接口,叫 Loadable Kernel Module。将程序编译成 LKM 可接受的格式后,通过 insmod 命令,可以将代码加载到内核态运行。

ARM 的硬件权限

在硬件上,ARM 为 ARMv8 AArch64 系统设计有多级软件执行权限,称之为异常等级(Exception Level)。各个 EL 等级所对应的软件功能不同,简单来说,软件执行时所处的硬件 EL 等级越高,软件代码的权限越大。而最高等级 EL3 的代码,往往是经过验证且不允许修改的 BootROM 代码。

借一张 ARM TEE 的经典结构图 [4]。举例来说,图中 EL0 级的应用,为普通用户态的程序。EL1 级为 root 权限的内核态程序,EL 2 级为通常服务器上构建虚拟化实例的程序,而 EL3 级为 BootROM 中的基础加载程序,负责启动流程和关键异常的处理,在 ARM 架构中,通常称为 ARM Trusted Firmware(ATF)。

一旦处理器处于 EL3 状态,意味着可以修改任意低于此权限的数据、代码和寄存器。因此,进入 EL3 状态的接口和渠道是被严格控制。正常情况下,从 EL1 提权到 EL3,只有通过 smc 指令陷入系统中断的方式进入,且中断处理程序是 BootROM 编译时就固定好的,无法在运行时修改。

ARM 的核间调试

在 ARMv8-A 的处理器家族中,几乎所有的设计都以多核的方式成为最终产品。从 A53 / A55 / A57,到 A72 / A73 / A75,“多核 A53 架构” 和 “big.LITTLE” 是近几年最耳详能熟的产品架构代称。ARMv8-A 处理器在 ARM 的 IP 设计之初,除了能够通过 JTAG 接口连接硬件调试器调试之外(如下图 [5]),在多核产品中还可以通过核间调试的方式,通过 ARM 核心内的调试模块,用一个核心调试另一个核心中的程序。这样不光免去了购买、连接硬件调试器的麻烦,更可以达到“人在家中坐,机器天上调”的目的。

为了达到这样上帝般的体验,ARM 在 IP 设计时为每一个 ARMv8-A(包括部分 ARMv7-A)核心都默认包括了这样的调试电路。翻开六千多页的 ARMv8 Architecture Reference Manual(ARM ARM,真鸡贼) [6],在 'External Debug Register Descriptions' 中就可以看到两组寄存器描述,External Debug Registers(EDR) 和 Cross-Trigger Interface registers(CTI),它们是用来控制 ARM 核心的调试状态和操作。ARM 为它们及它们的兄弟们取了一个响亮的名字,叫 CoreSight(R)。CoreSight 组件中的其他功能寄存器,在此不再赘述。对于 Nailgun Attack 而言,以上两组寄存器几乎可以满足全部攻击需求。

如果让处理器核心 Core 0 处于调试状态,它执行程序时,硬件的异常等级该如何判定呢?

答案是:不判定。即代码执行时,不受硬件 EL 等级的限制。

漏洞

由于处于调试状态的 Core 0 所执行的代码不受当前异常等级的限制,因此 Core 0 实际可以执行任意异常等级状态下的代码。结合核间调试的功能,我们就可以从 Core 1 通过调试接口发送高权限的执行代码给 Core 0,让 Core 0 执行完毕后通过调试接口将结果回复给 Core 1,这样我们就从处于 EL1 的程序执行了 EL3 程序才能执行的操作。

攻击链

  1. 找到 Core 0 的调试寄存器地址;
  2. 编写调用 Core 0 调试寄存器的代码。在代码中通过 Core 0 读写 EL3 权限下的数据;
  3. 在 root 权限下将代码编译成 LKM,并注入到内核中;
  4. 在内核中默默完成执行,通过 printk 或其他方式(甚至做成驱动返回到用户态)回复数据;

影响面

所有 ARMv8-A 多核处理器产品都可能受此影响。

由于通过 CoreSight 进行核间调试是硬件特性,因此只有芯片供应商进行硬件改版才能从根本上解决问题。

阻止攻击链中 root 权限或 LKM 加载功能,也可以达到缓解的目的。

后记

尽管攻击链只有短短四步,但第一步中如何找到调试寄存器就很玄妙... :P

由于 SoC 生产厂商并不会公开所有的寄存器地址,即使从 ARM 公开的手册中获取到 EDR 或 CTI 的偏移地址,其基址位置仍然是秘密。通过一些厂商的 SoC 开发板资料可以略窥一二,不过准确的寄存器基址还需要有对 ARM 架构更深层的研究。

欲知后事如何,且听下回分解。

参考资料

[1] https://compass.cs.wayne.edu/nailgun/>

[2] https://googleprojectzero.blogspot.com/2019/10/ktrw-journey-to-build-debuggable-iphone.html

[3] https://bugs.chromium.org/p/project-zero/issues/detail?id=1900

[4] https://developer.arm.com/docs/100935/0100/switching-betwen-the-normal-and-secure-worlds

[5] https://www.suse.com/c/debugging-raspberry-pi-3-with-jtag/

[6] DDI0487b_a_armv8_arm.pdf (最新版本已进化到 DDI0487f)