back to blog

Ekan Smart Doorbell: Full Firmware Teardown

Firmware image: blob.bin (16 MB, ARM little-endian, Allwinner V837S)

I picked up an Ekan Smart Video Doorbell and wanted to see what was going on inside. What followed was a pretty straightforward teardown that turned up some genuinely bad stuff: fleet-wide hardcoded secrets, unsigned OTA updates, and a root shell waiting on the serial console with zero authentication required.


Getting the Firmware

I got the firmware off the device via chip-on extraction with a SOP8 clip and a CH341A along with flashrom.

The firmware came off the device as a single 16 MB blob. Running it through binwalk revealed a layered structure typical of Allwinner/Tina Linux devices:

After extraction the directory layout looked like:

/rootfs/             → primary root (SquashFS)
/squashfs-root-1/    → system/app partition (SquashFS)
/jffs2-root/         → writable overlay (JFFS2)

The build string embedded in the firmware pinned the exact version:

tina.wenxiaokang.20240131.063920 / Neptune / 5C1C9C53
Target: v837s-fastboot/generic v2.1

So we’re looking at Tina Linux 4.0 (OpenWrt-derived), running on an Allwinner V837S, an ARM Cortex-A7 paired with a RISC-V E907 co-processor. The main application responsible for all the cloud communication and doorbell logic is a binary called ciapp, sitting at /squashfs-root-1/usr/bin/ciapp (1.5 MB, ARM LE).


Filesystem Recon

Before touching the binary I did a pass over the filesystem for any obvious low-hanging fruit.

The Shadow File

$ cat jffs2-root/shadow
root:91rMiZzGliXHM:...

That 91rMiZzGliXHM hash is DES, the original Unix crypt format from the 1970s. Hashcat mode 1500 cracks these in seconds on any modern GPU. The credentials themselves aren’t the worst part though: the shadow file had permissions 0755. World-readable. Any process on the device can read the root hash without needing elevated privileges.

The /etc/passwd also had a parallel MD5 hash $1$0WlvKUDR$.yqcW5hBKyVJKCHQ4njdB/, which is only marginally better.

WiFi AP Config

; /jffs2-root/ciconfig.ini
[wifi_ap]
ssid=test104
password=11110000

These are the credentials for the device’s own WiFi access point mode, used during initial setup. They’re hardcoded test values that clearly never got swapped out for production. The same values appear verbatim in a binary config blob at config_data.dat. Anyone within WiFi range of a device in AP mode can connect with test104 / 11110000.

Device Identity

Digging into config_data.dat at offset 0x63 revealed the device’s UUID:

EKDB_C2D0B896-7C0F-D1AE-E15C-C4CABA50C403

This will become relevant shortly when we get to the API.

Debug Tools in Production

The production firmware ships with gdb, strace, tcpdump, and several other development/debugging utilities. None of these belong in a shipping product; they significantly expand the post-exploitation toolkit available to anyone who gets a shell, and they suggest the firmware image was never hardened before release.


Reversing ciapp

The main binary is where it gets interesting. ciapp handles all of the device’s cloud communication, OTA updates, P2P video/audio streaming, and local configuration.

First, a quick check of the binary protections:

$ checksec ciapp
Arch:   arm-32-little
RELRO:  Full RELRO
Stack:  No canary found
NX:     NX enabled
PIE:    No PIE (0x10000)

No stack canaries. No PIE. So if you find a memory corruption bug, exploitation is considerably easier: no stack cookie to bypass, no ASLR on the binary base. The imports include system(), popen(), sprintf(), and strcpy(), all of which are processing data received from the network.

Finding the API Dispatcher

The most interesting function in the binary is FUN_00052990. It’s a massive switch statement dispatching on message type across 22 different API endpoint cases. I’m calling it web_server_send_msg based on what it does.

Looking at the signing logic that runs before every outbound API call:

// From FUN_00052990, reconstructed
sprintf(acStack_1c38, "%s/%s/%s%s", udid, fw_version, hw_version,
        "EKDB_ni&Hb&Zt&zz^7qn9");
FUN_000527fc(acStack_1c38, &hmac_output);   // HMAC-SHA256

The string "EKDB_ni&Hb&Zt&zz^7qn9" is hardcoded in the binary. It’s appended to the canonical string for every single API call, across all 22 endpoints, on every device running this firmware. This is the fleet-wide HMAC signing key.

A second hardcoded secret shows up in the P2P session handling (thread_rtp_recv_main):

sprintf(auStack_9a8, "%s%s%s", "eead%Hb27Zf$v#vG", device_udid);

This prefix is used to derive P2P session keys. Same story: one key, every device.

The OTA Update Flow

The OTA case (0x15) in the dispatcher is where the most severe issue lives. The decompiled flow:

// case 0x15 - REQUEST_OTA_URL
sprintf(acStack_838, "/device_ota_latest/%s", udid);
// → HTTPS GET to api.v2.gdxp.com
// → parse JSON: {"resultCode": 0, "content": [{"url": "...", "version": "..."}]}
// → download URL → /tmp/fw_ota.bin
// → write to /dev/mtdblock*
// → reboot
// → (no signature check. no hash check. nothing.)

