rva3/CVE-2026-40003

GitHub: rva3/CVE-2026-40003

针对 ZXIC/Sanechips ZX297520V3 SoC BootROM 任意内存写入漏洞(CVE-2026-40003)的 PoC 利用代码,通过 USB 下载模式绕过安全启动实现任意代码执行。

Stars: 11 | Forks: 1

CVE-2026-40003(又名 Joselito)是在 ZXIC/Sanechips ZX297520V3 SoC BootROM 中发现的任意内存写入漏洞。 ## 状态 该漏洞应已于 2026 年 3 月在较新的 SoC 版本中修复。目前尚无已知产品使用了打过补丁的芯片。所有现有设备均受此漏洞影响。 ## 工作原理 *TL;DR: 查看 [加载器代码](./loader/src/main.rs)。* BootROM 从 0x4 地址开始执行,此时栈指针位于 `0x81FF0`。代码根据特定的测试点,或从闪存加载镜像的能力来执行模式选择。如果 BootROM 无法加载或验证闪存上的镜像,它将回退到 USB 下载模式。 我们可以在这里观察到此行为: ``` v0 = MEMORY[0x13B004] & 7; // hardware pin switch ( (char)v0 ) { case 0: if ( nand_init() ) goto LABEL_12; parse_header((Header *)0x8A000); v4 = "\nMODE:NAND\n"; goto LABEL_11; case 1: if ( !nand_init() ) { parse_header((Header *)0x8A000); uart_puts("\nMODE:USB-N\n"); } usbdl_init(0x1500000); v5 = "TURN TO NAND\n"; goto LABEL_16; /* ... */ LABEL_16: uart_puts(v5); LABEL_7: verify_and_jump((Header *)0x8A000, 0, 1); break; ``` `usbdl_init` 函数初始化 USB IP 并进入下载循环: ``` void __fastcall usbdl_init(int usb_base) { char *v1; // r0 if ( usb_base == 0x1500000 ) { alt_usb_base = 0; usb_init(); } else { alt_usb_base = 1; noidea(); } if ( usb_setup() ) { try_sync_with_host(); v1 = "FAILED\n"; } else { v1 = "\nNOLINK\n"; } uart_puts(v1); } ``` 反汇编输出: ``` ROM:00001F68 ; void __fastcall usbdl_init(int usb_base) ROM:00001F68 usbdl_init ; CODE XREF: entry+5E↑p ROM:00001F68 ; entry+7C↑p ... ROM:00001F68 PUSH {R4,LR} ROM:00001F6A MOVS R2, #0x1500000 ROM:00001F6E LDR R1, =dword_8014C ROM:00001F70 MOV R4, R0 ROM:00001F72 CMP R0, R2 ROM:00001F74 BNE loc_1F80 ROM:00001F76 MOVS R0, #0 ROM:00001F78 STRB R0, [R1,#(alt_usb_base - 0x8014C)] ROM:00001F7A BL usb_init ROM:00001F7E B loc_1F88 ROM:00001F80 ; --------------------------------------------------------------------------- ROM:00001F80 ROM:00001F80 loc_1F80 ; CODE XREF: usbdl_init+C↑j ROM:00001F80 MOVS R0, #1 ROM:00001F82 STRB R0, [R1,#(alt_usb_base - 0x8014C)] ROM:00001F84 BL noidea ROM:00001F88 ROM:00001F88 loc_1F88 ; CODE XREF: usbdl_init+16↑j ROM:00001F88 MOV R0, R4 ROM:00001F8A BL usb_setup ROM:00001F8E CMP R0, #0 ROM:00001F90 BEQ loc_1F9E ROM:00001F92 BL try_sync_with_host ROM:00001F96 ADR R0, aFailed ; "FAILED\n" ROM:00001F98 ROM:00001F98 loc_1F98 ; CODE XREF: usbdl_init+38↓j ROM:00001F98 BL uart_puts ROM:00001F9C POP {R4,PC} ROM:00001F9E ; --------------------------------------------------------------------------- ROM:00001F9E ROM:00001F9E loc_1F9E ; CODE XREF: usbdl_init+28↑j ROM:00001F9E ADR R0, aNolink ; "\nNOLINK\n" ROM:00001FA0 B loc_1F98 ROM:00001FA0 ; End of function usbdl_init ``` `try_sync_with_host` 等待同步字节(`0x5A`)并进入主循环。 ``` void try_sync_with_host() { int v0; // r4 unsigned int i; // r0 int v2; // r5 int v3; // r2 int v4; // r3 int v5; // r2 int v6; // r3 int v7; // r0 v0 = 0x805A0; time_to_boot = 0; for ( i = 0; i < 0x40; ++i ) *(_BYTE *)(i + 0x805A0) = 0; g_state = 0; v2 = 0x200; if ( usb_recv(byte_805A0, 0x200, 0x5A) == 1 ) // 0x5a = ack sent by the host { uart_puts("TIMEOUT\n"); } else { resp[0] = 0xA5; usb_send((int)resp, 1, v3, v4); g_state = 1; while ( !time_to_boot ) { v7 = sub_11CA(v0, v2, v5, v6); handler(v0, v7); if ( g_state == 4 ) { v2 = size; v0 = start_addr; } else { v0 = 0x805A0; v2 = 0x200; } } verify_and_jump(unk_80160, 0, 1); } } ``` 握手完成后,BootROM 进入主循环,等待阶段 1 二进制文件,该文件通常用于恢复变砖的设备。然而,BootROM 并未验证目标地址,这允许通过 `0x7A` 命令进行任意写入。`0xA1` 状态表示镜像地址和大小已被接受。 ``` usb_send: resp[0] = status; v5 = 1; v6 = resp; return usb_send((int)v6, v5, state, v3); case 3u: // 0x7A if ( *(int *)&resp[4] >= 4 ) { if ( *(int *)&resp[4] < 8 ) *(_DWORD *)&resp[12] = (*(_DWORD *)&resp[12] << 8) | result; } else { *(_DWORD *)&resp[8] = (*(_DWORD *)&resp[8] << 8) | result; } v3 = *(_DWORD *)&resp[4] + 1; *(_DWORD *)&resp[4] = v3; if ( v3 != 8 ) continue; *(_DWORD *)&resp[4] = 0; start_addr = *(_DWORD *)&resp[8]; size = *(_DWORD *)&resp[12]; g_state = 4; status = 0xA1; goto usb_send; ``` 如果设备已熔丝(fused),`verify_and_jump` 函数将执行镜像验证。 ``` int __fastcall verify_and_jump(Header *a1, int zero, int must_verify) { int *p_sp; // r6 int result; // r0 p_sp = (int *)&a1->sp; result = memcmp((int)a1->magic, 0x80008, 2u); if ( !result ) { if ( !zero || (result = a1->usbdl_en, result == 0x5A) ) { if ( !must_verify ) return jump(p_sp); result = verify(a1); if ( !result ) return jump(p_sp); } } return result; } ``` `verify` 函数将使用硬件 MD5 和 RSA 引擎来验证镜像的完整性,而 `jump` 会将栈指针设置为镜像头中的地址,并跳转到入口点。 在 ARM/Thumb 调用约定中,函数调用通常以 `PUSH {regs, LR}` 开始,并以尾调用 `BX LR` 或 `POP {regs, PC}` 结束。`usbdl_init` 没有被编译器优化为尾调用,因此包含了 `PUSH` 和 `POP` 指令。 ***您已经明白其中的原理了吗?*** 由于 BootROM 没有针对一次性下载的标志,我们可以发送任意数量的镜像。该漏洞利用了这一特性(尽管这本身不是一个漏洞)结合任意内存写入来实施攻击。 1. 将 payload 发送到指定地址 2. 计算栈偏移量:6(`entry` 函数:`PUSH {R3-R7,LR}`)+ 2(`usbdl_init` 函数:`PUSH {R4,LR}`)。乘以寄存器大小,即 8 * 4 = 32 3. 将跳转地址发送到栈上保存的 `LR` 寄存器(为简单起见,PoC 对所有保存的寄存器重复使用相同的地址) 4. 发送跳转命令 5. 让镜像验证失败(缺少 header 魔数) 6. `verify_and_jump` 函数返回到 `usbdl_init` 7. `usbdl_init` 打印 `FAILED` 并执行 `POP {R4, PC}` 指令,从而运行 payload ## 从源代码构建 - 安装 curl、llvm-objcopy(通常是 llvm 包的一部分)和 gcc。Ubuntu 22.04 示例:`sudo apt update && sudo apt install curl llvm gcc` - 使用 `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh` 或特定发行版的包管理器安装 rustup - 确保 rust 命令可用:`. "$HOME/.cargo/env"` (bash) 或 `source "$HOME/.cargo/env.fish"` (fish) - 构建 payload:`cd payload && ./build.sh && cd ..` - 运行主机工具:`sudo -E cargo r -p loader -- target/thumbv6m-none-eabi/release/payload` 现在连接已触发 USB 下载模式(通过特定测试点或损坏的闪存)的设备,并查看 UART 输出。 ## 时间线 - 2 月 5 日 - 提交初始报告 - 3 月 18 日 - 被 ZTE PSIRT 确认 - 5 月 7 日 - 公开发布 ## 外部致谢 - 大量的 SoC 逆向工程:[Stefan Dösinger](https://github.com/stefand) - 在已熔丝设备上测试:[Mio Naganohara](https://github.com/Mio-sha512) - 在另一台已熔丝设备上进行预发布测试:[exp-3](https://github.com/exp-3) ## 许可证 [AGPLv3](./LICENSE)
标签:BootROM漏洞, CISA项目, CVE-2026-40003, IoT安全, Rust语言, Sanechips, SoC安全, USB下载模式, ZX297520V3, ZXIC, 中兴微电子, 二进制分析, 云安全运维, 云资产清单, 任意内存写, 可视化界面, 固件安全, 嵌入式安全, 提示词注入, 文档安全, 硬件黑客, 逆向工程, 通知系统