
D'une carte nue à l'accès root : piratage matériel d'un ZyXEL P-870HN (BCM6368) via UART — CVE-2025-0890 + CVE-2024-40891, sur mon propre matériel.
Deux photos prises au téléphone d'une carte de routeur non étiquetée → console série → bypass d'authentification → shell root — reproduction de la chaîne réelle, toujours non corrigée, CVE-2025-0890 (compte supervisor caché) + CVE-2024-40891 (injection de commande CLI) sur du matériel qui m'appartient.
par damik0 · 2026-08-13 · un walkthrough de hardware-hacking
📷 PCB photos ─▶ 🔬 chip recon ─▶ 🏷️ model ID ─▶ 📍 find UART (J2)
└─▶ 🔌 serial console ─▶ 🚪 hidden account ─▶ ⛓️ break out of CLI
└─▶ 🐚 root shell ─▶ 🔓 dump + crack every credential
J'avais une carte de routeur lambda qui traînait sur mon bureau. Sans boîtier, sans étiquette, sans la moindre idée de ce que c'était. En partant de rien, seulement deux photos, j'ai déchiffré chaque puce du PCB, déterminé le modèle exact, trouvé le connecteur série (UART), m'y suis branché, suis entré par un compte backdoor d'usine, me suis échappé d'un menu constructeur verrouillé vers un shell Linux root, et j'ai extrait tous les mots de passe de la machine — cassés en moins d'une seconde.
Aucun de ces bugs n'est de moi. Ce sont des problèmes ZyXEL documentés que le constructeur a dit qu'il ne corrigerait pas. Ce repo porte sur le comment, de bout en bout, et le raisonnement à chaque étape.
J'ai déduit l'identité du modèle à partir du numéro de carte sérigraphié 45-402-000022 et du code-barres, recoupés avec TechInfoDepot, la table matérielle d'OpenWrt et l'ID FCC I88P870HN51B :
Pourquoi le numéro de carte est la clé qui ouvre tout : les passerelles ODM comme celle-ci sont des designs de référence. Une fois que le numéro de pièce sérigraphié correspond à une carte documentée, vous héritez des devoirs de quelqu'un d'autre — la puce flash exacte, l'emplacement et le brochage de l'UART, les comptes par défaut. L'identification n'est pas une formalité ; c'est ce qui transforme le sondage à l'aveugle en attaque ciblée.
Petite amélioration du confort d'abord : j'ai bricolé un petit uploader de photos en LAN (stdlib Python, zéro dépendance) pour pouvoir photographier la carte avec mon téléphone et que les photos arrivent directement sur ma machine. La moitié d'une bonne session, c'est d'éliminer ce genre de frictions.
Je suis passé puce par puce sur les photos haute résolution. Les types de boîtiers racontent l'histoire avant même de lire les marquages : le BGA au centre est le cerveau, le TSOP-48 est presque toujours la flash parallèle, la petite boîte métallique avec un câble coaxial est la radio. Le SoC était caché sous un blindage RF (là pour l'EMI et comme dissipateur thermique), j'ai donc ouvert le capot pour lire le marquage en dessous — un Broadcom BCM6368.

La carte, cartographiée : (1) SoC BCM6368, (2) RAM DDR2, (3) WiFi BCM43222, (4) transformateur de ligne DSL, (5) magnétiques Ethernet, (6) connecteurs USB internes, (7)(8) marquages de la carte, (9) le connecteur console J2, (10) une empreinte JTAG non soudée.
La sérigraphie 45-402-000022 correspondait exactement à la carte documentée, et chaque puce — SoC, flash, WiFi, les 64 Mo de RAM — correspondait pièce par pièce à la variante -53b. Désormais, je ne savais pas seulement ce que c'était ; je savais où vivait son UART et quel compte me laisserait entrer.
Juste à côté du SoC, il y avait un connecteur 6 broches à angle droit, sérigraphié J2, avec un triangle marquant la broche 1. Confirmé avec la page OpenWrt du modèle :
115200 8N1, TTL 3,3 V.

