Sitemap

I Reverse-Engineered Ring’s “End-to-End Encryption” — What I Found Should Terrify 40 Million Households

35 min readMay 25, 2026

--

10 kill switches. Zero tamper detection. A facial recognition cloud. A police API. A door-unlock endpoint. And an encryption system whose database password is your public user ID. This is what’s actually running on your
+ doorbell

A comprehensive reverse engineering analysis of Ring APK v3.104.0, 30 ARM64 native libraries, and the Greco E2EE system — revealing how a $64 billion company’s “end-to-end encryption” is held together by feature flags, plaintext database passwords, and dead code.*

Introduction

Ring doorbells, owned by Amazon since 2018, are installed on over 10 million American doorsteps. In 2022, facing mounting privacy criticism and a $5.8 million FTC settlement, Amazon introduced “End-to-End Encryption” (E2EE) for Ring cameras — marketing it as a fundamental security guarantee that “only you can view your videos.”

We decided to test that claim.

Over the course of this research, we fully decompiled Ring’s Android APK (v3.104.0, comprising 6,292+ Java files) and reverse-engineered all 30 of its ARM64 native shared libraries — including the 17MB Greco E2EE core, the 153KB SFrame encryption library, the 11MB custom WebRTC stack, and the 463KB Cerulean legacy crypto module. We mapped every API endpoint, every feature flag, every cryptographic code path, and every debug backdoor left in the production binary.

What we found is not a battle-hardened encryption system. It’s a house of cards — one where the server controls whether encryption exists at all, where the E2EE key database is “protected” by a publicly-known user ID, and where four separate code paths will silently skip decryption if conditions aren’t perfect. Worse, Amazon left production debug backdoors that can inject arbitrary encryption keys via SharedPreferences, toggle encryption on and off per-device via API calls, and completely disable E2EE through a single feature flag response.

This report documents every finding.

Part 1: Understanding Ring’s Encryption Architecture

Before diving into vulnerabilities, it’s essential to understand what Ring *claims* to do versus what the code *actually* does.

The Marketing Claim

Ring’s E2EE marketing page states: “With End-to-End Encryption, your videos are encrypted on your Ring device and can only be decrypted and viewed on your enrolled mobile device.”

The Actual Architecture

Ring’s video encryption operates at three distinct layers, each with independent controls:

Layer 3: SFrame (per-frame encryption) ← Greco E2EE / Cerulean
Layer 2: SRTP (transport encryption) ← DTLS-SRTP key exchange
Layer 1: TLS (API/signaling encryption) ← OkHttp / WebSocket

**Layer 1 — TLS** protects REST API calls and WebSocket signaling. Our analysis found that Ring’s OkHttpClient configuration includes `ConnectionSpec.CLEARTEXT` in the default connection specifications — meaning every HTTP client in the app will silently fall back to unencrypted HTTP if the server doesn’t enforce TLS. There is zero certificate pinning on Ring API endpoints. Only the Amazon Cerulean key management service (a separate system) implements certificate pinning.

**Layer 2 — SRTP** protects the actual media transport. SRTP keys are derived from a DTLS handshake, and the DTLS certificate is exchanged during WebRTC signaling (SDP offer/answer). Whoever controls the signaling controls the DTLS certificate, and therefore controls the SRTP keys. This is by design in WebRTC — trust is delegated to the signaling layer.

**Layer 3 — SFrame** is Ring’s “E2EE” layer. It encrypts individual video and audio frames *inside* the SRTP transport using AES-128-GCM. Keys are provisioned through the Greco MLS (Messaging Layer Security) protocol. This is the layer Amazon markets as “End-to-End Encryption.”

The critical insight: **every one of these layers is independently disableable**, and multiple mechanisms exist at each layer to weaken or bypass encryption. The system was designed to be flexible, not to be secure.

The Complete Protocol Stack

Ring doorbells communicate through four distinct layers:

┌─────────────────────────────────────────────────────────────┐
│ RING MOBILE APP │
├─────────────────────────────────────────────────────────────┤
│ Layer 1: REST API (HTTPS) → Device mgmt, recordings │
│ Layer 2: WebSocket Signaling → WebRTC session setup │
│ Layer 3: WebRTC Media → Live video/audio stream │
│ Layer 4: FCM Push → Real-time event alerts │
└─────────────────────────────────────────────────────────────┘
│ │ │ │
▼ ▼ ▼ ▼
api.ring.com signalling. STUN/TURN ring-17770
ring.devices. (AWS Kinesis) (Firebase)
a2z.com

**Layer 1** handles all REST API communication — device management, recording downloads, settings, and authentication. OAuth2 authentication uses the client ID `ring_official_android` with hardware IDs derived from `UUID5(“379378b0-f747–4b67-a10f-3b13327e8879”, str(uuid.getnode()) + user_agent)`. 2FA is supported via `2fa-support: true` and `2fa-code: <OTP>` headers.

*Source: `OAuthTokenRequest.java` — `CLIENT_ID = “ring_official_android”` confirmed across LoginActivity, VerifyAccountService, and TokenAuthenticator.*

**Layer 2** handles WebRTC session establishment through WebSocket signaling at `wss://api.prod.signalling.ring.devices.a2z.com:443/ws`. Tickets are obtained from `POST /api/v1/clap/ticket/request/signalsocket`. The complete message protocol includes:

| Method | Direction | Purpose |
| — — — — | — — — — — -| — — — — -|
| `live_view` | Client → Server | SDP offer + stream options |
| `sdp` | Server → Client | SDP answer |
| `ice` | Bidirectional | ICE candidates (trickle) |
| `session_created` | Server → Client | Session confirmed |
| `session_started` | Server → Client | Session active |
| `activate_session` | Client → Server | **Must be sent within 70 seconds** or stream terminates |
| `stream_options` | Client → Server | Toggle audio/video |
| `camera_options` | Client → Server | Stealth mode, IR control |
| `ping` / `pong` | Bidirectional | Keep-alive (5-second interval) |
| `notification` | Server → Client | Status events (“camera_connected”) |
| `close` | Bidirectional | End stream |
| `camera_started` | Server → Client | Camera ready |
| `stream_info` | Server → Client | Stream metadata |

An alternative single-HTTP-request path exists at `POST /integrations/v1/liveview/start` that performs the entire SDP exchange in one request/response — no WebSocket, no E2EE negotiation. This is used by `ring-nodejs` for all streaming.

**Layer 3** carries the actual video and audio. Video uses H.264 High Profile Level 4.1 (`profile-level-id=640029`) with RTX retransmission. Some cameras also support HEVC (`settings.video_settings.hevc_enabled`). Audio uses Opus at 48kHz stereo (primary) with PCMU/G.711 at 8kHz as fallback. ICE candidates are resolved through Amazon Kinesis Video Streams STUN servers (`stun:stun.kinesisvideo.us-east-2.amazonaws.com:443`) and Google STUN servers (`stun:stun.l.google.com:19302`). Bundle policy is `disable` (separate audio/video transports), and ICE transport policy is `all`.

**Layer 4** provides real-time push notifications via Firebase Cloud Messaging (project `ring-17770`, sender ID `876313859327`). Event categories include doorbell press (`com.ring.pn.live-event.ding`), motion detection (`com.ring.pn.live-event.motion`), intercom events, and virtual unlock commands.

The E2EE Stack

The frame-level encryption sits within a multi-library architecture:

┌──────────────────────────────────────────────────────────────────┐
│ Greco E2EE Stack │
├──────────────────────────────────────────────────────────────────┤
│ Java Layer │ Rust Layer │ C Layer │
│ GrecoCekManager │ libamzn_greco_core.so │ libsecure- │
│ GrecoE2eeDeviceManager│ (17MB, UniFFI bindings) │ frame.so │
│ ContentEncryption- │ MLS group management │ (153KB) │
│ KeysOverrides │ Key derivation │ sframe_*() │
│ │ SQLCipher storage │ AES-128-GCM │
├──────────────────────────────────────────────────────────────────┤
│ libjingle_peerconnection_so.so (11MB) │
│ RingFrameEncryptor / RingFrameDecryptor (JNI) │
├──────────────────────────────────────────────────────────────────┤
│ libsrtp2-fsv.so (SRTP) │ libceruleancore.so (legacy crypto) │
│ libcrypto.so / libssl.so │ libsqlcipher.so (key database) │
└──────────────────────────────────────────────────────────────────┘

