Reverse engineering the Mitsubishi heat-pump WiFi adapter

Posted on Sat 04 June 2022 in reverse-engineering

Recently we had a heat pump (A/C, air conditioner, AirCon) installed. The indoor units came with the usual IR remote, but they also had a built-in WiFi module along with a smartphone app to control them from anywhere. I'm not a big fan of connecting home appliances to clouds for a variety of reasons:

  • Running a cloud service requires a continuous stream of money to keep the service running. So either you will have to pay some sort of subscription, or the service will (eventually) stop working. There is also the possibility that the manufacturer is very altruistic, but that isn't very likely.

  • If my appliance is connected to the internet, the manufacturer can choose to alter its functionality. Or make it stop working entirely.

  • Connecting things to the cloud makes them more vulnerable to attacks. Right now, an attacker needs to be within infrared range of my unit (a few meters) to control it. Once the device phones home to its cloud, an attacker can potentially control thousands of units from their couch.

Don't get me wrong. I love when appliances offer some sort of API to control them externally. And I see why a cloud-service is a nice addition for a lot of people. But I prefer local control.

The Mitsubishi WiFi adapter

My heat pump came with a MAC-577IF2-E adapter built-in. So I set out to try and locally control the unit via WiFi. If you're in a hurry, I'll save you some time: I didn't succeed. But here is what I did find out, in the hope it can be useful for other people.

I started by looking around to interesting projects: I found the meldec GitHub project that can decode the messages sent to MELcloud. Another related project I found was HeatPump, but that focuses on communicating directly with the unit over its serial interface.

First discoveries

For setup, I switched the adapter into Access Point mode. After associating with it using the SSID & WPA-PSK printed on the box, I could connect to a web-server to configure the network settings. The page was fairly simple:

network setup screen

Once the details were filled in, it connected to my home WiFi network just fine. The MAC address of the unit stats with 70:61:BE, which is assigned to Wistron Neweb Corporation. After requesting an IP via DHCP, it started to phone home, of course. First thing it did was a DNS-lookup for production.receiver.melcloud.com.

The first request to that domain is a plain-text HTTP POST-request to /synchro:

POST /synchro HTTP/1.1
Host: production.receiver.melcloud.com:80
Accept: */*
User-Agent: MAC-577IF-E
Pragma:  no-cache
Content-type:  text/plain; charset=UTF-8
Content-Length: 68

<?xml version="1.0" encoding="UTF-8"?><LSV><SYNCHRO></SYNCHRO></LSV>

The response contained the current datetime:

HTTP/1.0 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Date: Sat, 4 Jun 2022 09:47:47 GMT
Server: Microsoft-IIS/10.0
X-Robots-Tag: noindex, nofollow, noimageindex

<?xml version="1.0" encoding="UTF-8"?><CSV><SYNCHRO><DATE>2022/06/04 09:47:47</DATE></SYNCHRO></CSV>

So far so good. The next connection, however, was an encrypted HTTPS request to the same domain. production.receiver.melcloud.com presents an HTTPS certificate that is signed by MELCloud Root Authority, and thus not trusted by browsers (by default). I tried to redirect this request to my own server, presenting a look-alike certificate chain. But the device refused with an Unknown CA error: it didn't like my own spoofed CA and wanted to see the actual Mitsubishi CA. Normally, I consider this a good thing for security, but in this particular case it's a bummer...

Inbound

There also seems to be a webserver listening on the device itself on TCP port 80. But according to nmap, there is nothing else listening on TCP (all other ports are connection-refused). Note that a normal nmap-scan crashes the web server, so you won't see it as open on a consecutive scan.

But the web server seems to be locked down: requests to / require authentication, which I don't have/know:

> GET / HTTP/1.1
> Host: IP-address-of-unit-omitted-for-privacy
> User-Agent: curl/7.79.1
> Accept: */*
>

< HTTP/1.1 401 Unauthorized
< Content-Type: text/plain
< Content-Length: 22
< WWW-Authenticate: Basic realm="generic"
<
< Authorization Required

Some URLs do work without authentication: /license, /common.css, /javaScript.js work. Others return a 404 instead of a 401.

Digging deeper

Based on the /license content, MbedTLS is used. No version is specified, but since the name MbedTLS is used, it's at least 1.3.10 from beginning 2015. (Before that, the name was still PolarSSL)