Using C Forth to reverse engineer the Milo Champions Band fitness tracker protocol |
| quozl@us.netrek.org
| up |
|
The Milo Champions Band fitness tracker is only supported with Android and iOS, yet it reveals itself to scans over Bluetooth Low Energy (BLE) on Linux;
% sudo hcitool lescan LE Scan ... xx:xx:xx:xx:xx:xx MILO 000000Perhaps it will work with Linux? Let's find out.
gatttool --device XX:XX:XX:XX:XX:XX -I [ ][XX:XX:XX:XX:XX:XX][LE]> connect [CON][XX:XX:XX:XX:XX:XX][LE]> primary [CON][XX:XX:XX:XX:XX:XX][LE]> attr handle: 0x0001, end grp handle: 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x0008, end grp handle: 0x000b uuid: 00001801-0000-1000-8000-00805f9b34fb attr handle: 0x000c, end grp handle: 0xffff uuid: 0000fff0-0000-1000-8000-00805f9b34fb [CON][XX:XX:XX:XX:XX:XX][LE]> characteristics [CON][XX:XX:XX:XX:XX:XX][LE]> handle: 0x0002, char properties: 0x0a, char value handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb handle: 0x0004, char properties: 0x02, char value handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb handle: 0x0006, char properties: 0x02, char value handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb handle: 0x0009, char properties: 0x20, char value handle: 0x000a, uuid: 00002a05-0000-1000-8000-00805f9b34fb handle: 0x000d, char properties: 0x10, char value handle: 0x000e, uuid: 0000fff7-0000-1000-8000-00805f9b34fb handle: 0x0010, char properties: 0x0e, char value handle: 0x0011, uuid: 0000fff6-0000-1000-8000-00805f9b34fb [CON][XX:XX:XX:XX:XX:XX][LE]> char-desc [CON][XX:XX:XX:XX:XX:XX][LE]> handle: 0x0001, uuid: 2800 handle: 0x0002, uuid: 2803 handle: 0x0003, uuid: 2a00 handle: 0x0004, uuid: 2803 handle: 0x0005, uuid: 2a01 [CON][XX:XX:XX:XX:XX:XX][LE]>Note the fff7 UUID has NOTIFY support in properties. So it is likely to be serial receive data. The other UUID fff6 is therefore likely serial transmit data.
Let's set up notifications for receiving on 0x000e, and then transmit on handle 0x0011.
One of the build targets of C Forth is bluez64, a host-based Forth with support for Bluetooth on the Linux kernel. It runs as a Linux process.
Building the target on 64-bit Linux needed a couple of trivial fixes, see my bluez64 branch.
git clone -b bluez64 https://github.com/quozl/cforth-1 cd cforth-1/build/bluez64 make ./forthResult is;
C Forth Copyright (c) 2008 FirmWorks ok █That's C Forth running. See Forth Lessons for a quick introduction to Forth.
Word | Stack effect | Meaning |
---|---|---|
bdaddr>binary | ( adr -- bdaddr ) | convert a hexadecimal string to a bluetooth address |
(connect) | ( bdaddr -- ) | connect to a bluetooth device |
.primaries | ( -- ) | print primaries |
.chars | ( -- ) | print characteristics |
.devname | ( -- ) | print device name |
.notifications | ( -- ) | print characteristics that support notifications and whether notifications are on or off |
notify-on | ( handle -- ) | turn on notifications for a characteristic handle |
wait-notify | ( timeout -- true | adr len false ) | wait up to timeout milliseconds for a notification, returning either true on timeout, or false with a pointer and length of the reply |
byte-write-handle | ( byte handle -- ) | write a byte to a handle |
write-handle | ( adr len handle -- ) | write a buffer to a handle |
ok " xxxxxxxxxxxx" drop bdaddr>binary (connect) ok █Where xxxxxxxxxxxx is the address shown by lescan without colons.
ok .primaries SIG 1800 S: 0001 E: 0007 SIG 1801 S: 0008 E: 000b SIG fff0 S: 000c E: ffff ok .chars SIG 2a00 H: 0002 P: 0a VH: 0003 SIG 2a01 H: 0004 P: 02 VH: 0005 SIG 2a04 H: 0006 P: 02 VH: 0007 SIG 2a05 H: 0009 P: 20 VH: 000a SIG fff7 H: 000d P: 10 VH: 000e SIG fff6 H: 0010 P: 0e VH: 0011 ok .devname MILO 000000 ok █
ok .notifications SIG fff7 H: 000d OFF ok e notify-on ok .notifications SIG fff7 H: 000d ON ok █
\ print a byte as two digits in hexadecimal : .2 (s n -- ) u>d <# # # #> type space ; \ print a buffer as hexadecimal : cdump.2 ( adr len -- ) bounds ?do i c@ .2 loop ; \ wait 100ms for a response from the device and if there is one dump it : .response ( -- ) #100 wait-notify 0= if cdump.2 then ; \ send a one-byte command and print a response if any : cmd ( n -- ) dup .2 ." --> " 11 byte-write-handle .response cr ;Now to use it;
ok 00 cmd 00 --> ok 01 cmd 01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 ok █So there was no reply to 00, but a 16-byte reply to 01.
Let's try another few bytes.
00 --> 01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 02 --> 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 04 --> 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 08 --> 88 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 10 --> 20 --> a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a0 40 --> 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 80 -->For some bytes, there was no response. Otherwise, there was a 16-byte packet, with the last byte equal to the first.
Let's try all 256 possibilities.
ok 100 0 do i cmd loop 00 --> 01 --> 81 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 02 --> 82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 03 --> 04 --> 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 05 --> 85 00 00 00 00 00 00 00 00 00 00 00 00 00 00 85 06 --> 07 --> 87 00 00 00 00 00 00 00 00 00 00 00 00 00 00 87 08 --> 88 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 09 --> 89 00 00 00 00 00 00 00 00 00 00 00 00 00 00 89 0a --> 8a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8a 0b --> 8b 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8b 0c --> 0d --> 0e --> 0f --> 8f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 8f 10 --> 11 --> 12 --> 92 00 00 00 00 00 00 00 00 00 00 00 00 00 00 92 13 --> 93 00 00 00 00 00 00 00 00 00 00 00 00 00 00 93 14 --> 15 --> 16 --> 17 --> 18 --> 19 --> 1a --> 1b --> 1c --> 1d --> 1e --> 1f --> 20 --> a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a0 21 --> 22 --> a2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a2 23 --> a3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a3 24 --> a4 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a4 25 --> a5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a5 26 --> a6 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a6 27 --> a7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 a7 28 --> 29 --> 2a --> 2b --> 2c --> 2d --> 2e --> ae 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ae 2f --> 30 --> 31 --> 32 --> 33 --> 34 --> 35 --> 36 --> 37 --> b7 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b7 38 --> b8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 b8 39 --> 3a --> 3b --> 3c --> 7c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7c 3d --> bd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 bd 3e --> be 00 00 00 00 00 00 00 00 00 00 00 00 00 00 be 3f --> 9b 04 07 00 00 00 00 00 00 00 00 00 00 00 00 a6 40 --> 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 40 41 --> 41 16 09 11 16 14 04 00 00 00 00 00 00 00 00 9f 42 --> c2 00 00 00 00 00 00 00 00 00 00 00 00 00 00 82 43 --> c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c3 44 --> 45 --> 46 --> 47 --> 48 --> c8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c8 49 --> c9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c9 4a --> ca 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ca 4b --> cb 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cb 4c --> cc 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cc 4d --> cd 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cd 4e --> 4f --> cf 00 00 00 00 00 00 00 00 00 00 00 00 00 00 cf 50 --> 51 --> 52 --> 53 --> 54 --> 55 --> 56 --> 57 --> 58 --> 59 --> 5a --> 5b --> 5c --> 5d --> 5e --> 5f --> 60 --> 61 --> 62 --> 63 --> 64 --> 65 --> 66 --> 67 --> 68 --> 69 --> 6a --> 6b --> 6c --> 6d --> 6e --> 6f --> 70 --> e0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e0 71 --> e9 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e9 72 --> 73 --> e3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 e3 74 --> 75 --> 76 --> 77 --> 78 --> 79 --> 7a --> 7b --> 7c --> 7d --> 7e --> 7f -->Everything from 80 and above returned nothing.
From this we can deduce a few more details.
\ print then send a buffer, and print the response : prod ( adr len -- ) ." > tx " 2dup cdump.2 cr ( adr len ) 11 write-handle ( ) ." < rx " .response cr ( ) ; \ a 16-byte buffer #16 buffer: mine \ send a command as a 16-byte packet with checksum : pcmd ( n -- ) mine #16 erase ( n ) dup mine c! ( n ) \ set the command mine #15 + c! ( ) \ use as checksum mine #16 prod ( ) \ send ;Now to try it out;
ok 01 pcmd > tx 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 < rx 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ok 41 pcmd > tx 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 41 < rx 41 16 10 01 22 47 51 06 00 00 00 00 00 00 00 28 ok █While the 01 command didn't seem to do anything, the reply was 01 instead of 81, suggesting that the high bit in 81 means an error.
But the 41 command was very interesting, it gave the date and time in year, month, day, hour, minute, second and day of week form.
ok 12 pcmd > tx 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 ok █At this point, hcidump showed a disconnect, the device powered down, and would not respond to touch. Bricked? No, it woke up in charger.
The 12 command did nothing previously, it was only in a packet that it worked.
From this we can deduce:
gatttool --device XX:XX:XX:XX:XX:XX -I [ ][XX:XX:XX:XX:XX:XX][LE]> connect [CON][XX:XX:XX:XX:XX:XX][LE]> char-write-cmd 0xf 0300 [CON][XX:XX:XX:XX:XX:XX][LE]> char-write-cmd 11 07000000000000000000000000000007 Notification handle = 0x000e value: 07 00 00 15 05 01 00 00 00 00 00 00 00 00 00 22 Notification handle = 0x000e value: 07 01 00 15 05 01 00 00 00 00 00 00 00 00 00 23 [CON][XX:XX:XX:XX:XX:XX][LE]>