Now, let me just preface this with a disclaimer:
… and get used to this puppo, as you may see this a few more times in this post :’).
In light of continuing my bumbling foray into the hardware hacking landscape, I bought a bottom-of-the-line $10
TP-Link TL-841N(EU) router off a random fellow on Facebook Marketplace, in a thrilling, high-stakes deal conducted in the middle of… a nearby mall.
My intention behind such a daring exchange was to practice the end-to-end process of pulling apart an IoT device to poke at it, access debug logs, potentially get a root shell, practice various kinds of firmware extraction (via UART & via on-chip flash memory), and even have a mosey about the filesystem for some potential security vulnerabilities later down the road.
Here’s a short overview of the journey so far, documented below:
Hang around until the end for discovering what kinds of various plaintext, hardcoded creds from the previous owner (censored) could be found on this device… one of which would survive even a full “router reset”.
Yeah. Kinda… no bueno?
Let’s dig in.
I began by searching the router model number up on the
FCCID to check for its datasheet, to get information on the PCB, voltage levels & the available chips on the board.
Opening it up and finding the very-well-labelled
UART ports for serial/debug interface access was straightforward, with one quirk being that the flash was found on the underside of my board – model number GD25Q64CSIG (
datasheet).
If your device isn’t as friendly with labelling, you can also use a multimeter to probe each suspected UART port at boot time to determine which pin is which – the device’s TX pin should have a fluctuating voltage during boot (due to it transmitting various boot logs in the form of serial 1s and 0s, corresponding to fluctuations in voltage), and the GND pin should be 0V.
My trusty AliExpress
USB-to-UART adapter (which I checked to confirm supports both 3.3V and 5V output, so we don’t fry anything) had the following pinout/wire colouring:
So, I connected the USB GND to the router’s GND port (or can just touch grounded metal on the board), USB RX to the discovered router’s TX port, and USB TX to router’s suspected RX port (which can be found by trying the remaining two pins, until you get a successful input by spamming a key on your keyboard once connected).
I soldered mine in for a better/persistent connection, & plug-&-play functionality 😜.
Once I identified what /mnt/dev device my USB-to-UART device was and got connected up with picocom -b 115200 –logfile bootlog-tplink.txt /dev/tty.usbserial-1210 (using the common baud rate of 115200, which worked), we can see we’re dropped into a root shell once again! Too easy 😅.
Connecting my computer directly via one of the router’s LAN ports, we can be assigned an IP and access the admin console as expected:
So, now I could see & navigate the filesystem, I wanted to dump & transfer it to my local device to perform some preliminary analysis!
Listing the flash partitions at /proc/mtd, as well as a few other mount details, gives us a good point to start:
So we can transfer the firmware files over, I installed & started a
tftp-now server with tftp-now serve on my Mac (with brew, as am on apple silicon).
NOTE: Ensure to create a folder in your home directory corresponding to where you’re transferring the file from on your router (for me, from ~/var), otherwise you’ll get a “No such file exists” error when the tftp agent attempts to put its remote file on your computer (as it does so at the same path that it’s uploading it from, /var/).
To solve the (many) errors shown below, I created a temporary var folder inside my ~ home directory to match up with the file path the router’s tftp wanted to “put” to:
Switching back to our UART connection to the router, and checking we can begin to create a dump of the first flash partition with a tool like cat (if your router doesn’t have dd, like mine).
NOTE: Keep an eye on your router’s storage – if yours is also full (like mine was – use df to check), write the firmware dumps incrementally into the /var directory, which is RAM-backed and my largest partition (still only ~6.4 MiB).
To avoid overloading the RAM, for each partition dump, I used the below process:
Tedious, but how it had to be (unless performing an on/off-flash chip extraction, or you have a USB interface on your router to write to).
However, for a CLI quality of life boost as stated in
this article, “we can make our lives much easier by uploading our own fully-loaded BusyBox via the existing TFTP client on the device. You may find a precompiled little-endian MIPS BusyBox binary on the
official website.” This will give us access to significantly more standard linux commands to use on the router.
Again, well, it should be evident, but…
We’re learning. It worked in the end. Firmware files extracted to my local computer over tftp & UART!
An alternative way of analysing the filesystem is via an on-chip (or off-chip, if you want to desolder a tiny IC and have a hot air gun) firmware extraction. For this, I used the ”
poor man’s flash extraction tool” – the CH341A.
I first found the flash chip – on the back of the board – and checked it was a flash chip with it’s model number. This also told me that the flash rom voltage is at 3.3V (
datasheet) so the CH341A i have is safe to use. Some chips run at 1.8V, not 3.3V, so before carefully placing the clip on the clip and plugging in the programmer, check your chip’s voltage (via online datasheets or multimeter). If it’s 1.8V, use a voltage adapter (bought with the adapter) to avoid possible damage.
Also, ensure the clamp-side’s red cord is in line with to PIN 1 of the router’s flash chip, which should be (typically marked with a small circle in the corresponding corner).
Beginning of the chip model number often corresponds to type: so for our 25Q64CSIG, the 25 at the start means we would position our 8 pins over the slots marked as 25 SPI BIOS, whereas if our chip said 26, it would go on the EPROM I2C side.
After seating the PCB & connected cord in the USB programmer with the RED cord on the top right, as pictured below, double check that the red cord on the clamp-side is attached to PIN 1 of the router’s flash chip (typically marked with a small circle, as mentioned before).
Then, simply install & fire up
flashrom, and run the following command to extract the firmware!
Now we have modem.bin, which (being a full flash dump) should contain inside it, the same manually-extracted flash partitions as extracted via ftpd. However, to analyse it more meaningfully, we can split it up into the various boot, config, rootfs sectors like before with dd.
Using the memory sizes/addresses found before with /proc/mtd when on the router, we can manually carve our modem.bin flash dump into the identified sectors for analysis.
To get our first sector, mtd0, which presumably contains
U-Boot based on our observed boot logs, we can use dd to carve the specified chunk out of modem.bin:
In this case, my U-boot sector wasn’t initially identified by binwalk automatically when I first analysed modem.bin, due to likely being a customised, vendor-modified version of U-boot. Hence why I decided to carve out the sectors manually according to the router’s /proc/mnt information, just for some additional accuracy and organisation.
The presence of a customised U-boot was confirmed with strings finding Ralink UBoot Version in the mtd1 partition (the presumed linux kernel) when this part was carved out and analysed later on, indicating this was a
Ralink-modified U-Boot version.
Anyway, running the strings command again on the extracted suspected U-boot mtd0 partition confirms it is the part of the flash containing U-Boot, and helps us identify more info about it, including U-Boot’s version number and behaviour.
I repeated the above process to carve our each sector of the firmware dump, until I had all sectors, mtd0-mtd6, for analysis.
I asked the friendly (?) neighbourhood AI chatbot to help map a visual summary of the chip so far:
If we use binwalk -e to extract what it can automatically from the original modem.bin, it also gives us the uncompressed kernel image (mtd1) in the form of decompressed.bin, which checks out when looking at its identified magic bytes below:
Another reason that I had to carve out and extract the mtd2 squashfs filesystem manually with dd was, once again, because I’m on a mac. And the sasquatch command initiated as part of binwalk’s extraction process (when extracting a squashfs sector) continued to fail for me (despite being installed…),
Oh well. At least we know what to do from above to get and un-squash the rootfs for analysis:
Note: If you get errors like create_inode: could not create character device squashfs-root/dev/zero, because you’re not superuser!, don’t panic. As the filesystem contains device nodes under /dev, your computer (MacOS for example) may not permit a normal user to create those special device files. However, they’re (allegedly) not usually needed for firmware analysis, but you can use pure Linux to unsquash this while preserving the device nodes with sudo.
(yes, i know this would’ve just “all worked” on linux. i am waiting for my poor mac baby to die before the final leap.)
Comparing the two extraction methods (left done via Method 1, tftpd, and right via on-chip extraction, Method 2), we can see nearly identical copies of the filesystem to analyse!
Now, we can proceed to analyse the dumped binaries/unsquashed filesystems using strings, grep, and binwalk, to understand how the router works and perhaps even uncover some juicy vulns later down the track…!
But, I’ll leave the groundbreaking vulnerability reversing to part 2 (or more capable souls than I), as this is getting long, but thanks for sticking along for the ride! We’ve eztracted firmware from a router in two ways; via tftp over UART, as well as by using flashrom for a direct on-flash-chip firmware extraction. \
But… we should have a peek before we go, shouldn’t we? Just a little?
NOTE: I am just poking around here to learn more about what is/isn’t exposed & accessible on a fairly typical IoT device that I own and have full authority over. The device was factory reset before being sold to me, and no discovered credentials were used to access any unauthorised material.
Now, to introduce you to the cheap & nasty firmware analyst’s favourite tool: strings.
As stated on it’s man page, strings “…looks for ASCII strings in a binary file, object, or standard input.”. So we can quickly parse compiled .bin files and the like for a dirty peek at any readable strings that may exist within, which is most likely to be either text the program prints, or plaintext configuration values (depending on compilation type, of course).
Examining the mtd3-config.bin first, where router-specific configs should live, with strings mtd3-config.bin | less, we can actually find the router’s hard-coded WPS password in plaintext, i.e. the one printed on the back of the router!
Well, uh, upon further research: we’ve stumbled upon what appears to be a previously discovered “CVE” (
CVE-2026-4346), in fact:
“The vulnerability affecting TL-WR850N v3 allows cleartext storage of administrative and Wi-Fi credentials in a region of the devices flash memory while the serial interface remains enabled and protected by weak authentication. An attacker with physical access and the ability to connect to the serial port can recover sensitive information, including the router’s management password and wireless network key. Successful exploitation can lead to full administrative control of the device and unauthorized access to the associated wireless network.”
(Although I’d say, if someone’s got hardware access to your router’s serial interface/flash chip in the first place, you’re already pretty borked. And I wouldn’t call it protected by “weak authentication”… no authentication.)
Another gem uncovered by strings mtd3-config.bin | grep -ri “pass” are hardcoded credentials for what appears to be previous
PPPoe ISP/router provisioning credentials (the @unitiair portion strongly suggesting an ISP/service-provider account).
Oopsies.
And for an even bigger oopsies/yikes, even after performing a router factory reset, these credentials were still accessible via the freshly-dumped config partition of the firmware.
Given that I am not the local small business whose email is listed in plaintext in the un-redacted version of the picture above, nor a customer of Unitiair, I don’t believe I should still be seeing this after the router was “reset”.
This means that not even a FACTORY reset of the device (holding down RESET button for 10 seconds, and verifying reset was performed by checking boot logs, as detailed in the
TP-Link documentation), which most consumers would presume would wipe all user OR ISP-modified settings from it, can truly erase the ISP
PPPOe WAN credentials.
So, a factory reset leaves a previous subscriber’s broadband authentication credential recoverable from persistent flash storage., in Firmware Version: 0.9.1 4.16 v0001.0 Build 170622 Rel.64334n (the latest for
my EU model of the router)
Whilst you don’t seem to be able to authenticate to this ISP itself (which, luckily for them, requires an account number to login, unlike my ISP which uses email), user/ISP modified credentials stored in plaintext persisting after a reset is… not a great privacy sign for what is considered a “factory reset” device.
Anyway…
Searching for more “sensitive” strings in the extracted root-fs (mtd2), we get both the router login from ./web/help/RestoreDefaultCfgHelpRpm.htm, as well as the hashed password of the admin OS user.
So, we can login to the router admin interface thanks to the golden keys of… admin:admin. Of course!
Additionally, as we recovered an UNSALTED admin hash from /etc/shadow, we can try and crack the admin OS user account’s password (whose permissions are mapped to root, GUID=0, so an account that’s kinda useful):
Our master account password was, of course, cracked within a few seconds…
admin:1234 – fun times.
i’m quaking in my boots :’).
well, until next time – see you for the
squeakquel!
I have not participated in a blogging challenge before, but I’ll give Blaugust a try this year. (Oh god, what am I getting myself into. I regret it already.)
—
In AuguStory, the story of your blog, Robert asks:
How did you first start blogging (what got …
A hypothesis on the not-so-distant future of software, books, music, and
movies, in which most of what we consume gets cheaper, more abundant, and
noticeably worse, while the human-made variant moves into a luxury
segment of its own.
I am officially on vacations. Vacations will last until the day before my birthday — three weeks. On my birthday I go back to work. I look forward to going next week to our vakantiepark, letting children roam free. We go to the same one as last year becau…
got a tetanus and hepatitis b shot. my left arm feels sore and i think i had a fever that stopped me from getting a good night’s sleep yesterday. i ended up napping midday. glad i picked a day where i can rest after getting vaccinated. i think the wor…
The summer holiday is halfway. I somehow managed to read eight books in the past three to four months—including cheating by resurrecting a previously discarded book and by diagonally scanning a creativity-related book to find out if t…
Yup, I said it.
“All I could do was to offer you an opinion upon one minor point—a woman must have money and a room of her own if she is to write fiction; and that, as you will see, leaves the great problem of the true nature of woman and the true nature of fiction u…
With the window open and the breeze flowing through, I hear the calls and songs of birds. A calling pigeon in particular stands out. I love the calls of pigeons in the evening. The sound reminds me of childhood, looking out the window, onto Nature. A floc…
built with Hugo + ☕️ + a customised
console theme.
… waiting for something to happen ? 🐱 | visit my wobble! ~>