I searched the entire binary and the full filesystem for any of the strings you’d expect to see if verification were implemented:

sha256     → 0 matches
sha512     → 0 matches
verify     → 0 matches
signature  → 0 matches
public_key → 0 matches
rsa        → 0 matches
ecdsa      → 0 matches

Zero. The device downloads whatever URL the server tells it to and writes it straight to flash.

The embedded TLS implementation is mbedTLS; the handshake entry point is at FUN_000ad274 (mbedtls_ssl_handshake_client_step). I searched for any pinned certificate hashes, subject strings, or public key fingerprints in the filesystem. Also zero. No certificate pinning is implemented, so the HTTPS connection to api.v2.gdxp.com can be intercepted by anyone who can redirect DNS.

Cloud Infrastructure Map

While I was in the binary I mapped out all the cloud endpoints:

Endpoint Protocol Address
API HTTPS api.v2.gdxp.com:443
Push server HTTP http://120.24.87.105:58720
CMD server TCP 120.24.87.105
UDP P2P UDP 120.24.173.159
STUN UDP 47.107.28.145
P2P relay TCP 47.243.228.110
Video/audio storage HTTP http://oss-cn-shenzhen.aliyuncs.com (bucket: sz-aiwit)

The push notification server and the video/audio OSS storage bucket are both plaintext HTTP. Anyone on-path can read notification payloads and intercept video/audio stream data without any decryption.


Physical Access

Before getting to the network findings, two physical access issues worth documenting.

UART Console

The inittab has this entry:

/dev/console::respawn:-/bin/sh

The UART pins on the Allwinner V837S PCB are accessible after opening the case. Connect a USB-serial adapter, hit enter at boot, and you have a root shell. No password prompt, no authentication, nothing. The -/bin/sh means it also respawns if you exit.

USB ADB

The adbd init script runs at priority 80, is OOM-kill protected (OOM_ADJ=-17), and the USB gadget is configured with Android vendor/product IDs (0x18D1 / 0xD002). Plug in a USB cable:

$ adb devices
List of devices attached
C2D0B896  device

$ adb shell
# whoami
root
#

No RSA key pairing. No authorization prompt. Instant root shell over USB.


Vulnerability Summary

CRITICAL - Unsigned OTA Firmware Update (CVSS 9.8)

The device fetches and flashes firmware with no cryptographic verification whatsoever. Combined with the absence of TLS certificate pinning on the HTTPS connection to api.v2.gdxp.com, a network attacker who can redirect DNS has a clear path to persistent root-level code execution on any affected device:

  1. Redirect DNS for api.v2.gdxp.com to an attacker-controlled server
  2. Present any TLS certificate (self-signed is accepted)
  3. Wait for the device’s periodic OTA check
  4. Return a JSON response pointing to a malicious firmware URL
  5. Device downloads, flashes, and reboots into attacker-controlled firmware

No credentials required. No user interaction required. Persistent across reboots and any subsequent legitimate update attempts.

CWE-494 (Download of Code Without Integrity Check), CWE-295 (Improper Certificate Validation)


CRITICAL - Fleet-Wide Hardcoded API Signing Key (CVSS 9.1)

The HMAC key EKDB_ni&Hb&Zt&zz^7qn9 is now extracted from a publicly purchasable device. Anyone with this key and a target device’s UDID can forge valid signed API requests for that device. UDIDs follow a predictable format (EKDB_ + UUID) and are visible in network traffic or configuration files.

With a forged signing key you can:
- Access another user’s cloud-stored video/audio recordings via OSS
- Trigger OTA updates on other users’ devices (combine with the unsigned OTA finding for fleet-wide RCE)
- Send authenticated commands to any device on the platform

The P2P session key prefix eead%Hb27Zf$v#vG is similarly compromised.

CWE-321 (Use of Hard-coded Cryptographic Key)


CRITICAL - Hardcoded Default WiFi AP Credentials (CVSS 8.8)

SSID test104, password 11110000, hardcoded in both ciconfig.ini and the binary config blob. Any device in AP mode (during initial setup or after a factory reset) is reachable over WiFi with these credentials. From the AP network an attacker has local access to the device.

CWE-798 (Use of Hard-coded Credentials)


CRITICAL - Unauthenticated Root Shell on Serial Console (CVSS 6.8)

/dev/console::respawn:-/bin/sh in inittab. UART pins accessible after opening the case. Complete device compromise with a $3 USB-serial adapter.

CWE-306 (Missing Authentication for Critical Function)


HIGH - ADB Enabled by Default via USB (CVSS 6.8)

adbd runs at boot, is protected from OOM kill, requires no key pairing, and gives a root shell over USB immediately. The Android USB vendor/product IDs are a cosmetic disguise; the debug interface is fully functional.

CWE-306 (Missing Authentication for Critical Function)


HIGH - Weak Root Password Hash + World-Readable Shadow File (CVSS 7.5)

DES hash (91rMiZzGliXHM) crackable in seconds with Hashcat mode 1500. The shadow file that contains this hash has permissions 0755, readable by any user or process on the system. These two issues compound each other badly.

