a quick and dirty orbnext hack

This Christmas my brother and his wife sent me an ORBNext, a very nerdy gift somewhat akin to a Philips Hue but more hackable by virtue of being built on the Electric Imp platform. The most interesting part of which is the "blink up" technology which involves an app on your phone pulsating the screen brightness while the imp sits atop it reading the pulses with a photosensor. The result is a device with no screen and no buttons which can be connected to your wifi with less hassle than a chromecast which is pretty cool.

The mobile app has some basic functionality built in, set the colour based on the temperature or the price of your favourite stock symbol and that sort of thing. It also has IFTTT integration which is a handy service I've used for many years. Unfortunately it's not quite as useful as I'd like. For example if you trigger a "blink the light" event by some action the light just keeps blinking until you intervene which I'm not thrilled about.

But then if you got a problem with a hackable light you go ahead and you hack it.

I noticed that the response time between my pressing a button on the mobile app and the light updating itself was extremely fast, so fast that I assumed the app had to be communicating with the light directly over wifi. I fired up ettercap and ran a MITM attack between my phone and the light (MITM on a light, this is the world we live in?) but found no traffic going between them.

Next I ran it between the light and my router looking for it's control channel. I found that it established a TCP connection to imp02b.boxen.electricimp.com:31314 but couldn't make heads or tails of the binary protocol in use so I moved on to the app. Unfortunately the mobile app was communicating via HTTPS so I switched over to mitmproxy so I could see inside the encrypted traffic.

Here's what I found.

The app was doing a nice simple POST of some straightforward form data to control the light. During setup each unit produces a unique device code which is used to identify it, this is the URI being addressed.

It seemed like it'd be easy to replicate so I set about curling to see if I could do it but it ended up getting late and I found myself frustrated by countless 404 responses. I replicated every header, the user agent, even went so far as to script up a request that would copy the lower case 'h' in the host: header with no success.

My request looked absolutely identical and still wouldn't work. I gave up and went to bed.

As is often the case when I looked at the problem with fresh eyes today the answer jumped out at me. The Content-Length in my spoofed request was way off, lots more data than the app was sending. I compared the payloads in hex and found the problem.

The trick it turns out is to send the payload with an application/x-www-form-urlencoded header but not actually URL encode it. I couldn't figure out how to make cURL or my HTTP client library of choice do something so stupid but of course urllib2 has so such qualms.

import urllib2  
import json

YOUR_DEVICE_CODE = '<thing goes here>'

URL = 'https://agent.electricimp.com/{}'.format(YOUR_DEVICE_CODE)

def set_color(color):  
    data = {"program":"Demo","color":"#{}".format(color)}
    req = urllib2.Request(URL, json.dumps(data))
    response = urllib2.urlopen(req)
    foo = response.read()

if __name__ == '__main__':  
    set_color('FFFFFF')

And since it works from anywhere on the internet now I can activate disco mode when I'm not even home!