Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacyΒ© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CMF-Watch-Pro-2-BLE-Protocol β€” Reverse-engineered BLE protocol for the CMF Watch Pro 2, documenting GATT layout, AES-128-CBC encrypted command frames, authentication handshake, and health data sync for alternative companion app development. | Kitploit
Tools/GitHubGitHub/joshuapassos/cmf-watch-pro-2-ble-protocol
Embedded Systems SecurityBluetooth SecurityIoT SecurityReverse EngineeringWireless SecurityCryptographyMobile SecurityHardware & IoT SecurityFirmware Analysis

Most Popular

View all β†’

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools β†’
Share
GitHubjoshuapassos/cmf-watch-pro-2-ble-protocol

CMF-Watch-Pro-2-BLE-Protocol

Reverse-engineered BLE protocol for the CMF Watch Pro 2, documenting GATT layout, AES-128-CBC encrypted command frames, authentication handshake, and health data sync for alternative companion app development.

View RepositoryWebsite
3492 months agoNot yet reviewed

CMF Watch Pro 2 β€” BLE Protocol (reverse-engineered)

Unofficial. This document describes the Bluetooth Low Energy (BLE) protocol of the CMF Watch Pro 2 (CMF by Nothing), reconstructed by reverse engineering for an alternative companion app. It is not affiliated with or endorsed by Nothing/CMF. Use at your own risk.

All multi-byte integers in the frame header and opcodes are big-endian. Integers inside command payloads are little-endian unless stated otherwise (this mirrors the device firmware) β€” watch out for the exceptions (GOALS_SET, GPS_PUSH, bulk-transfer offset/length are big-endian).

Confidence markers

Every non-obvious claim below is tagged with how it was established:

  • βœ… validated on-device β€” observed in a decrypted live capture or exercised against a real watch.
  • πŸ”Ž from firmware / APK RE β€” extracted by decompiling the firmware (1.0.0.73) or the official APK (3.5.7); consistent with the code but not runtime-tested.
  • 🟑 partially proven β€” structurally established (offline, corpus-wide, or by RE) but the remaining step needs the watch and hasn't been run.
  • ⚠️ [uncertain] β€” inferred, not confirmed; may be wrong.

Where a later section corrects an earlier one, the earlier text is kept with a pointer rather than deleted β€” knowing which readings were tried and refuted saves the next person the same detour.

Test device for all captures: CMF Watch Pro 2-5485, fw 1.0.0.73, serial CI04102520008192, MCU Actions ATS3089C (Cortex-M4), screen 466Γ—360.


1. GATT layout

The phone is the GATT client; the watch is the peripheral, advertising as CMF Watch Pro 2-XXXX (4 hex chars).

PurposeServiceCharacteristicProperties
Command write0000fff0-0000-1000-8000-00805f9b34fb0000fff2-…Write
Command notify0000fff0-…0000fff1-…Notify
Shell write (AT)β€”77d4ff01-2fe2-2334-0d35-9ccd078f529cWrite
Shell notify (AT)β€”77d4ff02-…Notify
Bulk data writeβ€”02f00000-0000-0000-0000-00000000ffe1Write
Bulk data notifyβ€”02f00000-…ffe2Notify

Enable notifications by writing 01 00 to each CCCD (00002902-…). The command channel (fff1/fff2) carries the framed protocol below. The shell channel (77d4…) carries plain AT-style text (e.g. AT GETSECRET; see Β§14). The data channel (02f0…) carries large binary blobs (watchface, firmware, AGPS), coordinated by control opcodes on the command channel.

Service UUIDs β€” the watch advertises ~10 primary services. Enumerated on a real unit: 0xfff0 (command), 0x180f (battery), 0x180a (device info), 0xefe7, 0xffd0, 02f00000-…ffe0 and 02f00000-…fe00 (data), 77d4e67c-2fe2-2334-0d35-9ccd078f529c (shell / pairing), e49a3001-f69a-11e8-8eb2-f2801f1b9fd1, f48a23c0-f69a-11e8-8eb2-f2801f1b9fd1.

⚠️ The shell service UUID is 77d4e67c-…, not 77d4ff00-…. Earlier revisions of this document assumed the service shared the ff00 prefix of its characteristics (77d4ff01/77d4ff02, Β§14) β€” it does not, at least on the unit this was checked on (finding from freethinkel/fmc, see Β§Sources). The characteristic UUIDs are unchanged. Not verified whether 77d4e67c is stable across units β€” enumerate rather than hardcode.