The CEK retrieval flow traces through seven steps:

1. App needs to decrypt video for `deviceId` + `cekId`
2. `GrecoCekManagerImpl.contentEncryptionKey(deviceId, cekId)`
3. `ContentEncryptionKeysOverrides` checked **FIRST** (SharedPreferences backdoor)
4. `GetCekRoutine().getCekSync(cameraGroupClient, groupId, cekId)`
5. Calls into `libamzn_greco_core.so` via UniFFI
6. Looks up key in local SQLCipher database (password = userId)
7. If not found, syncs with Greco cloud service → returns `Cek(key, e2eeStatus)`

The override at step 3 means an attacker who controls SharedPreferences bypasses the entire MLS protocol.

Part 2: The E2EE Kill Switch — Feature Flags That Control Encryption

How Ring’s Feature Flags Work

Ring’s app fetches feature flags from `POST /flags/v2/flags` at `api.ring.com` on every launch. The response is a JSON object containing boolean toggles and configuration values that control app behavior. Flags are resolved in this priority order:

1. **Local overrides** (empty in production)
2. **Server-provided values** (from the API response, stored in a `ConcurrentHashMap`)
3. **Hardcoded defaults**

The flags are cached in the app’s `RingStorage` under the key `_remote_cache_` and persist across app restarts until the next successful refresh.

*Source: `FeatureFlag.java:41–48`, `FeatureFlagStorageImpl.java:93`*

The Encryption Feature Flags

The following flags, all server-controlled, govern the entire E2EE system:

| Flag | Default | What It Controls |
| — — — | — — — — -| — — — — — — — — — |
| `go_ee_v2_enabled` | `false` | **Master E2EE toggle** — gates all Greco encryption |
| `grc_ae_bz_enabled_inc` | `false` | Greco baseline encryption enabled |
| `go_ae_bz_my_enabled_inc` | `false` | Whether E2EE is mandatory (when false, it’s optional) |
| `go_ee_cy_ck_enabled` | `false` | E2EE client compatibility check |
| `grc_debug_enabled_inc` | `false` | Exposes Greco debug screens with encryption controls |
| `dhav2_debug_root_enabled_inc` | `false` | DHAv2 debug root access for camera streaming |

*Source: `EncryptionFeatureFlagsImpl.java:42–49`, `FeatureFlagsResponse.java:132–356`*

E2EE eligibility in the app is checked at `GrecoInfoRepositoryImpl.java:152` — it requires either `grecoE2eEnabled` OR `grecoBaselineEnabled` to be `true`. Setting both `go_ee_v2_enabled=false` and `grc_ae_bz_enabled_inc=false` disables E2EE entirely on the client side.

The Implication

Anyone who controls the DNS resolution for `api.ring.com` — a network administrator, a compromised router, a rogue ISP, or a local cloud emulator — can serve a feature flag response that disables E2EE for every Ring device on that network. The flags are cached, so the effect persists even after the attacker stops intercepting traffic.

This is not a bug. This is architecture. Amazon designed E2EE as a server-controlled toggle, not as a client-enforced guarantee.

Part 3: The SQLCipher Password — “Encryption” with a Public Key

The Discovery

Deep in Ring’s Greco encryption client factory, we found the function responsible for protecting the local E2EE key database:

// GrecoClientFactory.java, lines 144–148
private final byte[] getDatabasePassword(String userId) {
byte[] bytes = userId.getBytes(Charsets.UTF_8);
return bytes;
}

The Greco E2EE system stores all MLS private keys, account group keys, and per-camera group keys in a SQLCipher-encrypted database on the device. SQLCipher is a well-respected encrypted SQLite implementation used by Signal, WhatsApp, and other security-focused applications.

But the password for this database is the user’s numeric user ID — a value that is public. It appears in API responses, in device information, in URLs, and can be trivially obtained by anyone with network access to Ring API traffic.

*Source: `GrecoClientFactory.java:144–148`, database path at line 170: `context.getDatabasePath(String.valueOf(userId))`*

What This Means

If an attacker obtains the SQLCipher database file — through a device backup, through root access to the phone, or through physical access to the device’s storage — they can open it with the numeric user ID and extract every E2EE key that device has ever used. This includes historical keys, enabling decryption of past recordings.

The database contains two tables:
- `account_group_client_sqlite_file` — Account-level encryption keys
- `camera_group_client_sqlite_file` — Per-camera encryption keys

The entire E2EE system’s security, for local key storage, reduces to knowing a public number.

Part 4: SDP Negotiation — Where Encryption Is Optional

How WebRTC Encryption Is Negotiated

When a Ring device starts a live view session, it performs a WebRTC handshake through a WebSocket signaling server. The key exchange happens via SDP (Session Description Protocol), with Ring-specific extensions:

a=e2ee-content-encryption-mode:3 ← Encryption mode (1=Cerulean, 2=Greco E2E, 3=Greco Baseline)
a=e2ee-manifest-id:xxx ← Key manifest identifier
a=e2ee-receive-stream-id:8 ← Stream identifier

The device includes these attributes in its SDP offer. The signaling server’s SDP answer determines whether encryption actually happens.

The Missing Attribute = No Encryption

The critical code path is in `BaseV4LiveSession.java` at line 448:

String mode = sdpParser.parse(“e2ee-content-encryption-mode”);
if (mode == null) {
atomicBoolean.set(false); // E2EE DISABLED — stream proceeds unencrypted
}

When the SDP answer from the signaling server **omits** the `e2ee-content-encryption-mode` attribute, the app marks E2EE as disabled and proceeds with the video stream. The stream is not aborted. No warning is displayed to the user.

Furthermore, `PeerConnectionManager` creates the WebRTC `PeerConnection` without setting `requireFrameEncryption` to `true` in the `RTCConfiguration`:

// PeerConnectionManager$create$1.java
PeerConnection.RTCConfiguration rTCConfiguration = new PeerConnection.RTCConfiguration(iceServers);
rTCConfiguration.sdpSemantics = UNIFIED_PLAN;
rTCConfiguration.rtcpMuxPolicy = REQUIRE;
// requireFrameEncryption is NEVER set to true

*Source: `BaseV4LiveSession.java:448–452`, `PeerConnectionManager$create$1.java`*

Three Paths to No Encryption via SDP

| SDP Attribute Value | E2EE Mode | Result |
| — -| — -| — -|
| **(absent)** | **None** | Stream proceeds completely unencrypted |
| `1` | Cerulean (legacy) | Weaker encryption, deprecated path |
| `2` | Greco Full E2E | Full MLS-based encryption |
| `3` | Greco Baseline | Reduced encryption with downgrade support |

An attacker who controls the signaling server simply omits the E2EE attributes from the SDP answer. The device happily streams plaintext video.

Part 5: The SFrame Encryption Layer — Weaker Than It Appears

No Key Derivation — Raw Keys Used Directly

The SFrame encryption library (`libsecureframe.so`, 153KB) implements per-frame video encryption using AES-128-GCM. Our complete disassembly of `sframe_set_key()` and `sframe_crypto_setup()` reveals a critical design choice:

**There is zero key derivation.** The raw Content Encryption Key (CEK) is `memcpy`’d directly into the crypto context:

- `sframe_set_key()` requires exactly 32 bytes
- The first 16 bytes are used as the AES-128 key (despite accepting 32 bytes)
- The second 16 bytes are used for a separate authentication key schedule
- No HKDF, no PBKDF2, no hash function in the key path
- GCM IV is a 12-byte sequential counter starting at zero

If you capture the CEK during key exchange — via any of the methods described in this report — every frame encrypted with that key can be immediately decrypted with a single AES-GCM operation.

*Source: Complete disassembly of `sframe_set_key()` and `sframe_crypto_setup()` in `libsecureframe.so`*

CBC Mode Has Zero Authentication

The library supports two encryption modes:
- **Mode 0 (CBC)**: `CRYPTO_cbc128_encrypt` — **no authentication whatsoever**
- **Mode 1 (GCM)**: `CRYPTO_gcm128_encrypt` — authenticated encryption

