The Fitbit is a small clipon accelerometer that functions as a pedometer, distance meter, and calorie counter. It uses the ANT protocol to talk to a base, connected to a computer via USB. This document outlines communication both with the base unit/device via USB, as well as synchronizing with fitbit’s web service.
We recommend the following references to go along with this document:
-
ANT Message and Protocol Documentation: http://www.thisisant.com/images/Resources/PDF/1204662412_ant%20message%20protocol%20and%20usage.pdf
-
FitBit FAQ: http://www.fitbit.com/faq
-
FitBit Manual: http://www.fitbit.com/manual
-
Base: Refers to the ANT base station, connected by USB to the host computer.
-
Tracker: The clip-on Fitbit device
Packet layouts in this document will use the full ANT packet to keep things consistent. The packet layout is documented in the protocol/message document, but is also listed here for sake of clarity:
-
Byte 0x0: Always 0xA4
-
Byte 0x1: Packet Data Length (up to 0x9)
-
Byte 0x2: Packet Type
-
Byte 0x2 + Packet Data Length: 8-bit checksum consisting of XOR’d bytes of packet. In packet layouts where we have variable bytes, the checksum is represented as ??.
As this section will probably be updated frequently, I’m putting it near the top for now. This lists things I haven’t figured out about the fitbit’s protocol and client yet.
-
How the erase opcodes work
-
Exactly what data we’re getting from the website to send to the device in the erase step
-
How the seconds data is stored/parsed
-
What the encrypted attribute on opcodes in the website response xml means
-
How authentication setup with the website works
-
How device bonding with the website account works
Reverse engineering the Fitbit is relatively easy. From the USB side, it is not using encryption to talk to the base station, and it communicates using an unmodified ANT protocol, so the basic gist of messages can be learned simply by reading the ANT protocol documentation. Any USB based filter driver may be used to log the data flowing to and from the base station.
For further analysis, the Fitbit’s service logs are amazingly helpful. For the windows installation of the Fitbit software, the logs are located at
C:\Documents and Settings\All Users\Application Data\Fitbit\Tracker\logs
Daily logs are stored in plain text with the filename format
log-YYYYDDMMHHMMSS.txt
There may be multiple log files generated. Each log file has a complete record of both USB and website communications, with copious amounts of annotation. Each call to and response from the device and website is logged and even sometimes documented.
The USB information in the logs truncates most of the ANT protocol specific information. USB communication looks something like
02/02 22:25:36 Setting channel id [number: 65535, type: 1, transmission: 1]... 02/02 22:25:36 [40] 0x00, 0x51, 0x00 02/02 22:25:36 Channel id set. 02/02 22:25:36 Opening channel... 02/02 22:25:36 [40] 0x00, 0x4b, 0x00 02/02 22:25:36 Channel opened. 02/02 22:25:42 [9A] 0x00, 0x58, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Acquired beacon. Sending RESET packet... 02/02 22:25:42 Queuing packet to send... 02/02 22:25:42 >>>>> SEND 0x78, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Sending 8 bytes of ACK data... 02/02 22:25:42 [9A] 0x00, 0x58, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 [05] 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Pending acks: 0 02/02 22:25:42 Tracker reset. Sending LINK REQUEST... 02/02 22:25:42 Queuing packet to send... 02/02 22:25:42 >>>>> SEND 0x78, 0x02, 0x8b, 0x7a, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Sending 8 bytes of ACK data... 02/02 22:25:42 [9A] 0x00, 0x58, 0xf7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 [05] 0x00, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Pending acks: 0 02/02 22:25:42 Closing channel... 02/02 22:25:42 [40] 0x00, 0x4c, 0x00 02/02 22:25:42 Close request acknowledged... 02/02 22:25:42 [02] 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 02/02 22:25:42 Waiting for close message -- ignoring incoming data 02/02 22:25:42 [07] 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
This is not a complete log of USB messages, however, the explanations of some of the messages are quite useful for translating the actions into code.
While working on the web communication, it was also found that error messages about communications flow were quite helpful. Errors are served as a comment block at the beginning of a response. For example:
<!-- Error: getDeviceInfoHandler should be getting result of one opCodes --> <!-- Error: Tracker object is null. This is impossible within normal flow. Something is hitting com.fitbit.app.device.tracker.dumpData.DumpDataActionBeanviolating a predefined flow. -->
These logs and messages were massively helpful in finishing the web protocol commmunication. Many thanks to the fitbit engineers for writing readable error messages and logs.
Not everything is happiness and sunshine, however.
While information stored on Fitbit’s website may not be of the utmost privacy importance (though having random people know when you sleep is a little creepy), this information should still be guarded by at least semisecure means. However, there are multiple situations where this is not the case.
During initial login via the client software, user passwords are passed to the website as part of POST data, in the clear. They are also stored to the text log files in the clear. This can be seen in the following log block, with my personal account data removed.
01/28 23:31:55 Sending 4357 bytes of HTML to UI...
01/28 23:31:55 Processing request...
01/28 23:31:55 Waiting for minimum display time to elapse [1000ms]...
01/28 23:31:56 Waiting for form input...
01/28 23:31:57 [POWER EVENT] POWER STATUS CHANGE
01/28 23:32:04 [POWER EVENT] POWER STATUS CHANGE
01/28 23:32:14 UI [\\.\pipe\Fitbit|kyle]: F
01/28 23:32:14 Processing action 'form'...
01/28 23:32:14 Received form input: email=[YOUR UNENCRYPTED EMAIL HERE]&password=[YOUR UNENCRYPTED PASSWORD HERE]&[other stuff]
01/28 23:32:14 Connecting [2]: POST to http://client.fitbit.com:80/device/tracker/pairing/signupHandler with data:
email=[YOUR UNENCRYPTED EMAIL HERE]&password=[YOUR UNENCRYPTED PASSWORD HERE]&[other stuff]
01/28 23:32:14 Processing action 'http'...
01/28 23:32:14 Received HTTP response:
The URLs and POST data are saved, and the processing action is "http", not "https". Oddly enough, login on Fitbit’s actual website is https.
When syncing data to the website, no authentication is used, and all requests are sent in clear http. The tracker ID is bonded to the account of the user, and when the website receives the tracker ID, it responds with the user ID. Once again, since all of this happens in the clear, it would be easy to inject data into anyone’s account, via either ANT or website sniffing. Why one would do this is beyond me, just saying, is all.
Communication with the website happens via HTTP requests to a REST API, with replies of XML blocks. These blocks contain opcodes to send to the device, as well as information the website will need to identify the device in later commands.
Opcodes and their results are encoded via base64.
The data flow between the website and the client happens in the follow order (all commands go to http://client.fitbit.com, REST locations are listed below):
-
Client receives beacon from tracker, establishes link (see Fitbit Communication section)
-
Client contacts website at /device/tracker/uploadData, sends basic client and platform information
-
Website replies with opcode for tracker data request
-
Client gets tracker data (serial number, hardware revision, etc…), sends base response. Sends to /device/tracker/dumpData/lookupTracker
-
Website replies with website tracker and user ids based on tracker serial number, and opcodes for data dumping
-
Client dumps data from device, sends to /device/tracker/dumpData/dumpData
-
Website replies with commands to erase data from device, and synchronize time (and possibly data?) with website.
-
Client sends successful responses on all opcodes (but no specific data) to website at /device/tracker/dumpData/clearDataConfigTracker
-
Website replies with command to close tracker, and have the beacon sleep for 15 minutes (probably conserves power).
<?xml version="1.0" ?>
<fitbitClient version="1.0">
<response host="client.fitbit.com" path="/device/tracker/dumpData/dumpData" port="80">trackerPublicId=[REDACTED]&userPublicId=[REDACTED]&deviceInfo.serialNumber=[REDACTED]&deviceInfo.hardwareRev=10&deviceInfo.bslVerMajor=2&deviceInfo.bslVerMinor=23&deviceInfo.appVerMajor=2&deviceInfo.appVerMinor=23&deviceInfo.inModeBSL=false&deviceInfo.onCharger=true&parseData=on</response>
<device type="tracker" pingInterval="4000" action="command" >
<remoteOps errorHandler="executeTillError" responder="respondNoError">
<remoteOp encrypted="false">
<opCode>IgUAAAAAAA==</opCode>
<payloadData></payloadData>
</remoteOp>
<remoteOp encrypted="false">
<opCode>IgQAAAAAAA==</opCode>
<payloadData></payloadData>
</remoteOp>
<remoteOp encrypted="false">
<opCode>IgIAAAAAAA==</opCode>
<payloadData></payloadData>
</remoteOp>
<remoteOp encrypted="false">
<opCode>IgAAAAAAAA==</opCode>
<payloadData></payloadData>
</remoteOp>
<remoteOp encrypted="false">
<opCode>IgEAAAAAAA==</opCode>
<payloadData></payloadData>
</remoteOp>
</remoteOps>
</device>
</fitbitClient>
This section covers communicate with the base and tracker via USB and ANT, in the context of the synchronization communications with the website.
It is assumed that the reader is familiar with the USB and ANT protocols. For those not familiar with ANT, it’s fairly easy to follow along with the Message and Protocol documentation in the references section. For those not familiar with USB, go outside and play. Preferably with your fitbit on.
Initializing the base unit happens whenever the unit is plugged in, or the client program is brought up. This consists of sending USB control messages and ANT protocol messages to configure the device to the right baud rate, channel, etc…
To start setting up the base station, we send a bank of control messages. The following is the control messages from the python library, with
-
Argument 1: bmRequestType
-
Argument 2: bmRequest
-
Argument 3: wValue
-
Argument 4: wIndex
-
Argument 5: data (If a list, send data. If an int, receive data.)
ctrl_transfer(0x40, 0x00, 0xFFFF, 0x0, [])
ctrl_transfer(0x40, 0x01, 0x2000, 0x0, [])
ctrl_transfer(0x40, 0x00, 0x0, 0x0, [])
ctrl_transfer(0x40, 0x00, 0xFFFF, 0x0, [])
ctrl_transfer(0x40, 0x01, 0x2000, 0x0, [])
ctrl_transfer(0x40, 0x01, 0x4A, 0x0, [])
# Receive 1 byte, should be 0x2
ctrl_transfer(0xC0, 0xFF, 0x370B, 0x0, 1)
ctrl_transfer(0x40, 0x03, 0x800, 0x0, [])
ctrl_transfer(0x40, 0x13, 0x0, 0x0,
[0x08, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00])
ctrl_transfer(0x40, 0x12, 0x0C, 0x0, [])
We then receive a packet of information, usually a reset validation packet.
After this, we initialize the base channel (via ANT messages) to:
-
Period: 0x1000
-
Frequency: 0x2
-
Transmit Power: 0x3
-
Search Timeout: 0xFF
-
Channel ID - Device Number: 0xFFFF, Device Type ID: 0x01, Trans Type: 0x01
This sets up the base to listen for the beacon packet from the tracker device.
Once the base is configured, it starts sending out read requests on channel 0. Assuming it hasn’t been told to sleep, the tracker sends out a beacon once a second on channel 0. The beacon is a data packet that looks like:
--> A4 09 4E 00 00 00 00 00 00 00 00 00 E3
Once the beacon is received by the base station, the base station sends a reset packet:
--> A4 09 4F 00 78 00 00 00 00 00 00 00 9A
Then, assuming the tracker is not on the base, it sends a "Channel Hop" message:
--> A4 09 4F 00 78 02 XX XX 00 00 00 00 ??
Where XXXX is a random 16-bit little-endian number denote the new channel for the device to talk on. The device and base switch to that channel, and the base runs the channel initialization sequences listed in the last step again, except this time with the channel ID set to the XXXX value instead of 0xFFFF. The base waits for the device beacon on this channel, and once received, initializes transfer.
Non-burst-data packets to the tracker take the form:
--> A4 09 4F 00 3W XX 00 00 00 00 00 00 ??
-
W - Starts at 0x9, increments on every new packet send, wraps at 0xF to 0x8. From here on out, refered to as the sequence nibble.
-
XX - Command to send
-
All bytes after YY up to checksum - Data
We will usually get two packets back from the tracker
<-- A4 03 40 00 01 05 E3 <-- A4 09 4F 00 3Y ZZ 00 00 00 00 00 00 ??
-
Y - Should match W from packet sent
-
ZZ - Return status
If burst data is sent after the status is received, burst data packets will follow the 0x2X/0x4X/0x6X format outlined in the ANT documentation. See the ANT documentation on burst transfers for more info.
Now that the base is connected to the tracker, we can start querying the tracker for information about itself.
--> A4 09 4F 00 3X 24 00 00 00 00 00 00 ?? - Query <-- A4 03 40 00 01 05 E3 - ANT Acknowledgment <-- A4 09 4F 00 3X 42 00 00 00 00 00 00 ?? - Device Contact Success --> A4 09 4F 00 3Y 70 00 02 00 00 00 00 ?? - Query for Info <-- A4 03 40 00 01 05 E3 - ANT Acknowledgment <-- A4 09 50 00 3Y 81 0C 00 00 00 00 ?? ?? - Start burst, 0x0C length, bytes after length random? <-- A4 09 50 20 GG GG GG GG GG HH II JJ ?? - Device Data <-- A4 09 50 C0 KK LL MM NN OO 00 D9 BD ?? - More Device Data
Taking the last two Device Data Portions:
-
Bytes 0-4 (G) - 5 byte Device Serial Number
-
Byte 5 (H) - Hardware revision
-
Byte 6 (I) - BSL Major Version
-
Byte 7 (J) - BSL Minor Version (i.e. II.JJ = bsl version)
-
Byte 8 (K) - App Major Version
-
Byte 9 (L) - App Minor Version
-
Byte 10 (M) - In BSL Mode
-
Byte 11 (N) - Is Device currently on the charger?
-
Byte 12 (O) - Unknown?
All remaining bytes up until checksum are ignored, can be anything.
Once we’ve gotten the tracker information, it’s time to pull the data off the device. We send the following packet to the tracker:
--> A4 09 4F 00 3W 22 0X 00 00 00 00 00 ??
-
W - Sequence nibble
-
X - Data Type that burst requests will send back. Conjectures listed below based on value.
-
0 - Daily information, or information generated on every read of the tracker?
-
1 - 5 minute interval data
-
2 - Second Information? Only seems to return last few seconds.
-
3 - Haven’t tried it
-
4 - Unknown
-
5 - Unknown
-
After we send this, we get back the usual acknowledgement:
<-- A4 03 40 00 01 05 E3
And then begins the data dumping
--> A4 09 4F 00 3W 60 00 02 XX 00 00 00 ??
-
W - Sequence nibble
-
XX - Memory Bank ID we’re querying
The memory bank id goes from 00 to whenever we’re out of data, which we can tell by the burst data returned from the command
<-- A4 03 40 00 01 05 E3 <-- A4 09 50 00 3W 81 YY YY 00 00 00 ?? ??
-
W - Matching sequence nibble for input command
-
YYYY - 16 bit little endian value for data length being transmitted, starting at the beginning of the next packet. We get 8 bytes of data per burst packet.
If YYYY is 0, we know that we’re querying a null bank, and have hit the end of the information type we’re currently getting. We can append all of the data received from all burst commands for this data type.
This section outlines the data sent over during the "Data Dump" portion of the communications.
e0 1d 52 4d 0f 02 05 85 00 00 e3 0b 00
XX XX XX XX ?? ?? ?? ?? ?? ?? ?? ?? ?? ??
-
XXXXXXXX - 32-bit little endian seconds since Jan 1 1970
4d 52 22 c0 81 1c 00 4d 52 27 ac 85 19 00 85 1c 13
XX XX XX XX MM NN OO
-
XXXXXXXX - 32-bit little endian seconds since Jan 1 1970
-
MM - Unknown, always seems to start around 0x81?
-
NN - Unknown
-
OO - Unknown