🌐 Web Bluetooth callout. Chromium only ever discovers services that the page listed in optionalServices, even for an unfiltered getPrimaryServices() call β€” a page listing 3 services sees 3, while chrome://bluetooth-internals (Chrome's own C++ layer, unscoped) shows all 10. If you write a browser client, list every UUID above up front or pairing will fail with services that plainly exist. No Web Bluetooth in Firefox/Safari; needs a user gesture + HTTPS/localhost.

βœ… A whole real session ran on the single command channel β€” during a 160 s heavy-use capture there was no traffic on the data/firmware or shell channels except during an explicit OTA/watchface transfer.


2. Frame format (0xF5)

Every command-channel message is wrapped in one or more 11-byte-header frames:

+------+-----------+--------+-------------+-------------+--------+-------------------+
| 0xF5 | chunkLen  | cmd1   | chunkCount  | chunkIndex  | cmd2   | chunk bytes …     |
| 1 B  | 2 B (BE)  | 2 B BE | 2 B BE      | 2 B BE      | 2 B BE | chunkLen bytes    |
+------+-----------+--------+-------------+-------------+--------+-------------------+
        \__________________________ 11-byte header ____________________________/
  • cmd1/cmd2 together form the opcode (see Β§6). πŸ”Ž confirmed against the official app's frame builder (C6117b.m30831g).
  • chunkCount = total chunks for this command; chunkIndex is 1-based.
  • chunkLen = number of bytes of chunk in this frame.
  • A single BLE write may be fragmented by the link MTU; the receiver buffers raw bytes and re-extracts complete frames. Large payloads are split into multiple chunks (same cmd1/cmd2, increasing chunkIndex) and reassembled in order.

Opcode convention (βœ… confirmed on the wire)

  • cmd1 = 0xFFFF: cmd2 in 0x80xx/0x90xx = phoneβ†’watch (request/set); 0x00xx/0xa0xx = watchβ†’phone (reply). Pairs match by the low byte (0x9055↔0xa055, 0x8051↔0x0051).
  • feature-specific cmd1: cmd2 suffix = 0x0001 SET, 0x0002 GET, 0x0003 ACK.

Chunk body

For each chunk, the body is payloadPiece β€– CRC32_LE(payloadPiece) (4-byte CRC, little-endian, zlib/IEEE). If the command is encrypted (see Β§3), the whole payloadPiece β€– CRC is then AES-128-CBC/PKCS7 encrypted and that ciphertext becomes the frame chunk.

Plaintext quirk: for plaintext opcodes the watch counts the 4-byte CRC in chunkLen but does not transmit it. So when decoding a plaintext frame, the actual data length is chunkLen βˆ’ 4. (Encrypted frames carry the CRC inside the ciphertext as normal.)

Chunk sizing (so encrypted chunks land on AES block boundaries), with maxWrite = mtu βˆ’ 3:

  • encrypted: floor((maxWrite βˆ’ 11) / 16) * 16 βˆ’ 4 βˆ’ 1
  • plaintext: maxWrite βˆ’ 11 βˆ’ 4 βˆ’ 2

βœ… All observed encrypted frame chunkLen values were multiples of 16 (block alignment holds).


3. Cryptographic primitives

  • AES-128-CBC with PKCS7 padding and a fixed IV (from firmware CmfCharacteristic.AES_IV): 50 51 52 53 54 55 56 57 60 61 62 63 64 65 66 5A.
  • CRC32 (zlib/IEEE), emitted as 4 little-endian bytes.
  • SHA-256 over the concatenation of parts.

Key derivation:

authkey      = SHA256( rnd1 β€– rnd2 β€– secret )[0..16]      // persisted across sessions
sessionKey   = SHA256( nonce β€– authkey )[0..16]           // per connection
  • secret = 16-byte device secret (obtainable from the watch via the shell command AT GETSECRET β†’ GETSECRET:<32-hex>,OK).
  • rnd1 = 16 random bytes chosen by the phone; rnd2 = 16 random bytes from the watch.
  • nonce = bytes from the watch's nonce reply.

After the key is set, all command-channel frames are AES-encrypted except the plaintext opcodes listed in Β§5.

βœ… Both derivations validated: authkey recovered from a rooted phone's ntwatch.db matched the value derived from a captured rnd1/rnd2/secret; sessionKey reproduced from a captured nonce decrypts live frames.


4. Authentication / pairing handshake

Two entry paths share the same nonce/confirm tail.

4.1 First-time pairing (have the device secret)

Download Tool