In CBC mode, `sframe_crypto_auth()` returns success immediately without performing any check. If an attacker forces a scheme downgrade from GRCO (GCM) to CBCS (CBC) — by controlling the signaling and specifying mode 1 (Cerulean) instead of mode 2/3 (Greco) — all frame authentication is lost. Frames can be modified, injected, or replayed without detection.

Truncated Authentication Tags

Even in GCM mode, the authentication tag is truncated to 8 bytes (64 bits) instead of the full 16 bytes (128 bits). This reduces tag forgery difficulty from 2¹²⁸ to 2⁶⁴ — a massive reduction that, while not trivially exploitable today, represents a significant departure from cryptographic best practices.

Disableable Replay Detection

`sframe_set_property()` with property ID 1 sets a flag at `ctx+0x115` to disable replay checking. A separate flag at `ctx+0x116` disables authentication checks entirely. Both are togglable at runtime through the library’s public API.

Sequential Nonces — Catastrophic on Reboot

The frame counter at `ctx+0x1c` is sequential from zero, incremented per frame. If the device reboots without rotating its key, the same nonce is reused with the same key — a catastrophic failure for GCM mode that enables full key recovery through XOR of ciphertext pairs.

Codec-Aware Encryption — Metadata Leakage

SFrame doesn’t encrypt entire frames blindly. It’s codec-aware, selectively encrypting payload while preserving structure:

sframe_codec_h264_find_nal_unit_type() — H.264 NAL boundary detection
sframe_codec_h265_find_nal_unit_type() — H.265 NAL boundary detection
sframe_codec_h26x_is_idr() — IDR (keyframe) detection
sframe_codec_h26x_sanitize() — NAL unit sanitization
sframe_codec_h26x_epb_add/remove() — Emulation prevention bytes
sframe_codec_opus_pad() — Opus audio alignment padding
sframe_codec_aac_pad() — AAC audio alignment padding
sframe_codec_jpeg_fixup_h264() — JPEG/H.264 conversion

Unencrypted NAL unit headers leak: frame type (I-frame vs P-frame vs B-frame), frame size, timing information, and codec parameters (SPS/PPS/VPS). This enables traffic analysis even with encryption active — an attacker can determine when someone appears at the door, how long they stay, and whether the camera is recording continuously or event-triggered.

Encrypted File Formats

Ring uses three encrypted container formats for stored recordings:

**FSV-E (Ring FSV Encrypted)** — Ring’s proprietary format with types v1, v2, v3, lpd_v1, lpd_v2. Decrypted via `VideoDecryptor.nativeDecryptFsv(input, output, type, base64Cek)`. Uses SRTP-level decryption through `libsrtp2-fsv.so`. The `SetKeys` function takes THREE key strings (video SRTP, audio SRTP, RTCP). `SetProfileCipherAuth` configures the cipher/auth combination — including null cipher + null auth.

**MP4E (Encrypted MP4)** — Container with `magf` (0x6667616D) magic bytes. Uses `libsecureframe.so` for per-frame SFrame decryption. `Mp4eConverter::SetKeyCallback` takes a function pointer for on-demand key lookup. The library tracks auth failures, key failures, replay failures, and footer failures — each a potential side channel.

**Plaintext Snapshots** — Critically, on-demand snapshots fetched via `GET https://app-snaps.ring.com/snapshots/next/{id}?extras=force` are **unencrypted JPEG**. The `force` parameter triggers on-demand capture and returns the image without any E2EE. Similarly, `GET /clients_api/dings/{id}/recording?disable_redirect=true` serves already-decrypted MP4 recordings — the server decrypts before serving.

The corruption recovery path in FSV-E is also notable: `”FSV packet size too big, possible corruption! Attempting to seek to next audio packet!”` — the parser recovers from corruption by seeking forward. Crafted files could potentially exploit this to skip past encrypted regions.

— -

Part 6: The WebRTC Layer — Four Ways to Skip Decryption

Silent Decryption Bypass Paths

Ring’s custom WebRTC library (`libjingle_peerconnection_so.so`, 11MB) contains four distinct code paths that silently skip frame decryption, passing encrypted (or unencrypted) frames through the pipeline:

| Bypass Path | Trigger Condition | Log Message |
| — — — — — — -| — — — — — — — — — -| — — — — — — -|
| Unsupported scheme | SFrame scheme not recognized | `”Skipping Due to Unsupported Encryption Scheme”` |
| Configuration error | Config error flag set | `”Skipping Due to configuration error”` |
| Missing key callback | Key provider not registered | `”Skipping Due to no _sframeNotifyNewKeyCallback”` |
| Bad initialization | SFrame context not initialized | `”Skipping frame due to bad init”` |

Each of these paths logs a debug message and silently drops the decryption step. In a scenario where the signaling server doesn’t properly set up the encryption context — for instance, by omitting E2EE attributes from the SDP answer — any of these conditions can be triggered, and frames pass through without encryption or decryption.

The Explicit Disable Path

Additionally, the library contains an explicit unencrypted transport path:

```
“Creating UnencryptedRtpTransport, becayse encryption is disabled.”
```

The typo “becayse” (instead of “because”) confirms this is Ring’s custom code, not upstream WebRTC. This path creates an entirely unencrypted RTP transport when a `disableEncryption` flag is set. While Ring’s production code doesn’t set this flag through normal flow, it’s trivially accessible via Frida.

The Encryption Kill Switch at Object Offset 0x68

At the binary level, `RingFrameEncryptor` has a 32-bit flag at offset `0x68` in its native object:

- `-1` (0xFFFFFFFF): Uninitialized — frames are dropped
- `0`: Encryption **disabled** — frames pass through in plaintext
- `1`: Encryption **enabled** — SFrame encryption applied

The `nativeSetEncryptionEnabled` function at address `0xa77980` is remarkably simple — just 20 bytes of ARM64:

```asm
tst w3, 0xff ; test boolean parameter
cset w1, ne ; w1 = (param != 0) ? 1 : 0
str w8, [x0, 0x68] ; store at encryption_enabled offset
```

A single Frida one-liner disables all SFrame encryption:

Interceptor.attach(Module.findExportByName(“libjingle_peerconnection_so.so”,
“Java_org_webrtc_ring_RingFrameEncryptor_nativeSetEncryptionEnabled”),
{ onEnter: function(args) { args[3] = ptr(0); } });

*Source: Disassembly of `libjingle_peerconnection_so.so` at addresses `0xa77980` (setter) and `0xa78198` (transform)*

Injectable WebRTC Field Trials

The WebRTC library accepts field trial strings via `Java_org_webrtc_PeerConnectionFactory_nativeInitializeFieldTrials`. Relevant trials include:

- `WebRTC-ForceDtls13 Enabled/Disabled/Only` — force DTLS version
- `WebRTC-EnableDtlsPqc` — post-quantum crypto toggle
- `WebRTC-SrtpRemoveReceiveStream` — SRTP stream control
- `WebRTC-IceHandshakeDtls` — ICE/DTLS handshake behavior

These are injectable at runtime and can alter encryption, DTLS, and SRTP behavior at the transport level.

— -

Part 7: The Greco MLS Protocol — Server-Controlled E2EE

E2EE Is a Toggle, Not a Guarantee

The Greco core library (`libamzn_greco_core.so`, 17MB, written in Rust and exposed via UniFFI) contains explicit routines to control E2EE state:

```
uniffi_amzn_greco_core_checksum_constructor_sete2eeroutine_new
uniffi_amzn_greco_core_checksum_constructor_setencryptionparametersroutine_new
```

Source paths embedded in the binary confirm this:
- `amzn-greco-core/src/routines/set_e2ee.rs`
- `amzn-greco-core/src/routines/set_encryption_parameters.rs`

Internal status flags include:
- `a-sign-disabled` — signing can be disabled
- `es-encrypt-disabled` — encryption can be disabled
- `cs15-pad-disabled` — padding can be disabled

E2EE has four states: `E2eeEnabled`, `E2eeDisabled`, `E2eeEnablementRequested`, and `E2eeDisablementRequested`. The system also supports “baseline” mode — a weaker alternative to full E2EE with explicit `BaselineDecreaseStrategy` support, meaning baseline encryption can be further weakened.

Cloud Member Presence Disables E2EE

A critical validation rule found in the MLS group code:

```
“Cannot have cloud member when in e2ee mode”
```

