Hacking the D-Link DSP-W215 Smart Plug

The D-Link DSP-W215 Smart Plug is a wireless home automation device for monitoring and controlling electrical outlets. It isn’t readily available from Amazon or Best Buy yet, but the firmware is up on D-Link’s web site.

The D-Link DSP-W215

TL;DR, the DSP-W215 contains an unauthenticated stack overflow that can be exploited to take complete control of the device, and anything connected to its AC outlet.

The DSP-W215 firmware contains all the usual stuff you would expect from a Linux-based device:

DSP-W215 Firmware Analysis

After unpacking and examining the contents of the file system, I found that the smart plug doesn’t have a normal web-based interface; you are expected to configure it using D-Link’s Android/iOS app. The apps however, appear to use the Home Network Administration Protocol (HNAP) to talk to the smart plug.

Being a SOAP-based protocol, HNAP is served up by a lighttpd server running on the smart plug, and the following excerpt from the lighttpd configuration file(s) shows that HNAP requests are passed off to the /www/my_cgi.cgi binary for processing:

From the memset it is obvious that the post_data_buf stack buffer is only intended to hold up to 500,000 bytes. Since the Content-Length header is trusted blindly, POSTing more than 500,000 bytes will overflow this buffer, but there are quite a few more variables on the stack; it takes 1,000,020 bytes to overwrite everything on the stack up to the saved return address:

What’s more, because the POST data is read into the buffer with an fgetc loop, there are no bad bytes – even NULL bytes are allowed. That’s nice, because at 0x00405CAC in my_cgi.cgi there is this little bit of code that loads $a0 (the first function argument register) with a pointer to the stack ($sp+0x28) and calls system():

system($sp+0x28);

We just need to overwrite the saved return address with 0x00405CAC and put whatever command we want to run onto the stack at offset 0x28:

Controlling a wall outlet can have more serious implications however, as exemplified the following D-Link advertisement:

A Rather Misleading D-Link Advertisement

While the smart plug may be able detect overheating, I suspect that it can only detect if the smart plug itself is overheating – it has no way to monitor the actual temperature of any devices plugged into the wall outlet. So, if you’ve left a space heater plugged in to the outlet and some nefarious person surreptitiously turns the outlet back on, you’re in for a bad day.

It’s unclear if the smart plug attempts to make itself remotely accessible (using UPnP port forwarding rules, for example), as the Android configuration app simply doesn’t work. It couldn’t even establish an initial connection to the smart plug, although my laptop had no problems. When it finally did, it refused to create a MyDlink account for remote access, with the very helpful error message “could not create account”. Although it said it had configured the smart plug to connect to my wireless network, the smart plug did not connect to my network, and it ceased to present itself as an access point for initial configuration. With the wireless borked and no ethernet connection, I was left with no means to further communicate with it. Oh, and there’s no hard reset button either. Ah well, it’s going in the bin anyway.

I suspect that anyone else who has purchased this device hasn’t been able to get it to work either, which is probably a good thing. At any rate, I’d be wary of connecting such a device to either my network or my appliances.

Incidentally, D-Link’s DIR-505L travel router is also affected by this bug, as it has a nearly identical my_cgi.cgi binary.

The C code shown above is only representative of the relevant code that causes the vulnerability, not the entire do_hnap function, which is quite large. There are many other variables on the stack between the post_data_buf variable and the saved return address.

Damn. The exploit is just icing on the cake. Can only be configured by an Android/iOS app that requires some sort of “MyDlink account”? Bricked it by trying to configure it? That’s already plenty of reason to stay far away from this thing.

Try setting up this devise if you are using an Airport router, not easy. Ales it seems that if you move the devise from one outlet to another the device needs to be set up again. This leads my to believe that in the event of a power outage you will need to set up every device you own independently.

@Sandro: Your uClib looks wrong, either you dont have it at /lib folder, or it is compiled for another kind of mips (le-be msb-lsb). I got the same problem today with other device. Finally I got the library from another firmware from the same device, but it was compiled different (LSB instead MSB). Finally I found one fine for my binary in another firmware version. Good luck!

@Craig: Is there any tricks to debug libraries? I mean, sometimes the most interesting is in the libraries, but there are not binaries. Or if there are binaries linked with this libraries, I have to patch plenty of exceptions. I’m trying to jump to the important function and changing values in order to keep running the binary. Sometimes is not possible. And it is a big pain.

Thank you Craig for your reply.
By your tips and following the guide on Zach Cutlip’s blog, I’m now able to run Debian (wheezy) mips on qemu.

