Skip to content

Hacking your dishwasher (or Cloudless Home-Connect Appliances)

This is a rough draft of a talk given at the Tweakers Dev Summit 2022, along with some annotations. It is not yet organized. Sorry, there is no video, although there is source code: github.com/osresearch/hcpy

Intro

Hi. I’m Trammell Hudson. you might remember me from other projects like porting python to Ikea light bulbs. or the Magic Lantern firmware for running extensions in canon dslr cameras.

But did you know that computers are not just in light bulbs and cameras?

Computers are everywhere

that’s right! there are computers in your dishwasher. and your oven. and your laundry. and even your coffee maker. (I don’t have one of the siemens coffee makers, so I had to fake that last one)

and they connect to the cloud

Many of these embedded computers connect to the cloud, which is another way to say "Other People's Computers". This cloud dependence can create weird failure modes, such as "I can't do dishes, the Internet is down" or "my dishwasher won't start until I let it update its firmware".

The other major problem with cloud connected devices is that you don’t control the cloud side of the system, which means you don't actually own cloud connected devices. The vendor can "alter the rules" and remove functionality or break how you use the devices, such as Toyota charging to keep using remote start functions.

The S in IoT stands for Security

And finally, there’s a reason the joke is thathe "the S in IoT stands for Security". Most cloud connected devices have a horrible track record for privacy and security, such as insecure firmware in cheap webcams used by the Mirai malware to create the world's largest botnet. Dan Tentler's "Stupid things you can put on the internet" presentation is a hilarious example of internet connected devices that really shouldn't be.

However, that is not what this talk is about...


Bosch/Siemens HomeConnect

I want to make a point of mentioning that Bosch-Siemens (or BSH as it is referred to in their app) has actually built a fairly well designed IoT products. The protocols are sound and mostly based on modern standards like TLS and strong ciphers with decent key management. They even have a reasonable REST API that goes through their servers for integration with HomeAssistant and other tools.

No-cloud mode

However, perhaps inline with the German attitudes towards privacy, the BSH "HomeConnect" appliances have a no-cloud mode built into their app without any hacks required to disconnect them from the internet. They do require a one-time connection to perform key exchange of a long-live authorization key, but from then on the appliances can be operated entirely disconnected from the network.

There is a dialog to remind you that they won't receive firmware updates, which is still a weird thing to say about appliances, and they won't work when you're away from home unless you setup your own reverse proxy or something. I usually don't care about the dishwasher status while I'm not home, so that seems like a fine trade-off to me...

The real major drawback to the no-cloud mode this is that it also disables their REST API integrations. In no-cloud mode the only way to interact with the devices is through their app, and an app isn’t always the most convienent way to interact with devices in the home.


Alternate interfaces

Dashboards

At our home we have servers that log all sorts of household data about device usage and display them with grafana.

As well as so many small dashboard displays that use ZigBee or MQTT to interface with the automations. For instance, some dashboards around the house to make it easy to tell if we’ve left the dakteras roof hatch open, or ones that display our total power consumption now that it is so much more expensive. Often this is easier than digging out the phone to check an app.

For the BSH appliances our biggest desire is to know how much longer the laundry has. It takes a few hours, and pulling out a phone every so often is a real pain, so having this information displayed around the house on the different dashboards or posting messages to our chat servers is a much easier way to find out the status.

Accessibility

We also have many custom physical interfaces that integrate many different systems, like this big e-stop. Because when it’s late, and you’re tired, and you just want to turn off all the lights, close the blinds, and start the dishwasher, it is much more satisfying to hit the Giant Red Button than fiddle with multiple apps.

These sorts of physical interfaces are also much more accessible than the cap-sense buttons and touch screens on the appliances. It is very challenging to use them if you have limited vision or dexterity issues, so being able to build custom interfaces makes it possible to adapt it to different users' needs.


How do I use it?

Ok, this all sounds awesome, right? But you might be asking, "how do I use this to automate my home?".

It does currently require some command line usage and a little bit of glue to work well; hopefully if you're doing custom integrations and fancy automations, this won't be too bad. First you’ll need to checkout my hcpy github tree to get the scripts and tools from github.com/osresearch/hcpy:

git clone https://github.com/osresearch/hcpy

hc-login

The next step is that you need to retrieve all of the information about your appliances from the BSH servers by running (fill in your username and password):

hc-login ${username} ${password} > config.json

This is the only operation that requires you to talk to their systems; once you have created the config file the system is standalone and self-hosted, and you'll only need to re-run this if you ever add new devices to your home.

