June 30, 2026
From a Slow Repeater to a CVE: A Crossover Into Hardware Hacking
It Started With an Annoyance
By Zakaria Essaidi
7 min read
It Started With an Annoyance
My background is automotive embedded systems (ECUs, CAN buses, diagnostics protocols, safety-critical firmware...) Consumer networking gear is something I use, not something I think about professionally.
So whenever my WiFi repeater started feeling sluggish, my reaction was practical: reset it. Nothing dramatic was wrong, the main router was fine, other devices were responsive, but every reset meant waiting more than a minute before the repeater came back. A minute-plus to bring a networking device online felt disproportionate, and it nagged at me.
I assumed software at first: something unnecessary at startup, maybe a config issue. But the question slowly shifted from "how do I fix this" to something more basic , what is actually running on this device? I'd always assumed consumer routers ran lean, bare-metal firmware burned into flash. That assumption turned out to be wrong, and correcting it was only the beginning.
Opening the device
Inside was a compact PCB with different parts arranged the usual way. Near the edge of the board sat four unpopulated through-holes: no silkscreen, no labels.
In the embedded industry, points like these usually mean one thing: a debug or programming header left over from development. I didn't assume, though. I started with a multimeter checking continuity to find ground and reading the voltage on each pad to see which were active and which were power or ground and only then connected a logic analyzer to watch the pads during boot.
Reading the Signals
During boot, two of the four pads showed activity. Of the other two, the multimeter confirmed one as ground and one as a constant logic high.
The active lines carried asynchronous serial framing, the kind of debug serial that's common on embedded boards. Rather than guess the line rate, I measured the timing of the transitions and worked my way to isolate a single bit period , which came out to roughly 8.7 µs:
Baud rate ≈ 1 / (~8.7 µs) 114,942 bpsBaud rate ≈ 1 / (~8.7 µs) 114,942 bpsThat number is recognizable: It's too close to the classic 115,200 bps which is one of the standard UART rates and by far the most common for debug consoles. I decoded a few bytes in the analyzer to confirm the framing, wired up a USB-serial adapter with the usual crossover, and opened a terminal.
What Was Actually Running
The first surprise: this device doesn't run bare-metal firmware. It runs eCos , a compact real-time OS , loaded from a compressed image in flash. I'd never worked with eCos; automotive embedded has its own OS landscape (OSEK, QNX, the occasional FreeRTOS), and eCos simply isn't part of it, so I'd had no reason to encounter it.
The boot log laid out a full software stack: a U-Boot bootloader runs first, decompresses an LZMA kernel image from flash into RAM, then hands off to eCos, which loads the router application. That sequence (decompress, load, init OS, start app) answered my original question. The repeater isn't slow because of a bug; it's slow because it decompresses nearly a megabyte of firmware into RAM on every boot. On a constrained processor, that takes time. Mystery solved in the first minute of serial output. What came next was not what I was looking for.
The log also handed me the flash geometry, unprompted:
chip__no chip__id dev_size era_size chipName
0000000h 0684015h 0200000h 0001000h BH25D16
blk_size blk__cnt sec_size sec__cnt pageSize page_cnt
0010000h 0000020h 0001000h 0000200h 0000100h 0002000hchip__no chip__id dev_size era_size chipName
0000000h 0684015h 0200000h 0001000h BH25D16
blk_size blk__cnt sec_size sec__cnt pageSize page_cnt
0010000h 0000020h 0001000h 0000200h 0000100h 0002000hTotal flash 0x200000 (2 MB), 64 KB erase blocks, 4 KB sectors. Useful later.
The Line That Stopped Everything
A few lines on, between WiFi driver messages, was this:
hxl++ [wlconf_vaps_start::507] -ath1-wpa2-[REDACTED_SSID]-[REDACTED_PASSWORD]-hxl++ [wlconf_vaps_start::507] -ath1-wpa2-[REDACTED_SSID]-[REDACTED_PASSWORD]-I stopped reading. That was the WPA2 passphrase for my upstream network !!! The main router this repeater connects to , printed in full, in plaintext, to the serial console !!!
My first reaction was disbelief, so I tested it the only way that would settle it: I changed the upstream WiFi password on the main router, rebooted the repeater, and watched the new password appear in the same line, same format. Every boot, reproducibly. Not a fluke, this was how the device behaved by design.
The whole statement has the shape of debug logging added during development. I can't prove it was meant to be stripped before shipping; only the vendor knows that. But it's the kind of line that normally is.
Reading the Flash
U-Boot is open source with a well-documented command set, and the device offers a brief window during boot to drop into its interactive shell. From there, the flash was directly readable with no authorization of any kind . (In automotive, sensitive regions sit behind a challenge-response unlock; here there was nothing.) I read all 2 MB out.
I automated the extraction with a script that pulled the flash over serial in chunks, working around the link's buffer limits until I had a complete image. Tedious, but straightforward. It split into four partitions: The U-Boot partition, WIFI RF Calibration data, OS & Application firmware and finally the device configuration storage (nvram).
Confirming It in Ghidra
I loaded the decompressed firmware into Ghidra, set to MIPS, to find the code behind that boot line.
I found the string, and the format-string template that generates it, placeholders for function name, line number, interface, security type, SSID, and passphrase. Matching the template against the boot output lined up exactly: same format, same field order. The binary also held the nvram key names the WiFi init queries to fetch the stored passphrase before passing it to the logging call. The chain was complete: stored in nvram → read at boot → passed to a debug log → printed to serial.
The CLI: No Password Required
Once eCos finished booting, the serial terminal dropped to an interactive shell. A help command listed everything available, including commands for the nvram configuration store.
Running the command to dump all nvram values produced network config, interface settings, operational parameters, and the WiFi credentials. The scope was the surprise: not just the upstream passphrase from the boot log, but also the repeater's own broadcast passphrase. Both networks, both passwords, plaintext, from one command with no authentication. Anyone who walked up to this device with no prior access could run that single command and walk away with credentials to two separate networks. No login, no PIN, nothing.
An Unexpected Finding: Memory Without Limits
Two entries in the command list stood out: commands to read and write hardware registers.
I read an address I could check against the flash dump, it matched. I found an unused RAM region (consecutive zero reads), wrote a known value, read it back, confirmed it, then restored the zero. It worked completely: no authentication, no address restrictions, no checks.
Coming from automotive, where reading certain regions requires a cryptographic handshake, this was interesting to see. The nearest comparison from my day job is full read/write access to ECU memory without unlocking a single security level, which simply doesn't happen so easily, especially with modern ECUs. Here it was the device's default state.
The consequences go past reading runtime data. With serial access, an attacker could read the live passphrases straight from RAM (no nvram command needed), overwrite stored values to knock the repeater off the upstream network, or modify runtime structures and function pointers to corrupt behavior or force a persistent DOS all without touching flash and without obvious traces.
Putting It Together
By this point the picture was clear:
Finding 1: Credentials in the boot log. The upstream WPA2 passphrase is printed to serial on every boot, via a debug statement compiled into production firmware.
Finding 2: Unauthenticated credential access via CLI. All stored WiFi credentials, upstream and the repeater's own, are retrievable through a single CLI command with no authentication.
Finding 3: Plaintext storage. Credentials sit unencrypted in the nvram partition, confirmed by direct flash extraction.
Finding 4: Unrestricted memory read/write. CLI commands give unauthenticated read/write to arbitrary memory, with no restrictions or gating.
Being a repeater amplifies the finding. A repeater stores the upstream network's passphrase to reconnect after power loss, so the exposed credentials don't belong to the repeater, they belong to the main network. Extracting them doesn't get you the repeater's extended WiFi; it gets you the primary infrastructure. And repeaters live in secondary spots (hallways, back rooms …) wherever coverage is weak, often with less physical oversight. So the easier device to reach physically is the one that hands over the more valuable network's keys.
Disclosure and the CVE
Before doing anything with this, I checked whether it was already known. That search introduced me to CVEs (Common Vulnerabilities and Exposures) , which are standardized identifiers (CVE-YEAR-NUMBER) for publicly known vulnerabilities, coordinated by MITRE and mirrored by the National Vulnerability Database. Nothing there matched this device and firmware; the issues appeared undocumented.
So: what's the right thing to do? The community norm is coordinated disclosure, which consists on notifying the vendor privately, give them a defined window (typically 90 days) to fix it, then publish regardless, because users also deserve to know what their devices do. I contacted the vendor's security team with a summary and a 90-day timeline.
The same day, I filed CVE requests with MITRE, two of them, splitting the credential issues from the memory-access issue. MITRE consolidated them into a single CVE, on the basis that both reach the device through the same unauthenticated serial console. That was my first lesson in how assignment actually works: related issues with a shared root cause might travel under one ID, not necessarily one each.
The CVE
CVE-2026–38571: Unauthenticated UART Console Exposes Credentials and Arbitrary Memory Access on Tenda F3 (V603)
Reflections
I came in asking why a reboot takes a minute and left understanding the device decompresses an OS image into RAM every time and with a CVE for problems I wasn't looking for.
Two things stayed with me. First, the hardware fundamentals carried over boot sequences, serial, memory maps, bootloaders were all familiar, but the adversarial framing did not. Asking "what does this reveal" instead of "how does this work" is a different posture, and I had to consciously adopt it. Second, the assumption that consumer gear runs opaque bare-metal firmware is a kind of false comfort. These are real software stacks with real interfaces, and here those interfaces answered to anyone who could touch four pads on a board, handing out the credentials to the network they were supposed to protect. In a device that lives in hallways and back rooms, that's a meaningful gap.
Tools Used
- Multimeter
- Logic Analyzer
- USB-serial adapter
- Ghidra
- Some scripting
Zakaria ES-SAIDI / ZEssaidi : linkedin.com/in/essaidi-zakaria