(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.
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.
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
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
18.104.22.168; ls or
22.214.171.124&&ls doesn’t work.
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
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.
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
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:
var looked to contain state
information about devices it has seen, while
syscfg.tmp file seems to be a list of null terminated
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_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
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
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 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.
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
Unfortunately, I ran into authentication problems (PAM maybe?) when trying to log in via SSH, and soon ran out of time.
Have fun runing arbitrary code on your router :). I probably won’t check the comments very often, so feel free to email me at email@example.com with questions/comments/snide remarks. Or follow me on twitter @matteotom.