When a “cloud member” is present in the encryption group, E2EE is forced off. Our cloud emulator can ensure a cloud member is always present in every encryption group, keeping E2EE permanently disabled.

The Greco API — We Control Everything

The Greco MLS API consists of 15 endpoints, all of which our cloud emulator implements:

| Endpoint | Attack Capability |
| — — — — — | — — — — — — — — — -|
| `POST v1/deleteGroup` | Delete the E2EE group entirely — disables encryption for all members |
| `POST v1/replaceRegisteredGroup` | Replace group registration with our controlled keys |
| `POST v1/getGroupContext` | Return controlled encryption context |
| `POST v1/publishKeyPackage` | Inject our key package into the group |
| `POST v1/publishGroupChanges` | Modify group membership |
| `POST v1/deleteWelcomes` | Block new member E2EE enrollment |
| `POST v1/updateBackup` | Upload modified E2EE backups (bypasses checksum — sends `x-amzn-content-checksum-none:none` header) |
| `POST v1/getBackup` | Serve controlled backup data |

The MLS External Commit mechanism (`getExternalCommitInfo`) is particularly notable — it allows joining an encryption group using only the group’s public `ExternalPub` extension and a valid credential, without an invitation from existing members.

Part 8: Production Debug Backdoors

Content Encryption Key Override (SharedPreferences Injection)

Ring shipped two separate CEK override mechanisms in the production APK:

**Mechanism 1 — Cerulean Path** (`SimpleContentEncryptionKeyProvider.java:57`):

if (localSettings.getBoolean(“content_enc_key_override”, false)) {
key = localSettings.getString(“content_enc_key”, null);
}

Setting `content_enc_key_override=true` and `content_enc_key=<base64_key>` in SharedPreferences injects an arbitrary encryption key that overrides the real key for all video decryption.

**Mechanism 2 — Greco Path** (`ContentEncryptionKeysOverrides.java`, checked at `EncryptionCekManagerImpl.java:57–62`):

The override list is checked **first** — before attempting real Greco SDK key retrieval:

Result overrideResult = this.overrides.getKey(deviceId, keyId);
if (overrideResult != null) {
return overrideResult.getValue(); // Returns override key, bypasses SDK entirely
}

Override keys are stored as JSON: `[{“device_id”: 12345, “key_id”: 1, “key_data”: “base64_key”}]`

On a rooted device, injecting a known key into these SharedPreferences causes all video streams to be decrypted (or encrypted) with an attacker-controlled key. No code modification required.

Test Infrastructure in Production

The production binary contains:

- **MockGrecoStorage** — A mock storage implementation from the `mockall` testing framework, compiled into the production binary alongside the real implementation
- **Test RNG** — OpenSSL’s `test_rng.c` (with `test_rng_new`, `test_rng_generate`, `test_rng_get_seed`) is compiled into `libamzn_greco_core.so`. If activated, cryptographic randomness becomes predictable
- **GroupInvite bypass** — A documented testing shortcut: *”This type should only be used for testing as a quick and dirty way to add a member to a group bypassing app KP validation logic.”*

Root Detection: 5KB of Security Theater

Ring’s root detection is handled by `libbugsnag-root-detection.so` — a 5KB library that checks for the existence of well-known root files:

- `/system/app/Superuser`, `/system/app/Superuser.apk`
- `/su/bin`, `/system/bin/su`, `/system/xbin/su`, `/system/xbin/daemonsu`
- `/sbin`, `/system/app/SuperSU`, `/system/app/SuperSU.apk`

Single JNI entry: `Java_com_bugsnag_android_RootDetector_performNativeRootChecks`

This is trivially bypassed by Magisk Hide, Zygisk DenyList, or a single Frida hook that returns false.

Part 9: Network Security — Cleartext and No Pinning

All HTTP Clients Accept Cleartext

`NetworkingConstants.java` defines the default `ConnectionSpec` list used by every OkHttpClient in the app. This list includes `ConnectionSpec.CLEARTEXT` alongside `MODERN_TLS`. The base `OkHttpClient.Builder` factory `NetworkingUtils.m14772a()` applies this to all clients:

- All Retrofit API builders (Ring API, Clients API, Solutions API)
- WebRTC signaling HTTP client
- Player and data download clients

Every Ring HTTP client will silently downgrade to plaintext HTTP if the server responds without TLS.

Zero Certificate Pinning on Ring API

We audited every `OkHttpClient.Builder` instantiation in the decompiled source. The main Ring API clients — covering authentication, device management, streaming, and event handling — do not configure:

- `certificatePinner()` — no certificate pins
- `sslSocketFactory()` — default system SSL
- `hostnameVerifier()` — default OkHttp verifier (except one case below)

The **only** exception is Amazon Cerulean (the key management service), which pins 5 Amazon Root CA SHA256 public keys. This protects key distribution but not any other API surface.

Active Accept-All Hostname Verifier in Production

While Ring’s own `AllHostnameVerifier` class (which returns `true` for all hostnames) is dead code — defined but never referenced in the DI graph — an identical accept-all verifier from Amazon’s Sidewalk SDK **is actively used**:

// OkHttpLibraryGlideModule.java, line 30
builder.hostnameVerifier(new C23136eG0());

`C23136eG0` (obfuscated from `com.amazon.sidewalk.obfuscated.eG0`) is an accept-all hostname verifier wired into Glide’s OkHttpClient for image loading. This means snapshot thumbnails and Neighbors Hub images are fetched without TLS hostname verification.

Part 10: The SRTP Layer — Null Ciphers Included

Available SRTP Crypto Policies

Ring’s custom SRTP library (`libsrtp2-fsv.so`, 2.5MB) ships with null cipher and null authentication implementations:

```
srtp_crypto_policy_set_null_cipher_hmac_null — NO encryption, NO authentication
srtp_crypto_policy_set_null_cipher_hmac_sha1_80 — NO encryption, authentication only
srtp_crypto_policy_set_aes_cm_128_null_auth — AES encryption, NO authentication
srtp_crypto_policy_set_aes_cm_192_null_auth — AES-192, NO authentication
srtp_crypto_policy_set_aes_cm_256_null_auth — AES-256, NO authentication
```

Additionally, `srtp_replace_cipher_type` and `srtp_replace_auth_type` can swap cipher and authentication algorithms at runtime for an active SRTP session. Through Frida, an attacker with code execution in the app process can force SRTP packets to be sent completely unprotected.

— -

Part 11: The Device Setup Protocol — Encryption Enrollment We Control

HTTP Server During Factory Reset

When a Ring doorbell is factory reset, it creates a WiFi access point (named “Ring-XXXX”) and serves an HTTP server at `192.168.240.1`. This server exposes endpoints for device configuration, including E2EE enrollment:

| Endpoint | Purpose |
| — — — — — | — — — — -|
| `/ring/system/config/network` | WiFi configuration |
| `/ring/e2ee/enrollment` | E2EE key enrollment |
| `/ring/e2ee/certificate` | Certificate provisioning |
| `/ring/e2ee/status` | E2EE status check |

Three security tiers exist:
1. **INSECURE** — Plain HTTP, no TLS, no mutual authentication
2. **SECURE** — HTTPS with server certificate only
3. **SECURE_V2** — DHAv2 hardware certificate mutual authentication

Older and cheaper Ring models may use INSECURE or SECURE tiers — meaning we can connect to the setup server, control E2EE enrollment, and configure the device to join our network (with DNS redirect) without any authentication.

Get harmless-teddy’s stories in your inbox

Join Medium for free to get updates from this writer.

Firmware Over BLE — MD5 Only

Ring transfers firmware updates via Amazon Sidewalk BLE (not WiFi). The firmware manifest format reveals:

```json
{
“firmware_version”: “x.x.x”,
“file_list”: [{
“target_platform”: “…”,
“file_type”: “…”,
“filename”: “…”,
“md5_hash”: “…”,
“index”: 0
}]
}
```

At the mobile app level, **only MD5 checksums** are visible — no cryptographic signatures. MD5 collisions are trivially computable. Device-side signature verification status is unknown.

— -

Part 12: Pre-Production Environments Left in Production

Cerulean Mock and Staging Endpoints

`libceruleancore.so` contains hardcoded URLs for four non-production environments:

**Mock:** `https://mock/v1/participant/me` with `dummy_ea_id`