The hc-login flow is built on OAuth and requires your username and password to retrieve a Bearer token from their auth server. This token is signed by their server and has limited allowed uses controlled by the Scope parameter. In a real application, this bearer token would be used on each startup to re-login, although we only use it during this process to request appliance information for the account.

Once the hc-login tool has this bearer token, it then contacts their api server to request the account info and proves that it is allowed to do so by sending the bearer token as authorization. The api server can validate the auth token signature and validity without knowing the username and password, and then replies with a JSON structure of all of the appliances ids and, most importantly, the appliances long-lived authorization credentials.

Then, for each appliance connected to the account, the hc-login tool contacts an asset server and sends it the appliance id. The asset server replies with a ZIP file containing XML descriptions of the features and commands that the device supports. These are pretty neat - they encode, in normal XML baroqueness, all of the features and controls on the device.

hc-login converts the XML files into an simpler JSON and writes the appliance credentials plus the configuration into the config.json file.

At this point you can disconnect your computer from the internet, because it no longer needs to talk to the outside world.

hc2mqtt

The next program hc2mqtt reads in the JSON config file and establishes websocket connections to each appliance (based on the hostname that should work with mdns, although this might require some hacks for your specific network).

We've seen two different types of websockets: some appliances use HTTPS with PSK-CHCHA20 and others use HTTP with AES-CBC-HMAC on the websocket data. Don't worry about the devices using unencrypted HTTP - they HMAC each message with a key derived from the PSK so that a local adversary can't cause bitflip or other CBC attacks on the events. Internally the different device types are identified by the existence of an IV parameter in the pre-shared-key; that indicates an AES connection, while the HTTPS connections use TLS to establish the session key and IV parameters.

The websocket provides a long-lived, message-oriented connection and provides real-time status updates from the devices. The events that it sends include things like the currently selected operation, the time remaining in the program, the current desired and actual temperatures, and so on. hc2mqtt turn this into a JSON formatted MQTT message and publishes it to the local MQTT server, which then resends it to any dashboard devices that have subscribed to the appliances’ topics.

This works really well and satisfies many of our requirements -- it is easy to build small physical dashboards that subscribe to events and update their displays to show the messages.

Examples dashboard

Here's a nearly complete esphome.io example for a small ESP32 development board with an LCD screen. The code subscribes to the MQTT event homeconnect/oven/state, which is a JSON formatted message that should have the two keys temperature and temperature_set for the actual and desired temperatures. Every second (update_interval: 1s) the firmware draws the current temperature on the display.

Now when the oven is pre-heating, we have a readout of the actual internal temperature. This is something that the official front panel doesn't have (it has a bargraph that sort of shows the relative amount of heating to go), and something that can be useful for knowing if the oven is ready for baking.


But how, really?

So, you might ask, how really do you figure this out?.

This is not necessary if you just want to integrate your BSH HomeConnect devices with your MQTT server, although it might be useful if you're trying to understand a different manufacturer's appliance control app, or if you are curious about the reverse engineering process.

One thing to keep in mind that most talks or blog posts on this sort of thing show the garden path to the end result, while in reality most reverse engineering is chasing down dead-ends and realizing that piece you've just spent an entire day tracing wasn't important or invovled in the actual thing you're examing. As @mjg59 said "I'm not good with computers, I'm just bad at giving up", which is a good attitude for dealing with the repeated failures and lack of visible progress.

The first observation that I made is that the BSH app still can talk to the devices when it is on the same local WiFi network. This means there are probably TCP or UDP ports open on the appliance.

nmap

nmap is the go-to tool for doing this sort of initial network probing. Running it against my dishwasher's local IP address found exactly one open port, 443 for HTTPS. However, when I tried to load that page in a browser it gave an error and running curl against it produced this curious "unknown cipher returned" error.

curl's error message is not helpful, so we go back to nmap and use the ssl-enum-ciphers script that runs through all of the possibilities and it prints out the list. All of them include the term PSK, which at the time I wasn't familiar with, although I knew exactly what to do...

stackoverflow and man pages

Yes, I searched for "TLS PSK" and read this helpful stackoverflow post about using "Pre-Shared Keys" with OpenSSL.

Seriously, internet search engines are one of the absolute best reverse engineering tools out there. Just watch out for Mulliner's "adwords canary" if you are doing reverse engineering for adversarial interopability...

