The APK is stored in the firmware system
partition, and gets updated as a side-effect of OTA firmware updates. Thus, a certain OS version implies a particular APK version. The OS version numbers are more compact, so I'll use them to identify APK versions below.
v0.8.50
seems like a pre-prod version that accidentally got shipped on some early devices. Like all future versions, it sends the device's IMEI during account activation.
v0.8.67
is the "launch day" firmware. It sets the OS-Version
and App-Version
HTTP headers. It also sends the device's IMEI during authentication.
I don't have information for versions v0.8.77
, v0.8.78
.
v0.8.83
appends a hardcoded string to the OS-Version
HTTP header. This is the last release with no obfuscation whatsoever.
v0.8.86
introduces light-to-moderate obfuscation, and also takes steps to verify that it's running on a real R1 device. The actual behaviour of the app (in terms of how it talks to the API) is unchanged (except the hardcoded OS-Version
suffix has a new value).
v0.8.99
introduces heavier obfuscation, documented here. It adds some new app integrity checks, along with a new Device-Health
header, documented functionally here.
v0.8.103
makes no substantial changes.
Build.DISPLAY: rabbit_OS_v0.8.50_20240407162326
APK versionName: 0.8.0
APK versionBrief: VerInfo: 0.8.0/8000/mainEnv, BuildTime: 2024-04-06 20:41:33/GMT-08:00(by yongle), Branch: release_sprint1/49cf6491(has uncommitted change: true),
This version apparently shipped in "Batch 0" (early batch 1?) devices.
It uses API endpoint wss://q1-main.transactional.pub/session
, and prepends the string 0c8fef49-3dd7-4a3f-a199-f26e3452f6ed+
to the user's login token. It doesn't set any extra HTTP headers.
During account activation, the query parameter deviceId
is appended to the URL, with value set to the device's IMEI.
Build.DISPLAY: rabbit_OS_v0.8.67_20240420094131
APK versionName: 20240418.0-5-g4fd1bbff-dirty
APK versionBrief: VerInfo: 20240418.0-5-g4fd1bbff-dirty/2077777700/productionEnv, BuildTime: 2024-04-19 17:39:07/GMT-08:00(by jenkins), Branch: /4fd1bbff(has uncommitted change: true),
This is the version that most (?) Batch 1 devices shipped with, at launch. The APK was published on Twitter some time after.
The API endpoint moved to wss://r1-api.rabbit.tech/session
, the login token prefix switched to rabbit-account-key+
, and it adds the App-Version
and OS-Version
HTTP headers. The API authentication message now also includes the device's IMEI.
Build.DISPLAY: rabbit_OS_v0.8.77_20240430100257
TODO: I haven't yet seen this version.
Build.DISPLAY: rabbit_OS_v0.8.78_20240502195250
TODO: I haven't yet seen this version.
Build.DISPLAY: rabbit_OS_v0.8.83_20240509120550
APK versionName: 20240429.6-1-g66aa0f20
APK versionBrief: VerInfo: 20240429.6-1-g66aa0f20/2077777700/productionEnv, BuildTime: 2024-05-08 20:03:39/GMT-08:00(by jenkins), Branch: /66aa0f20(has uncommitted change: false),
A static string is appended to the OS-Version
HTTP header, which is returned by the getKey
native method (stored in libbase.so
).
Build.DISPLAY: rabbit_OS_v0.8.86_20240523151103
APK versionName: 20240517.18-dirty
APK versionBrief: VerInfo: 20240517.18-dirty/2077777700/productionEnv, BuildTime: 2024-05-22 23:08:36/GMT-08:00(by jenkins), Branch: /40f4615a(has uncommitted change: (true),
This version introduces light obfuscation (symbol shortening) to the Java/Kotlin code.
The getKey
native method is now moderately obfuscated (control flow flattening, string "encryption"), and attempts to verify that it's running on a real R1 device, before returning a similar hardcoded static string (different to the one in the previous version).
It checks for the presence of /sys/devices/platform/step_motor_ms35774/orientation
(part of the R1's GPL-violating stepper motor driver, used for camera rotation).
It also checks the contents of /proc/cpuinfo
, looking for Hardware\t: MT6765
.
It also checks the android system property ro.product.model
, looking for value r1
Build.DISPLAY: rabbit_OS_v0.8.99_20240606175556
APK versionName: 20240603.15-dirty
APK versionBrief: VerInfo: 20240603.15-dirty/2077777700/productionEnv, BuildTime: 2024-06-06 01:50:56/GMT-08:00(by jenkins), Branch: /8d8ca2f4(has uncommitted change: true),
This version introduces heavier obfuscation, which I'm documenting separately, here. The main change is that many Java methods have been converted into obfuscated native methods. Some of the assets seem to be encrypted, and I haven't figured out how that works yet. I'll likely need an R1 in-hand to make progress on that front.
A new HTTP header was added, Device-Health
, which I document the functionality of in more detail in my API Docs.
Implementation-wise, the Device-Health
header value is computed using new native methods added to libbase.so
. getKey
no longer attempts to assess device integrity, instead returning a new hardcoded string unconditionally. The integrity checks were moved into a new function, getHealth
, which performs the aforementioned checks, and some new checks, before returning a timestamp string. This timestamp string gets encrypted by another native method, loadPublicKeyAndEncrypt
, which uses OpenSSL (staticly linked) to RSA-encrypt the timestamp string, using a hardcoded RSA public key.
The new checks are:
-
Checks that the APK was signed by one of two hardcoded signing keys (one is used in prod, and presumably the second is used during dev/testing)
-
Checks that the APK's package name is
tech.rabbit.r1launcher.r1
Build.DISPLAY: rabbit_OS_v0.8.103_20240620101341
APK versionName: 20240615.10-dirty
APK versionBrief: VerInfo: 20240615.10-dirty/2077777700/productionEnv, BuildTime: 2024-06-19 18:08:45/GMT-08:00(by jenkins), Branch: /039d6627(has uncommitted change: true),
There's a new OS-Version
suffix, but the overall behaviour of the app is otherwise unchanged, in terms of how it communicates with the API.
Build.DISPLAY: rabbit_OS_v0.8.107_20240701130253
APK versionName: 20240626.7-dirty
APK versionBrief: VerInfo: 20240626.7-dirty/2077777700/productionEnv, BuildTime: 2024-06-30 20:58:25/GMT-08:00(by jenkins), Branch: /b9373d4b(has uncommitted change: true),
There's a new library dependency, libsqlcipher.so
(https://github.com/sqlcipher/sqlcipher). It's not obfuscated at all.
libbase.so has a new RSA public key: (actually this is the same key, just encoded in a slightly different format. Oops!)
-----BEGIN RSA PUBLIC KEY-----
MIIBigKCAYEAqLNRPcujKw1elkNJc+10o37YVbb7OjYa4Cv2pG2BzfSV3Ev7LMva
A2w0PAy25DhQU2NI7RU2a51OvTz0DsXM69oakuN0oSrKa9Eit2GPnX89H702MXGX
iRDZWEufAx67AaxK9d80Bajh2Abn06Bwaz9Z4D8vMxUOGsYkVKMW0LrmnW4984XI
UqT3+lOiEijBamodU/mORTeuxc5cdan00fq8qTOYuGFuKlPJSI3EExFHP3ONHD6z
44+PxXmhw532uAiNnT74yKXBoVYU19b8AAWLiSKyjf1eeus7dTobPKcpMemlJgxH
tVHtaSgnUugQ0a3XvmTVQpSeytPw8bL+/3c5KXfjGxPchoEZi7d71wv/AufDiSXr
gaew1KfJZBsr8Somr03b8xsHRJruPT61iPceh9bTWscwnK3WmDpAxnjdPQiflt/m
KkPEETtKGx0X5kUImHnr1jhUdYKmEOHfwkXBKVc66hpn85WGJ7MPVyixIOpzScAY
nKjVsP4ma6iFAgMBAAE=
-----END RSA PUBLIC KEY-----