Début novembre 2026, l’expert en rétro-ingénierie Andrew Spode a dévoilé un hack matériel particulièrement impressionnant : sur un appareil photo pour enfants sans marque acheté en ligne pour une douzaine d’euros, il a réussi à porter et exécuter le légendaire jeu de tir DOOM. Le portage propose une expérience de jeu complète — déplacements latéraux, tirs et exploration — avec un rendu fluide à environ 30 images par seconde, accompagné d’effets sonores polyphoniques et de la bande originale.
Le boîtier en plastique grossier arbore des motifs de dessins animés. L’emballage ne mentionne aucun fabricant ni marque déposée, et l’unique référence inscrite sur le manuel d’utilisation est le code « X9S ». Sous la coque en plastique se cache pourtant un processeur 32 bits de conception chinoise : le SoC HX3302B.
Pour réduire les coûts au strict minimum, la chaîne logistique des produits de marque blanche recourt systématiquement à des puces clés en main prêtes à l’emploi. Ce jouet bon marché s’est ainsi retrouvé doté d’une puissance de calcul qui dépasse largement les besoins d’un simple capteur photo. Conçu à la hâte sur la base d’un design de référence, le firmware d’origine omet la moindre vérification de sécurité, transformant cet appareil anodin en un formidable terrain d’expérimentation pour la bidouille matérielle.
Figure : L’appareil photo jouet exécutant DOOM à 30 FPS sur son écran couleur miniature. Source : Spode’s Abode.
DOOM sur 891 Ko de mémoire vive : un jouet à dix euros délivrant 120 MIPS
Avant le démontage, les informations techniques disponibles sur Internet étaient quasi inexistantes. Le seul indice provenait des métadonnées EXIF des photos de test : le champ constructeur indiquait « HX-Tech », rappelant les démontages menés précédemment par le hacker Jeroen Domburg.
Spode a dessoudé les fils d’alimentation de la batterie pour extraire la carte mère. Au cœur du circuit imprimé se trouvent le SoC HX3302B et une puce mémoire flash SPI de 4 Mo à 8 broches. En extrayant le firmware à l’aide d’un programmateur, l’analyse binaire a révélé que le processeur repose sur le jeu d’instructions OpenRISC.
Les microbenchmarks ont rapidement mis en lumière le profil de la puce. Ce composant basse consommation intègre 8 Ko de cache d’instructions L1 et 8 Ko de cache de données, avec un débit de cache de 164 Mo/s. Le processeur délivre jusqu’à 120 MIPS en crête et maintient environ 82 MIPS même en cas d’erreurs fréquentes de prédiction de branchement. Une puissance largement suffisante pour prendre en charge les calculs de projection 3D d’un moteur vieux de trente ans.
Le goulot d’étranglement majeur résidait dans la mémoire vive. Le système ne dispose au total que de 2 Mo de RAM physique (cadencée à 65 Mo/s en lecture). Les tâches système et le tampon d’affichage consommant l’essentiel de l’espace, la mémoire disponible pour les applications tierces ne dépassait pas 891 Ko. Bien que les cycles d’horloge soient largement suffisants, l’intégralité du moteur de jeu devait tenir dans moins de 1 Mo d’espace de travail.
Figure : Le SoC HX3302B et la mémoire flash à 8 broches sur le circuit imprimé. Source : Spode’s Abode.
Dump de firmware à l’aveugle via l’EXIF : un design générique dépourvu de contrôle
Le mécanisme de mise à jour du firmware a constitué la première brèche. Au démarrage, l’appareil scrute la racine de la carte SD à la recherche d’un fichier nommé Xly100s.bin. Bien que l’en-tête contienne un champ réservé à une somme de contrôle, le programme de mise à jour ne prend jamais la peine de vérifier la valeur de hachage.
De plus, la procédure applique un écrasement direct : l’écriture débute à l’adresse initiale de la mémoire flash et s’arrête à la taille exacte du fichier fourni. En chargeant un fichier de 1 Mo, l’appareil réécrit uniquement le premier mégaoctet, laissant intacts les 3 Mo restants. Une simple modification de l’horodatage dans l’en-tête déclenche l’écriture, faisant du lecteur de carte SD un point d’injection direct de code non signé.
Après avoir validé l’écriture, il fallait encore cartographier l’organisation mémoire. Spode a conçu une méthode ingénieuse : sauvegarder des images de la mémoire directement dans les en-têtes EXIF des clichés JPEG. La norme autorise jusqu’à 64 Ko de métadonnées EXIF par image, alors que le firmware natif n’en utilisait que 730 octets.
Spode a modifié la routine d’enregistrement photo. L’encodeur matériel JPEG ne pouvant pas lire directement la mémoire de démarrage interne, il a mobilisé le processeur pour transférer par blocs la zone ciblée vers la mémoire tampon de l’EXIF. Chaque appui sur le déclencheur extrayait 4 Ko de mémoire et décalait le pointeur vers la section suivante. En 16 clichés, les 64 Ko de la mémoire de boot ont été intégralement écrits sur la carte SD. L’analyse des données a confirmé l’absence de masque ROM : l’ensemble du code de bas niveau était exposé sans défense.
29 Ko de boucle critique en RAM : le recours au XIP pour briser le seuil des 4 Mo
Sur PC, la version originale de DOOM nécessitait au moins 4 Mo de mémoire vive. L’exécuter dans un espace restreint de 891 Ko imposait une refonte complète de la gestion mémoire. En purgeant les tampons résiduels de l’appareil photo, Spode a réussi à dégager environ 1,4 Mo de mémoire disponible.
Néanmoins, cet espace fragmenté posait problème. Les allocations continues d’une certaine taille échouaient régulièrement en raison des allocations morcelées du moteur originel. Spode a créé des pools de mémoire dédiés aux petites structures et programmé la libération immédiate des textures et sons dès qu’un niveau était terminé.
Le placement du code binaire représentait un autre défi de taille. Le binaire compilé de 260 Ko ne pouvait pas tenir en RAM aux côtés des ressources du jeu. Si la puce permet l’exécution directe depuis la mémoire flash SPI (XIP), les bancs d’essai ont révélé que l’exécution depuis la flash était 18 fois plus lente que depuis la RAM interne.
Le profilage du code sur un émulateur a fourni la solution : 99 % du temps processeur était absorbé par une boucle d’exécution ne pesant que 29 Ko de code machine. Le reste du binaire ne servait qu’à l’initialisation et à la navigation dans les menus. Spode a donc verrouillé ces 29 Ko critiques dans la RAM physique, laissant les routines secondaires s’exécuter à la demande depuis la flash via XIP. Cette architecture hybride a permis au jeu de tourner à pleine vitesse sans saturer la RAM.
Figure : L’appareil photo jouet X9S sans logo ni marque commerciale. Source : Spode’s Abode.
Contournement de la boucle d’événements : un polling direct pour atteindre 30 FPS
Une fois la contrainte mémoire levée, l’architecture logicielle limitait la cadence d’affichage. Le système d’exploitation d’origine reposait sur une boucle d’événements coopérative dont la surcharge de distribution atteignait 38 millisecondes par trame, divisant par deux le taux de rafraîchissement.
Spode a totalement contourné le gestionnaire d’événements du fabricant au profit d’une boucle directe minimaliste. Le code scrute directement les registres matériels des GPIO : les flèches contrôlent les déplacements, le déclencheur sert au tir continu, une impulsion courte sur le bouton marche/arrêt permet de changer d’arme, un appui prolongé ouvre le menu des réglages et percuter un mur actionne l’ouverture des portes. En éliminant cette surcharge d’ordonnancement, le taux d’affichage a grimpé à 30 FPS constants.
La section audio présentait un défi comparable : la carte ne disposait que d’une sortie mono PWM. Spode a conçu un mixeur logiciel pour synthétiser plusieurs canaux d’effets sonores en un signal audio unique.
La musique d’ambiance a elle aussi été réimplémentée sans puce sonore dédiée. Les morceaux d’origine au format MUS s’appuyaient autrefois sur la synthèse FM des circuits OPL2. La carte du jouet étant dépourvue d’un tel composant, Spode a programmé un générateur de formes d’ondes ultra-léger ne consommant que 2 % des ressources du processeur. En combinant ondes carrées et ondes triangulaires, il a produit une mélodie polyphonique à 16 voix qui restitue fidèlement la bande sonore originale sur le haut-parleur miniature intégré.
Exécution arbitraire via SCSI : injection USB sans aucun démontage
Le jeu étant fonctionnel, restait à trouver une méthode pour partager le hack avec la communauté. Distribuer directement une image compilée contenant les éléments du jeu posait des risques de violation de droits d’auteur ; il fallait donc mettre au point un moyen de flasher et d’extraire le firmware sans fer à souder ni démontage physique.
L’interface de stockage de masse USB dissimulait une faille élémentaire. Une fois monté comme clé de stockage, le contrôleur acceptait des commandes SCSI propriétaires non documentées. L’ordinateur hôte pouvait ainsi envoyer des paquets commandant à la puce de sauter à une adresse mémoire spécifique pour y exécuter du code. Spode a injecté une sonde mémoire via le protocole SCSI : le microcode chargé en RAM a pris le relais pour lire les 4 Mo de la flash SPI et renvoyer les données par blocs vers l’ordinateur.
Grâce à cette méthode, n’importe quel passionné peut extraire et sauvegarder le firmware d’origine en quelques secondes à l’aide d’un câble USB classique. Cette interface de débogage oubliée dans la conception de référence a pulvérisé les maigres défenses logicielles de l’appareil.
Le marché de l’électronique à bas coût fonctionne sous la pression permanente des marges et des cadences de production. Pour rogner quelques centimes et gagner quelques semaines de développement, les fabricants inondent les rayons d’appareils bâtis sur des puces génériques dotées d’un surplus de puissance insoupçonné, mais totalement dépourvues de sécurité. Une fois sortis des usines, ces circuits échappent au contrôle de leurs créateurs. Les prouesses de la communauté des hackers prouvent qu’avec un peu de rétro-ingénierie, le moindre gadget oublié peut se muer en une plateforme informatique ouverte et programmable.
Liens de référence :
- Article dédié sur Hackaday
- Blog de rétro-ingénierie de Spode’s Abode
- Analyse de firmware d’appareils photo pour enfants par Sprite_tm