**Alpha:** `https://us-east-1.alpha.{service}.cerulean.devices.amazon.dev`
**Beta:** `https://us-east-1.beta.{service}.cerulean.devices.amazon.dev`
**Gamma:** `https://us-east-1.gamma.{service}.cerulean.devices.amazon.dev`

Services include `authz`, `forwarder`, `identity`, `key-mgmt` (non-production only), `manifest-mgmt`, and `token-auth`. The stage and region are runtime-configurable through `ctx->config.endpoint.stage` and `ctx->config.endpoint.region`.

Notably, `key-mgmt` only exists in non-production environments, suggesting production uses a different key management path that may be more difficult to manipulate — but the alpha/beta/gamma paths are still present in the binary and potentially reachable.

Additional Crypto Weaknesses

**PBKDF2 with 1000 iterations and a fixed salt** (`FrcCookiesManager.java:225`): Amazon’s identity FRC (First Run Cookies) uses `PBEKeySpec` with only 1,000 PBKDF2 iterations (NIST recommends 10,000 minimum; modern guidance suggests 600,000+) and the literal string `”AES/CBC/PKCS7Padding”` as a fixed, non-random salt.

**`java.util.Random` for cryptographic purposes** (`ClientRequestContext.java:19`): The Cerulean encryption service generates request context IDs using `java.util.Random` — a predictable linear congruential generator — instead of `SecureRandom`. The iLoq lock SDK has the same flaw for NFC challenge-response nonces.

**Bleichenbacher-vulnerable RSA** (`C10436v4.java:94`): The iLoq lock SDK (integrated into Ring for smart lock support) uses `RSA/ECB/PKCS1Padding`, which is vulnerable to Bleichenbacher’s adaptive chosen-ciphertext attack.

**Static BLE key derivation salt** (`AbstractC7200w8.java:43`): Amazon Sidewalk BLE uses a fixed, hardcoded string `”Mobile_KDF_Device_Mobile_GCM_COUNTER”` concatenated with four null bytes as the salt for AES-CMAC key derivation. All devices share the same salt.

— -

Part 13: The Hardware Path — SigmaStar SoC and SPI Flash

Confirmed Hardware

TechInsights teardown data confirms Ring doorbells use a SigmaStar SoC with SigmaStar memory, paired with an 8MB Winbond SPI NOR flash chip.

This is significant because:

1. **SigmaStar SoCs are supported by OpenIPC** — an open-source IP camera firmware project. If the exact SoC variant matches (SSC335, SSC337, SSC337DE, or SSC338Q), it’s possible to flash entirely open-source firmware with native RTSP/ONVIF streaming and zero cloud dependency.

2. **SPI NOR flash is trivially readable and writable** with a $3 CH341A USB programmer and a $5 SOIC-8 test clip — no desoldering required.

3. **UART debug pads are exposed on the PCB**, providing shell access at 115200 baud/3.3V for boot log capture and potential root shell access.

### Minimum Viable Firmware Patch

For the SPI flash approach, only four changes are needed:

1. **`/etc/hosts`** — Add entries mapping Ring domains to local server IP
2. **CA certificate** — Replace with self-signed CA for TLS termination
3. **OTA disable** — Neuter the firmware update daemon
4. **Telemetry disable** — Block Minerva/analytics endpoints

All original hardware drivers — camera ISP, PIR motion sensor, IR LEDs, audio codec, doorbell button — remain untouched and fully functional.

— -

Part 14: Complete Attack Chains

### Chain A: Cloud Emulator + Feature Flag Kill Switch (Software Only — Primary)

```
1. DNS redirect: api.ring.com, oauth.ring.com, *.a2z.com → local emulator
2. Emulator serves POST /flags/v2/flags with go_ee_v2_enabled=false
3. App disables E2EE client-side (cached, persists across restarts)
4. Emulator handles WebSocket signaling → SDP answer omits E2EE attributes
5. App negotiates standard DTLS-SRTP (no SFrame layer)
6. Emulator provides its own DTLS certificate → holds private key → knows SRTP keys
7. Result: plaintext video/audio accessible at local emulator
```

**Cost:** $0 (software only)
**Requirements:** Network-level DNS control (router, Pi-hole, or DHCP)

Chain B: Greco API Takeover

```
1. DNS redirect → local emulator
2. Emulator implements all 15 Greco v1/ endpoints
3. Device calls getGroupContext → emulator returns controlled/empty context
4. Device calls registerGroup → emulator acknowledges with controlled group
5. Alternatively: deleteGroup → E2EE disabled for entire group
6. Alternatively: replaceRegisteredGroup → inject emulator-controlled keys
7. Result: E2EE keys known to emulator, all content decryptable
```

Chain C: SQLCipher Key Database Extraction (Physical Access)

```
1. Obtain SQLCipher database from device (root/backup/physical access)
2. User ID is public — visible in any API response
3. Open database: password = userId.getBytes(“UTF-8”)
4. Dump all MLS private keys, account group keys, camera group keys
5. Result: decrypt all past and future E2EE content for that account
```

Chain D: Frida Runtime Hooks (Rooted Phone)

```
Multiple independent bypass points:
1. Hook nativeSetEncryptionEnabled → force false → no SFrame encryption
2. Write 0 to [encryptor_object + 0x68] → direct memory kill switch
3. Hook sframe_set_property → disable auth and replay checking
4. Call SetE2EERoutine via UniFFI → native-level E2EE toggle off
5. Inject CEK override into SharedPreferences → known decryption key
6. Hook sframe_set_key → capture all CEKs as they’re loaded
```

Chain E: SPI Flash Firmware Patch (Hardware — 0 Component Changes)

```
1. Open doorbell housing (Torx screws, ~5 minutes)
2. Connect CH341A + SOIC-8 clip to SPI flash (~2 minutes)
3. Read firmware: flashrom -p ch341a_spi -r ring-dump.bin
4. Extract filesystem: binwalk -e ring-dump.bin
5. Patch /etc/hosts, replace CA cert, disable OTA + telemetry
6. Write patched firmware: flashrom -p ch341a_spi -w ring-patched.bin
7. Reassemble doorbell (~5 minutes)
8. Start local cloud emulator (docker-compose up)
9. Connect doorbell to WiFi → auto-registers with local server
Total time: ~47 minutes. Total tool cost: ~$8–16.
```

Chain F: Setup Mode Hijack (0 Component Changes)

```
1. Factory reset device → Ring-XXXX WiFi AP appears
2. Connect to AP → access http://192.168.240.1
3. Use /ring/e2ee/enrollment to control encryption configuration
4. Configure WiFi to connect to network with DNS redirect
5. Device initializes with attacker-controlled crypto config
6. All subsequent video streams use emulator-controlled encryption
```

— -

Part 15: Leaked Internal Infrastructure

Our strings analysis of the 30 native libraries revealed Amazon’s internal build infrastructure:

- **Jenkins CI**: `/home/jenkins/workspace/iOS_V2_greco-sdk_develop/` (Greco SDK built on iOS pipeline for Android), `/home/jenkins/workspace/gin_fsv-converter-android_master/`, `/home/jenkins/workspace/Android_custom_builds/ring-android_deploy-release/`
- **Perforce depot**: `/local/p4clients/pkgbuild-const/workspace/src/CeruleanManagementAndroidSDK/`
- **Internal Confluence**: `https://confluence.atl.ring.com/pages/viewpage.action?pageId=3441611122&spaceKey=GRECO`
- **Internal code browser**: `https://code.amazon.com/packages/RingMLSDeliveryServiceModel/`
- **AI Labs endpoint**: `https://app.ai.ring.amazon.dev/`
- **Firebase credentials**: `apiKey: ‘AIzaSyCv-hdFBmmdBBJadNy-TFwB-xN_H5m3Bk8’`, `projectId: ‘ring-17770’`

— -

Part 16: The Local Cloud Emulator

Based on our findings, we built a fully functional Ring cloud emulator — a local server that implements 59 API routes covering Ring’s entire cloud surface:

