back to blog

Boykeep K30 IoT Camera: Firmware Security Assessment

Target

The Boykeep K30 is a budget indoor Wi-Fi camera sold under multiple OEM labels. The firmware blob is an 8MB SPI NOR flash dump. The SoC is an Anyka AK3918EV300L (ARM Cortex-A5, 32-bit little-endian). The main application binary is viCam, a 4.6MB C++ ELF that handles video, audio, cloud relay, and device configuration over a proprietary protocol.

Flash Layout

Running binwalk against the blob gives the following partition layout, confirmed by /squashfs-root-0/system/part.env:

Offset      Size     Contents
0x000000    0x220000  U-Boot + kernel (XZ-compressed)
0x220000    0x2C0000  SquashFS (rootfs - BusyBox, kernel modules, scripts)
0x4E0000    0x250000  SquashFS (user partition - viCam application)
0x730000    0x0D0000  JFFS2 (config/state - certs, saved config)
0xF00000    0x100000  Factory partition (device-specific keys, MAC, model)

The factory partition at /dev/mtd5 stores the device serial, MAC address, and registration key. The getver.sh script reads this at boot to set device identity.

Toolchain / OS

Architecture:  ARM Cortex-A5, 32-bit LE
OS:            Embedded Linux (no version string found)
libc:          uClibc 1.0.31
Compiler:      gcc 3.3.2 (build artifacts in binary metadata)
Shell:         BusyBox ash
Init:          /etc_default/inittab -> /etc_default/init.d/rcS
Wi-Fi:         ATBM6132 USB (atbm6x3x_wifi_usb.ko)
Camera sensor: GC2083 MIPI 2-lane (sensor_gc2083.ko)

Boot Process

The full startup chain:

inittab -> rcS -> S01passwd, S07devinfo, S97speech, S98ntp, S99dotstart
S99dotstart -> watchdog process -> dotstart.sh -> runall.sh
runall.sh -> wifi_stream.sh (configures wlan0) -> watchdog.sh viCam
watchdog.sh -> start.sh (symlink to app.sh) -> viCam start

If viCam crashes, watchdog.sh saves a crash log to /etc/crash.log and calls /sbin/reboot. There is no crash reporting or rate limiting on the reboot loop.

S01passwd copies the shadow file from ../passwd/shadow (relative to the squashfs root) to /etc/shadow on first boot if /etc/syspasswd does not exist. This means the shadow file shipped in the firmware is the active credential store.

S07devinfo reads the Anyka watchdog timer register via devmem to detect the previous reboot cause, and writes device info to SD card if present.

wifi_stream.sh configures the wlan0 interface by writing /etc/hostapd.conf using sed substitutions, then starts either hostapd (AP mode) or wpa_supplicant (STA mode) depending on the stored network configuration.

Filesystem Survey

$ checksec --file=squashfs-root-0/vicam/viCam
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE

Running the same check across all 65 ELF binaries in the firmware:

No binary in this firmware has stack canary protection. Combined with no PIE on the main application binary, any stack overflow in viCam is exploitable without needing to leak addresses.

Hardcoded Credentials

Default Admin Password

/squashfs-root-0/vicam/users.default.json:

[{"Username": "admin", "Password": "closeli"}]

This file is copied to the active user database on first boot. The password is cleartext and identical across all devices. There is no per-device password generation, no mandatory change on first login, and no documentation in the product materials warning about this credential.

Authentication Key

/squashfs-root-0/conf/base_conf.ini:

[auth]
key=1111111111111111

This 16-byte key is used for HMAC authentication of configuration messages between the app and the cloud backend (confirmed by cross-referencing it with the check_auth.sh script and string references in the viCam binary). The same key is hardcoded in every device.

Shadow File

/etc_default/shadow (copied to /etc/shadow at boot):

The root account has a non-empty password hash (SHA-512 format, $6$...). Cracking was not attempted, but the hash is static across all firmware versions, so any device with this firmware version has the same root password hash. If cracked, the password is valid on every device.

There is no telnet service running by default. However, see the SD card telnet activation finding below.

SD Card Telnet Activation

/squashfs-root-0/sh/sdcard_action.sh runs on every SD card insertion (triggered by mdev):

${THISDIR}/start_debug.sh ${MNTPOINT}

start_debug.sh reads a debug.cfg file from the SD card root. It first calls ukey -t to verify a cryptographic signature on the config file. If the signature check passes:

check_cfg TELNET YES && /usr/sbin/telnetd

Line 97 of start_debug.sh starts telnetd if TELNET=YES appears in debug.cfg. The debug script also dumps logs to the SD card and can run arbitrary debug commands listed in the config.

