Here is a quick writeup of the protocol for the iKettle taken from my Google+ post earlier this month. This protocol allows you to write your own software to control your iKettle or get notifications from it, so you can integrate it into your desktop or existing home automation system.
The iKettle is advertised as the first wifi kettle, available in UK since February 2014. I bought mine on pre-order back in October 2013. When you first turn on the kettle it acts as a wifi hotspot and they supply an app for Android and iPhone that reconfigures the kettle to then connect to your local wifi hotspot instead. The app then communicates with the kettle on your local network enabling you to turn it on, set some temperature options, and get notification when it has boiled.
Once connected to your local network the device responds to ping requests and listens on two tcp ports, 23 and 2000. The wifi connectivity is enabled by a third party serial to wifi interface board and it responds similar to a HLK-WIFI-M03. Port 23 is used to configure the wifi board itself (to tell it what network to connect to and so on). Port 2000 is passed through to the processor in the iKettle to handle the main interface to the kettle.
Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 |
100C | 95C | 80C | 65C | Warm | On |
So, for example if you receive "sys status key=!" then buttons "100C" and "On" are currently active (and the kettle is therefore turned on and heating up to 100C).
sys status 0x100 | 100C selected |
sys status 0x95 | 95C selected |
sys status 0x80 | 80C selected |
sys status 0x100 | 65C selected |
sys status 0x11 | Warm selected |
sys status 0x10 | Warm has ended |
sys status 0x5 | Turned on |
sys status 0x0 | Turned off |
sys status 0x8005 | Warm length is 5 minutes |
sys status 0x8010 | Warm length is 10 minutes |
sys status 0x8020 | Warm length is 20 minutes |
sys status 0x3 | Reached temperature |
sys status 0x2 | Problem (boiled dry?) |
sys status 0x1 | Kettle was removed (whilst on) |
You can receive multiple status messages given one action, for example if you turn the kettle on you should get a "sys status 0x5" and a "sys status 0x100" showing the "on" and "100C" buttons are selected. When the kettle boils and turns off you'd get a "sys status 0x3" to notify you it boiled, followed by a "sys status 0x0" to indicate all the buttons are now off.
set sys output 0x80 | Select 100C button |
set sys output 0x2 | Select 95C button |
set sys output 0x4000 | Select 80C button |
set sys output 0x200 | Select 65C button |
set sys output 0x8 | Select Warm button |
set sys output 0x8005 | Warm option is 5 mins |
set sys output 0x8010 | Warm option is 10 mins |
set sys output 0x8020 | Warm option is 20 mins |
set sys output 0x4 | Select On button |
set sys output 0x0 | Turn off |
If you're interested in looking at the web interface you can enable it by connecting to port 23 using telnet or nc, entering the password, then issuing the commands "AT+WEBS=1\n" then "AT+PMTF\n" then "AT+Z\n" and then you can open up a webserver on port 80 of the kettle and change or review the settings. I would not recommend you mess around with this interface, you could easily break the iKettle in a way that you can't easily fix. The interface gives you the option of uploading new firmware, but if you do this you could get into a state where the kettle processor can't correctly configure the interface and you're left with a broken kettle. Also the firmware is just for the wifi serial interface, not for the kettle control (the port 2000 stuff above), so there probably isn't much point.
CVSS v2 Base Score 6.5 (AV:N/AC:L/Au:S/C:P/I:P/A:P)
This is really a moderate severity flaw because you need a remote attacker who has the ability to start/stop/control ZoneMinder, and you really should protect your ZoneMinder installation so you don't allow arbitrary people to control your security system. (Although I think at least one distributor package of ZoneMinder doesn't protect it by default, and you can find a few unprotected ZoneMinder consoles using a web search).
I discovered this because when we went on holiday early in April I forgot to turn down the heating in the house. Our heating system is controlled by computer and you can change the settings locally by talking to a Jabber heating bot (Figure 1). But remotely over the internet it's pretty locked down and the only thing we can access is the installation of ZoneMinder. So without remote shell access, and with an hour to spare at Heathrow waiting for the connecting flight to Phoenix, I figured the easiest way to correct the temperature was to find a security flaw in ZoneMinder and exploit it. The fallback plan was to explain to our house-minder how to change it locally, but that didn't seem as much fun.
So I downloaded ZoneMinder and took a look at the source. ZoneMinder is a mixture of C and PHP, and a few years ago I found a buffer overflow in one of the C CGI scripts, but as I use Red Hat Enterprise Linux exploiting any new buffer overflow with my ZoneMinder compiled as PIE definately wouldn't be feasible with just an hours work. My PHP and Apache were up to date too. So I focussed on the PHP scripts.
A quick grep of the PHP scripts packaged with ZoneMinder found a few cases where the arguments passed to PHP exec() were not escaped. One of them was really straightforward to exploit, and with a carefully crafted URL (and if you have authorization to a ZoneMinder installation) you can run arbitrary shell code as the Apache httpd user. So with the help of an inserted semicolon and one reverse shell I had the ability to remotely turn down the heating, and was happy.
I notified the ZoneMinder author and the various vendors shortly after and updates were released today (a patch is also available)
Figure 1: Local heating control
ZoneMinder is able to stream to browsers by making use of the Netscape server push functionality. In response to a HTTP request, ZoneMinder will send out a multipart replace header, then the current captured frame as a jpeg image, followed by a boundary string, followed by the next frame, and so on until you close the connection. It's perhaps not as efficient as streaming via mpeg or some other streaming format, but it's simple and lets you stream images to browsers without requiring plugins.
So I wrote the quick Perl/Tk program below to test streaming from ZoneMinder. It does make some horrible assumptions about the format of the response, so if you want to use this with anything other than ZoneMinder you'll need to edit it a bit. It also assumes that your network is quite good between the client and ZoneMinder; the GUI will become unresponsive if the network read blocks.
My first attempt ran out of memory after an hour -- I traced the memory leak to Tk::Photo and it seems that you have to use the undocumented 'delete' method on a Tk::Photo object otherwise you get a large memory leak. The final version below seems to work okay though.
Russell Handorf used threads to support more than one camera at a time (although I would probably do this in a loop or with select instead of threads)# Test program to decode the multipart-replace stream that # ZoneMinder sends. It's a hack for this stream only though # and could be easily improved. For example we ignore the # Content-Length. # # Mark J Cox, mark@awe.com, February 2006 use Tk; use Tk::X11Font; use Tk::JPEG; use LWP::UserAgent; use MIME::Base64; use IO::Socket; my $host = "10.0.0.180"; my $url = "/cgi-bin/zms?mode=jpeg&monitor=1&scale=50&maxfps=2"; my $stop = 0; my $mw = MainWindow->new(title=>"test"); my $photo = $mw->Label()->pack(); $mw->Button(-text=>"Start",-command => sub { getdata(); })->pack(); $mw->Button(-text=>"Stop",-command => sub { $stop=1; })->pack(); MainLoop; sub getdata { return unless ($stop == 0); my $sock = IO::Socket::INET->new(PeerAddr=>$host,Proto=>'tcp',PeerPort=>80,) ; return unless defined $sock; $sock->autoflush(1); print $sock "GET $url HTTP/1.0\r\nHost: $host\r\n\r\n"; my $status = <$sock>; die unless ($status =~ m|HTTP/\S+\s+200|); my ($grab,$jpeg,$data,$image,$thisbuf,$lastimage); while (my $nread = sysread($sock, $thisbuf, 4096)) { $grab .= $thisbuf; if ( $grab =~ s/(.*?)\n--ZoneMinderFrame\r\n//s ) { $jpeg .= $1; $jpeg =~ s/--ZoneMinderFrame\r\n//; # Heh, what a $jpeg =~ s/Content-Length: \d+\r\n//; # Nasty little $jpeg =~ s/Content-Type: \S+\r\n\r\n//; # Hack $data = encode_base64($jpeg); undef $jpeg; eval { $image = $mw->Photo(-format=>"jpeg",-data=>$data); }; undef $data; eval { $photo->configure(-image=>$image); }; $lastimage->delete if ($lastimage); #essential as Photo leaks! $lastimage = $image; } $jpeg .= $1 if ($grab =~ s/(.*)(?=\n)//s); last if $stop; $mw->update; } $stop = 0; }
House modifications are coming along well, with updates to the Home Automation security software (a few suprises for any intruder), and some large black marble balls on a rockery out the front. Tracy has been spending a few days pressure-washing the driveway which is fun apart from the occasional lump of sand that gets blasted at random parts of your body. Sand in your nose is quite annoying.
# Extended X10 control of LW11G dimmer # # Unlike other L*11* modules the LW11G # seems to only respond to code 53. Set the data to # # 0 = immediate off # 255 = immediate on # 1-254 = slowly dim or bright to that level, turns on if not already