- **OAuth2 authentication** (accepts any credentials)
- **Feature flags** (`POST /flags/v2/flags` returns `go_ee_v2_enabled=false`)
- **Device settings** (returns `encryption_enabled=false`)
- **WebSocket signaling** (full Ring protocol with SDP E2EE stripping)
- **WebRTC session** (real SDP answer generation via aiortc)
- **All 15 Greco MLS endpoints** (controlled key distribution)
- **Device RPC** (firmware command interception)
- **Sheila internal API** (local storage mode)
- **Remote camera commands** and light control
- **Firmware check** (always UP_TO_DATE — blocks OTA)
- **MQTT event bridge** (motion/ding events to Home Assistant)
- **Recording storage** (incoming video saved to MP4)
- **Telemetry sinks** (Minerva and analytics silently discarded)
- **Catch-all logger** (captures unknown endpoints for protocol discovery)

The emulator is backed by a device simulator that exercises all 10 test steps end-to-end without real hardware, verifying: OAuth token issuance, session creation, device listing, firmware check, signaling ticket, WebSocket SDP exchange (with E2EE strip verification), feature flag delivery (E2EE kill switch), device settings (encryption toggle), Greco group deletion, and event submission.

Part 17: The Surveillance Machine Behind the Doorbell

Everything above deals with how Ring’s encryption fails. This section deals with what happens when it works exactly as Amazon designed it.

Law Enforcement Gets First-Class Infrastructure

Ring doesn’t bolt on police access as an afterthought. It’s a standalone subdomain with its own dedicated API:

- **`agency.ring.com`** — A complete law enforcement API with its own feed type (`FeedType.AGENCY`), case management (`EventCaseInfoCreateInput` with `agency_name`, `case_number`, `is_crime`, `officer_name`), and the `ContactAgencyActivity` that facilitates direct communication between users and police.

- **Axon Integration** — At `PublicAssistance.java:119`, the `canShareInfo` flag is set to `TRUE` only when `publisherData.getPublisherId()` equals `”axon”`. Axon is the police body-cam company (formerly TASER International). Ring has hardcoded a special trust relationship with a police hardware vendor.

- **Two Video Sharing Pipelines for LE** — DVR (Direct Video Request) for clip-by-clip requests, and DUOS for emergency bulk snapshot sharing. The DUOS system uses `PUT duos/v1/critical_events/sharing` to push video to law enforcement during critical events. Authorization role `777` grants consent; device opt-out role is `666`.

- **Identity Verification for Neighbors** — `IdentityVerificationStatus` GraphQL types and `CreateIdentityVerificationInquiryMutation` allow verified identity confirmation, creating a chain from anonymous neighbor posts to verified real-world identity.

*Sources: `MainEnvironment.java:16`, `PublicAssistance.java:119`, `DuosCriticalEventApi.java`, `AuthorizationApi.java`*

Your Face Is in Amazon’s Cloud

The FreezeTag SDK (135 classes) is a complete cloud-processed facial recognition system:

| Endpoint | What It Does |
| — — — — — | — — — — — — -|
| `GET v1/identities/all` | Fetch all known face identities |
| `POST v1/identity/enrollment-images/create` | Upload face enrollment images |
| `POST v1/identity/merge` | Merge two face identities |
| `PATCH v1/identity/{id}` | Update identity metadata |
| `DELETE v1/identity/{id}` | Delete a face identity |

Face enrollment images are uploaded to Ring’s servers for cloud-side processing. `IdentityEvent` links face recognition to specific events via `eventId`, `deviceId`, `startTime`, and `thumbnailUrl`. This is not on-device face detection — it’s cloud-processed facial recognition with persistent identity profiles.

*Source: `FreezeTagSdkApi.java:21–57`, `IdentityEvent.java`*

The Camera Is Always Recording

Ring doorbells don’t start recording when motion is detected. They’re already recording:

- **Pre-roll** — The camera continuously records to a ring buffer. When a motion event triggers, the recording includes footage from *before* the event. This means the camera’s sensor and encoder are active 24/7.
- **24x7 Mode** — `Recording24x7Mode` enum confirms continuous recording capability with `RECORDING_24x7` and `DISABLED` states.
- **Camera LED behavior is firmware-only** — Our `strings` analysis of all 31 `.so` files found zero LED control functions. The LED state during pre-roll recording is determined entirely by firmware, not the app.

The privacy question: is the LED on during pre-roll? Only the firmware knows. The app cannot control it, and Amazon’s documentation doesn’t address it.

our Home Is Amazon’s Mesh Network Node

Amazon Sidewalk comprises 5,660 classes in the Ring APK. Your Ring doorbell acts as a bridge for Amazon’s 900MHz/BLE mesh network:

- `BulkDataTransfer.writeBulk(source, blockSize, metadata)` — Transfers data from Sidewalk mesh through your doorbell
- `allowUplinkWithLocation` parameter — Sidewalk data includes GPS coordinates
- Coverage forecasting sends your GPS to Amazon for mesh planning

You’re not just being surveilled. You’re infrastructure for surveilling your neighborhood. Every Ring doorbell is a node in Amazon’s city-wide mesh network.

*Sources: `BulkDataTransfer.java`, `Sidewalk.java` (5,660 classes total)*

What Ring Sends Home — Every Single Event

Every analytics event transmitted by the Ring app includes:

**Per-request (every API call):**
- `Hardware_ID` — permanent device fingerprint (non-resettable, survives factory resets)
- `X-Ring-UserAgent-DeviceHardwareId` — same ID, sent twice for tracking persistence
- `wifi_bssid` — your router’s MAC address (physical location fingerprint)
- `wifi_ssid` — your network name
- `mobile_phone_net_operator` / `mobile_phone_sim_operator` — carrier identity
- `active_network_interfaces` — ALL network interfaces including VPNs

**Per-motion-event:**
- GPS coordinates
- Full video with pre-roll (started before the event)
- Acoustic classification results (`BABY_CRY`, `GLASS_BREAK`, `DOG_BARK`, `CO2_SMOKE_ALARM`, `CAR_ALARM`)
- Facial recognition data (if enrolled)

**Crash data goes to Ring’s own BugSnag** at `events.mobile.crashtracking.prod.ring.com` — not BugSnag’s servers. Crash reports include device ID, locale, CPU arch, root detection status, memory usage, and 25 thread stack traces. 30+ analytics modules track every screen view, button tap, and setting change.

*Sources: `LocalInfoInterceptor.java:58–59`, `DefaultConnectivityInfoCollector.java:60–225`, `App.java:596`*

— -

Part 18: Zero Anti-Tampering — The Unlocked Front Door

For a security product installed on 10+ million doorsteps, Ring’s app has no protection against runtime manipulation.

No Root Detection

We searched every Ring Java class and all 31 ARM64 native libraries:

| Check | Result |
| — — — -| — — — — |
| `strings *.so \| grep -i root` | Zero matches in Ring code |
| `strings *.so \| grep -i frida` | Zero matches |
| `strings *.so \| grep -i xposed` | Zero matches |
| `strings *.so \| grep -i magisk` | Zero matches |
| `strings *.so \| grep -i substrate` | Zero matches |
| `grep -rl “SafetyNet\|PlayIntegrity”` | Not present in Ring code |

BugSnag’s built-in `RootDetector` exists in the APK at `com/bugsnag/android/RootDetector.java`, but it’s used solely for crash report metadata — not to block execution. The app runs perfectly on rooted devices with Frida attached.

Why This Matters

Banking apps detect Frida. Netflix detects root. Snapchat detects Xposed. These are entertainment and financial apps.

Ring — an app that controls your home security cameras, your doorbell, your smart locks, and your alarm system — detects nothing. A device with Frida Server running can:

- Extract every encryption key in real-time (via `sframe_set_key` hook)
- Disable all video encryption (via `nativeSetEncryptionEnabled(false)`)
- Inject arbitrary encryption keys (via SharedPreferences)
- Redirect all API traffic (via OkHttp interceptor hooks)
- Bypass any future security checks (before they execute)

This isn’t negligence at the edges. This is the front door, wide open, for a product whose entire value proposition is security.

— -

Part 19: The 200+ Endpoint API Surface — Everything Amazon Can Do

The Complete API Map

Our systematic mapping of every Retrofit interface, GraphQL operation, and WebSocket channel in the Ring APK produced 200+ endpoints across 25 categories. Here are the ones that matter for privacy:

Physical Access Control

```
POST kris_apis/v1/UnlockForRingUser ← Opens your door
POST kris_apis/v1/Activate ← Activates Amazon Key
GET kris_apis/v1/Avc/AccessCode ← Retrieves access codes
```

