Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2026-40003 — Exploit pour CVE-2026-40003, une vulnérabilité d'écriture mémoire arbitraire dans le BootROM du SoC ZXIC/Sanechips ZX297520V3, permettant l'exécution de code via le mode de téléchargement USB. | Kitploit
Outils/GitHubGitHub/rva3/cve-2026-40003
Sécurité des Systèmes EmbarquésExploitationRétro-ingénierieHacking MatérielDéveloppement de Charges UtilesAnalyse de MicrologicielExploitation de Binaires
GitHubrva3/cve-2026-40003

CVE-2026-40003

Exploit pour CVE-2026-40003, une vulnérabilité d'écriture mémoire arbitraire dans le BootROM du SoC ZXIC/Sanechips ZX297520V3, permettant l'exécution de code via le mode de téléchargement USB.

Voir le dépôt
1419il y a 4 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
Site web

CVE-2026-40003, alias Joselito, est une vulnérabilité d'écriture mémoire arbitraire découverte dans le BootROM du SoC ZXIC/Sanechips ZX297520V3.

[!CAUTION] Ce PoC a été publié à des fins de recherche uniquement. Vous ne devez PAS utiliser cet exploit à des fins malveillantes. N'utilisez PAS l'exploit sur des appareils qui ne vous appartiennent pas ou sans le consentement explicite du propriétaire.

Statut

Cette vulnérabilité devrait être corrigée depuis mars 2026 dans les révisions plus récentes du SoC. Aucun produit connu n'utilise encore de puces corrigées. Tous les appareils existants sont vulnérables.

Comment cela fonctionne-t-il ?

TL;DR : consultez le code du loader.

Le BootROM démarre à l'adresse 0x4 avec le pointeur de pile à 0x81FF0. Le code effectue une sélection de mode basée sur un point de test spécial, ou sur la capacité à charger l'image depuis la flash. Si le BootROM échoue à charger ou à vérifier l'image sur la flash, il bascule en mode de téléchargement USB.

Nous pouvons observer ce comportement ici :

root@kitploit:~
v0 = MEMORY[0x13B004] & 7; // broche matérielle
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;

La fonction usbdl_init initialise l'IP USB et entre dans la boucle de téléchargement :

root@kitploit:~
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);
}

Sortie du désassembleur :

root@kitploit:~
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

La fonction try_sync_with_host attend l'octet de synchronisation (0x5A) et entre dans la boucle principale.

root@kitploit:~
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 envoyé par l'hôte
  {
    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);
  }
}

Une fois la poignée de main terminée, le BootROM entre dans la boucle principale, attendant le binaire de l'étape 1, généralement utilisé pour récupérer un appareil brické. Cependant, le BootROM ne vérifie pas l'adresse de destination, ce qui permet une écriture arbitraire via la commande 0x7A. Le statut 0xA1 signifie que l'adresse et la taille de l'image ont été acceptées.

root@kitploit:~
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;

La fonction verify_and_jump effectuera la vérification de l'image si l'appareil est fusé.

root@kitploit:~
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;
}

La fonction verify utilisera les moteurs matériels MD5 et RSA pour vérifier l'intégrité de l'image, et jump définira le pointeur de pile à l'adresse de l'en-tête de l'image et sautera au point d'entrée.

Dans la convention d'appel ARM/Thumb, les appels de fonction commencent généralement par PUSH {regs, LR} et se terminent par un appel en queue, BX LR ou POP {regs, PC}. Le usbdl_init n'a pas été optimisé par le compilateur en appel en queue, il contient donc des instructions PUSH et POP.

Avez-vous déjà compris l'idée ?


Étant donné que le BootROM n'a pas de drapeau pour un téléchargement unique, nous pouvons envoyer autant d'images que nous le souhaitons. L'exploit abuse de cette fonctionnalité (bien qu'elle ne soit pas une vulnérabilité en soi) avec une écriture mémoire arbitraire.

  1. Envoyer la charge utile à l'adresse spécifiée
  2. Calculer le décalage de pile : 6 (fonction entry : PUSH {R3-R7,LR}) + 2 (fonction usbdl_init : PUSH {R4,LR}). Multiplier par la taille du registre, soit 8 * 4 = 32
  3. Envoyer l'adresse de saut au registre LR sauvegardé sur la pile (pour simplifier, le PoC répète la même adresse pour tous les registres sauvegardés)
  4. Envoyer la commande de saut
  5. Laisser la vérification de l'image échouer (magic d'en-tête manquant)
  6. La fonction verify_and_jump retourne à usbdl_init
  7. usbdl_init affiche FAILED et exécute l'instruction POP {R4, PC}, exécutant la charge utile

Pour compiler à partir des sources

  • Installer curl, llvm-objcopy (généralement inclus dans le paquet llvm) et gcc. Exemple pour Ubuntu 22.04 : sudo apt update && sudo apt install curl llvm gcc
  • Installer rustup avec curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ou via le gestionnaire de paquets spécifique à la distribution
  • S'assurer que les commandes rust sont disponibles : . "$HOME/.cargo/env" (bash) ou source "$HOME/.cargo/env.fish" (fish)
  • Compiler la charge utile : cd payload && ./build.sh && cd ..
  • Exécuter l'outil hôte : sudo -E cargo r -p loader -- target/thumbv6m-none-eabi/release/payload

Connectez maintenant l'appareil avec le mode de téléchargement USB déclenché (soit via le point de test spécial, soit via une flash corrompue) et observez la sortie UART.

Chronologie

  • 5 février - Rapport initial
  • 18 mars - Confirmé par le ZTE PSIRT
  • 7 mai - Publication publique

Crédits externes

  • Beaucoup de rétro-ingénierie du SoC : Stefan Dösinger
  • Test sur appareil fusé : Mio Naganohara
  • Test de pré-version sur un autre appareil fusé : exp-3

Licence

AGPLv3

Télécharger l’outil