Files
Shelly-Loqed-Touch/firmware/bridge/claude_firmware_analysis.md
T
2026-11-02 10:09:20 +01:00

5.6 KiB

Bridge Firmware Analysis — firmware.bin

Static analysis of the LOQED bridge firmware dump (firmware/bridge/firmware.bin, 4 MB, MD5 847d4795ee64714d3b02e2bdb3d20266).

Method: analyzed with local Binwalk + strings/dd + a partition-table parser (the same tools REA's firmware route wraps — the REA MCP server was not registered in that session). All findings are static (nothing was executed). Offsets below are byte offsets into firmware.bin.


Identity & build

  • Project LoqedBridge, ESP-IDF v4.4.1, ESP32-D0WD (single-core) + 64 Mbit PSRAM.
  • Git commit baked in: 33beadfc1ba5d4d3b0cb6a6ee673535a1d12972c, version 33beadf-dirty. The -dirty suffix means it was built from an uncommitted working tree.
  • The 4 MB image is essentially two copies of the same app (factory @ 0x20000 and ota_0 @ 0x160000), which is why every string appears twice (e.g. CA store at 0x372fa and 0x166b55).

Flash layout (partition table @ 0x8000)

Label Type Subtype Offset Size
nvs data 0x02 0x009000 16K
nvs_key data 0x04 0x00d000 4K
otadata data 0x00 0x00e000 8K
phy_init data 0x01 0x010000 4K
factory app 0x00 0x020000 1280K
ota_0 app 0x10 0x160000 1280K
ota_1 app 0x11 0x2a0000 1280K
coredump data 0x03 0x3e0000 64K

The nvs_key partition (subtype 0x04) means NVS encryption is provisioned — consistent with the boot log's secure_boot_v1.

Keys & crypto — correcting an earlier finding

  • No plaintext keys or certificates are embedded. The report.txt claim of "embedded RSA and EC Private Key structural markers" is a false positive: the PEM lines at 0x39439+ are mbedtls's null-terminated label table (contiguous -----BEGIN/END ...-----\0 strings preceded by ASN.1 OID bytes 2a 86 48 86 f7 0d 01 05 0d), i.e. library format constants — not a key body.
  • What is bundled: the standard ESP/mbedtls CA trust store (Entrust, XRamp, Trustwave, Starfield, COMODO, Certum, Actalis, China Financial…). These are public CA certs for TLS server validation, not secrets.
  • Crypto primitives referenced: mbedtls with SHA-1/SHA-256 (Binwalk flagged SHA256 constants at 0x2801c / 0x167fc4), AES, RSA, EC; TLS-PSK code paths exist (gated behind menuconfig).
  • Real device secrets (device identity, auth/key tokens, WiFi credentials) live in the encrypted nvs / nvs_key partitions, not in the code image.
  • ⚠️ Security observation: the firmware logs credentials in cleartext to the UART console — format strings id: %llu, token: %s, auth_token: %s, key token: %s, token: %s, and Setting wifi credentials to:. Anyone with UART access could harvest live tokens at runtime.

Endpoints (string literals)

  • mqtts://mqtt.production.loqed.com:8883 and mqtts://production.loqed.com:8883 (MQTT/TLS)
  • https://gateway.production.loqed.com:443, https://production.loqed.com:8080
  • OTA: https://firmware.loqed.com/firmware_bridge_… and …/firmware_lock_…
  • Core-dump exfil: https://app.loqed.com/Bridge-core-dump/bridge-core-dump2.php (uploads crash core dumps to LOQED as multipart, with "attachment" / "message" fields)

Functionality

  • Role: the bridge is the BLE central/client — full of esp_ble_gattc_* (scan, open, search_service, read/write char, register_for_notify, MTU). The lock is the BLE peripheral. Pairing/bonding via ESP_GAP_BLE_AUTH_CMPL_EVT.
  • Cloud transport: MQTT/TLS. Topic fragments: locks/, /to_backend, /to_lock, /state, /status → structure is effectively locks/<id>/to_lock and locks/<id>/to_backend. MQTT auth errors handled ("bad username or password", "not authorized").
  • Command protocol (bridge relays Backend ↔ Lock):
    • Backend->Bridge: REQUEST_STATUS_INFO_BRIDGE
    • Backend->Bridge: COMMAND_BRIDGE_FIRMWARE_UPGRADE / COMMAND_LOCK_FIRMWARE_UPGRADE
    • COMMAND_ELECTRONIC_DOOR / COMMAND_ELECTRONIC_DOOR_REBOOT (the actual door actuation)
    • Lock->Bridge: REQUEST_TIME_FROM_BRIDGE, RESPONSE_START_NEW_FIRMWARE_DOWNLOAD, RESPONSE_SEND_PART_FIRMWARE → the bridge downloads lock firmware over HTTPS and streams it to the lock over BLE in parts.
    • Payloads are binary/custom, not JSON (only the core-dump HTTP upload uses JSON keys).
  • Lock state machine (GO_TO_STATE_*): TWIST_ASSIST_{OPEN,LOCK,LATCH}, INSTANTOPEN_{OPEN,LATCH}, TOUCH_TO_LOCK, MANUAL_{LOCK,UNLOCK}_{BLE,REMOTE}_{OPEN,LATCH,NIGHT_LOCK}, MANUAL_UNLOCK_VIA_OUTSIDE_MODULE_{BUTTON,PIN}; status/errors STATE_CHANGED_{OPEN,LATCH,NIGHT_LOCK,UNKNOWN}, MOTOR_STALL. The three positions are open / latch / night-lock, reachable via twist-assist, touch, outside PIN/button, BLE, or remote.
  • OTA: HTTPS OTA (esp_https_ota_*) with A/B ota_0/ota_1 + rollback, validates image magic 0xE9, for both bridge and lock.
  • WiFi provisioning: ap.authmode / ap.passwd, "Set WiFi credentials (4)" — provisioned (likely over BLE) and stored in NVS.
  • Developer path leak persists: /home/pierre/bridge/components/button_driver/iot_button.c and /root/bridge/....

Coverage & limits

  • Static strings/structure only; encrypted NVS contents (the actual secrets) were not decrypted — that needs the device's flash-encryption key, which is not in this image.
  • Payload semantics (byte layout of the BLE/MQTT commands) are not recoverable from strings alone — that needs disassembly of the Xtensa code (Ghidra/IDA) around the command-handler functions.