CWE-916 (Use of Password Hash with Insufficient Computational Effort), CWE-732 (Incorrect Permission Assignment)


HIGH - No Binary Exploit Mitigations on Key Executables (CVSS 7.0)

All ten main executables lack stack canaries and PIE. ciapp imports system() and popen() and processes untrusted data from the network. The zlib CVEs below involve heap overflows in code that ciapp directly exercises via network-received data, with no stack canaries or ASLR to complicate exploitation.

CWE-693 (Protection Mechanism Failure)


HIGH - Known CVEs in zlib 1.2.8 (CVSS 9.8 individual)

/usr/lib/libz.so.1.2.8 is version 1.2.8, released in 2013. It’s carrying:

CVE CVSS Description
CVE-2022-37434 9.8 Heap overflow in inflate() via crafted gzip header
CVE-2023-45853 9.8 Integer overflow in MiniZip
CVE-2016-9841 9.8 Pointer arithmetic bug in inffast.c
CVE-2016-9843 9.8 crc32_big vulnerability
CVE-2016-9840 8.8 Pointer arithmetic in inftrees.c
CVE-2016-9842 8.8 Left-shift undefined behaviour in inflateMark
CVE-2018-25032 7.5 Memory corruption in deflate

CVE-2022-37434 in particular is reachable through ciapp’s network data processing path. With no stack canaries or PIE on the binary, a heap overflow here is significantly easier to weaponize than it would be on a hardened system.


MEDIUM - Unencrypted HTTP for Push Server and Cloud Storage (CVSS 5.9)

Push notifications flow over http://120.24.87.105:58720. Video and audio clips are stored to and retrieved from http://oss-cn-shenzhen.aliyuncs.com (bucket sz-aiwit), all plaintext. Anyone on-path between the device and the cloud can read door event notifications and access video/audio content without breaking any encryption.

CWE-319 (Cleartext Transmission of Sensitive Information)


MEDIUM - Debug/Development Tools Present in Production Firmware

gdb, strace, tcpdump, and other debug utilities are present in the production firmware image. These are not exploitable on their own, but they materially assist post-exploitation and suggest the build process lacks a production hardening step.


MEDIUM - Hardcoded Device UUID and MAC Address

The device UUID (EKDB_C2D0B896-7C0F-D1AE-E15C-C4CABA50C403) and MAC address are baked into the binary config blob config_data.dat. These identifiers are used as API authentication factors; if they’re static across a device family, or enumerable from the predictable EKDB_ + UUID format, they reduce the authentication strength of the API to just knowledge of the fleet HMAC key (which is already compromised).


The Full Remote Exploit Chain

Combining the unsigned OTA issue and the extracted fleet HMAC key:

  1. Extract firmware from a single purchased unit (or this writeup)
  2. Extract EKDB_ni&Hb&Zt&zz^7qn9 from ciapp
  3. Obtain target device UDID (from network traffic, OSS metadata, or enumerate the predictable format)
  4. Forge a signed OTA response using the fleet key
  5. Redirect DNS for api.v2.gdxp.com (or compromise the domain)
  6. Device fetches and flashes attacker-controlled firmware
  7. Persistent root on target device

This chain requires no physical access to the target device, no target credentials, and works from the internet against any device on the platform.


Remediation

Finding Fix
Unsigned OTA ECDSA P-256 firmware signing; embed verify key in read-only flash; check before flashing
Fleet HMAC key Rotate server-side immediately; provision per-device keys in hardware (eFuse/OTP)
WiFi AP credentials Remove; generate per-device credentials at manufacturing
UART shell Replace /bin/sh with authenticated getty or remove console entry entirely
ADB Remove from production build entirely
Shadow permissions chmod 0640 /etc/shadow; switch to SHA-512 ($6$); unique per-device passwords
Binary mitigations Recompile with -fstack-protector-strong -fpie -pie -Wl,-z,relro,-z,now
zlib Update to >= 1.3.2
HTTP endpoints Enforce HTTPS with valid certificates on all cloud endpoints
Debug tools Remove from production build (add a build system hardening step)
TLS pinning Implement certificate or public key pinning for api.v2.gdxp.com

Closing Thoughts

The most damaging single issue is the combination of the unsigned OTA update and the extracted fleet HMAC key. The key is out and about; anyone can build a tool that sends authenticated OTA triggers for arbitrary UDIDs. The vendor (Aiwit / GDXP Technology) needs to rotate that key on the server side immediately, before anything else. Everything else can be fixed in firmware; the compromised signing key is a live threat to every device on the platform until the server stops accepting it.

The Allwinner Tina Linux SDK defaults (UART console, ADB) are a recurring pattern across embedded devices built on this platform. The /dev/console::respawn:-/bin/sh entry in inittab ships with the SDK and needs to be explicitly removed for any production build, and it rarely is.

All findings were identified through static analysis of the firmware image only. No live devices other than the one purchased for this research were targeted, and no customer data was accessed. Findings were validated on a real device.

--- // --- // --- // ---