The ukey binary performs signature verification using a key stored in the factory partition. The security of this mechanism depends entirely on the strength of that key and whether it can be extracted from a single device. Once extracted, it can be used to create valid debug.cfg files for any device with the same firmware.

Beyond telnet, the debug script also calls packlog.sh to collect device logs and can execute arbitrary commands from the config file if additional directives are present.

Firmware Update (SD Card)

/squashfs-root-0/vicam/fwup_by_sdcard.sh handles OTA updates from SD card:

${PROGRAM_DIR}/viCam unpack ${FW_FILE} ${UNPACK_DIR}
${THISDIR}/fwup_cmd.sh preinst
${THISDIR}/fwup_cmd.sh install
${THISDIR}/fwup_cmd.sh postinst

There is no cryptographic signature check on the firmware image before unpacking. fwup_cmd.sh only validates the upgrade command string format and version number. An attacker with physical SD card access can install arbitrary unsigned firmware. The viCam unpack command extracts the firmware to a staging directory, and fwup_cmd.sh then writes it to flash.

This is a 30-second physical attack: insert SD card with crafted firmware, device auto-updates on next SD insertion, firmware replaced.

Certificate Store

/jffs2-root/conf/cert.ini contains the device CA bundle. It includes standard public CAs (COMODO, DigiCert, GlobalSign, ISRG Root X1, DST Root CA X3) plus one custom internal CA:

Subject: C=CN, ST=bj, O=VIot, CN=VI ROOT CA
Issuer:  C=CN, ST=bj, O=VIot, CN=VI ROOT CA (self-signed)
Valid:   2020-02-03 to 2050-01-26

The VI ROOT CA is trusted by the device for TLS validation alongside the public CAs. Any certificate signed by VI ROOT CA is accepted without error. If the VI ROOT CA private key is held by the vendor or any party, they can issue certificates that the device trusts unconditionally, enabling person-in-the-middle attacks against all HTTPS connections the device makes.

Cloud Infrastructure

Strings extracted from the viCam binary include:

http://ota.blurams.cn/firmware/
https://log.vimvp.cn/upload
18.244.0.188

All firmware update and log upload endpoints are hardcoded. There is no way to change the OTA server without reflashing. If blurams.cn resolves to attacker-controlled infrastructure (DNS hijack, domain expiry, or vendor compromise), the device will attempt to fetch and install firmware from that server. Combined with the lack of firmware signature verification, this is a remote unauthenticated code execution path for any attacker who can control DNS resolution for ota.blurams.cn.

The hardcoded IP 18.244.0.188 appears to be a fallback connection endpoint in the US-East-1 AWS region.

SBOM and CVE Scan

Running the SBOM generator and NVD scan against the firmware produced 17 CVEs across two components.

U-Boot 2019.10 (3 Critical, 12 High, 1 Medium, 1 Low)

Three critical CVSS 9.8 vulnerabilities:

CVE-2020-8432 - Double-free in the U-Boot verified boot chain. An attacker who controls bootloader environment variables can trigger a double-free in the FIT image parser, leading to arbitrary code execution during the boot sequence.

CVE-2022-34835 - Stack buffer overflow in the TFTP client via an overly long server response. Exploitable with network access to the bootloader if U-Boot TFTP is accessible (requires UART/network access during boot).

CVE-2022-30767 - NFS client stack buffer overflow. Same class as CVE-2022-34835.

All three require either UART access (physical) or control of the bootloader environment. U-Boot’s network boot features are the primary attack surface. Whether the bootloader exposes TFTP/NFS depends on the bootcmd environment variable, which is stored in flash and can potentially be modified by a prior firmware compromise.

gcc 3.3.2 (GCC runtime libraries)

The firmware ships with GCC 3.3.2 runtime libraries (libgcc, libstdc++). GCC 3.3.2 is from 2003 and has numerous known vulnerabilities in the compiled runtime. Most are not directly exploitable as standalone, but the age of the toolchain means the codebase was compiled without modern hardening features (stack canaries, FORTIFY_SOURCE, etc.), which is reflected in the binary analysis results.

viCam Binary Analysis

viCam is a 4.6MB ARM32 ELF, not stripped, dynamically linked against 23 custom platform libraries:

libplat_vi.so, libplat_vpss.so, libplat_venc.so, libplat_ai.so,
libplat_ao.so, libplat_isp_sdk.so, libplat_mem.so, libplat_osal.so,
libplat_thread.so, libplat_log.so, libplat_dbg.so, libplat_common.so,
libplat_timer.so, libmpi_venc.so, libmpi_osd.so, libapp_video.so,
libapp_osd_ex.so, libapp_its.so, libakaudiofilter.so, libak_mrd.so

