# 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//to_lock` and `locks//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.