Using C Forth to reverse engineer the Milo Champions Band fitness tracker protocol quozl@us.netrek.org | up |

2nd October 2016

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 000000

gatttool

fff6

fff7

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]>

fff7

fff6

Let's set up notifications for receiving on 0x000e , and then transmit on handle 0x0011 .

C Forth

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 ./forth

C Forth Copyright (c) 2008 FirmWorks ok █

Bluetooth Words

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

Connecting

ok " xxxxxxxxxxxx" drop bdaddr>binary (connect) ok █

lescan

Information

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 █

Enable Notifications

ok .notifications SIG fff7 H: 000d OFF ok e notify-on ok .notifications SIG fff7 H: 000d ON ok █

Sending Single Bytes

\ 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 ;

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 █

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 -->

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 -->

From this we can deduce a few more details.

all replies were 16-bytes and the 16th byte is a checksum, except for 42; perhaps a checksum failure test command,

most replies were the byte sent with the high bit set, except for 04, 3c, 3f, 40, 41, 70, 71, and 73; perhaps the high bit means an error reply,

command 41 reply contained the date and time as shown on the device; perhaps a READ CLOCK command,

Sending 16-byte Packets

\ 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 ;

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 █

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 █

hcidump

The 12 command did nothing previously, it was only in a packet that it worked.

From this we can deduce:

the device wants 16-byte packets with checksums, just like it sends us,

when the high bit is set in the reply, it means an error,

the 41 command sends back the date and time,

the 12 command powers down, for shipping,

Commands

43 sends back a histogram of activity, in 15 minute slots, 96 of them, for today, and also accepts an argument 01 for yesterday,

07 sends back a summary of activity, for today, and also accepts an argument 01 for yesterday,

11 sends back displayed values,

41 sends back the date and time,

01 sets the date and time,

2e reboots,

12 powers down, for shipping,

98 and 3f looks like a battery voltage measurement,

27 looks like a software version and date, "323030" followed by "151218",

3d changes the device name,

4d activates a vibration motor, with an incoming phone call icon,

4b sends back a 24-bit step goal,

0b sets the 24-bit step goal, which runs the vibration motor when the step counter reaches the goal,

20 shows an "APP PAIR" message for a few seconds,

09 reads steps and duration of exercise,

Alarms

24 initially returned zeroes,

23 changed what 24 returned, but only if byte 2 was a number between 0 through 4, and the observed change was only in bytes 3, 4 and 5,

23 is therefore a write,

24 is therefore a read,

byte 2 is an alarm number,

byte 3 is an enable mask, possibly single bit,

byte 4 is hour of day,

byte 5 is minute of hour,

bytes 6 to 11 can accept a zero or 1, but purpose unknown.

Cross checking with gatttool

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]>

Microscope photographs

DC input pins

DC charging bracket

Rear logo printing