研究了三个星期的实验,第一篇被推到公司公众号的文章 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 程序才能执行的操作。
攻击链
- 找到 Core 0 的调试寄存器地址;
- 编写调用 Core 0 调试寄存器的代码。在代码中通过 Core 0 读写 EL3 权限下的数据;
- 在 root 权限下将代码编译成 LKM,并注入到内核中;
- 在内核中默默完成执行,通过
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)