usbliter8如何用Zero-Write摧毀SecureROM?
摘要
報告提供 usbliter8 漏洞 的全面技術分析,這是一個影響 Apple A12 與 A13 SoC 的新型 iPhone BootROM 漏洞 。該漏洞利用了 Synopsys DWC2 USB 控制器 中的關鍵硬體錯誤,特別是 DMA 緩衝區 Underflow ,並結合了韌體設定缺陷。我們詳細說明了底層漏洞、在 A12 和 A13 裝置上達成 Program Counter (PC) 控制的攻擊技術,包括在 A13 上 繞過 PAC (Pointer Authentication Codes) 。此外,我們分析了原始研究中的關鍵程式碼片段,以說明攻擊的基本操作。報告中也包含關於 bootloader 完整性的比較討論,以及此類硬體層級漏洞對現代嵌入式系統安全的更廣泛影響。這項研究目的在加深對 SecureROM 安全性 以及緩解不可變更硬體缺陷挑戰的理解。
1. 簡介:SecureROM 安全性與硬體漏洞
裝置的安全性從根本上依賴其開機程序,從嵌入在硬體中的不可變更程式碼開始,即 BootROM 或 SecureROM。這個初始階段至關重要,因為它建立了所有後續軟體元件的信任根基。任何存在於 BootROM 中的漏洞都特別嚴重,因為它無法修補,意味著無法透過軟體更新修復,通常需要更換硬體來降低風險 [1] 。此類漏洞構成重大威脅,因為它們可以授予攻擊者對裝置的深度控制權,繞過更高層級的安全機制。usbliter8 漏洞由 Paradigm Shift 發現,正是這類硬體層級 BootROM 漏洞的典型案例,專門針對 Apple 的 A12 和 A13 SoC [1] 。這份報告深入探討 usbliter8 的技術細節,檢視其底層機制以及用於攻擊的複雜技術。
2. 技術根本原因:DWC2 USB 控制器 DMA 緩衝區 Underflow
usbliter8 漏洞的核心在於 Synopsys DesignWare Cores USB 2.0 (DWC2) 控制器中的一個基本硬體缺陷,該控制器用於 Apple 的 A12 和 A13 SoC。此控制器管理 USB 通訊,包括在 USB 裝置和應用處理器 (Application Processor, AP) 主記憶體之間傳輸資料的直接記憶體存取(Direct Memory Access, DMA) 操作。DWC2 控制器運作的核心之一,在於其於控制傳輸時對 Setup 封包的處理方式。根據 USB 規範,每個控制傳輸都以一個 Setup transaction 開始,包含一個 TOKEN PACKET 和一個 DATA PACKET,資料 payload 恰好為 8 位元組,並包含 USB Device Request [1] 。
漏洞源於 DWC2 控制器處理其用於 Setup 封包的內部 DMA 緩衝區的方式。控制器設計為儲存最多三個連續的 Setup 封包。在收到第四個 Setup 事務時,透過
DOEPDMA
暫存器設定的 DMA base address 會在寫入新封包之前重設為其起始位置。此機制的用途在像 ring buffer 一樣運作。然而,控制器如何遞增和遞減
DOEPDMA
暫存器存在一個關鍵錯誤。在寫入每個接收到的封包後,控制器會將
DOEPDMA
增加所寫入資料的大小。目的在將指標返回到緩衝區開頭的重設(Reset)操作,是透過將
DOEPDMA
減少固定量 24 位元組來實現的。核心問題是控制器也接受較小的封包,這些封包總是以 4 位元組的區塊儲存。由於指標增量(對於較小的封包可能小於 8 位元組,但仍會導致緩衝區邏輯前進 8 位元組)與固定的 24 位元組減量不一致,因此會以 12 位元組為間隔建立一個 Buffer Underflow 基本操作
[1]
。這種差異允許攻擊者操控 DMA 指標,使其在預期緩衝區邊界之外寫入資料。
以下的序列圖說明了 DWC2 控制器對 USB Setup 封包的錯誤處理,導致 DMA 緩衝區 Underflow:
sequenceDiagram
participant Host
participant DWC2_Controller as DWC2 Controller
participant AP_Memory as AP Main Memory
Host->>DWC2_Controller: Send Setup Packet 1 (8 bytes)
DWC2_Controller->>AP_Memory: Write Packet 1 to DMA Buffer (DOEPDMA increments by 8)
Host->>DWC2_Controller: Send Setup Packet 2 (8 bytes)
DWC2_Controller->>AP_Memory: Write Packet 2 to DMA Buffer (DOEPDMA increments by 8)
Host->>DWC2_Controller: Send Setup Packet 3 (8 bytes)
DWC2_Controller->>AP_Memory: Write Packet 3 to DMA Buffer (DOEPDMA increments by 8)
Note over DWC2_Controller: DMA Buffer now holds 3 packets (24 bytes total)
Host->>DWC2_Controller: Send Setup Packet 4 (8 bytes)
Note over DWC2_Controller: Controller detects 4th packet, attempts reset
DWC2_Controller->>DWC2_Controller: Decrement DOEPDMA by 24 (intended reset)
Note over DWC2_Controller: If previous packets were smaller than 8 bytes (e.g., 4-byte chunks),
Note over DWC2_Controller: the actual increment was less than 24, but the decrement is fixed at 24.
Note over DWC2_Controller: This creates a 12-byte underflow (e.g., 3*4=12 increment vs 24 decrement)
DWC2_Controller->>AP_Memory: Write Packet 4 to DMA Buffer (at underflowed address)
AP_Memory-->>Host: DMA Buffer Underflow (writes outside intended region)
這個硬體錯誤是 DWC2 控制器固有的,並非 Apple 實作所特有。然而,它在 A12 和 A13 SoC 上的可利用性得益於 USB DART (DMA Address Remapping Table) 設定為繞過模式,允許漏洞自由覆蓋 SRAM 資料。相比之下,A11 裝置沒有漏洞,因為其 USB 驅動程式會手動重設 DMA 位址,而 A14 及更新世代則正確設定了 DART,防止了漏洞利用 [1] 。
3. A12/A13 上的漏洞利用方法
usbliter8 的利用策略在 A12 和 A13 SoC 之間有所不同,主要原因是 A13 引入了 Pointer Authentication Codes (PAC),增加了一層重要的安全性。在 A12 上,達成 Program Counter (PC) 控制相對直接,而 A13 則需要多步驟繞過多種緩解措施。
3.1. A12 上的 PC 控制
在 A12 上,USB 控制器的 DMA 緩衝區配置在 heap 上,通常緊跟在 USB 任務的推疊之後。這種接近性允許直接且有效的攻擊技術。透過利用 DMA 緩衝區 Underflow,攻擊者可以覆蓋推疊上儲存的 Link Register (LR)。LR 儲存函式呼叫的返回位址。當排程器進行 context 切換回到 USB 任務時,損壞的 LR 被載入,授予攻擊者直接的 PC 控制權 [1] 。這種簡單性突顯了底層硬體錯誤在與較不嚴格的記憶體保護機制結合時的影響。
3.2. A13 上的 PC 控制:繞過 PAC 與緩解措施
A13 SecureROM 引入了 PAC,用於簽署推疊上儲存的 LR,防止了 A12 上使用的直接 LR 覆蓋技術。為了在 A13 上達成 PC 控制,漏洞利用採用了一種複雜的多步驟方法來繞過 PAC 和其他緩解措施,例如 Heap metadata checksums 以及 Context 切換期間的 LR 簽署 [1] 。
3.2.1. 透過 DART 相關資料覆蓋實現 Zero-Write 基本操作
第一步涉及覆蓋位於 USB 控制器 DMA 緩衝區之前的 Heap 中的 DART 相關資料。這提供了一個有限的 Write 基本操作,可以在退出 DFU 迴圈時觸發一次。此基本操作用於將指向 DART 配置的 Global 指標歸零,以防止在清理常式期間驗證 Heap checksums 時發生 panic [1]。
- void dart_stop(unsigned int dart_id)
- {
- // [1] we can fully overwrite the heap memory for this object
- 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] this gives us a 16-byte zero write primitive
- *(_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] we need to use the zero write primitive for these second deref
- // to return 0 and make the free a no-op
- dart = darts[dart_id];
- free(dart);
- darts[dart_id] = 0;
- }
在
dart_stop
函式中,
[2]
處的迴圈展示了一個 16 位元組的 zero-write 基本操作。透過控制
mmio_base + v4 + i
,攻擊者可以將零寫入特定的記憶體區域。這對於
dart_free
函式至關重要,在
[3]
處,使用 zero-write 基本操作使
dart
的第二個 dereference 回傳 0,有效地將
free(dart)
操作轉變為無操作,並防止 Heap 損壞 panic
[1]
。
3.2.2. Panic 計數器覆蓋
在 zero-write 基本操作之後,漏洞利用使用
0xf
Write 基本操作來覆蓋 global panic 計數器。這確保了下一個 panic 事件會導致 CPU 進入無限迴圈而不是重新啟動,允許攻擊者保持控制並繼續攻擊
[1]
。
- void dart_flush_maybe(__int64 mmio_base)
- {
- __dmb();
- // [4] these give us a 0xF write primitive targeting the panic depth counter
- *(_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] after overwriting this global we would enter an infinite loop on panic
- if (++panic_cnt >= 3) {
- spin();
- }
- // [...]
- }
在
[4]
所示的
dart_flush_maybe
程式碼片段中,展示了 0xF 的寫入基本操作。透過將
0xF
寫入距離
mmio_base
偏移 52 位元組的位置,漏洞利用目標是 panic depth 計數器。這直接影響了
[5]
處
panic
函式的行為。如果
panic_cnt
(global panic 計數器) 被覆蓋為一個導致其超過 3 的值(例如,將其設為 0xF),隨後對
panic()
的呼叫將導致透過
spin()
進入無限迴圈,而不是系統重新啟動
[1]
。
3.2.3. 避免 USB 任務環境損壞
為了防止破壞 USB 任務的 context,特別是 LR 和 SP 暫存器,DMA 寫入會仔細計時,使其在任務喚醒時發生。這確保了當任務讓出時,正確的暫存器值會覆蓋任何損壞的值,從而保持任務狀態的完整性 [1] 。
3.2.4. 在啟用 IRQ 的情況下觸發 Panic
漏洞接著鎖定 task 結構中的 critical-section depth 欄位。透過操控此欄位,可以在啟用 IRQ (Interrupt ReQuests) 的情況下觸發 panic。這會導致執行進入先前建立的無限迴圈,同時仍然允許 Interrupt Service Routines (ISR) 執行。關鍵的是,USB 控制器保持在可以繼續將資料寫入記憶體的狀態[1]。
- 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] on each call increments task's 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] should be equal to the amount of entries to `enter_critical_section`
- // but we can overwrite it with a smaller count
- critical_section_depth = current_task->critical_section_depth;
- // [3] on first entry it will enable interrupts and on second entry it will panic
- if (critical_section_depth <= 0) {
- panic();
- }
- critical_section_depth = critical_section_depth - 1;
- current_task->critical_section_depth = critical_section_depth;
- if (!critical_section_depth) {
- irq_enable();
- }
- }
enter_critical_section
函式在
[1]
處增加
critical_section_depth
。
exit_critical_section
函式在
[2]
和
[3]
處遞減它,並在達到 0 時啟用 IRQ。透過將
current_task->critical_section_depth
覆蓋為一個在單次遞減後使其變為小於或等於零的值,漏洞利用可以強制執行
irq_enable()
隨後跟隨
panic()
。這允許系統在仍然啟用中斷的情況下進入無限 panic 迴圈,這是進一步利用的關鍵狀態
[1]
。
3.2.5. 透過 USB IRQ Handler 覆蓋獲得 PC 控制
在系統處於受控 panic 狀態且能夠繼續寫入記憶體的情況下,最後一步是覆蓋包含 USB IRQ handler 的 global 變數。此 handler 是
irq_handler_ctx
結構陣列的一部分。透過用任意控制值覆寫 USB 中斷 context 的
handler
欄位,攻擊者在觸發 USB IRQ 時獲得完整的 PC 控制權
[1]
。
- // [1] an array of `irq_handler_ctx` structs lives in the BSS section which we can reach with our bug
- 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] after the Setup we can freely overwrite handler for USB interrupt with a controlled value
- handler = irq_list[irq_num__].handler;
- if ( handler )
- // [3] fully controlled handler gets called with fully controlled arg
- handler(irq_list[irq_num__].arg);
- _clr_mask_irq(irq_num__);
- irq_num = MEMORY[ 0x23B102004 ];
- }
- }
- }
irq_handler_ctx
結構在
[1]
處定義了 interrupt handler 的結構,包括一個函式指標
handler
及其引數
arg
。在
handle_irq
函式中,在
[2]
處,漏洞利用可以覆寫
irq_list
陣列中 USB 中斷的
handler
。隨後,在
[3]
處,當 USB 中斷被觸發時,現在受控的
handler
函式會以受控的引數被呼叫,導致任意程式碼執行和完整的 PC 控制權
[1]
。
4. 比較分析:Bootloader 完整性與安全性
usbliter8 漏洞突顯了嵌入式系統安全性的一個關鍵層面:硬體層級元件(特別是 BootROM)的持久脆弱性。雖然軟體的漏洞通常可以修補,但像 DWC2 USB 控制器錯誤這樣的硬體缺陷是不可變更的,使得緩解它們變得更加困難。這與軟體 bootloader 漏洞(例如在 GRUB2、U-Boot 或 Barebox 中發現的漏洞)形成對比,後者雖然嚴重,但通常可以透過軟體更新或設定更改來解決 [2] 。
在前期文章中關於 "破解汽車車用主機:揭露RTOS重大安全漏洞"的文章 [2] 透過詳細描述汽車車用主機 RTOS 中的漏洞,包括弱 bootloader checksum 和不完整的韌體完整性控制,提供了相關的比較。這表明即使在不同的嵌入式環境中,開機程序和韌體驗證中的基本安全弱點仍然普遍存在。汽車 bootloader 中的 1 位元組 CRC checksum 漏洞 [2] 類似於 usbliter8 中 DWC2 控制器的固定減量邏輯,兩者都由於 驗證機制不足 而導致可利用的 完整性繞過 。usbliter8 漏洞強調了硬體層級漏洞,特別是像 USB 控制器這樣的元件中的漏洞,可能產生深遠的影響,使攻擊者能夠繞過甚至像 A13 上 PAC 這樣強大的軟體安全功能。SecureROM 中此類缺陷的持續存在,突顯了在整個 SoC 開發生命週期中進行嚴格的硬體設計驗證和全面安全審計的關鍵需求。雖然軟體緩解措施可以解決許多漏洞,但硬體錯誤通常需要根本性的重新設計,或者像 usbliter8 的情況一樣,遷移到更新、已修補的硬體 [1] 。
5. 結論
usbliter8 漏洞代表了一個重要的展示,說明細微的硬體設計缺陷如何導致無法修補的 BootROM 漏洞,對現代行動裝置產生深遠的安全影響。漏洞的核心是 Synopsys DWC2 USB 控制器中的 DMA 緩衝區 Underflow,展示了一個關鍵的弱點,允許攻擊者在預期邊界之外操控記憶體。針對 A12 和 A13 SoC 的詳細利用方法,包括在 A13 上繞過 PAC 的複雜技術,突顯了破壞安全開機鏈所需的獨創性。這項研究強化了這樣的理解:即使有先進的安全功能,初始開機程序的完整性(植根於不可變更的硬體)仍然至關重要。與其他 bootloader 漏洞的比較進一步強調了完整性檢查不足以及此類疏忽的嚴重後果這一反覆出現的主題。最終,usbliter8 作為一個鮮明的提醒,穩健的安全性必須根植於系統設計的每一層,從矽晶片開始,才能有效對抗持久且無法修補的硬體威脅。對於受影響的裝置,遷移到更新的硬體仍然是針對此類漏洞最有效的緩解策略。