Owning My Linksys Router

(skip to after Disclaimer if you just want the technical details)

When I arived home for my month-long winter break, I realized my parents had a problem: their two year old wireless-N router couldn’t quite handle the up to 20+ devices they and my siblings would have connected in the evenings. It was an Asus rt-n66u, which I had been quite happy with, but it just couldn’t keep up, and anyways it only supported wireless-N and wireless-AC is the New Cool Thing™. I decided to try a modern Linksys router, given my fond memories of the wrt54g, and settled on the ea8300 for its quad core CPU and two channels of 5ghz (and of course one of 2.4ghz).

Unfortunately, its firmware tries to be user friendly, and thus fails to be user friendly to power users (myself) or to be simple for most other users (my parents). It checks all the boxes: IPv6, DLNA media streaming and FTP via an external USB device, DDNS support, and a bit more, but my big problems are the interface is a pain and it isn’t supported by dd-wrt/openWRT/LEDE/Tomato/etc.
At least Asus routers have Asuswrt-merlin if you want features like Tor support or a root SSH shell.

So, I decided to see if I could “hack” it to run my own programs.

Disclaimer

I will be describing a method to gain local root code execution on a modern Linksys router (well, I assume it’d work with other models, but I haven’t tested it). I don’t think it is a Real Problem™ worthy of responsible disclosure to Linksys, since it requries physical access and admin access, with which one could already flash malicious firmware. And more importantly, I hope it doesn’t get fixed, so I can continue to use it (and recieve normal security updates).

Also, I take no repsonsibility if you brick your router, anger the FCC, cause the downfall of western civilization, or anything else positive or negative comes from reading this post.

Dead Ends

Flashing New Firmware

My first though was to simply download a firmware update, modify it, and flash it via normal procedures. I spent about an hour trying to reverse engineer the file format (the binwalk tool, which I’ll talk about later, was useful for this), but to no success. I’m sure given more time I could figure it out, but I decided to move on to other options.

Also, I have too many memories of spending all night trying to fix a bricked wrt54g, so I’d rather not mess with flashing firmware.

Code injection via the web UI

The web UI has ping and traceroute features. I thought it might just do sh -c 'ping $server' if you entered $server into the UI; unfortunately it seems the firmware validates the input, and something like 8.8.8.8; ls or 8.8.8.8&&ls doesn’t work.

Results

Interlude: external drives and some research

The firmware supportes DLNA media streaming, FTP, and samba on a external drive if plugged in to the USB port. This will be important soon.

I decided to research vulnurabilities for the Linksys ea8300, and found the page http://192.168.1.1/sysinfo.cgi (I can’t find the original source, so if you know it please let me know!). It contains a bunch of information, some of which will become useful.

Backup/restore

Next, I looked for other possible sources for script injection (having crossed out firmware updates and ping/traceroute). The restore half of backup/restore looked interesting. I downloaded a backup, which came if the form of a file called backup.cfg. file didn’t know what to think of it, so I tried binwalk (“…a tool for searching a given binary image for embedded files and executable code…“, see binwalk.org). I had much more luck here than with the firmware image, since it found a gzip archive at byte 13. I extracted it with dd if=backup.cfg bs=1 count=13 of=backup.gz, and then extraced the gzip (which turned out to be a gzip’d tarball) with tar xf backup.gz. This resulted in two files: tmp and var. var looked to contain state information about devices it has seen, while tmp contained syscfg.tmp. The syscfg.tmp file seems to be a list of null terminated key=value pairs (ie key=value\0). I found the easiest way to look though this was cat syscfg.tmp | sed 's/\x00/\n/g' | sort.

The lines that stood out were guardian_register_sh=/etc/guardian/register.sh and guardian_unregister_sh=/etc/guardian/unregister.sh. These seem to be paths to scripts that the router will run. A good start!

Next we need some way to add our own script. Luckily, the sysinfo.cgi page tells us that the external drive is mounted at /tmp/sda1. Maybe I can put a script there and point the register script at it? (Note that I still don’t know what the guardian register/unregister scripts do.) So, I did sed -i 's#/register\.sh#/../../tmp/sda1/register.sh#' syscfg.tmp, to change /etc/guardian/register.sh to /etc/guardian/../../tmp/sda1/register.sh. Then to re-tar-gz the files, and create a file with the first 13 bytes from the original backup followed by the new gzip.

I tried to use the restore UI function with the new file, but got a error about a wrong file size…hmm…if it’s complaining about the size, the size must be mentioned somewhere in the file. So I looked at those 13 bytes ( dd if=backup.cfg bs=1 count=13 | hexdump -C), which was “0x0002\n11107\n” (ASCII). Well, it turns out the 11107 is the size of the gzip, in bytes. So I updated that to the new file size, and retried. The restore worked!

The script

The original script that I put at /register.sh on the external drive looked something like this:

#!/bin/sh

if [[ -f /tmp/FLAG ]]; then
    exit
else
    # Make sure this only runs once
    touch /tmp/FLAG
    # Note that this will include the contents of /tmp/sda1, so don't put too much here!
    tar cf /tmp/sda1/all.tar / --exclude proc --exclude sys --exclude dev
fi

I plugged in the external drive, rebooted the router, mounted the router’s external drive via samba…and found an all.tar file! It worked!

I have a method to execute arbitrary scripts (as root) on the router.

Next steps

I didn’t actually make it much further than that. Some investigation showed the router is running a ARM chip with uclibc. I combiled a static dropbear binary for arm (instructions from here, just do the make step as make STATIC=1).

Unfortunately, I ran into authentication problems (PAM maybe?) when trying to log in via SSH, and soon ran out of time.

Conclusion

Have fun runing arbitrary code on your router :). I probably won’t check the comments very often, so feel free to email me at matthew@bentley.link with questions/comments/snide remarks. Or follow me on twitter @matteotom.