diff --git a/Document0-Initial_Information_Gathering.md b/Document0-Initial_Information_Gathering.md index 668958f..1e536ae 100644 --- a/Document0-Initial_Information_Gathering.md +++ b/Document0-Initial_Information_Gathering.md @@ -209,16 +209,13 @@ pipx run --spec esptool==3.3.2 esptool.py -p /dev/ttyUSB1 -b 115200 read_flash 0 > Writing this down in case I need it again. +#### FW Analysis + I found an [IoT FW analysis tool](https://github.com/ericyoc/iot_device_firmware_analysis_poc/tree/main) on GitHub, which I used for generating a quick [report](firmware/bridge/report.txt). -Interesting findings: -- Lock state tracking (`GO_TO_STATE_INSTANTOPEN`, `MOTOR_STALL`, ...) -- Confirmed cloud communication via MQTT and lock communication via BLE -- Contains embedded RSA and EC Private Key structural markers -- Implements `secure_boot_v1` -- Uses `mbedtls` for modern ciphers -- Exposes path from dev's machine (his name seems to be Pierre, lol) `/home/pierre/bridge/components/button_driver/iot_button.c` -- Runs FreeRTOS -- Uses SPIRAM + +##### Reverse Engineer Anything + +Using [REA](https://github.com/morluto/rea), we ran the firmware through Claude Code and received a short [report](firmware/bridge/claude_firmware_analysis.md). ### Inner Module diff --git a/firmware/bridge/claude_firmware_analysis.md b/firmware/bridge/claude_firmware_analysis.md new file mode 100644 index 0000000..056bf90 --- /dev/null +++ b/firmware/bridge/claude_firmware_analysis.md @@ -0,0 +1,97 @@ +# 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.