Tenda AC9 Firmware Analysis

Reverse Engineering the Tenda AC9 Firmware

Routers are the unsung heroes of our connected lives—quietly humming away, keeping us online. But what’s really going on inside them? I recently got curious about my Tenda AC9 router and decided to crack open its firmware—US_AC9V1.0BR_V15.03.05.15_multi_TDE01.bin—to see what secrets it holds. Spoiler: it’s a mix of the expected, the quirky, and the slightly unsettling. Here’s what I found, how I got there, and why it might matter.

Step 1: Cracking the Shell with Binwalk

First things first—I needed to unpack the firmware. It’s a 7.4MB file, a u-boot uImage for a Linux/ARM kernel, timestamped October 29, 2020. Sounded like a job for binwalk, a go-to tool for firmware analysis. After installing it (sudo apt install binwalk), I ran a quick scan:

wget https://github.com/mrxehmad/Tenda-AC9-Firmware-Analysis/raw/refs/heads/main/US_AC9V1.0BR_V15.03.05.15_multi_TDE01.zip
binwalk US_AC9V1.0BR_V15.03.05.15_multi_TDE01.bin

The output revealed a TRX header, an LZMA-compressed kernel, and a SquashFS filesystem. To get inside, I extracted it:

binwalk -e US_AC9V1.0BR_V15.03.05.15_multi_TDE01.bin

This gave me a folder with a compressed kernel (5C.LZMA) and a filesystem (18EB48.squashfs). One more command to decompress the filesystem:

unsquashfs -f -d squashfs-root 18EB48.squashfs

And voilà—a treasure trove of files in squashfs-root/. Time to explore.

Step 2: Open Ports and a Network Surprise

Before diving into the files, I scanned my router (10.1.15.1) with Nmap to see what it’s exposing:

PORT      STATE SERVICE
80/tcp    open  http
5500/tcp  open  hotline
8180/tcp  open  unknown
9000/tcp  open  cslistener
10004/tcp open  emcrmirccd

Port 80? Expected for the web interface. 8180? Probably a secondary UI (more on that later). But 5500, 9000, and 10004? Those raised eyebrows. “Hotline” and “emcrmirccd” didn’t scream “router,” so I knew I had some digging to do.

Step 3: The Filesystem Tells a Story

Inside squashfs-root/, I found a mix of binaries, libraries, and configs. Nginx was running on port 8180 (/usr/bin/nginx), serving a Luci interface with a FastCGI backend on 8188 (/usr/bin/app_data_center). Port 80 was handled by httpd—standard stuff. But then things got interesting.

Hardcoded Connections

Grepping through the files, I spotted hardcoded IPs and domains:

  • cloud.tenda.com.cn
  • 182.254.148.51
  • download.cloud.tenda.com.cn
  • 182.254.218.214
  • 182.254.136.200

These popped up in files like ./bin/auto_discover, which sends your MAC address and timestamp to api.cloud.tenda.com.cn/route/mac/v1. Device tracking, perhaps? Meanwhile, httpd fetches ad URLs from api.cloud.tenda.com.cn/route/adverts/v1, defaulting to www.tenda.com.cn if it fails. An old-school User-Agent (MSIE 5.01) hinted this code hasn’t been touched in years.

Quirky Binaries

  • ./bin/speedtest: Pings domains like www.taobao.com and www.google.com—likely a bandwidth test.
  • ./bin/88ip: Sends XML to link.dipserver.com for dynamic DNS.
  • ./bin/logserver: Tries (and often fails) to ship logs somewhere external.
  • ./usr/sbin/telnetd: A Telnet server! No startup script activated it, but its presence felt like a debug leftover.

Firewall Rules

Hardcoded iptables commands caught my eye:

iptables -A OUTPUT -p udp --dport %d -m state --state NEW -j %s
iptables -A %s -o %s -j DROP

These seem to manage outbound traffic—maybe to those Tenda cloud servers?

Step 4: What Does It Mean?

This firmware is chatty. It phones home to Tenda’s cloud, potentially sharing device info, fetching ads, and running services on non-standard ports (5500 and 10004 still elude me—debug backdoors?). The Telnet binary is dormant but tempting for anyone with root access. And those hardcoded IPs? A security researcher’s red flag—perfect for spoofing or tracking.

Why It Matters

Routers like the Tenda AC9 are everywhere, yet we rarely peek inside. This little adventure showed me how much they’re doing behind the scenes—some useful, some questionable. I’ve dumped my findings on GitHub (link below) with extraction steps, hoping others might join the fun. Could 5500 be a mislabeled Telnet port? Is libupnp.so behind 9000? I’m a newbie at this, but tools like Ghidra are calling—I’ll dive deeper if you do.

What do you think? Ever analyzed your own gear? Let’s swap notes!