micolous / helvetic

Hacking / reverse engineering the Fitbit Aria (WiFi enabled bathroom scales)
GNU Affero General Public License v3.0
61 stars 11 forks source link

user management woes #2

Open urandom2 opened 7 years ago

urandom2 commented 7 years ago

The user blob is being accurately returned to the scale as part of ScaleUploadView(), I have confirmed that all my users are in the scale_users set and the response blob, but the scale never recognizes them.

This may be an issue with my management of the database, but I am looking for good ways to debug this, since the aria is such a black box.

micolous commented 7 years ago

Yeah, I got the user blob wrong, which is causing problems when there is >1 user.

When I picked this up again recently I got offered a firmware upgrade, so I started writing notes:

https://github.com/micolous/helvetic/commit/8d8838d96ed2bf8ee3f9677e776ed3781eb0235e#diff-7327496955357b9792bab8720dc507d6

But when I last looked at this code (2014), there wasn't such a firmware update available.

However I haven't actually put that into the testserver or Django implementation of the protocol yet. As a result, the scales won't work properly with this if you have >1 user.

urandom2 commented 7 years ago

hmm, I consulted your notes and came up with the following diff:

@@ -161,8 +160,7 @@ class ScaleUploadView(View):
                                if min_var < 0:
                                        min_var = 0
                                max_var = last_weight + 4000
-
-                       response += struct.pack('<L16x20sLLLBLLLLLLLLL',
+                       response += struct.pack('<L16x20sLLLBLLLLLLL',
                                profile.user.id,
                                profile.short_name_formatted(),
                                min_var,
@@ -177,14 +175,15 @@ class ScaleUploadView(View):
                                0, # another weight
                                0, # timestamp

-                               0, # always 0
-                               3, # always 3
                                0  # always 0
                        )

-               response = response + struct.pack('<HBB',
+               response = response + struct.pack('<LLLHBB',
+                       0x03, # update status: no
+                       3, # unknown
+                       0, # unknown
                        crc16xmodem(response), # checksum
-                       0x66, # always 0x66
+                       0x66, # always 0x66 sometimes also 0xac
                        0x00, # always 0x00
                )

however, this triggers a NO SYNC error; it is worth noting that I am attempting to push two users. I have also tried this:

@@ -161,8 +160,7 @@ class ScaleUploadView(View):
                                if min_var < 0:
                                        min_var = 0
                                max_var = last_weight + 4000
-
-                       response += struct.pack('<L16x20sLLLBLLLLLLLLL',
+                       response += struct.pack('<L16x20sLLLBLLLLLLL',
                                profile.user.id,
                                profile.short_name_formatted(),
                                min_var,
@@ -177,14 +175,14 @@ class ScaleUploadView(View):
                                0, # another weight
                                0, # timestamp

-                               0, # always 0
-                               3, # always 3
                                0  # always 0
                        )

-               response = response + struct.pack('<HBB',
+               response = response + struct.pack('<LLHBB',
+                       0x03, # update status: no
+                       0, # unknown
                        crc16xmodem(response), # checksum
-                       0x66, # always 0x66
+                       0x66, # always 0x66 sometimes also 0xac
                        0x00, # always 0x00
                )

but this too had no success; my thought process was that since the current code does succeed for 1 user that the 3 and 0, uint32 values should be bottom factored out of the loop; am I misunderstanding your notes, or is it time to break out the proxies and capture some packets going to the fitbit servers?

urandom2 commented 7 years ago

p.s. my suggested implementation breaks the crc16 checksum, and potentially has the wrong magic number at the end (0x66 is given by fitbit for 1 user, 0xb3 is given for 2)

micolous commented 7 years ago

Ah, so was the version in that pull request functional or not? Did you solve it by using that trailer value?

urandom2 commented 7 years ago

oh, sorry I forgot to document my later findings; it looks like that trailer value is some sort of length parameter that is correlated with the number of scale users

this formula gives the accurate value:

0x19 + (len(scale_users) * 0x4d)

so, you should be in buisness if the last response = response + stanza looks like this:

response = response + struct.pack('<HH',
        crc16xmodem(response), # checksum
        0x19 + (len(scale_users) * 0x4d), 
)

I can provide intercepted packets if you like.