
استغلال لثغرة CVE-2026-40003، وهي ثغرة كتابة عشوائية في الذاكرة في BootROM الخاص بمعالج ZXIC/Sanechips ZX297520V3 SoC، مما يتيح تنفيذ التعليمات البرمجية عبر وضع التنزيل عبر USB.
CVE-2026-40003 المعروف باسم Joselito هو ثغرة كتابة ذاكرة عشوائية تم اكتشافها في BootROM الخاص بمعالج ZXIC/Sanechips ZX297520V3 SoC.
[!CAUTION] تم نشر هذا الـ PoC لأغراض البحث فقط. يجب ألا تستخدم هذا الاستغلال لأي أغراض خبيثة. لا تستخدم الاستغلال على أجهزة لا تملكها أو دون موافقة صريحة من المالك.
يجب أن تكون هذه الثغرة قد أُصلحت منذ مارس 2026 في إصدارات SoC الأحدث. لا توجد منتجات معروفة تستخدم رقائق مُصححة حتى الآن. جميع الأجهزة الحالية معرضة للخطر.
باختصار: اطّلع على كود المُحمّل.
يبدأ 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 والدخول في حلقة التنزيل:
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 في الحلقة الرئيسية، بانتظار ثنائي المرحلة الأولى، والذي يُستخدم عادةً لاستعادة جهاز تالف. ومع ذلك، لا يتحقق 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 بالتحقق من الصورة إذا كان الجهاز مدمجًا (fused).
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 أو مدير الحزم الخاص بالتوزيعة. "$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.