All platform libraries are in the user squashfs alongside the binary. The binary source path is leaked in the symbol table:

/root/.ci/workspace/NRN/private/mainprocess/FullrelayMsg.cpp

This reveals the CI system runs builds as root, the project is named NRN, and the internal source tree is at /root/.ci/workspace/NRN/.

Key Functions

FUN_0016d400 (18 KB) - SHA-512 compression
Identified by the characteristic rotation constants for the SHA-512 Sigma functions and the 80-round structure. This implements the SHA-512 hash algorithm used for credential verification.

FUN_003169a4 (11 KB) - MP4/ISO base media box parser
Handles MPEG-4 container parsing: ftyp, moof, mdat, moov, hvcC, avcC, ctts, stsd, stts, seds, minf, mdia. Parses box lengths and types from a byte stream. No length validation issues found in the visible code path, but the sheer number of box types and nested parsing leaves room for subtle issues.

FUN_00230c28 (10 KB) - OnSetDeviceConfiguration
The main configuration dispatch handler, sourced from FullrelayMsg.cpp. Dispatches on a sub-request opcode to apply device configuration changes. Handles timezone, LED, night mode, motion regions, privacy shelter, schedules, AI features, buzzer, and disk formatting. Contains a stack buffer overflow in the timezone handler (see Findings).

FUN_001f65b4 (17 KB) - libcurl option setter
Sets CURLOPT values on the curl easy handle. Handles URL configuration, proxy settings, TLS certificates, credentials, and request parameters. Includes length bounds checks on URL strings (max 8,000,000 bytes) but the sheer number of options means the attack surface is wide.

FUN_002034c0 (10 KB) - libcurl easy setup
Initializes a curl transfer: URL parsing, scheme detection, HSTS upgrade, proxy environment variable resolution, protocol handler selection, and TLS setup. Uses the VI ROOT CA bundle from cert.ini for TLS validation.

Stack Buffer Overflow in Timezone Handler

In OnSetDeviceConfiguration, sub-request 0x12 handles timezone configuration:

uint local_33c;       // 4-byte stack variable
void *local_338;      // adjacent 4-byte pointer (next stack slot)
char value_buf[100];  // input buffer

FUN_0010613c(param_1, "value", &value_buf, 3);  // reads user string
pcVar5 = strstr(value_buf, "offset ");
if (pcVar5 != NULL) {
    strncpy((char *)&local_33c, pcVar5 + 7, 9);  // copies 9 bytes into 4-byte variable
}

strncpy with count 9 into a 4-byte uint. This overwrites 5 bytes past the end of local_33c into local_338. With no stack canary and no ASLR (no PIE), an authenticated attacker sending a configuration message with a timezone value containing "offset XXXXXXXXX" (where X is 9 non-null bytes) corrupts the adjacent stack slot. This is reachable from the cloud backend once the device is registered, or from a local attacker who knows the auth key (which is hardcoded as 1111111111111111).

Fuzzing

Automated fuzzing was attempted against the ambulance recovery binary using AFL++ in QEMU mode with desock. The Docker container for the fuzzing infrastructure returned a permission error, so the campaign did not start. Manual fuzzing of viCam requires cross-compilation of the QEMU user-mode emulator for ARM and the custom platform library stubs. A recommended approach:

  1. Extract the user squashfs and set up a chroot with QEMU user-mode ARM emulation
  2. Write stubs for the 23 libplat_*.so libraries that return success from initialization calls
  3. Feed crafted configuration JSON payloads to OnSetDeviceConfiguration via a harness that calls the function directly, targeting sub-request 0x12 (timezone) and 0x38 (motion regions) as priority targets
  4. Seed corpus: valid timezone strings with the "offset " prefix, motion region JSON with varying id, left, top, right, bottom, sensitivity, enable combinations

Summary of Findings

Severity Title Location
CRITICAL Hardcoded default credentials (admin/closeli) users.default.json
CRITICAL U-Boot 2019.10 - three CVSS 9.8 RCE CVEs U-Boot bootloader
HIGH Stack buffer overflow in timezone handler viCam FUN_00230c28
HIGH SD card telnet daemon activation start_debug.sh:97
HIGH Hardcoded symmetric authentication key base_conf.ini
HIGH All 65 binaries lack stack canaries, 37 lack PIE firmware-wide
HIGH SD card firmware update without signature check fwup_by_sdcard.sh
MEDIUM Custom VI ROOT CA trusted by device cert.ini
MEDIUM Hardcoded cloud infrastructure URLs viCam binary
LOW Internal CI/CD build path in binary viCam binary
--- // --- // --- // ---