add: REA analysis

This commit is contained in:
2026-11-02 10:09:20 +01:00
parent 434b733f58
commit b381f9b4ea
2 changed files with 103 additions and 9 deletions
+6 -9
View File
@@ -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
@@ -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/<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.