add: REA analysis
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user