One of the stackoverflow answers mentioned using psk_client_callback, "documented" in this man page, which is called when the OpenSSL library makes a TLS connection that wants to use a PSK cipher suite. This is user code, not library code, and is passed a "hint" so it can fetch the appropriate preshared key.

Note that this is making a big assumption: that the application is using OpenSSL for establishing the connection to the appliance. However, it is fairly safe most of the time since many vendor dev teams are lazy and won't write their own TLS library if Android provides one for them...

jadx

Android applications are usually written in Java (or other JVM language) and the APK files can be unpacked with tools like jadx. It has a really delighfuly simple command line usage in the normal case:

jadx Home-Connect_v8.2.0.com.apk

This command will unpack from the APK all of the class files, native libraries, images and other resources. It also will "decompile" the Java Dvalik class files to produce something like human readable source code.

I spent quite some time unsuccessfully poking through the source files trying to find where various SSL calls were made or other cryptographic operations. Seriously, I'm just bad at giving up.

Eventually I identified that much of the Java code was calling into a native library through JNI, and guessed that the 10MB libHCPService.so was the likely target. It's bigger than everything else in the resources/lib/arm64-v8a directory and HCP probably means something with HomeConnect, so I decided to dive it into.

ghidra

Unlike the "human-readable" decompiled Java, the native library isn't as easy to read. To make sense of it we need another tool: ghidra. This is an absolutely amazing suite of programs from the US National Security Agency and hopefully not a trojan horse for the NSA to get code execution security people's computers. It has disasemblers for dozens of architectures (including armv8), decompilers, symbol tracking, cross-reference generators, string finders, and so on.

Actually using ghidra requires a whole class. It is a very complex tool, so I'm glossing over quite a bit of the steps here...

Once the library is loaded and the initial analysis has run, the Symbol Tree window will have all of the functions that have been identified. I searched for the set_psk_client_callback and double clicked on it to bring up the disassembly. That listing has a XREF field that shows it is called in exactly one place named on_secure_tls_init, which is really a good sign. Double clicking on the xref opens the decompiled version of that function and reveals the full namespaced name as hcp::webSocketManager::on_secure_tls_init, which is an excellent sign that we're in the right place for the HomeConnect code that deals with WebSockets. Searching through the decompiled code we find that the function is passed a function named client_psk_callback, which is the one that will return the actual PSK once the connection is made.

One minor note is that this is a C++ library, so the actual name of the function is this weird __ZN3hcp... string, which is the "mangled name" that allows C++ programs to overload function names based on type signatures and namespaces, while the linker assumes that all functions have unique names.

Now that we know where the PSK is generated, we have to figure out what the value of the PSK is. We could try to reverse engineer this function, but it's long and probably fetches things from a local application database at runtime, so reversing it won't reveal what we want to answer: what is the PSK for our appliance?

frida

To answer that question, we turn to another tool: frida. This is an amazing "dynamic instrumentation toolkit" that allows you to "hook" functions in Android applications and run little Javascript snippets to process their arguments and poke at the internals. The biggest downside is that it does require a rooted phone to debug apps that use native library; I have an old burner that I use for this sort of hacking. (On applications that don't use libraries, you can run in a rooted Android emulator instead)

I learned about it frida from Axelle Apvrille's talk at hack.lu 2019, in which she demonstrated how to analyze what applications are sending to their servers by hooking SSL_read() and SSL_write() calls in applications to get access to the clear text data after or before encryption. This is very similar to what I needed to do and one of the most useful talks I've seen on the tool.

After much trial and error I hacked together a find-psk.frida script that waits one second after starting the app (since dlopen() of the native library doesn't happen instantly), and then searches that library for the C++ name mangled version of client_psk_callback and hooks both the onEnter and onLeave events.

Remember from the SSL manpage that the fifth argument is the PSK to use for the connection, so the onEnter event caches the args[4] argument (0-indexed) and then the onLeave event reads the application memory pointed to by that arg and then outputs the bytes of the array.

Yes, frida bring console.log() debugging to Android application reverse engineering. The future is here!

On our adb attached system, I launch the app with Frida and the debug script:

frida --no-pause \
  -f com.bshg.homeconnect.android.release \
  -U -l find-psk.frida

The app starts up and connects to the appliances, which triggers the callback, which causes my script to dump the PSK. Looks good so far!

Now comes the moment of truth: can we use openssl s_client with this 32-byte value?

And yes, yes we can! OpenSSL connects, receives the identity hint, and negotiates a ECDHE-PSK-CHACHA20-POLY1305 connection using our PSK.

tl;dr

2022 Talks


Last update: August 29, 2022