98 lines
5.6 KiB
Markdown
98 lines
5.6 KiB
Markdown
# 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.
|