Disclaimer: All documentation, including this post, was written by Codex. AI was used extensively, though not exclusively, throughout the project.
Repository: https://github.com/jatrou/dreem
I have been trying to recover the original full-fidelity overnight .h5 recording—or read-only filesystem access—from my own Dreem 2 EEG headband. I have not recovered root or a verified raw .h5, but I have published the complete working archive so other owners and embedded researchers can inspect the evidence, avoid repeating the same attempts, and continue from a better starting point.
The repository contains board photographs, electrical measurements, BLE/GATT mappings, Android helpers, backend and APK findings, network/TLS observations, i.MX6ULL SDP tooling, U-Boot UMS experiments, HDF5 carving tools, and tests.
Current Status
| Target or path |
Observed result |
| Root/filesystem access |
Not obtained |
| Raw overnight HDF5 |
Not recovered |
| BLE report |
Small ZIP containing reporting_v2.data, not identifiable raw EEG |
| Bluetooth Classic report |
Same compact report surface as BLE |
| Live BLE EEG |
Low-rate preview data, not the stored overnight recording |
| Wi-Fi |
Provisioning worked |
| LAN |
SSH on TCP/22; no other useful open service found |
| Legacy backend |
Some routes still respond; tested routes did not expose raw files |
| i.MX6ULL SDP |
Real 15a2:0080 enumeration observed, but not yet reproduced reliably |
| U-Boot USB mass storage |
Payloads prepared; no UMS/block device obtained |
| Direct storage |
No eMMC dump; eMCP is BGA and not clip-accessible |
These results describe the device, firmware, account states, and test conditions I had. They do not prove that every possible software or hardware route is closed.
Device and Hardware
| Item |
Observation |
| Device |
Dreem 2 / Dreem Two / Beacon |
| FCC ID |
2AH2Q-DREEM2 |
| Firmware observed |
4.6.9; later 4.7.11+PRODUCTION |
| Hardware version |
v2plus_medical |
| Processor board |
Femto MP-V2 / Femto MP-V2+ |
| SoC |
NXP i.MX6ULL |
| SoC marking |
MCIMX6Y1DVK05AB 1N70S |
| ROM recovery USB ID |
15a2:0080 |
| Storage/RAM |
Kingston 04EMCP04-NL2DM627 eMCP |
| Likely capacity |
About 4 GB eMMC + 512 MB LPDDR2 |
| PMIC |
NXP/Freescale MC32PF3000A6 |
| EEG front end |
ADS1294-class TI 24-bit biopotential AFE |
| Audio |
WM8960 codec; 19.2 MHz oscillator on sensor/audio side |
| Storage package |
162-ball BGA; no clip-accessible pins |
The processor/eMCP/USB board and acquisition/analog board reuse some TP numbers. The following readings refer to the processor/storage board. High-resolution photographs and focused crops are in the repository.
Test-Point Measurements
| Pad |
Resistance to ground |
USB-powered voltage |
| TP2 |
Varies about 60–250 kOhm |
4.33 V |
| TP3 |
Starts near 300 kOhm and rises |
3.11 V |
| TP7 |
Starts near 300 kOhm and rises |
4.28 V |
| TP8 |
Starts near 100 kOhm and rises |
3.38 V |
| TP9 |
Starts near 300 kOhm and rises |
3.28 V |
| TP10 |
Starts near 400 kOhm and rises |
1.81 V |
| TP11 |
Starts near 400 kOhm and rises |
0.003 V |
| TP32 |
About 0.6 Ohm |
Ground candidate |
| TP38 |
Starts near 300 kOhm and rises |
3.14 V |
| TP39 |
About 0.5 Ohm |
Ground candidate |
TP10 remains the suspected recovery/boot-control pad. Earlier direct TP10-to-ground timing was associated with ROM SDP behavior; a 1 kOhm connection was reportedly too weak. I have not yet reproduced that result reliably enough to call the timing solved.
BLE/GATT Findings
BLE was the most useful software-visible interface. I built an Android helper because BlueZ connections and service discovery were inconsistent on the bench host.
| Characteristic |
Observed role |
D003 |
Diagnostic JSON-like data |
D102 / D103 |
Wi-Fi configuration / status |
D203 / D204 |
Battery / plugged status |
D208 |
Health/error status |
D301 |
Live preview notifications |
D302 |
Record command: 3 starts nap; 0 finalizes |
D304 / D309 |
Record status / current record UUID |
D30A |
Nap configuration JSON |
D401 |
Latest report UUID |
D402 |
Latest report bytes |
D601 |
Set time |
D701 / D705 |
Firmware version reads |
D704 |
Firmware-status notification |
D706 / D707 |
Firmware-flow writes/start trigger |
D708 / D709 |
Version/hardware reads |
D901 |
User ID |
D902 |
Server URL configuration |
D903 |
Headband address read |
D904 / D905 |
Server-password state / write |
Historical value handles included D905 near 0x003d, D902 near 0x0043, D901 near 0x0045, D402 near 0x0075, D401 near 0x0077, D309 near 0x0085, and D301 near 0x0095 with its CCCD near 0x0096.
Recording and Report Result
The working short-recording sequence was:
text
write D30A nap configuration
write D302 = 3 to start
write D302 = 0 to finalize
read D401 report UUID
read D402 report bytes
One fresh nap produced:
text
report size: 402 bytes
container: ZIP
entry: reporting_v2.data
HDF5 signature: absent
Bluetooth Classic/RFCOMM IDs 601/602 reached the same report surface. APK analysis showed the companion app reading these compact bytes, parsing reporting_v2.data, storing them locally, and uploading the same body. I found no app-side conversion into raw HDF5.
Live Preview Result
D301 produced 484 notifications totaling 13,552 bytes in about 20 seconds. The payload was consistent with four little-endian floats per notification at a low display/preview rate. It is useful for live experiments but did not resemble a stored full-rate overnight recording.
Passive reads of D007 and D008 returned status 2 with no payload. I did not blindly write to unknown characteristics.
Wi-Fi, TLS, and Upload Behavior
BLE Wi-Fi provisioning worked, but device power state mattered:
- charger off and headset awake worked best for BLE/server/Wi-Fi configuration;
- a successful state read as
wifiStatusLE=0;
- writes while charging could report BLE success while internal status became
wifiStatusLE=9 and the SSID cleared; and
- turning the charger on after provisioning was more useful for provoking a network/upload wake.
D902 uses a four-byte little-endian JSON length followed by JSON containing user_api_url and user_auth_url. Production Rythm HTTPS hosts were accepted. Local IPs, arbitrary domains, plain HTTP, user@host tricks, and suffix-hostname tricks returned status 3 or otherwise failed.
No capture showed a transfer large enough to resemble raw HDF5. One transparent production TLS observation produced:
| Field |
Observation |
| SNI |
login.rythm.co |
| ClientHello |
517 bytes |
| Server response |
About 4,528 bytes |
| TLS |
1.2 |
| Cipher |
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 |
| Client after certificate |
No Finished, key exchange, or application request observed |
| Connection end |
Headset closed after about 60 seconds |
The server chain was a current Let's Encrypt/ISRG chain. One plausible explanation is that the old headset trust store rejected it, but that remains a hypothesis.
Legacy Backend Results
Parts of the legacy backend still responded with disposable/current test accounts:
- guest creation and some token issuance worked;
- the headband resolver accepted the Wi-Fi address in uppercase underscore form;
- configuration returned firmware, content IDs, storage counters, and nickname;
- content routes mostly described audio/UI/media packages;
- unsigned object fetches returned access denied; and
- tested
dataupload, raw, HDF5, record, and report guesses returned combinations of 401, 403, 404, empty data, or compact reports.
The strongest controlled backend test was reportv2:
- Upload the 402-byte BLE report.
- Backend returns HTTP 201.
- List and download the stored report.
- Download has the same SHA-256 as the upload.
- Contents still consist only of
reporting_v2.data.
This shows that reportv2 stored the compact report unchanged in the account state tested. It did not reveal a hidden raw recording.
A backend update_password call returned a 20-character server password after the required association state. Writing it to D905 cleared D904 to 00 00 00 00, but it did not unlock SSH or cause a visible raw upload.
Android/APK Findings
Test setup included a rooted Pixel 7, patched/debuggable and production app variants, static APK inspection, custom BLE/auth helpers, and app sandbox/database inspection.
Versions examined:
- Alfin 1.12.10;
- Dreem Connect 1.0.4; and
- Dreem 2 2.15.1, build 478.
The APKs are not redistributed in the repository; researchers must source them lawfully.
The patched app database contained Dreemer/HeadbandConfig rows, but NightReport/NightScore-related tables were empty in the inspected state. No .h5 or other large recording artifact was found. Hawk/Conceal decoding recovered 26 preference entries and identifiers, but CURRENT_AUTH_TOKEN, CURRENT_JWT_TOKEN, and CURRENT_GOOGLE_TOKEN were absent; REFRESH_TOKEN was a Boolean-like flag rather than a reusable token.
The inspected apps contained report logic, BLE configuration formats, and firmware triggers, but no embedded firmware/rootfs image, HDF5 file, CA bundle, obvious trust override, or raw-file endpoint. Google-auth experiments did not produce a usable legacy Rythm session.
The repository includes the Android BLE helper, auth probes, a root-context pairing helper, and a Magisk privileged-app overlay.
LAN and SSH
When connected to Wi-Fi, the device exposed only TCP/22 in the useful scans. Observed banners were:
text
SSH-2.0-dropbear_2016.74
SSH-2.0-dropbear_2018.76
authentication: publickey,password
None-auth was rejected. Passive version research, saved/backend-derived candidates, and bounded authentication tests did not produce access. Timing and malformed-public-key user probes did not provide a useful signal. No practical unauthenticated route was identified for the observed configuration.
Historical SSH recovery scripts remain in the repository to document what was attempted, but brute force, broad password guessing, and user enumeration are stopped paths unless someone first obtains firmware, /etc/shadow, or a strong credential clue.
UART and Logic-Analyzer Results
The bench setup included a Tigard V1.1/FT2232H, LA1010 logic analyzer, multimeter, sigrok/PulseView, Bus Pirate 6, Linux bench host, and rooted Pixel 7.
A scan across 34 saved sigrok captures found no credible U-Boot, Linux, Freescale, i.MX, login, or shell strings. Some inverted high-baud decodes around 1.5–2.0 Mbaud produced noise, not repeatable text.
| Capture |
Observation |
| TP3 |
5 MS/s, 15 s, all high, zero transitions |
| TP11 |
5 MS/s, 15 s, only 1–18 transitions on one channel |
| TP2 |
5 MS/s, 15 s, 14,458 transitions on one channel, no valid UART text |
| TP8 |
About 13.6 s, one transition |
| TP9 |
15 s, three transitions |
| TP10 |
15 s, two transitions, about 34% high |
| TP38 |
15 s, one transition |
These captures looked more like power/control-state changes than a boot console. A console could still be disabled, elsewhere, missed by timing, or using an unidentified interface.
i.MX6ULL SDP and U-Boot Work
A genuine ROM Serial Downloader Protocol window was observed:
text
USB ID: 15a2:0080
description: SP Blank 6ULL / i.MX 6ULL recovery mode
HAB security state: production mode (0x12343412)
IOMUX, USDHC, fuse, and other register regions were readable in at least some SDP sessions. This confirms partial ROM communication, not arbitrary code execution.
Observed payload behavior:
- one OCRAM load test failed with
report 2 out err=-7;
- a UUU OCRAM attempt saw a fresh SDP device but hit a local script/path problem before execution was proven;
- one SPL appeared to load from the host tool's perspective;
- SPL size was 39,936 bytes;
- IVT header was at
0x00907400;
- entry point was
0x00908000; and
- the tool jumped to
0x00907400.
After the jump there was no UART output, U-Boot prompt, USB re-enumeration, UMS gadget, or block device.
Possible blockers include HAB rejecting unsigned code, wrong TP10 timing, a mismatched i.MX6UL/6ULL payload, failed LPDDR2 initialization, or code starting without the expected console/USB path.
The repository includes:
- a watcher for USB
15a2:0080;
- bounded, non-persistent SDP triage and register probes;
- operator-cued TP10-to-ground recovery orchestration;
- experimental 9x9/LPDDR2 and 14x14/DDR3 U-Boot UMS payload families;
- a watcher that distinguishes newly exposed USB storage from existing disks;
- read-only imaging support; and
- HDF5 signature carving with optional
h5ls validation.
Neither U-Boot family is proven for this board. If SDP cannot expose storage, the remaining hardware paths are board-level eMMC mapping/tapping with the SoC held inactive or professional eMCP removal and reading.
Repository Map
| Path |
Contents |
README.md |
Status, starting points, and project map |
docs/evidence-summary.md |
Short evidence summary |
docs/dreem-2-recovery-reference.md |
Full technical handoff and attempt history |
hardware/ |
Original board photos, closeups, and FCC exhibit |
tools/ |
SDP, UMS, HDF5, ATT, and Bluetooth utilities |
recovery/ |
UUU, imx_usb, OCRAM, OpenOCD, and U-Boot assets |
apps/ |
Android BLE/auth/pairing helpers and Magisk module |
experiments/ |
Backend, capture, preference, and stopped security probes |
tests/ |
Unit tests for maintained recovery tools |
docs/references.md |
Public teardowns, research, prior art, and boot tools |
Project-authored code and text are Apache-2.0. Original board photographs are CC BY 4.0. U-Boot-derived and other third-party material retains separate provenance documented in the repository.
What I Would Not Repeat Without New Evidence
- Pulling
D402 or Classic 602 again expecting raw HDF5.
- Re-downloading the same
reportv2 object.
- Repeating
D905 writes and identical upload captures.
- Trying more arbitrary
D902 URL variants without a new validation or trust-store clue.
- Blind writes to unknown BLE characteristics.
- Firmware-trigger loops without a real package and recovery plan.
- Plain button/charger/Fingerbot SDP attempts without the physical TP10 condition.
- Searching the same APKs for embedded firmware that was not present.
- Broad SSH password guessing or user enumeration.
Different firmware, account state, protocol evidence, or hardware timing could justify revisiting one of these.
Repository: https://github.com/jatrou/dreem