The Xplora children's watch in my lab finally got the promised update! You know what that means...

Let's see what we can find in a casual reversing afternoon ✨
1/x

I expected that Xplora might change the debug PIN and maybe even use a new debug menu activity, so I beefed up my unlocker app with a custom reverse shell and the ability to sideload arbitrary java bytecode from the internet — fun exercise! Waste of time, too... my old debug pin generator works just fine for the new version 🎉
2/x

with adb enabled, we can pull firmware just like we did before and throw the results into JADX. we know that all the authentication stuff is implemented in the XPCommonService app, so let's look there first. first impressions:

- static secrets are gone! great!
- promising strings: ECDSA, AES-GCM, AndroidKeyStore
- new crypto library import with cute dragon mascot

3/x

the bread-and-butter authentication logic seems to be unchanged! they're still using the same encryptIMEI and encryptSignature functions to generate auth headers. with the static keys gone, key material has to come from somewhere though..
4/x
aha! the INIT_SECRET and INIT_KEY values come from the Config class, where they are set by a RegisterManager during, presumably, device registration.
let's actually see what that looks like to the user before we dig through more code
5/x

the watch is still paired using a QR code, but it looks a little different and no longer contains the device IMEI but what looks like a 6-byte random value (good!)

previously, just scanning the code was enough, but now users have to enter a 6-digit code on the watch to confirm the pairing. i'm not sure if that additional interaction actually adds security (scanning a code containing unpredictable (!) data should already be enough to establish physical presence), but it certainly doesn't hurt.

with that, attackers can no longer activate arbitrary watches from a distance. small wins! yay ✨
6/x

oh gods the crypto is still weird 😳
hang on

...okay, i *think* this is still in the realms where i can just post about it, so strap in 🧚🏻
(disclaimer: this is all at a glance, i may well be wrong about something)

high-level, the process to get credentials from the server looks like this:

1. send our encrypted* S/N to the server to get a QR code with a random identifier (as seen in #6)
2. user scans code, gets 6-digit pin from server, enters it on the watch
3. we build a request containing a random string, our encrypted* S/N and IMEI, the 6-digit pin, a timestamp, our ECDSA public key, and a matching ECDSA signature*
4. the server validates this request somehow, and sends us INIT_SECRET and INIT_KEY values in response

7/x

now, when i say 'encrypted', i'm referring to this new function here (code simplified by me).

it does AES-GCM, and it's the best cryptographic code i've seen from Xplora so far! sure, it calls the key a nonce for some reason, but it does authenticated encryption, hashes the key string before use (doesn't even use md5!), and uses a securely random IV every single time! 🤯

however...
8/x