I picked up one of these cheap “lightsocket” security cameras off of Amazon. The flash is 8MB, SquashFS over JFFS2, running on an Allwinner Neptune SoC with TinaLinux (an OpenWrt derivative). Pulled the flash, got a dump called flashdump.bin, and ran it through binwalk.
Offset Size Type
------------ ------------ ----------------------------------------
0x0002D1D4 7.4 MB LZ4 compressed data
0x00790000 192.1 KB JFFS2 filesystem
The SquashFS extracts to a fairly standard OpenWrt root. The main application binary is /usr/bin/ciapp, 1.28MB, ARM32 ELF, not stripped, dynamically linked against libcjson.so, libeolog.so, libstdc++.so.6, and libc.so. The not-stripped part is nice, there are real symbol names to work with.
Methodology
Loaded flashdump.bin into Wairz, ran the standard battery of checks: filesystem permissions, init script analysis, hardcoded credential scan, binary protections survey, SBOM generation, and CVE matching. Then loaded ciapp into Ghidra for deeper analysis on the interesting functions. The binary scored 86/100 as a fuzzing target (dangerous sinks: system, popen, strcpy, sprintf, vsprintf; network input via recvfrom, recv, socket).
Findings
Unauthenticated Root Shell on Serial Console (Critical)
First thing I check on any embedded Linux device is /etc/inittab. This one delivers:
/dev/console::respawn:-/bin/sh
No getty, no login, no password prompt. Connect a USB-to-serial adapter to the UART pads, hit enter, you have a root shell. The - prefix on /bin/sh makes it a login shell but there is no authentication step configured. Physical access to the board equals full root access, full stop.
ADB Daemon Enabled at Boot (Critical)
The device ships with the Android Debug Bridge daemon enabled unconditionally. The init script at /etc/init.d/adbd:
START=80
STOP=99
PROG=/bin/adbd
SERIALNUMBER="20080411"
START=80 means it comes up late in the boot sequence, after filesystems are mounted. The serial number is hardcoded as 20080411 and only gets overridden if androidboot.serialno appears in /proc/cmdline, which it does not on this device.
Once the device is connected over USB and ADB is up, adb shell drops you directly into a root shell with no authentication prompt. The ADB daemon is not gated on developer mode, a pairing flow, or any user-visible toggle. Every unit ships this way.
Hardcoded Cloud API HMAC Signing Key (Critical)
This is the interesting one. All cloud API requests from ciapp to api.v2.gdxp.com are signed with an HMAC, but the signing secret is hardcoded in the binary. Searching for it:
offset 0x000eb354:
hex: 3132302e32342e3137332e313539000068747470733a2f2f6170692e76322e676478702e
The string at that offset is EKDB_ni&Hb&Zt&zz^7qn9. It shows up in the decompiled web_server_send_msg function (originally FUN_0005125c) appended to every request before signing:
// Battery level report
sprintf(sign_input, "%s/%d%s", g_device_udid, dev->battery_pct, "EKDB_ni&Hb&Zt&zz^7qn9");
compute_hmac(sign_input, sign_hex);
sprintf(path_buf, "/device_update_battery_level_v2/%s/%d/%s",
g_device_udid, dev->battery_pct, sign_hex);
// OTA version report
sprintf(sign_input, "%s/%s/%s%s",
g_device_udid, cfg->fw_version, cfg->hw_version, "EKDB_ni&Hb&Zt&zz^7qn9");
compute_hmac(sign_input, sign_hex);
sprintf(path_buf, "/device_update_version_v2/%s/%s/%s/%s",
g_device_udid, cfg->fw_version, cfg->hw_version, sign_hex);
The same key is used across all 21 API command types: device registration, OTA requests, WiFi credential changes, file sync metadata, P2P library version reports, everything. Since the key is identical across all firmware instances, anyone who has extracted this firmware (or any copy of it) can construct valid signed requests to the vendor backend impersonating any device by UDID. The vendor presumably validates only the signature, not whether the request is coming from the actual device.
Hardcoded P2P Session Key Derivation Secret (High)
The UDP/RTP media streaming thread (thread_rtp_recv_main) derives session sync tokens using a second hardcoded string. Found at offset 0x000fdb2c:
6e616c5f6e756d203d2025640a0000006565616425486232375a66247623764700000000
^--- "eead%Hb27Zf$v#vG"
The derivation in the decompiled code:
char key_material[128];
sprintf(key_material, "%s%s", "eead%Hb27Zf$v#vG", g_device_udid);
sha_init(&sha_ctx);
sha_update(&sha_ctx, key_material, strlen(key_material));
sha_final(hmac_raw, &sha_ctx);
// hmac_raw encoded as hex, used as "k" field in sync messages:
// {"cmd":"sync","from":3,"udid":"%s","peer":"APP","k":"0%s","rtcp":1}
The k field in those sync messages is what the P2P relay server uses to authorize a peer session. Since the derivation is just hash(hardcoded_prefix + udid) and both components are either static or observable (UDIDs appear in logs and API URLs), an attacker can precompute valid k values for any target device and register themselves as a peer, getting access to the live video and audio stream.
Weak Root Password Hash and World-Readable Shadow (High)
/etc/shadow:
root:91rMiZzGliXHM:1:0:99999:7:::
That is a 13-character DES crypt hash. DES crypt is capped at 8 characters and has a 12-bit salt. hashcat can exhaust the full 8-character printable ASCII space in minutes on consumer hardware. The hash is also in /etc/passwd as a second copy:
root:$1$0WlvKUDR$.yqcW5hBKyVJKCHQ4njdB/:0:0:root:/root:/bin/ash
That one is MD5 crypt ($1$), which is better but still not something you want exposed. Both files are world-readable:
0755 /etc/shadow
0755 /etc/passwd
0755 /etc/group
Shadow is supposed to be readable only by root. These permissions mean any process running on the device, regardless of privilege, can read the root hash and attempt offline cracking.
No Binary Hardening Across the Entire Firmware (High)
Checked protections on all 37 ELF binaries:
Path Type NX RELRO Canary PIE Score
-----------------------+-------+-----+---------+-------+------+------
/usr/bin/ciapp exe Y full N N 2.0/5
/bin/adbd exe Y full N N 2.0/5
/bin/busybox exe Y full N N 2.0/5
/usr/sbin/wpa_cli exe Y none N N 1.0/5
/lib/ramparser exe Y none N N 1.0/5
/usr/bin/rtwpriv exe Y partial N N 1.5/5
Zero stack canaries across the board. 35 of 37 binaries without PIE. No FORTIFY_SOURCE anywhere. ciapp has NX and full RELRO, which is something, but without a canary and without PIE the binary base is fixed at every boot, making ROP trivial if you can trigger a stack overflow. Given that ciapp scores 86/100 as a fuzzing target and has dangerous sinks including strcpy, sprintf, vsprintf, popen, and system in its import table, the lack of mitigations matters.
zlib 1.2.8 with Multiple Critical CVEs (High)
The SBOM scan came back with zlib 1.2.8, which has accumulated a lot of CVEs:
| CVE | CVSS | Description |
|---|---|---|
| CVE-2022-37434 | 9.8 | Heap buffer over-read/overflow in inflate via large gzip extra field |
| CVE-2016-9841 | 9.8 | Improper pointer arithmetic in inffast.c |
| CVE-2018-25032 | 7.5 | Memory corruption in deflate with many distant matches |
| CVE-2016-9842 | 8.8 | Left shift undefined behavior in inflateMark |
| CVE-2016-9840 | 8.8 | Pointer arithmetic in inftrees.c |
ciapp uses zlib for compression of data sent to the cloud. With no stack canaries and no PIE, a heap corruption in zlib triggered via attacker-influenced compressed data (e.g., a crafted OTA payload or a malicious server response) has a straightforward path to reliable exploitation.
Vendor Diagnostic Backdoor (Medium)
Inside net_check_work there is a block that runs when bit 3 of the net_check_flag field is set. The cleaned decompilation:
if (check_flags & NET_CHECK_RD_SERVER) {
int sock_fd = tcp_connect_with_timeout(g_rd_server_url, 0x1e18, 5, 0);
char session_key[48];
derive_session_key(g_device_udid, SESSION_KEY_RD, session_key);
sprintf(work_buf, "{\"%s\":\"%s\", \"%s\":\"%s\", \"%s\":\"0%s\"}\n",
"cmd", "login",
"udid", g_device_udid,
"k", session_key);
send(sock_fd, work_buf, strlen(work_buf), 0);
// bidirectional throughput loop for up to 15 seconds
while ((get_time_ms() - start_ms) < 15000) {
send(sock_fd, work_buf, 0x2800, 0);
recv(sock_fd, work_buf, 0x800, 0);
}
}
The device opens an authenticated TCP session to a vendor-controlled “remote diagnostics” server, logs in with the device UDID and a derived key, and runs a bidirectional data exchange. The port 0x1e18 is 7704. Nothing about this is disclosed to the user. The flag that triggers this check is set server-side through the command channel, so the vendor can silently activate it on any deployed device at any time.
Plaintext HTTP for Push Notifications (Medium)
From ciconfig.ini:
push_server_url=http://120.24.87.105:58720
oss_endpoint=http://oss-cn-shenzhen.aliyuncs.com
Push notifications and the OSS upload endpoint both use plain HTTP. Any network-level observer on the path between the camera and those servers can read notification payloads and potentially modify them.
Hardcoded Cloud Infrastructure (Medium)
Everything is hardcoded, none of it is configurable:
web_server_url=https://api.v2.gdxp.com
cmd_server_ip=120.24.87.105
cmd_server_port=8888
udp_server_ip=120.24.173.159
udp_server_port=9007
stun_server_ip=8.218.91.142
stun_server_port=17051
p2p_server_ip=47.242.63.121
p2p_server_port=17051
oss_bucket=sz-aiwit
oss_endpoint=http://oss-cn-shenzhen.aliyuncs.com
All of those IPs resolve to Alibaba Cloud infrastructure in the Shenzhen region. Users have no way to audit what data goes to which server, no way to opt out of the cloud connection, and no way to run the device without phoning home to vendor infrastructure.
Summary
| Finding | Severity |
|---|---|
| Unauthenticated root shell on serial console | Critical |
| ADB daemon enabled at boot, no auth | Critical |
| Hardcoded cloud API HMAC signing key | Critical |
| Hardcoded P2P session key derivation secret | High |
| Weak DES root password hash, world-readable shadow | High |
| No stack canaries, no PIE across all binaries | High |
| zlib 1.2.8, multiple critical CVEs | High |
| Vendor diagnostic backdoor (rd server) | Medium |
| Plaintext HTTP push notifications | Medium |
| Hardcoded cloud infrastructure | Medium |
Recommendations
For vendors shipping similar devices: disable ADB before production, replace the inittab console entry with a getty that requires a password, rotate any API signing secrets per-device at provisioning time rather than using a shared firmware constant, and compile everything with -fstack-protector-strong -fpie -D_FORTIFY_SOURCE=2. Update zlib to at minimum 1.2.13. The diagnostic backdoor channel should either be removed or require explicit user consent to activate.
For anyone who owns one of these cameras: it should not be on a network segment with anything you care about. The ADB issue in particular means physical access to the USB port is equivalent to owning the device completely. The hardcoded signing key means your device’s cloud API surface is not actually protected by anything an attacker cannot derive from a firmware dump, which is public information once one unit is purchased.