`UnlockForRingUser` is a POST endpoint that **remotely unlocks a physical door**. No local confirmation required. No button press on the lock. No biometric check. If an attacker compromises your Ring account — trivial given no client secret, no cert pinning, and password reuse — they don’t just watch your camera. They walk through your front door.

Evidence Destruction

```
DELETE recordings/public/footages/{deviceId}/delete_all ← Erase ALL footage
DELETE evm/v2/dings ← Bulk delete events
DELETE evm/v2/dings/{event_id} ← Delete specific event
DELETE evm/v2/events/time-based-deletion/{source_id} ← Delete by time range
DELETE doorbots/history ← Delete camera history
```

A compromised account can erase all evidence of its own access. Break in, watch the cameras, delete the footage, leave no audit trail. The `delete_all` endpoint doesn’t even require specifying which recordings — it wipes everything for the device.

Access Control Bypass

GET device_info/v3/devices?ignore_rbac ← RBAC bypass parameter
GET location_info/v3/locations?ignore_rbac ← Same bypass for locations

The V3 device and location APIs accept an `ignore_rbac` query parameter. RBAC (Role-Based Access Control) is the system that prevents shared users from accessing owner-level data. The fact that this parameter exists in the client means it was designed to be used. If the server honors it — even partially, even in edge cases — it means a LIMITED or SHARED_USER role can see data restricted to the account OWNER.

In a domestic violence context, this is the difference between “my ex can see the front door camera I shared” and “my ex can see every camera, disable the alarm, and unlock the door.”

Alarm System Root Keys

```
GET rs/masterkey ← Ring Secure alarm master key
```

The Ring Secure alarm system’s master key — the root credential for professional monitoring — is served via API. It’s stored server-side and transmitted over the network. Not derived locally, not hardware-bound, not protected by the user’s passphrase. An API endpoint returns it.

The Firmware Pipeline

GET fms/device-firmware ← Firmware distribution endpoint

A single endpoint handles firmware distribution. Combined with no certificate pinning and no client secret, a MITM attacker can serve modified firmware to Ring devices. Device-side signature verification exists in firmware but is not auditable from the APK.

Device Remote Command Execution

PUT devices/{device_id}/device_rpc ← Generic command execution

This endpoint sends arbitrary JSON commands directly to doorbell firmware. Known commands include `RESET_TO_DFU` (which bricks the device into Device Firmware Update mode), diagnostic commands, and configuration changes. Any account with device access can send these commands. There is no confirmation, no rate limiting visible in the client, and no undo for destructive commands like DFU reset.

The OAuth Problem — Anyone Can Be Ring

```
POST oauth/token
client_id: “ring_official_android”
scope: “client”
// No client_secret. At all.
```

OAuth client secrets exist for one purpose: proving the application requesting credentials is legitimate. Ring has none. Any script, any APK, any curl command can present itself as `ring_official_android`. Combined with no certificate pinning, a MITM attacker can stand up a fake Ring login, collect credentials, and replay them. Ring’s servers cannot tell the difference.

A second client ID `ring_neighborhoods_android` exists for the Neighbors app — also with no secret.

Every third-party Ring integration (`ring-mqtt`, `python-ring-doorbell`, `ring-client-api`) exploits this to access accounts. It’s why they work at all — and why they’re indistinguishable from the real app.

— -

Part 20: The Debug Toolkit Amazon Left in Your Doorbell App

A Full Debug Panel in Every Installed Copy

The production Ring APK contains a complete debug activity suite:

- **`DebugActivity`** — Environment switcher with STAGE/QAE/QAMASTER/LIVE options, theme override, **”Crash Me” button** (intentional crash trigger), media config overrides
- **`TestNotificationActivity`** — Inject arbitrary push notifications: Admin, Pet, Community, FireOS, Search Party types
- **`EnvConfigActivity`** — Edit ALL 30+ environment URLs (auth, API, devices, video, control center, events, etc.)
- **`FeatureFlagFragment`** — Edit feature flags directly from the UI
- **`AQAOverridesHolder`** — Automated QA override framework

The activities call `finish()` early in `onCreate()` — a gate intended to prevent them from displaying. But a single Frida hook bypassing that `finish()` call keeps the activity alive with full functionality. The environment switcher allows redirecting ALL app traffic to staging servers — or to our emulator.

*Sources: `DebugActivity.java`, `TestNotificationActivity.java`, `EnvConfigActivity.java`, `AQAOverridesHolder.java`*

Pre-Production Servers Compiled Into Every APK

| URL | Environment |
| — — -| — — — — — — -|
| `https://gql.neighbors.dev.ring.com/query` | Development |
| `https://gql.neighbors.qa.ring.com/query` | QA |
| `https://clientsapigw.us-east-1.beta.v2.gws.ring.amazon.dev/blueprints/v1/` | Beta |
| `https://mock/v1/participant/me` | Mock (from Cerulean core) |
| `https://us-east-1.alpha.*.cerulean.devices.amazon.dev` | Alpha |
| `https://us-east-1.beta.*.cerulean.devices.amazon.dev` | Beta |
| `https://us-east-1.gamma.*.cerulean.devices.amazon.dev` | Gamma |

The `Environment` data class has 30 configurable URL fields. Switching from LIVE to STAGE redirects the entire app — authentication, device management, video, events, and encryption — to Amazon’s internal test infrastructure.

The Feature Flag Filesystem Cache

All feature flags — including the E2EE kill switches — are persisted to the device filesystem:

```
RingStorage key: “_remote_cache_”
Format: JSON (full FeatureFlagResponse serialized via Gson)
Location: app’s private SharedPreferences directory
```

On a rooted device (or via ADB with debuggable backup), modifying this file changes every feature flag the app reads. Write `”go_ee_v2_enabled”: false` and all encryption features vanish. Write `”grc_debug_enabled_inc”: true` and the Greco debug enrollment UI appears. The cache persists across app restarts until the next successful server refresh — which, if DNS is redirected to our emulator, never happens.

*Source: `FeatureFlagStorageImpl.java:32–100`, `FeatureFlagStorageImpl$onRemoteOverride$2.java:46`*

Dummy Keys — Phantom Encryption Enabled by Default

The feature flag `ad_go_dy_ky_cn_enabled` defaults to `Boolean.TRUE` — the only encryption flag that does. This means the app creates “dummy” encryption keys and uploads them to Google’s BlockStore cloud backup, even when E2EE itself is disabled (`go_ee_v2_enabled` defaults FALSE).

Your phone is generating encryption key material for a system that isn’t turned on, and backing it up to Google. No consent prompt. No UI. No ability to revoke. These are pre-positioned keys — sitting in a cloud backup, waiting for a moment when they might be useful.

*Sources: `EncryptionFeatureFlagsImpl.java:47` (default TRUE), `BlockStoreMetricsManagerImpl.java`, `GrecoDummyKeyCreationEvent.java`*

Part 21: The Ethical Hacker’s Verdict

The Pattern

Every finding in this report shares a common trait: it contradicts a specific claim Ring makes to its users.

| Ring’s Claim | Code Reality |
| — — — — — — -| — — — — — — -|
| “Only you can view your videos” | 8 independent E2EE disable paths, all server-reachable |
| “Encrypted on your device” | `content_enc_key_override` in SharedPreferences bypasses all encryption |
| “End-to-end encrypted” | Server controls feature flags; omitting one SDP attribute removes E2EE |
| “Secure authentication” | OAuth client_id with no secret — any app can impersonate Ring |
| “Your privacy is our priority” | Non-resettable Hardware_ID sent plaintext on every request |
| “Protected by encryption” | Test crypto backend (`InMemoryGrecoStorage`) ships in production |
| “Security updates” | Zero anti-tampering: no root, no Frida, no integrity attestation |
| “Securely stored on device” | AES key derived from public `firstInstallTime` timestamp |

This is not a collection of bugs. This is a design philosophy. Ring was built to be flexible at every layer — and flexibility is the enemy of security guarantees.

The Privacy Cost: What Amazon Knows About 40 Million Homes

Based on our code analysis, every active Ring installation provides Amazon with:

