Recherche ANZEN : comment nous avons compromis Keystone via une vulnérabilité USB

Mis à jour le 25 août 2026
Recherche ANZEN : comment nous avons compromis Keystone via une vulnérabilité USB

Points clés

• Le USB SDK ne vérifie pas la longueur contrôlée par l'hôte, entraînant des lectures et écritures hors limites sur un tampon de taille fixe

• Un attaquant peut détourner la table de rappels de class USB pour transformer une écriture hors limites en exécution de code arbitraire sur le MCU

• Une configuration erronée de la MPU permet d'exécuter directement le shellcode situé en SRAM

• Après avoir obtenu le Passcode, l'OTP et les éléments cryptographiques des puces de sécurité, la clé AES peut être reconstruite pour extraire la phrase mnémonique BIP39

• Les SDK tiers font partie intégrante du périmètre de sécurité critique d'un portefeuille matériel et doivent être soumis à un audit complet

Mise à jour de sécurité (juillet 2026) : Le problème abordé dans cet article a été découvert par OneKey Anzen en mars 2026 lors d'un audit de sécurité conjoint avec Keystone, et affecte le micrologiciel du Keystone 3 Pro intégrant le USB SDK du fournisseur de MCU. Keystone a publié le micrologiciel v2.4.0 le 1er avril 2026, corrigeant intégralement ce problème ; à la date de la publication de l'annonce conjointe, aucune des deux parties n'a trouvé de preuve que cette vulnérabilité ait été exploitée pour attaquer des utilisateurs de Keystone. Une exploitation réussie nécessite qu'un attaquant possède physiquement l'appareil, connaisse le PIN correct, déverrouille l'appareil, obtienne l'approbation de l'utilisateur pour la connexion USB et connecte l'appareil à un ordinateur contrôlé par l'attaquant. Les utilisateurs exécutant un micrologiciel antérieur à la version v2.4.0 doivent immédiatement effectuer la mise à niveau vers la dernière version ; l'utilisation de la signature par QR isolée permet d'éviter ce vecteur d'attaque USB spécifique, mais la mise à niveau reste recommandée. Pour plus de détails, consultez la mise à jour de sécurité conjointe de Keystone et OneKey Anzen.

Début août s'est tenue l'édition 2026 de Black Hat USA à Las Vegas. Pour des raisons de visa, notre présentation a été convertie au format on-demand. Ce sujet est issu de notre audit du portefeuille Keystone, au cours duquel nous avons découvert un problème critique dans le USB SDK fourni par le fabricant de la puce, permettant in fine d'extraire la phrase mnémonique.

En réalité, ce n'est pas la première fois que la pile logicielle USB présente des failles. Au cours de la dernière décennie, d'innombrables attaques sévères sur divers appareils ont été liées aux piles USB. Des vulnérabilités de pilotes USB et de redirection distante en 2014, à Fusée Gelée, checkm8 et Kamakiri, jusqu'à la publication d'usbliter8 en 2026.

Les vulnérabilités répétées du protocole USB

2014 : Les attaques USB nécessitent-elles vraiment un accès physique ?

En 2014, NCC Group présentait à Black Hat Asia l'exposé intitulé « USB Attacks Need Physical Access Right? Not Any More... ».

À l'époque, les vulnérabilités USB étaient généralement classées parmi les « attaques physiques locales » : l'attaquant devait brancher un appareil malveillant sur la machine cible pour déclencher une faille dans le pilote via des descripteurs malformés ou des données spécifiques à une classe (class-specific data).

Dans cette recherche, les experts ont utilisé la redirection USB RemoteFX pour rediriger un périphérique USB bas niveau vers un serveur Windows Server distant. Le périphérique USB malveillant était physiquement connecté au client de l'attaquant, mais le pilote qui analysait réellement les données USB malveillantes s'exécutait sur le serveur distant.

2018, Fusée Gelée : Confondre la taille demandée avec la taille de l'objet

Dévoilée en 2018, Fusée Gelée est l'un des cas les plus classiques de vulnérabilité de BootROM USB. La faille résidait dans le Recovery Mode (RCM) de NVIDIA Tegra. L'appareil démarre la pile USB dans un environnement BootROM très précoce et attend que l'hôte téléverse une image de restauration. La vulnérabilité se trouvait dans une requête de contrôle GET_STATUS en apparence banale.