Comment trouver un UART quand aucun wiki ne donne le brochage — la partie qui montre que ça ne consiste pas juste à suivre une recette :
- GND — continuité (bip) avec le plan de masse / le blindage, carte non alimentée.
- VCC — un rail qui se maintient à ~3,3 V stable une fois alimenté.
- TX — reste au niveau haut à ~3,3 V (état mark de l'UART) et chute/clignote visiblement dès que l'appareil démarre et crache son journal. Ce clignotement, c'est la console qui parle.
- RX — généralement la discrète : flottante ou faiblement tirée, aucune activité au démarrage.
Et si vous ne connaissiez pas le débit ?
115200est la valeur par défaut du CFE Broadcom, mais à l'aveugle vous balayeriez les débits courants (9600 → 115200) jusqu'à ce que le charabia devienne de l'ASCII, ou vous mesureriez la période de bit la plus courte sur un oscilloscope et feriez1 / t.
J'ai câblé un adaptateur USB-TTL (HW-597, PL2303) avec le cavalier sur 3,3 V. Ça compte : l'UART du BCM6368 est en TTL 3,3 V, pas en RS-232 (±12 V) — branchez-y un vrai port série ou un adaptateur 5 V et vous grillez la broche. La masse d'abord, puis RX/TX croisés, VCC laissé flottant pour que rien n'alimente la carte en retour. Avant de mettre sous tension, j'ai vérifié GND et VCC au multimètre, puis :
screen /dev/ttyUSB0 115200
Je l'ai alimentée, j'ai regardé le CFE passer la main au noyau, j'ai regardé BusyBox init démarrer, et j'ai atterri sur une invite de login.
Le login a cédé devant un compte d'usine caché que ZyXEL ne documente jamais pour l'utilisateur final :
user: supervisor
pass: zyad1234
Privilège système complet. C'est le cœur de CVE-2025-0890.
Ce login m'a fait atterrir dans une CLI constructeur verrouillée (consoled, une invite >) : pas de sh, aucun des outils Linux habituels — une prison enveloppant le vrai système. Mais elle exposait un diagnostic ping, et cette commande construisait son argument directement dans une chaîne shell avec zéro assainissement. Je lui ai donc fourni un métacaractère shell :
> ping 127.0.0.1; sh
…et j'ai atterri dans un shell BusyBox root (#).
Pourquoi un seul point-virgule suffit : le gestionnaire de la CLI fait l'équivalent de
system("ping " + userinput). Le;ferme la commandepinget en lance une deuxième —sh— qui hérite du stdin/stdout de la console, vous obtenez donc un shell interactif. Le compte était déjà uid 0 ; la CLI était la seule chose entre moi et un vrai shell, et une entrée non assainie a démoli ce mur. Enchaîner une injection CLI authentifiée sur le compte caché, c'est exactement le schéma référencé comme CVE-2024-40891.
Avec root, j'ai regardé le système et la flash :
# cat /proc/version
Linux version 2.6.30 ... (Buildroot 2010.02) #1 Mon Jun 11 2012
# cat /proc/mtd
dev: size erasesize name
mtd0: 004f5000 004f5000 "Physically mapped flash" # 5,197,824 bytes = the whole firmware
Ce que
/proc/mtdvous dit : la flash est une NOR parallèle, mappée en mémoire — le CPU la voit comme une plage d'adresses plate (vers0xB8000000en KSEG1 MIPS), exposée comme une seule partition MTD. C'est une excellente nouvelle pour un dump : la NOR se relit proprement, sans les octets de rechange/OOB ni les bizarreries d'ECC contre lesquelles on se bat sur la NAND./dev/mtdblock0est l'image du firmware.
Puis les mots de passe. Il n'y a pas de /etc/shadow — les hash sont directement dans /etc/passwd, avec l'antique DES crypt :
supervisor:SuO7vycdWI/rU:0:0:Administrator:/:/bin/sh
support:xoKf506EVkGKw:1:0:Technical Support:/:/bin/sh
user:QWOftoXez8Goo:2:0:Normal User:/:/bin/sh
admin:OJGXQ9dWyb9m2:100:0:Administrator:/:/bin/sh
Je les ai extraits et cassés hors ligne avec John the Ripper (--format=descrypt). Les six sont tombés en moins d'une seconde :
Pourquoi ils s'évaporent instantanément : le DES
crypt(3)n'a jamais utilisé que les 8 premiers caractères du mot de passe et un sel de 12 bits, passés dans 25 rounds de DES. Sur un CPU moderne, un cracker DES en bitslice mâche des dizaines de millions de candidats par seconde — des mots de passe d'usine triviaux commeadminousupportne comptent même pas comme un effort. Ce schéma était obsolète il y a des décennies ; le retrouver dans un firmware de série, c'est ça, la vraie découverte.
Quatre comptes d'usine avec privilège root, mots de passe triviaux, identiques sur chaque unité de ce modèle.
Toute la chaîne, en une ligne :
PCB photo → ID the SoC → find the UART (J2) → serial console
→ hidden account (F-1) → locked CLI → command injection (F-2)
→ root shell → dump + crack every credential (F-3)
Je n'ai rien découvert de tout ça. C'est de la reproduction, par accès physique, de bugs publics et toujours d'actualité :
supervisor:zyad1234, sur des CPE DSL ZyXEL hérités. → F-1Ces failles touchent du matériel en fin de vie que ZyXEL a dit qu'il ne corrigerait pas, et elles ont été vues exploitées dans la nature — c'est exactement pour ça que toucher du doigt les mécanismes compte. La valeur ici n'est pas un nouveau bug ; c'est le flux de travail complet, du silicium au shell, réalisé et documenté.
execve avec un vecteur d'arguments, pas system())./etc/shadow avec un hash moderne (bcrypt / SHA-512-crypt), pas le DES.mtd0 (/dev/mtdblock0, 5 197 824 octets) via le WiFi du routeur lui-même + nc, puis le déballer avec binwalk (attendez-vous à un en-tête CFE + noyau compressé LZMA + rootfs SquashFS + nvram). Méthode complète dans evidence/dump-methods.md.Analyse du PCB à partir de photos (ImageMagick), un multimètre, un adaptateur USB-TTL PL2303 (HW-597) en 3,3 V, screen pour la console série (115200 8N1), et John the Ripper (descrypt) pour les hash. Identification via OpenWrt, TechInfoDepot et la base FCC.
Identification de l'appareil
45-402-000022, flash MX29LV640EBTI-70G)Vulnérabilités
supervisor cachéMatériel et technique
Outils
Fait sur mon propre matériel, air-gappé, aucun système tiers impliqué. Les bugs sont publiquement documentés et cités ci-dessus. Ce que je signe, c'est la méthode de hardware-hacking de bout en bout et son exécution — pas la découverte des failles.
| Modèle | ZyXEL P-870HN-53b — passerelle VDSL2/ADSL2+ WiFi, fabriquée par MitraStar |
| Époque | ~2013 (les codes de date indiquent la semaine 21 de 2013) ; une unité distribuée par un FAI |
| SoC | Broadcom BCM6368UKPBG — MIPS double cœur (BMIPS4350), ~400 MHz |
| RAM | 2× Winbond W9425G6JH-5 — 256 Mbit DDR2 ×16 chacune = 64 Mo sur un bus 32 bits |
| Flash | Macronix MX29LV640EBTI-70G — NOR parallèle de 8 Mo, TSOP-48 |
| WiFi | Broadcom BCM43222 (802.11n ; silicium double bande, câblé en 2,4 GHz uniquement) |
| AFE DSL | Broadcom BCM6302 |
| Firmware | Linux 2.6.30 + BusyBox v1.00, bootloader Broadcom CFE (compilé le 2012-06-11) |
| Broche | Signal |
|---|
| 1 | VCC 3,3 V | laisser déconnecté |
| 2 | Tx | → RX de l'adaptateur |
| 3 | Rx | → TX de l'adaptateur |
| 4 | GND | → GND de l'adaptateur |
| 5 | NC |
| Compte | Mot de passe | UID | GID | Privilège |
|---|
| supervisor | zyad1234 | 0 | 0 | root |
| support | support | 1 | 0 | groupe root |
| user | user | 2 | 0 | groupe root |
| admin | admin | 100 | 0 | groupe root |
| nobody | zyad1234 | 99 | 99 | ftp |
| # | Constat | CWE | Sévérité |
|---|
| F-1 | Comptes d'usine cachés avec privilège root (supervisor, support, user, admin) — identifiants codés en dur, identiques sur chaque unité. | CWE-798 | Élevée |
| F-2 | Injection de commande authentifiée dans le diagnostic ping de la CLI → évasion vers un shell root, franchissant la frontière de privilège de la CLI. | CWE-78 | Élevée |
| F-3 | Stockage faible des identifiants — hash DES-crypt dans /etc/passwd (pas de shadow) → cassage hors ligne instantané. | CWE-916 / CWE-256 | Moyenne |