- **A permanent device fingerprint** (Hardware_ID) that survives VPNs, account changes, and factory resets
- **Your physical location** (router BSSID, WiFi SSID, GPS coordinates, Sidewalk mesh topology)
- **Your network identity** (carrier, SIM operator, all network interfaces including VPN connections)
- **A continuous video feed** (pre-roll means the camera is always recording, even between events)
- **Your face** (FreezeTag enrollment images uploaded to cloud for persistent identity profiles)
- **Your home’s audio signature** (acoustic detection classifying baby cries, glass breaks, dog barks, smoke alarms)
- **Your neighborhood mesh** (Sidewalk turns every Ring device into an Amazon sensor node)

This data doesn’t stop flowing when you turn on “E2EE.” The encryption only covers video content in transit. Metadata, telemetry, facial recognition, acoustic classification, and mesh network data continue to flow to Amazon regardless of your encryption settings.

Why “Medium” Severity Is Not Medium Here

Traditional vulnerability scoring (CVSS) rates many of these findings as “medium” because they require local access, network position, or account compromise. But CVSS was designed for enterprise IT — not for a device pointed at your front door:

- **SecureStore install-time key**: In a domestic abuse case, the abuser’s forensic tool recovers Ring OAuth tokens from a phone backup. No password needed. The key is a public fact.

- **Hardware_ID tracking**: For a journalist or activist, this is a permanent correlation key. ISPs, governments, and corporate networks can track all Ring activity to a physical device — forever.

- **`UnlockForRingUser`**: Every authentication weakness in this report now has physical consequences. Account compromise doesn’t mean data breach — it means home invasion.

- **`ignore_rbac`**: If an ex-partner has SHARED_USER access (common post-breakup), this parameter could escalate them to full owner control — alarm disable, footage deletion, door unlock.

- **Zero anti-tampering**: Any app on a rooted phone can hook Ring’s encryption functions. On devices shared between family members, employer-managed devices, or phones with sideloaded apps, the encryption is an illusion.

What This Means for the FULU Bounty

Every vulnerability in this report is also an opportunity. The same weaknesses that make Ring a privacy liability under Amazon’s control become liberation tools when redirected to a local server:

- No cert pinning → our emulator’s TLS is accepted without patching
- No client secret → our emulator is indistinguishable from Ring’s cloud
- Feature flag cache → pre-disable E2EE before the device ever connects
- Zero anti-tampering → Frida scripts work without resistance
- 200+ mapped endpoints → our emulator can serve every request the app expects
- Hardware_ID → identify and manage devices on the local network

The local cloud emulator we built doesn’t just replicate Ring’s functionality — it removes Ring from the equation. No telemetry exfiltration. No facial recognition uploads. No Sidewalk mesh participation. No law enforcement API. No feature flag kill switches controlled by a company that has already paid $5.8 million in FTC settlements for privacy violations.

The Architecture Is the Backdoor

Edward Snowden taught us that mass surveillance doesn’t require secret backdoors when the architecture makes access the default. Ring doesn’t need a hidden wiretap because:

1. **E2EE is a feature flag.** Amazon can disable it for any device, any account, any deployment, at any time, with a single API response. No warrant needed. No user notification. No audit log.

2. **The key database password is public.** Every E2EE key ever generated for your account is “protected” by your numeric user ID. Any forensic tool that obtains the database file gets everything.

3. **Law enforcement has dedicated, first-class API infrastructure.** `agency.ring.com` isn’t an afterthought — it’s a parallel system with its own endpoints, data types, and Axon body-cam integration.

4. **Your doorbell is a sensor node in Amazon’s mesh network.** Sidewalk turns every Ring device into infrastructure for monitoring the surrounding area, routing data from other Amazon devices through your bandwidth.

5. **The app has no self-defense.** No root detection, no Frida detection, no integrity checks. Any actor with device access can extract all keys, disable all encryption, and access all data in real-time.

The only meaningful defense for Ring users is to remove Amazon from the loop entirely. That’s what local operation achieves — and that’s why this research exists.

— -

Conclusion

Ring’s “End-to-End Encryption” is not end-to-end in any meaningful cryptographic sense. It is a server-controlled feature that can be disabled through:

1. **A single API response** (feature flags — `go_ee_v2_enabled=false`)
2. **Omitting an SDP attribute** (signaling control — `e2ee-content-encryption-mode`)
3. **A publicly-known database password** (SQLCipher password = user ID)
4. **Two SharedPreferences keys** (`content_enc_key_override=true` + `content_enc_key`)
5. **A Greco CEK override** (JSON in SharedPreferences with `device_id`, `key_id`, `key_data`)
6. **A filesystem cache write** (`_remote_cache_` in RingStorage)
7. **CFES API calls** (account-level and device-level encryption disable)
8. **Native runtime toggle** (`RingFrameEncryptor.setEncryptionEnabled(false)`)
9. **MLS group manipulation** (Greco `deleteGroup` or `replaceRegisteredGroup`)
10. **Native memory write** (offset 0x68 = 0 in encryptor object)

Ten independently-exploitable paths. Any one of them silently converts “end-to-end encrypted” video into plaintext. No notification to the user. No audit log in the app. No consent required.

Meanwhile, the app:
- Sends a permanent device fingerprint on every request (no opt-out)
- Uploads facial recognition images to Amazon’s cloud
- Participates in Amazon’s Sidewalk mesh network
- Records pre-roll video before motion events (camera always active)
- Classifies household sounds (baby cries, glass breaks, smoke alarms)
- Reports your router MAC, carrier, GPS, and all network interfaces to 30+ analytics modules
- Has a complete law enforcement API with Axon body-cam integration
- Includes a `POST UnlockForRingUser` endpoint that remotely opens doors
- Ships with a full debug activity suite, pre-production server URLs, and test crypto backends
- Implements zero anti-tampering: no root detection, no Frida detection, no integrity attestation

The system was designed for operational flexibility — the ability for Amazon to toggle encryption for individual devices, device groups, or entire deployments. This is fundamentally incompatible with the security guarantee implied by “end-to-end encryption,” where even the service provider cannot access the plaintext.

True E2EE systems — Signal, iMessage with ADP, WhatsApp — have **zero** server-reachable disable paths. Ring has ten. That is not a security system with bugs. That is a surveillance system with a marketing department.

The local operation bounty exists because 40+ million households deserve the security Ring promised them. Our cloud emulator delivers it — by removing the one actor the encryption was never designed to stop: Amazon itself.

— -

Methodology

**Target:** Ring APK v3.104.0 (Android, 317MB XAPK)
**Decompilation:** JADX for 6,292+ Java/Kotlin source files across 65 DEX files
**Native analysis:** Ghidra 12.1 for 30 ARM64 `.so` libraries (total ~35MB)
**String extraction:** Custom scripts across all native binaries
**DI analysis:** Manual Dagger component/module graph tracing
**Crypto audit:** Complete disassembly of SFrame, Greco, Cerulean, and SRTP libraries
**API surface mapping:** Systematic Retrofit interface extraction (200+ endpoints, 25 categories)
**Surveillance audit:** Targeted searches for LE infrastructure, facial recognition, telemetry, mesh networking
**Anti-tamper audit:** Full `strings` analysis of 31 native libraries + Java-level detection code search
**Emulator:** Python/FastAPI implementation of 59 Ring API routes with device simulator
**Automation:** Multi-agent parallel analysis for debug code, undocumented APIs, and crypto weaknesses

— -

Disclosure & Ethics

All research was conducted through static analysis of a publicly available APK and its bundled native libraries. No Ring devices were harmed, compromised, or accessed. No credentials were used, stolen, or intercepted. No Ring servers were probed, scanned, or attacked. No user data was accessed, viewed, or collected.

This research exists because consumers deserve to know what the software on their doorbell actually does — and because the gap between Ring’s marketing claims and Ring’s code is too wide to ignore.

If you own a Ring doorbell, you have the right to know:
- Your “E2EE” has ten disable paths
- Your face is in Amazon’s cloud
- Your camera is always recording
- Your doorbell is an Amazon mesh network node
- Your app has no protection against runtime manipulation
- Your “secure” credentials are encrypted with a public timestamp

--

--

harmless-teddy
harmless-teddy

Written by harmless-teddy

work: ethical hacker hobbies: hypnosis, vibe coding, lockpicking, anime, gaming not a skinwalker but a binwalker