usbliter8 - Apple A12/A13 bootROM漏洞利用

Lobsters Hottest 论文

摘要

本文详细介绍了苹果A12/A13 SoC中一种新颖的bootROM漏洞,该漏洞利用USB控制器中的硬件缺陷和配置缺陷,从而破坏了启动链。文中还提供了一个概念验证。

<p><a href="https://lobste.rs/s/sclobo/usbliter8_apple_a12_a13_bootrom_exploit">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/06/18 16:04

# 范式转变 - 介绍 usbliter8 来源:https://ps.tc/pages/blog-usbliter8.html **TLDR**\- 这里是PoC (https://github.com/prdgmshift/usbliter8) 本文详细描述了我们团队发现并利用的一个新颖的iPhone BootROM漏洞。内容包括底层bug、相关的利用技术,以及实现应用处理器启动链攻陷所需的漏洞利用后步骤。该漏洞利用结合了USB控制器中的硬件缺陷和设备固件中存在的特定配置错误。 当前支持的SoC包括Apple A12、S4/S5和A13。虽然技术上可能支持A12X/Z,但尚未实现。我们将实现限制在这些设备上,因为在这个范围内成功演示利用足以充分验证漏洞本身和利用策略。 通过发布这项研究及随附的概念验证,我们旨在记录此类硬件漏洞在现实世界中的影响,促进对现代BootROM安全性更广泛的理解,并证明即使是最新的SecureROM版本仍然容易受到细微硬件缺陷的影响。 由于这些漏洞存在于不可变代码中,受影响的用户应知悉,迁移到更新的硬件仍然是最有效的缓解措施。 ## USB设置包结构 根据USB规范,每个控制传输必须以设置事务开始。这是主机向连接设备发出任何类型请求的机制。 一个正确的设置事务包含主机发送的两个数据包: `` (1) TOKEN PACKET host → device ┌───────┬──────────┬─────────┬─────────┬────────┬──────┐ │ SYNC │ PID │ ADDR │ ENDP │ CRC5 │ EOP │ │ 8 bit │ 8 bit │ 7 bit │ 4 bit │ 5 bit │ │ │ │ (SETUP) │ device │endpoint │ │ │ └───────┴──────────┴─────────┴─────────┴────────┴──────┘ (2) DATA PACKET host → device ┌────────────────────────┐ ┌───────┬──────────┤ DATA (8 bytes) ├────────┬──────┐ │ SYNC │ PID │ = USB Device Request │ CRC16 │ EOP │ │ 8 bit │ 8 bit │ │ 16 bit │ │ │ │ (DATA0) │ This is what the USB │ │ │ └───────┴──────────┤ driver receives ├────────┴──────┘ └────────────────────────┘ `` 根据规范,设置事务的数据负载必须恰好为8字节,并遵循严格的格式。该负载中包含的设备请求结构会原封不动地传递给软件驱动程序进行处理。 为保持分析清晰,我们称该特定负载为“设置包”。 ## DWC2 直接内存访问 苹果在其SoC中使用的USB控制器是Synopsys的DWC2。关于该控制器如何工作的详细信息,可以通过分析其他现有的驱动程序实现(如Linux内核中的实现)来推断。 我们关心的是控制器如何将数据写入主内存。AP通过分配一块内存区域并将其物理地址写入控制器MMIO区域中的特定寄存器(`DOEPDMA`)来配置DMA。 控制器使用该缓冲区存储从SETUP和OUT数据包中接收的数据,然后由设备处理。 我们观察到,当USB控制器写入数据块时,它会直接递增`DOEPDMA`寄存器中存储的地址。这是一个关键细节,因为它可能意味着该寄存器值直接充当真理源,定义了下次DMA传输将使用的物理地址,而不仅仅是一个配置工具。 ### 漏洞 DesignWare USB控制器在内存中最多存储三个连续的设置包。 当收到第四个设置事务时,DMA基地址会在写入前重置到起始位置,类似于环形缓冲区机制。 每写入一个接收到的数据包后,控制器会将`DOEPDMA`增加写入数据的大小。重置操作通过将`DOEPDMA`减去24来实现。 USB DMA缓冲区动画 核心问题在于控制器也接受较小的数据包(尽管总是以4字节块存储)。 由于指针增量与固定的减量值不匹配,我们最终得到了一个以12字节为步长的缓冲区下溢原语。 USB DMA缓冲区动画漏洞 我们认为这是USB控制器本身固有的bug。虽然可能影响许多设备,但该漏洞仅在特定情况下有效。 截至目前,我们已确认A12和A13的SecureROM存在漏洞,而A11则没有。区别在于,A11的USB驱动程序在接收每个数据包后会手动将DMA地址重置为初始值。 在A12和A13上,USB DART配置为绕过模式,允许我们自由覆盖SRAM数据。相比之下,A14及更高版本似乎在SecureROM中正确配置了DART,使得该漏洞无法利用。 ## A12上的PC控制 在A12上实现PC控制是直接的,因为USB控制器的DMA缓冲区在USB任务堆栈之后不久分配在堆上。 最简单的方法是覆盖堆栈上的已保存LR,并在调度程序上下文切换回USB任务时获得直接PC控制。 ## A13上的PC控制 由于引入了PAC,A13 SecureROM上的情况并不那么容易。 根据我们的观察,PAC似乎只应用于堆栈存储的LR,但这足以阻止我们像在A12上那样直接针对USB任务的已保存LR。 过程中需要绕过多种缓解措施。这些措施包括堆元数据校验和(在堆操作期间验证),以及上下文切换期间的LR签名(每当USB任务被唤醒处理USB数据包时发生)。 经过多次迭代,我们提出了以下多步骤技术来实现PC控制。 第一步是覆盖位于USB控制器DMA缓冲区之前的堆中的某些DART相关数据。这为我们提供了非常有限的写原语,可以在退出DFU循环时触发一次。 我们利用一些清理例程(使用可控数据)将指向DART分配对象的全局指针清零。这一步是必要的,以防止损坏的分配被释放,否则在检查堆校验和时会触发panic。 `` void dart_stop(unsigned int dart_id) { // [1] 我们可以完全覆盖此对象的堆内存 dart = darts[dart_id]; mmio_base = dart->info->mmio_base; v4 = 16 * dart->ctx->field_11 + 0x200; for ( int i = 0; i < 16; i += 4 ) { // [2] 这给了我们一个16字节的零写原语 *(_DWORD *)(mmio_base + v4 + i) = 0; } dart_flush_maybe(mmio_base); } void dart_free(__int64 dart_id) { // [...] dart_stop(dart_id); enter_critical_section(); ref_count = info->ref_count - 1; info->ref_count = ref_count; if ( !ref_count ) irq_mask(info->int_irq_num); exit_critical_section(); // [3] 我们需要使用零写原语使第二次解引用返回0,从而使free成为空操作 dart = darts[dart_id]; free(dart); darts[dart_id] = 0; } `` 接下来,作为同一清理路径的一部分,我们使用一个`0xf`写原语覆盖一个全局的panic计数器。这样,下一次panic将导致CPU进入无限循环,而不是触发重启。 `` void dart_flush_maybe(__int64 mmio_base) { __dmb(); // [4] 这给了我们一个针对panic深度计数器的0xF写原语 *(_DWORD *)(mmio_base + 52) = 0xF; *(_DWORD *)(mmio_base + 32) = 0; ticks = get_ticks(); while ( 1 ) { v3 = get_ticks(); if ( (*(_DWORD *)(mmio_base + 32) & 4) == 0 ) break; if ( v3 - ticks >= 1000000 ) panic(); } } void __noreturn panic() { // [5] 覆盖此全局变量后,panic时会进入无限循环 if ( ++panic_cnt >= 3 ) spin(); // [...] } `` 下一步是避免破坏USB任务的上下文,特别是LR和SP寄存器,它们在上下文切换期间保存到任务结构并从任务结构恢复。为了实现这一点,我们安排DMA写入发生在任务唤醒时,这样当它让出CPU时,正确的寄存器值会覆盖我们损坏的值。 `` Task structure 0x000 ┌─────────────────────┐ │ │ │ Task state, │ │ scheduler data, │ │ crit. section depth │ │ │ 0x030 ├─────────────────────┤ │ │ │ Other registers │ │ │ <──┐ ├──────────┬──────────┤ │ 该区域需要在 │ LR │ SP │ │ USB任务运行时被覆盖 ├──────────┴──────────┤ │ │ │ <──┘ │ Safe to overwrite │ │ │ 0x1b0 └─────────────────────┘ `` 之后,我们可以针对任务结构内部的某个字段,该字段跟踪任务的临界区深度。这允许我们在IRQ开启的情况下触发panic,导致执行进入第一步建立的无限循环,同时ISR仍然可以运行。此外,USB控制器保持允许我们继续向内存写入数据的状态。 `` void enter_critical_section() { current_task = current_task(); critical_section_depth = current_task->critical_section_depth; if ( critical_section_depth < 0 || critical_section_depth >= 10000 ) panic(); // [1] 每次调用递增任务的critical_section_depth current_task->critical_section_depth = critical_section_depth + 1; if ( !critical_section_depth ) { irq_disable(); } } void exit_critical_section() { current_task = current_task(); // [2] 应等于进入`enter_critical_section`的次数 // 但我们可以用较小的计数值覆盖它 critical_section_depth = current_task->critical_section_depth; // [3] 第一次进入会启用中断,第二次进入会panic if ( critical_section_depth <= 0 ) panic(); critical_section_depth = critical_section_depth - 1; current_task->critical_section_depth = v2; if ( !critical_section_depth ) irq_enable(); } `` 完成所有这些设置后,我们可以自由地覆盖内存,直到到达包含USB IRQ处理程序本身的全局变量。通过用任意值覆盖它,我们获得了PC控制权。 `` // [1] 一个`irq_handler_ctx`结构数组位于BSS段,我们可以通过漏洞访问到 00000000 struct irq_handler_ctx // sizeof=0x18 00000000 { 00000000 void (*handler)(void *arg); 00000008 __int64 *arg; 00000010 _BYTE unk; 00000011 _BYTE shall_mask; 00000012 // padding byte 00000013 // padding byte 00000014 // padding byte 00000015 // padding byte 00000016 // padding byte 00000017 // padding byte 00000018 }; void handle_irq() { irq_num = MEMORY[0x23B102004]; if ( MEMORY[0x23B102004] ) { while ( irq_num ) { if ( (irq_num & 0x70000) != 0x10000 ) panic(); irq_num__ = irq_num & 0x1FF; // [2] 设置之后,我们可以自由地用可控值覆盖USB中断的处理程序 handler = irq_list[irq_num__].handler; if ( handler ) // [3] 完全可控的处理程序由完全可控的参数调用 handler(irq_list[irq_num__].arg); _clr_mask_irq(irq_num__); irq_num = MEMORY[0x23B102004]; } } else { ++stale; } } `` ## 漏洞利用后 从A12开始,SecureROM主要在EL0模式下运行。这意味着我们无法访问受保护的内存区域(即MMU表和ROM的相关元数据)或特殊CPU寄存器(例如`SCTLR`、`TTBR`等)。 SecureROM可以通过执行`SVC 0`指令将执行切换到特权EL1模式。这不是类似系统调用的接口:处理程序只是返回到`SVC`指令之后的代码,但执行在EL1模式下继续。不久之后,ROM执行`ERET`以继续在EL0模式下运行。 ROM中发生这种转换的地方非常少。我们选择了一个在负责将执行转移到下一阶段iBoot的函数中: `` # A12 SecureROM ... 1000088B0: SVC 0 # 切换到EL1,我们希望在此处着陆 1000088B4: LDR W8, [X22] 1000088B8: CBNZ W8, loc_10000891C 1000088BC: MOV W8, #1 1000088C0: STR W8, [X22] 1000088C4: STRB W8, [X23] 1000088C8: ADRP X9, #0x19C014033@PAGE 1000088CC: LDRB W10, [X9,#0x19C014033@PAGEOFF] 1000088D0: CBNZ W10, loc_10000891C 1000088D4: STRB W8, [X9,#0x19C014033@PAGEOFF] 1000088D8: LDRB W8, [X24,#0x19C014032@PAGEOFF] # 意外的编译器行为,稍后描述 1000088DC: CBZ W8, loc_10000891C 1000088E0: BL sub_100000430 # 清除一些SCTLR位 1000088E4: BL sub_100006798 # 清除某些临时寄存器中的内容? 1000088E8: BL sub_10000739C # 返回启动跳板地址 1000088EC: MOV X8, X0 1000088F0: CBZ X8, loc_100008904 1000088F4: MOV X0, X20 1000088F8: MOV X1, X19 1000088FC: BLR X8 # 跳转到启动跳板! ... `` ### A12 和 S4/S5 这些SoC在SecureROM中不使用PAC。我们通过破坏堆栈上的LR来获得代码执行。是时候进行经典ROP了! 链非常短——只有几个帧: 1. 对USB MMIO进行32位写入,更改DMA目标地址 2. 休眠一段时间(约400ms) 3. 休眠后,跳转到0x1000088B0(上述汇编) DMA目标设置为启动跳板内的某个位置。该内存显然不能从EL0写入(甚至不能从EL1写入,因为它本应是只执行的),但由于是DMA…… 启动跳板也总是存储在同一地址。 延迟给漏洞足够的时间将我们的shellcode写入启动跳板。 延迟后,我们最终执行跳转。固件实际上包含检查,以防止跳转到函数中间。它维护一系列状态标志,在每一步后更新。如果前一阶段对应的标志未设置,固件会触发panic。 有趣的是,编译器为这些检查生成了以下指令: `` ... 1000088C8: ADRP X9, #0x19C014033@PAGE # 获取标志区域页面地址到X9 1000088CC: LDRB W10, [X9,#0x19C014033@PAGEOFF] # 通过X9获取某些内容 1000088D0: CBNZ W10, loc_10000891C # 此检查不会失败,幸运的是 1000088D4: STRB W8, [X9,#0x19C014033@PAGEOFF] # 也通过X9存储另一个标志 1000088D8: LDRB W8, [X24,#0x19C014032@PAGEOFF] # 进行危险检查……通过X24! 1000088DC: CBZ W8, loc_10000891C ... `` `X24`是被调用者保存的寄存器,这意味着它的值存储在堆栈上。就是那个我们完全控制的堆栈,是的。 此时,我们得以用EL1权限执行自己的代码。我们现在要做的就是清理、应用我们的修改,然后像什么都没发生一样返回DFU。 长话短说: 1. 需要将原启动跳板复制回其所属位置 2. 所有堆

相似文章

usbliter8: A12/A13 SecureROM漏洞利用

Lobsters Hottest

usbliter8是一种针对苹果A12和A13 SoC的tethered bootrom漏洞利用,使用基于RP2350的微控制器板,通过利用USB控制器漏洞来获取底层设备访问权限。

CVE-2026-28952:Apple macOS 26.5 内核漏洞由 Claude 发现

Hacker News Top

Apple 发布了 macOS Tahoe 26.5 的安全更新,修复了多个漏洞,包括内核错误、拒绝服务攻击和沙盒逃逸。该更新修复了由不同研究人员发现的多个 CVE 漏洞,其中 CVE-2026-28952 据称由 Claude AI 发现。