Assumung you’re doing debug trough gdb, as what i saw gdbserver can’t handle a process if not starting it or handling one by PID which is alredy running.
So, the question is: how do you attach my_cgi.cgi process by gdbserver if the process is executed by lighttpd?

I haven’t looked at any of the source code that has been released for this product; the my_cgi.cgi binary analyzed here was taken from the firmware image. my_cgi.cgi may be included in the source code in binary form, though I doubt they will have released the source code for this.

Thanks Craig,
That was an amazing post. However, I do encounter some issue that I can’t bypass. After setting up qemu-mips and unzip the DIR-505_REVA_FIRMWARE_1.07.bin file. I have all the files in fmk/fmk/rootfs directory.
Then I found lighttpd is not config correctly yet. So after manually strings out system_manager binary. I copied all the needed files to www. and setup lighttpd.conf correctly. Then after running following command:
root@ses102:~/fmk/fmk/rootfs# chroot . ./qemu-mips usr/bin/lighttpd -D -f /etc/lighttpd/lighttpd.conf
It can successfully accept connections from client. However, after I ran metaexploit script for it. it tells me:
mod_cgi.c.1129: aborted
qemu: uncaught target signal 6 (Aborted) – core dumped
2014-10-07 16:15:55: (mod_cgi.c.1388) cleaning up CGI: process died with signal 6

You’re running lighttpd in user-mode Qemu, which means that any syscalls that lighttpd makes are sent to your underlying x86 kernel. So, when lighttpd performs a syscall to execute my_cgi.cgi it fails because your x86 kernel doesn’t know how to run a MIPS binary.

Many thanks Craig,
It works very well after copying all files into the pre-installed MIPS systems you gave me. 🙂
One more question, I am thinking to book the firmware system itself. After running the fmk script to the BIN file. I got two img files in the image_parts dir. binwalk show info like this:

the rootfs.img is the squashfile system file that I can use for the file system. However, I need to get that kernel image to boot QEMU. But the header.img is not recognized by file utility. It tells just data file. hexdump -C show following result?

Hi Craig,
I was looking at the android apk to found where the apk download the firmware to get a copy, and I found this:
z.setAuth(“admin”, “#!@3345678”, D);
String s3 = getActivity().getIntent().getStringExtra(“DEVICE_PINCODE”);
String s2;
if(s3 != null)
z.setAuth(“admin”, s3, D);
else
z.setAuth(“admin”, “”, D);

May be the “admin” “#!@3345678” sound familiar to you ? I haven’t this outlet at home so can’t try this username and password.

Months later, I’m playing around with one of these plugs. The credentials you’ve found are used to log in to my_cgi.cgi at http://_device-addr/login.htm to get version info. Obvs not the only use, but if I find any more I’ll reply again.

I have a couple of these switches.
when you first plug them in they act like a wireless access point.
You set your iPhone wireless to it. They give you a PIN number, it is on a card in the box and in tiny print on the switch label.
It then shows you the wireless access points it can see.
You pick your WAP and enter the password to your WAP.
I would guess that internally it is remembering the SSID and the PW and reboots as a client to your WAP.
at the point it ‘phones home’ to myDlink and tells the servers there how to reach it. And it works, Mostly….
I find that unless you plug them in one at a time and set them up one at a time, they screw up. the one you were not programming seems to some how un-program.
And if they are too close together physically one of them will just stop working or start to join and un-join the network.
I wish they just had a user interface like their wireless routers and their wireless cameras. I could deal with that better.

I have the EU version with the current firmware 2.20.
Since I want to directly receive measured values from the smart plug I found this:
On the address http:// you can login using PIN code. Then you are provided with basic device info.
On the address http:///HNAP1 there is a list of actions that you query the device.
I managed to retrieve some information from these actions: GetDeviceSettings, GetDeviceSettings2, GetFirmwareStatus, GetGroupSettings, GetInternetSettings, GetTimeSettings and GetWLanRadios.
Unfortunatelly there information doesn’t contain the measured values. I hope that these infos will be provided by the services GetModuleSOAPActions, GetModuleProfile or GetModuleGroup. Unfortunatelly requests for these actions always end with ERROR response.
It makes me think that there are some specific body attributes that must be contained in the request.
Can anybody help?
Thanks

The Beckhoff controller can execute these URL’s for example when a light switch is pushed. If this works, the smart plugs will be a cost-efficient extension of my existing hard-wired home automation system.

What do I have to do to achieve this functionality? Or even better: is this possibility already present in the device?