CVE-2026-40003,又名 Joselito,是在 ZXIC/Sanechips ZX297520V3 SoC BootROM 中发现的一个任意内存写入漏洞。
[!CAUTION] 此 PoC 仅用于研究目的发布。您不得将此漏洞利用用于任何恶意目的。请勿在您不拥有的设备上或未经所有者明确同意的情况下使用此漏洞利用。
自 2026 年 3 月起,该漏洞应在较新的 SoC 修订版中得到修复。目前尚无已知产品使用已修补的芯片。所有现有设备均存在漏洞。
TL;DR:查看 loader 代码。
BootROM 从 0x4 地址开始,堆栈指针位于 0x81FF0。代码根据特殊测试点或从闪存加载映像的能力进行模式选择。如果 BootROM 无法从闪存加载或验证映像,它将回退到 USB 下载模式。
我们可以在这里观察到这种行为:
v0 = MEMORY[0x13B004] & 7; // 硬件引脚
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 = 主机发送的确认
{
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;
如果设备已熔丝,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 没有一次性下载标志,我们可以发送任意数量的映像。该漏洞利用此功能(尽管它本身不是漏洞)结合任意内存写入。
entry 函数:PUSH {R3-R7,LR})+ 2(usbdl_init 函数:PUSH {R4,LR})。乘以寄存器大小,即 8 * 4 = 32LR 寄存器(为简单起见,PoC 对所有保存的寄存器重复相同的地址)verify_and_jump 函数返回到 usbdl_initusbdl_init 打印 FAILED 并执行 POP {R4, PC} 指令,运行载荷sudo apt update && sudo apt install curl llvm gcccurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 或发行版特定的包管理器安装 rustup. "$HOME/.cargo/env"(bash)或 source "$HOME/.cargo/env.fish"(fish)cd payload && ./build.sh && cd ..sudo -E cargo r -p loader -- target/thumbv6m-none-eabi/release/payload现在连接已触发 USB 下载模式的设备(通过特殊测试点或损坏的闪存),并观察 UART 输出。