Selon la sémantique USB, un objet de statut ne mesure que 2 octets. L'hôte indique via wLength la quantité maximale de données qu'il est prêt à recevoir, et le périphérique doit renvoyer min(longueur demandée par l'hôte, longueur des données réelles disponibles).

Cependant, lors du traitement du destinataire endpoint, la BootROM exécutait par erreur une logique similaire à celle-ci :

status = get_usb_endpoint_status(index);

/* Erreur : utilisation de la longueur de requête fournie par l'hôte */
size_to_tx = setup_packet.length;

memcpy(dma_buffer, &status, size_to_tx);

Autrement dit, la longueur réelle de l'objet de 2 octets n'était pas utilisée. La valeur wLength contrôlée par l'attaquant devenait directement la longueur passée à memcpy, pouvant théoriquement atteindre 65 535 octets.

De plus, avant de vérifier les signatures, le mode RCM permettait déjà à l'attaquant de placer un payload volumineux en mémoire ; cette zone de payload se trouvait par coïncidence à proximité de la pile d'exécution active de la BootROM. En choisissant le buffer DMA et en agençant la disposition mémoire, la copie hors limites permettait d'écraser la pile d'exécution avec des données contrôlées par l'attaquant.

À cette époque, l'environnement BootROM était dépourvu de stack canaries classiques, d'ASLR et de protections efficaces contre l'exécution de la mémoire. Résultat : avant même que les verrouillages de sécurité et les restrictions de privilèges ne soient appliqués, l'attaquant obtenait l'exécution de code arbitraire dans le contexte de la BootROM.

Cela présente une ressemblance directe avec la vulnérabilité du portefeuille matériel détaillée plus bas.

2019, checkm8 : L'attaquant ne contrôle pas seulement les données, mais aussi « l'inachevé »

Publiée en 2019, checkm8 est l'une des vulnérabilités de la SecureROM d'Apple ayant eu le plus d'impact. Touchant plusieurs générations d'appareils iOS, elle permettait à un attaquant d'obtenir l'exécution de code en mode DFU au sein du contexte BootROM, afin de dumper la SecureROM, déchiffrer le keybag du firmware ou basculer l'appareil dans un état de rétrogradation (demotion) autorisant le JTAG. La faille se situant dans la BootROM gravée lors de la fabrication de la puce, les appareils déjà commercialisés ne pouvaient pas être corrigés par une mise à jour iOS.

Contrairement à Fusée Gelée, checkm8 ne découle pas d'une simple erreur de vérification de longueur. Son cœur réside dans un use-after-free survenant au sein de la machine à états USB DFU d'Apple : une phase de données USB ne se terminait pas normalement, laissant inchangées les variables globales conservant l'état de la phase de données ; toutefois, le buffer DFU sous-jacent était libéré lors de la sortie de la pile USB.

En situation normale, après réception d'une requête DFU_DNLOAD comportant une phase DATA, la BootROM enregistre le buffer de réception et wLength. Une fois les données reçues, le rappel de complétion réinitialise ces états ; à la sortie du mode DFU, le buffer I/O correspondant est alors libéré.

DFU_DNLOAD(setup)
{
    ep0_buffer    = dfu_io_buffer;
    ep0_remaining = setup.wLength;
}

DATA_complete()
{
    ep0_buffer    = NULL;
    ep0_remaining = 0;
}

DFU_exit()
{
    free(dfu_io_buffer);
}

checkm8 tire parti de l'intervalle entre ces étapes. L'attaquant commence par initier une requête avec une phase DATA pour que la BootROM enregistre le pointeur de buffer et la longueur restante, puis s'abstient délibérément d'envoyer la totalité des données. En annulant le transfert ou en provoquant un dépassement de délai (timeout), le rappel de complétion DATA ne s'exécute pas ; l'attaquant déclenche ensuite un changement d'état DFU ou un reset USB, forçant la pile USB à quitter et à libérer le buffer initial.

À ce stade, la mémoire est libérée alors que l'ancien pointeur persiste dans l'état de réception :

free(dfu_io_buffer);
ep0_buffer = old_dfu_io_buffer;  // Pointeur pendant (dangling pointer)

Il reste ensuite à façonner le heap. L'exploit public provoque la fuite de plusieurs objets de requête USB pour modifier l'emplacement des allocations suivantes, obligeant le nouveau buffer DFU à être alloué ailleurs, tandis que l'ancienne adresse est réoccupée par des objets tels que usb_device_io_request.

Lors de l'envoi suivant de données USB, la BootROM continue d'écrire à l'adresse de ep0_buffer, écrasant en réalité le nouvel objet. L'exploit modifie des champs comme callback et next pour faire pointer l'adresse de rappel vers le payload préparé. Dès la fin de la requête USB suivante, la SecureROM saute dans le code de l'attaquant.

2020, Kamakiri : Comment transformer une requête USB en un saut indirect

Kamakiri cible le mode de téléchargement BootROM d'une série de SoC MediaTek. Elle permet de contourner le mécanisme d'authentification limitant les Download Agent non autorisés pour exécuter un payload de premier niveau non signé dans le contexte BootROM. Grâce à cette capacité d'exécution, les payloads ultérieurs peuvent manipuler la mémoire flash, dumper la mémoire, modifier l'état de démarrage ou restaurer un appareil incapable de démarrer normalement.

La chaîne d'exploitation de Kamakiri est particulièrement concise, car la BootROM de MediaTek fournit d'origine la première capacité nécessaire : téléverser des données dans une région SRAM connue via le protocole de téléchargement. L'attaquant y dépose le payload stage 1, puis déclenche la vulnérabilité via une requête de contrôle USB spécifique.

La faille se situe précisément dans le traitement de dispatch des requêtes de contrôle USB. L'analyse par rétro-ingénierie indépendante résume la logique correspondante sous une forme similaire à :

handler = handler_array[value * 13];
handler();

Le problème fondamental réside dans le fait que la valeur de sélection issue de la requête USB est utilisée pour indexer la table de pointeurs de fonction sans vérification de limites adéquate. Dès lors que l'attaquant trouve une valeur de contrôle appropriée pour que l'entrée lue corresponde à l'adresse du payload déposé en SRAM, une simple requête USB se transforme en un appel indirect vers le payload. L'agencement de la BootROM pouvant varier selon les SoC, l'indice exact et l'adresse du payload nécessitent parfois un ajustement ou une énumération.

Le PoC public envoie ce transfert de contrôle après avoir téléversé le stage 1 :

ctrl_transfer(
    0xA1,   # class request, device-to-host, interface recipient
    0,
    0,
    10,     # wIndex
    0
)

2026, usbliter8 : Le contrôleur USB déplace le pointeur d'écriture DMA avant le buffer

Publié en juin 2026, usbliter8 a de nouveau étendu l'exploitation BootROM non corrigeable par voie logicielle aux SoC Apple A12, A13 ainsi que S4 et S5.

Une fois l'exploit réussi, l'attaquant peut exécuter du code dans le contexte EL1 de la SecureROM, modifier le comportement DFU, rétrograder temporairement le production mode de la puce ou contourner les contrôles de signature pour démarrer un iBoot d'origine non signé. La chaîne de confiance de démarrage du processeur d'application est ainsi brisée de manière permanente pour les versions matérielles affectées.

usbliter8 diffère des cas précédents en exploitant principalement la sémantique du pointeur DMA du contrôleur Synopsys DWC2 lors du traitement des transactions SETUP.

Selon le protocole USB, la portion de données d'une transaction SETUP est fixée à 8 octets. Le contrôleur DWC2 accepte de recevoir consécutivement trois paquets SETUP et les écrit séquentiellement en mémoire par DMA. Trois paquets SETUP normaux occupent au total 24 octets. À l'arrivée du quatrième paquet SETUP, le contrôleur soustrait systématiquement 24 octets au pointeur d'écriture DOEPDMA afin de repositionner l'adresse au début du buffer de paquets SETUP. Pour des paquets SETUP réguliers de 8 octets, cette logique fonctionne parfaitement.

Cependant, le contrôleur accepte également des données SETUP anormales de moins de 8 octets et fait avancer le pointeur DMA en fonction de la taille réellement écrite, avec une granularité d'écriture interne de 4 octets. Si l'attaquant envoie à plusieurs reprises des paquets courts de 4 octets, les trois premiers paquets ne feront avancer l'adresse que de 12 octets au total. Lors de l'arrivée du quatrième paquet, le contrôleur recule toujours de 24 octets. Par conséquent, le lot de données SETUP suivant ne s'écrit plus au début du buffer d'origine, mais 12 octets avant celui-ci. En répétant ce processus, la position d'écriture DMA recule vers les adresses inférieures avec un pas prévisible, créant un buffer underflow stable.

En général, un DMA hors limites d'un contrôleur USB ne permet pas nécessairement de corrompre n'importe quelle zone de la mémoire système. Les SoC modernes s'appuient sur une IOMMU pour restreindre les plages d'adresses accessibles par le contrôleur USB. Toutefois, dans l'environnement SecureROM des puces A12 et A13, le DART USB est configuré en bypass, privant le DMA du contrôleur d'une isolation d'adresses effective. L'underflow ne se limite donc plus à un petit buffer SETUP, mais peut s'étendre et écraser d'autres objets en SRAM.

Sur l'A12, le buffer DMA USB est adjacent à la pile de la tâche USB. L'exploit peut écraser le link register sauvegardé et prendre le contrôle du pointeur de programme lors du changement de contexte. L'A13 intégrant le mécanisme Pointer Authentication, l'écrasement direct de l'adresse de retour n'est pas viable. L'exploit public utilise donc une chaîne de corruption mémoire plus complexe, écrasant finalement le pointeur du USB interrupt handler en BSS pour exécuter le code de l'attaquant lors de la prochaine interruption USB. Dans les deux cas, l'attaquant obtient l'exécution SecureROM en EL1.

De la lecture/écriture hors limites USB à l'exécution de code arbitraire

La cause fondamentale de la vulnérabilité présentée cette fois à Black Hat USA réside également dans un USB SDK.

Dans le CDC, les requêtes de line-coding ne nécessitent qu'une petite structure fixe. Cependant, le USB SDK intégré allouait un CmdBuff fixe de 8 octets, puis transmettait directement le paramètre req->wLength fourni par l'hôte aux fonctions sous-jacentes d'émission ou de réception. Le code n'effectuait aucune vérification de taille et n'imposait pas que la longueur corresponde à la taille fixe stipulée par le protocole, ouvrant la voie à des lectures et écritures hors limites.

Code de la vulnérabilité de lecture/écriture hors limites du USB SDKCode de la vulnérabilité de lecture/écriture hors limites du USB SDK

Disposer d'une primitive de lecture/écriture hors limites n'est qu'une première étape de corruption mémoire ; l'objectif final restait d'obtenir l'exécution de code arbitraire.

La faille se situant dans le chemin USB CDC, j'ai examiné la pile USB le long du flux de dispatch de la requête. En analysant g_usbDev, j'ai remarqué dev.class_cb. Il ne s'agit pas d'un simple pointeur de données, mais de la table de rappels d'exécution du USB class driver ; lors du traitement des requêtes, le USB core appelle des fonctions telles que Setup, DataIn et DataOut via cette table. Réécrire class_cb permet de diriger une requête USB normale ultérieure vers des pointeurs de fonction sous notre contrôle, ce qui en fait une cible de détournement idéale.

L'exploitation complète se décompose en deux requêtes USB.

La première requête utilise SET_LINE_CODING pour déclencher l'écriture hors limites. Durant cette écriture, trois opérations sont réalisées simultanément : placer le shellcode dans CmdBuff, disposer une fausse table de rappels dans la SRAM adjacente, et remplacer class_cb par l'adresse de cette fausse table.

Ensuite, j'envoie une requête ordinaire GET_LINE_CODING. Le USB core traite cette requête normalement et appelle le rappel Setup du class driver via class_cb->Setup(...). La différence est qu'à cet instant, class_cb ne pointe plus vers la table d'origine, mais vers notre table forgée, dont l'entrée Setup pointe vers le shellcode dans CmdBuff. La seconde requête USB amène ainsi directement le PC sur le code injecté en SRAM.

Le shellcode résidant en SRAM, il fallait également composer avec la protection contre l'exécution dans les zones de données. Cependant, le firmware utilisait un format de taille incorrect lors de la configuration de la MPU, aboutissant à une plage de protection de 0. La région MPU initialement prévue pour restreindre l'exécution du segment BSS était en pratique inactive. Ce code injecté dans CmdBuff a donc pu être exécuté directement.

Détournement de la table de rappels USB pour exécuter le shellcodeDétournement de la table de rappels USB pour exécuter le shellcode

Une fois la théorie validée, la phase pratique a débuté par l'évaluation de la portée réelle des lectures/écritures hors limites. En construisant des transferts de contrôle de 64 octets, la lecture hors limites pouvait fuiter en continu environ 24 KB de SRAM, mais l'écriture hors limites au-delà de 4 352 octets provoquait un plantage systématique.

L'analyse a révélé qu'à cet offset, l'écriture écrasait un pointeur toujours utilisé situé à CmdBuff + 0x1130, privant immédiatement EP0 de la capacité de recevoir la suite des données.

Emplacement où l'écriture hors limites provoque le plantage de l'appareilEmplacement où l'écriture hors limites provoque le plantage de l'appareil

La résolution s'est avérée simple : bénéficiant déjà de la lecture hors limites, il suffisait de lire l'état réel en mémoire et de charger ces données dumpées dans le shellcode pour reconstruire une logique complète d'exécution de code arbitraire.

Bien qu'il soit possible d'écraser class_cb, il était primordial de ne pas détruire le canal USB. L'écriture longue place d'abord le shellcode et la fausse table de rappels en SRAM, tout en faisant pointer temporairement class_cb vers cette fausse table ; l'envoi consécutif de GET_LINE_CODING amène le USB core à exécuter le shellcode via le faux Setup. Une fois sa tâche accomplie, le shellcode écrit les résultats à la suite de CmdBuff, restaure le class_cb d'origine et appelle le Setup initial afin de clore normalement le transfert EP0. Chaque détournement devient ainsi un simple détour temporaire : l'USB reste opérationnel et prêt pour l'injection des tâches suivantes.

Le firmware tentait bien de rendre le segment BSS non exécutable via la MPU, mais le code de configuration utilisait un format de taille erroné, aboutissant à une plage de protection calculée nulle.

De l'exécution arbitraire sur le MCU à l'extraction de la phrase mnémonique

Obtenir l'exécution de code arbitraire sur le MCU ne garantit pas immédiatement l'accès à la phrase mnémonique. L'architecture du portefeuille matériel se présente comme suit : les informations chiffrées de la phrase mnémonique sont stockées dans le SE DS28S60, et la clé AES nécessaire à leur déchiffrement implique conjointement l'OTP ainsi que deux éléments sécurisés : ATECC608 et DS28S60.

Architecture de sécurité du portefeuille matérielArchitecture de sécurité du portefeuille matériel

Le processus de génération de cette clé AES est illustré ci-dessous :

Flux de génération de la clé AESFlux de génération de la clé AES

Les identifiants intervenant dans les échanges avec le SE et la génération de la clé AES sont principalement générés à partir du Passcode (mot de passe de déverrouillage) et de l'OTP, selon le schéma suivant :

Flux de génération des identifiants Passcode et OTPFlux de génération des identifiants Passcode et OTP

Le Passcode peut être retrouvé par force brute après extraction du hash du mot de passe ; il ne reste alors plus qu'à récupérer le sel de l'OTP pour achever la génération de la clé AES. Pour extraire l'OTP, le shellcode s'exécutant au même niveau de privilège que le firmware légitime, il est possible d'écrire dans les registres MPU via la vulnérabilité USB pour désactiver la MPU et lire la mémoire OTP.

La méthode d'exploitation finale se déroule ainsi : une fois la clé AES récupérée, il suffit de déchiffrer l'entropie BIP39 pour reconstituer la phrase mnémonique.

Chaîne d'attaque complète de la vulnérabilité USB à l'extraction de la phrase mnémoniqueChaîne d'attaque complète de la vulnérabilité USB à l'extraction de la phrase mnémonique

Conclusion

Tout au long de cette recherche, l'IA a été largement mise à contribution dans l'analyse des vulnérabilités et le développement de l'exploit. L'émergence de l'IA améliore considérablement l'efficacité des chercheurs en sécurité et transforme le coût d'étude de telles failles. Alors que la découverte et l'exploitation de vulnérabilités demandaient auparavant un investissement humain massif pour parvenir à une exploitation stable, la compréhension du code, la correspondance de structures, la génération de scripts et le diagnostic d'erreurs s'en trouvent aujourd'hui nettement accélérés.

Par ailleurs, qu'il s'agisse de USB SDK ou d'autres composants tiers, ces éléments sont fréquemment négligés lors des audits de sécurité des produits. C'est pourtant bien souvent par ce maillon faible que l'ensemble de la sécurité d'un système finit par s'effondrer.

Sécurisez votre parcours crypto avec OneKey

View details for Boutique OneKeyBoutique OneKey

Boutique OneKey

Le portefeuille matériel le plus avancé au monde.

View details for Télécharger l'applicationTélécharger l'application

Télécharger l'application

Tradez des actifs mondiaux. Commencez en quelques minutes avec seulement votre e-mail.

View details for OneKey SifuOneKey Sifu

OneKey Sifu

Clarté Crypto—À un appel de distance.

Continuer à lire