Some time ago I got two iSP5 Smart Plugs for $30 each. The plugs can be controlled from dedicated iHome apps on iOS and Android. They also work with Apple HomeKit and Amazon Alexa. It’s great but I use OpenHab2 on Raspberry Pi 2 to control my smart home devices and there’s no integration/plugin for the plugs. I was wondering if it would be possible to reverse engineer the iHome API and write a small script to control the devices. The first thing I did was running nmap port scanning against one of the plugs which were connected to my WiFi.

sudo nmap -sS -sU -PN -Pn 192.168.0.5

Starting Nmap 7.40 ( https://nmap.org ) at 2017-04-30 23:41 EDT
Nmap scan report for 192.168.0.5
Host is up (0.038s latency).
Not shown: 1998 closed ports
PORT     STATE SERVICE
80/tcp   open  http
5353/udp open  zeroconf
MAC Address: 80:A5:XX:XX:XX:XX (AzureWave Technology)

Nmap done: 1 IP address (1 host up) scanned in 15.51 seconds

Victory! … well not really. Two ports are open. HTTP port always returns 404 so it’s not very helpful yet.

curl -i 192.168.0.5
HTTP/1.1 404 Not Found
Transfer-Encoding: chunked
Content-Type: text/plain

File / not_found%

The zeroconfig / mDNS/ Bonjour service on port 5353 tells us something interesting about the service being announced by the device. I checked mDNS packets in Wireshark and also installed Bonjour Browser on my OSX to see more details. mDNS tells us a service specification. In this case, it is HAP on TCP on port 80 on a device with IP 192.168.0.5. HomeKit Accessory Protocol (HAP) is what Apple desinged to control smart devices.

I know that the plugs can be controlled from my iPhone without the internet connection. It means that the iHome app must be able to talk to them directly. Interestingly, iHome Android app doesn’t work without the Internet. It’s a hint.

iHome iOS app uses the device HomeKit API directly when there’s no access to the internet. The Android app doesn’t support HomeKit and doesn’t work when the phone is offline but in the same WiFi with the devices.

I think the smart plug has only one API available directly over the local network and it’s the HomeKit API. HomeKit API has a very strong security and it’s close to impossible to hack. So how does the Android app work? The Android app sends the commands to iHome REST API in the cloud and they get passed to an MQTT channel. The plug, when it’s in WiFi with access to the Internet syncs its status with an iHome MQTT server. That is actually a quite good news. It is possible to sniff the API calls from the app and write a little script to replicate them.

Sniffing iHome REST API

There are many ways to sniff network traffic from a mobile phone. The simplest way is using Charles Proxy. You have to install a Chares Proxy ROOT cert on your phone in order to sniff the encrypted HTTPS traffic. More about it is on https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/ The root cert can only be installed from Safari!

The downside of this method is that it only works for HTTPS traffic which goes on port 443. The mobile app may not respect the proxy settings or use cert pinning. Cert pinning is the worst case since the app will NOT respect the fake temporary cert signed with the Charles ROOT cert installed on your phone.

Another method of sniffing is a bit more complicated and involves MITM attack. MITM allows for sniffing the entire network traffic from the phone on any protocols and ports. I wanted to test this method first to make sure that there is nothing unexpected going on there like the app talking to the API on an unusual port or protocol. It will give me some certainty that the Charles Proxy method will be effective. Ettercap and Wireshark are the right tools for the job.

You can install Ettercap with Brew on Mac.

brew install ettercap

Download and install Wireshark from https://www.wireshark.org/

Start Ettercap with

sudo ettercap -C -i <your_network_interface>

 

Add a mobile phone IP 192.168.0.4 to TARGET GROUP 1

Add a smart plug IP 192.168.0.5 to TARGET GROUP 1

Add ROUTER IP 192.168.0.1 to TARGET GROUP 2

Go to MITM menu and select ARP Poisoning and as a parameter type remote in order to sniff all the traffic from the victims’ devices not only the direct traffic between them.

Ettercap should start ARP poisoning. The ARP protocol is used to resolve an IP address into a MAC address and the other way around. Ettercap floods the network with spoofed ARP packets which will reach both Target Groups network interfaces and tell them that a MAC address for the opposite group IPs is a MAC address of an attacker computer. In this case, it’s my Mac 192.168.0.2. It allows for sniffing all the network traffic between the victims and remote servers. Network packets in an ethernet network are sent to their destination based on a destination MAC address.

This attack works because Ettercap sends many fake ARP packets per minute and they always poison a local ARP cache on a victim machine. The phone, the router, and the smart plug still announce their IPs with ARP but it happens with a much longer interval. So even if a victim’s local ARP cache gets updated with a correct MAC->IP data, it will be immediately overwritten by the constant flood of spoofed ARP packets.

ARP poisoning on its own is not enough. Luckily, Ettercap also handles packet routing to the correct IPs/network interfaces after they arrive on the attacker network interface. That closes the loop and allows the traffic to flow to its intended destination.

I used Wireshark to make sure that the only traffic between the Andriod phone with iHome app is to and from the iHome REST API. That is the case.

Next, it’s time to sniff the iPhone traffic. I opened up the iOS iHome app and switched the plug on and off. The app did not send any packets directly to the smart plug. Instead, all the requests were sent to https://api.evrythng.com/  and https://www.ihomeaudio.com/api/v3/login/. Unfortunately, those requests go over HTTPS and we can’t see their content yet.

The theory is confirmed. The iHome app talks directly to the iHome REST API. iOS app has an ability to use the HomeKit API and directly talk to the devices in the case when there’s no access to the Internet. It’s time to sniff the HTTPS calls with Charles Proxy.

Here is a diagram presenting my network configuration.

 

After I installed Charles Proxy on my Mac 192.168.0.2, I changed my iPhone WiFi configuration to use the proxy.

I also activated Charles ROOT cert.

When I started the proxy on my Mac I had to enable the SSL proxy for the two API domains otherwise Charles wouldn’t be able to see the content of the requests.

Next, I opened up an iOS iHome app and logged out and tried to log in with some fake credentials just to see what is going to happen.

 

Success! We captured the login request. The response with the invalid creds gave me 401 Unauthorized …

but the correct creds worked

curl -X POST \
  https://www.ihomeaudio.com/api/v3/login/ \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{"email":"hax0r@example.com","password":"XXXXXXXXX"}'
Login

and the response I got looks like this.

{
  "username": "hax0r@example.com",
  "email": "hax0r@example.com",
  "first_name": "Pawel",
  "last_name": "Raszewski",
  "evrythng_user_id": "MY_USER_ID",
  "evrythng_api_key": "MY_API_KEY",
  "everythng_api_key": "MY_API_KEY",
  "token": "MY_TOKEN"
}

Ok, so we got a token and an API key. everythng_api_key and evrythng_api_key values are the same. It’s probably a typo and the developers had to keep two keys for backward compatibility.

Let’s sniff more traffic. When the app UI loaded a few more requests were made. Notice that www.ihomeaudio.com API is only used to obtain the token and the API key. Not sure what the token is for. From now on the app talks to api.evrythng.com and always passes in the API key in the Authorization header. I played around for a while and this is what I discovered.

Get a list of all iHome products and their IDs.

curl -X GET \
  https://api.evrythng.com/products \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache'
[
  {
    "id": "UF4NsmAEM3PhY6wwRgehdg5n",
    "createdAt": 1487792439149,
    "customFields": {},
    "updatedAt": 1490115684305,
    "brand": "iHome",
    "properties": {},
    "description": "iSP6X SmartPlug",
    "fn": "iSP6X",
    "name": "iSP6X",
    "photos": [
      "https://www.ihomeaudio.com/media/uploads/product/images/iSP6_A.jpg.450x400_q85.jpg"
    ],
    "identifiers": {}
  },
  {
    "id": "UEG9ENp9h5xkwMbUxNBQ4h8m",
    "createdAt": 1470070207685,
    "customFields": {},
    "updatedAt": 1470070212941,
    "brand": "Nest",
    "properties": {},
    "fn": "Nest Thermostat",
    "name": "Nest Thermostat",
    "photos": [
      "http://support-assets.nest.com/images/Amber/a2-bacteria-prevention/a2-bacteria-icon.png"
    ],
    "identifiers": {}
  },
  {
    "id": "UYgGEmrPfeXwFkg8cdyhCAmh",
    "createdAt": 1466530270992,
    "customFields": {},
    "updatedAt": 1476292964672,
    "brand": "iHome",
    "properties": {},
    "description": "iSS50 SmartMonitor",
    "fn": "iSS50",
    "name": "iSS50",
    "photos": [
      "https://www.ihomeaudio.com/media/uploads/product/images/iSS50.jpg.450x400_q85.jpg"
    ],
    "identifiers": {}
  },
  {
    "id": "UXNtyNexVyRrWpAQeNHq9xad",
    "createdAt": 1457466689944,
    "customFields": {},
    "updatedAt": 1476292908273,
    "brand": "iHome",
    "properties": {},
    "description": "iSP8 SmartPlug",
    "fn": "iSP8",
    "name": "iSP8",
    "photos": [
      "https://www.ihomeaudio.com/media/uploads/product/images/iSP8_Bundle.jpg.450x400_q85.jpg"
    ],
    "identifiers": {}
  },
  {
    "id": "Ugrtyq8pAFVEqGSAAptgtqkc",
    "createdAt": 1457466603940,
    "customFields": {},
    "updatedAt": 1476292941493,
    "brand": "iHome",
    "properties": {},
    "description": "iSP6 SmartPlug",
    "fn": "iSP6",
    "name": "iSP6",
    "photos": [
      "https://www.ihomeaudio.com/media/uploads/product/images/iSP6_A.jpg.450x400_q85.jpg"
    ],
    "identifiers": {}
  },
  {
    "id": "Ugex7Dx3se5ancaDPAMUpmCt",
    "createdAt": 1447155225608,
    "customFields": {},
    "updatedAt": 1447155847224,
    "brand": "Nest",
    "properties": {},
    "description": "",
    "fn": "Nest Structure",
    "name": "Nest Structure",
    "photos": [
      "http://file.answcdn.com/answ-cld/image/upload/v1/tk/brand_image/c7d3fadd/c71c29fef0dd6c56a9b2777906bcf3245960c403.png"
    ],
    "identifiers": {}
  },
  {
    "id": "UCfXBRHnse5Rpw7PySPYNq7b",
    "createdAt": 1431716010342,
    "customFields": {},
    "updatedAt": 1431716021587,
    "properties": {},
    "description": "iSP5 Smart Plug",
    "fn": "iSP5",
    "name": "iSP5",
    "identifiers": {}
  }
]

Get a list of devices paired with your account.

curl -X GET \
  'https://api.evrythng.com/thngs?perPage=100&sortOrder=DESCENDING' \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache' 
[
  {
    "id": "DEVICE_ID_XXXX",
    "createdAt": 1474123450528,
    "customFields": {
      "initial_setup_app_version": "2.1.2 (130)",
      "initial_setup_os": "iOS",
      "initial_setup_os_version": "10.2.1",
      "room_name": "Living room",
      "service_type": "light"
    },
    "tags": [
      "fw_updated"
    ],
    "updatedAt": 1491123414174,
    "name": "Lamp",
    "product": "SMART_PLUG_ID_XXXX",
    "properties": {
      "appfwversion": "0.8.84",
      "auth_fail": "0",
      "currentpowerstate1": "1",
      "dhcp_fail": "0",
      "epoch": "6",
      "factoryreset": "0",
      "numoutlets": "1",
      "nw_not_found": "108",
      "outletinuse1": "1",
      "schedule": "[[1, 1, 10, 0, 62, \"xxxxx\", \"Lamp activity\", 2], [1, 0, 17, 0, 62, \"xxxxxx\", \"\", 2]]",
      "schedule_version": "100",
      "targetpowerstate1": "1",
      "timezone": "America/New_York",
      "~connected": true
    },
    "identifiers": {
      "firmware_version": "0.8.84",
      "homekitid": "SOME_ID_XXXX",
      "owner": "SOME_ID_XXXX",
      "serial_num": "XXXXXXX"
    }
  },
  {
    "id": "DEVICE_ID_ZZZZZZ",
    "createdAt": 1474123450705,
    "customFields": {
      "initial_setup_app_version": "2.1.2 (130)",
      "initial_setup_os": "iOS",
      "initial_setup_os_version": "10.2.1",
      "room_name": "Bedroom",
      "service_type": "light"
    },
    "tags": [
      "fw_updated"
    ],
    "updatedAt": 1491123457300,
    "name": "Bedroom Light",
    "product": "MY_SECOND_PLUG_ID_XXXX",
    "properties": {
      "appfwversion": "0.8.84",
      "auth_fail": "0",
      "currentpowerstate1": "0",
      "dhcp_fail": "0",
      "epoch": "9",
      "factoryreset": "0",
      "numoutlets": "1",
      "nw_not_found": "0",
      "outletinuse1": "1",
      "schedule": "[[1, 1, 12, 0, 62, \"xxxxx\", \"Bedroom activity\", 2], [1, 0, 14, 0, 62, \"xxxxx\", \"\", 2]]",
      "schedule_version": "100",
      "targetpowerstate1": "1",
      "timezone": "America/New_York",
      "~connected": false
    },
    "identifiers": {
      "firmware_version": "0.8.84",
      "homekitid": "SOME_ID_XXXX",
      "owner": "SOME_ID_XXXX",
      "serial_num": "XXXXXXXX"
    }
  }
]

Get a power history for a device.

curl -X GET \
  'https://api.evrythng.com/thngs/<DEVICE_ID>/properties/currentpowerstate1?from=1487564364896&to=1490156364896&perPage=100&sortOrder=DESCENDING' \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache'
[
  {
    "createdAt": 1490156295080,
    "timestamp": 1490156295080,
    "value": "0"
  },
  {
    "createdAt": 1490156289177,
    "timestamp": 1490156289177,
    "value": "1"
  },
  {
    "createdAt": 1490156283533,
    "timestamp": 1490156283533,
    "value": "0"
  },
  {
    "createdAt": 1490156222461,
    "timestamp": 1490156222461,
    "value": "0"
  },
  {
    "createdAt": 1490139271432,
    "timestamp": 1490139271432,
    "value": "1"
  },
  {
    "createdAt": 1490129022662,
    "timestamp": 1490129022662,
    "value": "0"
  },
  {
    "createdAt": 1490129022612,
    "timestamp": 1490129022612,
    "value": "0"
  },
  {
    "createdAt": 1490072869766,
    "timestamp": 1490072869766,
    "value": "0"
  },
  {
    "createdAt": 1490072662168,
    "timestamp": 1490072662168,
    "value": "0"
  },
  {
    "createdAt": 1490064092435,
    "timestamp": 1490064092435,
    "value": "1"
  },
  {
    "createdAt": 1490064090429,
    "timestamp": 1490064090429,
    "value": "0"
  },
  {
    "createdAt": 1490064087803,
    "timestamp": 1490064087803,
    "value": "1"
  },
  {
    "createdAt": 1490064087802,
    "timestamp": 1490064087802,
    "value": "1"
  },
  {
    "createdAt": 1490064085913,
    "timestamp": 1490064085913,
    "value": "0"
  },
  {
    "createdAt": 1490064084416,
    "timestamp": 1490064084416,
    "value": "1"
  },
  {
    "createdAt": 1490064081346,
    "timestamp": 1490064081346,
    "value": "0"
  },
  {
    "createdAt": 1490064015673,
    "timestamp": 1490064015673,
    "value": "1"
  }
]

And finally, this is how the plug can be turned off…

curl -X PUT \
  https://api.evrythng.com/thngs/<DEVICE_ID>/properties/targetpowerstate1 \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '[{"value":"0"}]'

…or turned on.

curl -X PUT \
  https://api.evrythng.com/thngs/<DEVICE_ID>/properties/targetpowerstate1 \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '[{"value":"1"}]'
[
  {
    "createdAt": 1493870058932,
    "timestamp": 1493870058932,
    "key": "targetpowerstate1",
    "value": "1"
  }
]

At this point, I got all I needed to write a Nodejs app to control my iHome smart plugs. I’m going to write a separate post about the node app later. Those of you who want to integrate iHome smart plugs with OpenHab or Domoticz can use my API calls examples and build dedicated plugins. I hope this was interesting. Stay tuned for more.

 

EDIT: I got an email from Jeremy ultraviolet7@gmail.com who use my my tutorial to hack his iSP6 smart home plug. He sent me those two example requests to turn on/off the plug. If you have more questions about iSP6 please contact him.

 

curl -X POST \
  https://api.evrythng.com/thngs/<DEVICE_ID>/actions/_turnOn \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{"type":"_turnOn"}'

 

curl -X POST \
  https://api.evrythng.com/thngs/<DEVICE_ID>/actions/_turnOff \
  -H 'authorization: MY_API_KEY' \
  -H 'cache-control: no-cache' \
  -H 'content-type: application/json' \
  -d '{"type":"_turnOff"}'

 

 

Leave a Reply