
From a bare PCB to root: hardware-hacking a ZyXEL P-870HN (BCM6368) over UART — CVE-2025-0890 + CVE-2024-40891, on my own hardware.
Two phone photos of an unlabeled router board → serial console → auth bypass → root shell — reproducing the real, still-unpatched chain CVE-2025-0890 (hidden supervisor account) + CVE-2024-40891 (CLI command injection) on hardware I own.
by damik0 · 2026-08-13 · a hardware-hacking walkthrough
📷 PCB photos ─▶ 🔬 chip recon ─▶ 🏷️ model ID ─▶ 📍 find UART (J2)
└─▶ 🔌 serial console ─▶ 🚪 hidden account ─▶ ⛓️ break out of CLI
└─▶ 🐚 root shell ─▶ 🔓 dump + crack every credential
I had a random router board sitting on my desk. No case, no label, no idea what it was. Starting from nothing but two photos of it, I read every chip off the PCB, pinned down the exact model, found the serial (UART) header, hooked into it, walked in through a factory backdoor account, broke out of a locked-down vendor menu into a root Linux shell, and pulled every password off the box — cracked in under a second.
None of these bugs are mine. They're documented ZyXEL issues the vendor has said it won't patch. What this repo is about is the how, end to end, and the reasoning at each step.
I worked the ID out from the silkscreen board number 45-402-000022 and the barcode, cross-referenced against TechInfoDepot, OpenWrt's hardware table, and the FCC ID I88P870HN51B:
| Model | ZyXEL P-870HN-53b — VDSL2/ADSL2+ WiFi gateway, built by MitraStar |
| Era | ~2013 (date-codes point to week 21 / 2013); an ISP-distributed unit |
| SoC | Broadcom BCM6368UKPBG — dual-core MIPS (BMIPS4350), ~400 MHz |
| RAM | 2× Winbond W9425G6JH-5 — 256 Mbit DDR2 ×16 each = 64 MB on a 32-bit bus |
| Flash | Macronix MX29LV640EBTI-70G — 8 MB parallel NOR, TSOP-48 |
| WiFi | Broadcom BCM43222 (802.11n; dual-band silicon, wired to 2.4 GHz only) |
| DSL AFE | Broadcom BCM6302 |
| Firmware | Linux 2.6.30 + BusyBox v1.00, Broadcom CFE bootloader (built 2012-06-11) |
Why the board number is the key that unlocks everything: ODM gateways like this are reference designs. Once the silkscreen part number matches a documented board, you inherit someone else's homework — the exact flash chip, the UART location and pinout, the default accounts. Identification isn't a formality; it's what turns blind probing into a targeted attack.
Small quality-of-life thing first: I threw together a tiny LAN photo-uploader (Python stdlib, zero deps) so I could shoot the board with my phone and have the pics land straight on my machine. Half of a good session is killing friction like this.
I went chip by chip off the high-res photos. Package types tell you the story before you even read the markings: the BGA in the middle is the brain, the TSOP-48 is almost always parallel flash, the little can with a coax pigtail is the radio. The SoC was hiding under an RF shield (there for EMI + as a heat spreader), so I popped the can to read the marking underneath — a Broadcom BCM6368.

The board, mapped: (1) BCM6368 SoC, (2) DDR2 RAM, (3) BCM43222 WiFi, (4) DSL line transformer, (5) Ethernet magnetics, (6) internal USB headers, (7)(8) board markings, (9) the J2 console header, (10) an unpopulated JTAG footprint.
The silkscreen 45-402-000022 matched the documented board exactly, and every chip — SoC, flash, WiFi, the 64 MB of RAM — lined up piece by piece with the -53b variant. Now I didn't just know what it was; I knew where its UART lived and what account would let me in.
Right next to the SoC there was a populated 6-pin right-angle header, silkscreened J2, with a triangle marking pin 1. Confirmed against OpenWrt's page for the model:
| Pin | Signal | |
|---|---|---|
| 1 | VCC 3.3V | leave it disconnected |
| 2 | Tx | → adapter RX |
| 3 | Rx | → adapter TX |
| 4 | GND | → adapter GND |
| 5 | NC |
115200 8N1, 3.3V TTL.

How you find a UART when there's no wiki telling you the pinout — the part that shows this isn't just following a recipe:
- GND — continuity (beep) to the ground plane / shield, board unpowered.
- VCC — a rail that sits at a steady ~3.3 V once powered.
- TX — idles high at ~3.3 V (UART mark state) and visibly dips/flickers the instant the device boots and starts spewing its log. That flicker is the console talking.
- RX — usually the quiet one: floating or weakly pulled, no boot activity.
And if you didn't know the baud?
115200is the Broadcom CFE default, but blind you'd either sweep the common rates (9600 → 115200) until the garbage turns into ASCII, or measure the narrowest bit period on a scope and do1 / t.
I wired up a USB-TTL adapter (HW-597, PL2303) with the jumper on 3.3V. This matters: the BCM6368's UART is 3.3 V TTL, not RS-232 (±12 V) — hang a real serial port or a 5 V adapter off it and you cook the pin. Ground first, then RX/TX crossed, VCC left floating so nothing back-powers the board. Before applying power I confirmed GND and VCC with a multimeter, then:
screen /dev/ttyUSB0 115200
Powered it up, watched the CFE hand off to the kernel, watched BusyBox init come up, and landed on a login prompt.
The login fell to a hidden factory account ZyXEL never documents for the end user:
user: supervisor
pass: zyad1234
Full system privilege. This is the core of CVE-2025-0890.
That login dropped me into a locked-down vendor CLI (consoled, a > prompt): no sh, none of the usual Linux tools — a jail wrapped around the real system. But it exposed a ping diagnostic, and that command built its argument straight into a shell string with zero sanitizing. So I fed it a shell metacharacter:
> ping 127.0.0.1; sh
…and dropped into a root BusyBox shell (#).
Why one semicolon is enough: the CLI handler does the equivalent of
system("ping " + userinput). The;closes thepingcommand and starts a second one —sh— which inherits the console's stdin/stdout, so you get an interactive shell. The account was already uid 0; the CLI was the only thing standing between me and a real shell, and unsanitized input tore that wall down. Chaining an authenticated CLI injection onto the hidden account is exactly the pattern tracked as CVE-2024-40891.
With root, I looked at the system and the 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