The Australian government released it’s own contact tracing app last weekend, and while it is claimed that they will release the source code, that has not happened yet. Plenty has already been written about how we are told it works, and there is some great work being done on the decompiled code by @GeoffreyHuntley and others to document how it does work. I’m not digging into the code much myself, but I did examine some of the data stored on my own device.
To start with I took a rooted Android phone and installed the COVIDSafe app. I also installed it on another phone, and left them next to each other to commune for a while and collect some data.
From the package name,
au.gov.health.covidsafe, we know that it can store data in
/data/data/au.gov.health.covidsafe. Root access is needed to read this directory, so I plugged the rooted phone into a computer and pulled that directory for further inspection:
~/code/covid $ adb shell "su -c 'tar cf - /data/data/au.gov.health.covidsafe'" | tar xf - removing leading '/' from member names ~/code/covid $ tree data/data/au.gov.health.covidsafe data/data/au.gov.health.covidsafe ├── cache ├── code_cache ├── databases │ ├── record_database │ ├── record_database-shm │ └── record_database-wal ├── files │ └── packet └── shared_prefs ├── embryo.xml └── Tracer_pref.xml 5 directories, 6 files
The databases directory contains a single SQLite database along with it’s logs. With some whitespace reformatting, it has this basic structure:
SQLite version 3.31.1 2020-01-27 19:55:54 Enter ".help" for usage hints. sqlite> .tables android_metadata record_table room_master_table status_table sqlite> .schema android_metadata CREATE TABLE android_metadata ( locale TEXT ); sqlite> .schema record_table CREATE TABLE `record_table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , `timestamp` INTEGER NOT NULL , `v` INTEGER NOT NULL , `msg` TEXT NOT NULL , `org` TEXT NOT NULL , `modelP` TEXT NOT NULL , `modelC` TEXT NOT NULL , `rssi` INTEGER NOT NULL , `txPower` INTEGER ); sqlite> .schema room_master_table CREATE TABLE room_master_table ( id INTEGER PRIMARY KEY , identity_hash TEXT ); sqlite> .schema status_table CREATE TABLE `status_table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , `timestamp` INTEGER NOT NULL , `msg` TEXT NOT NULL );
android_metadata table isn’t all that interesting
sqlite> .headers on sqlite> select * from android_metadata; locale en_AU
room_master_table stores information about a Room, an Android provided abstraction over SQLite, and doesn’t store much.
sqlite> select * from room_master_table; id|identity_hash 42|XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
That ID is curious, though. The decompiled source of
androidx/room/RoomMasterTable.java reveals that one of the developers is a Hitchhiker’s Guide fan:
public static final String DEFAULT_ID = "42";
They also aren’t too fussed about data types, since this Java
String is being persisted to the database as an
status_table logs when bluetooth scanning starts and stops, and it had quite a lot of records:
sqlite> select * from status_table limit 4; id|timestamp|msg 1|1588065769226|Scanning Started 2|1588065777229|Scanning Stopped 3|1588065819972|Scanning Started 4|1588065827980|Scanning Stopped
The timestamps here are stored to the millisecond. It scans roughly once per minute, and from looking further the longer breaks matched up with when the screen was off.
sqlite> with q as ( ...> select ...> (lead(timestamp, 1, NULL) over (order by id) - timestamp) / 1000 diff ...> from status_table ...> where msg = 'Scanning Started' ...> ) ...> select ...> avg (diff) ...> , min(diff) ...> , max(diff) ...> from q; avg (diff)|min(diff)|max(diff) 62.3049886621315|44|841
record_table is where interactions with other devices are recorded.
(Note that while I’ve redacted the
msg values, I’ve kept their lengths and uniqueness in tact.)
sqlite> .headers ON sqlite> select * from record_table limit 5; id|timestamp|v|msg|org|modelP|modelC|rssi|txPower 1|1588065902522|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|AU_DTA|LG-H990|ONEPLUS A6003|-48| 2|1588065905633|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|AU_DTA|ONEPLUS A6003|LG-H990|-58| 3|1588066045043|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|AU_DTA|LG-H990|ONEPLUS A6003|-54| 4|1588066146769|1|BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB|AU_DTA|ONEPLUS A5010|ONEPLUS A6003|-95| 5|1588066289884|1|AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|AU_DTA|LG-H990|ONEPLUS A6003|-30|
There’s some accounting stuff, like
v (version?), and the repeated value of
AU_DTA (I’m guessing because this app was released for AUstralia by the Digital Transformation Agency).
There is also some Bluetooth signal information in
rssi is the Received Signal Strength Indication, a measure of the signal power, and can be a good measure of distance between devices. All of my
txPower values are
msg presumably holds the encrypted temporary ID’s being exchanged between devices.
Here we see the same (redacted) temp IDs being repeatedly exchanged over time until they are rotated out of use.
sqlite> select msg, count(*) from record_table group by msg; msg|count(*) BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB|6 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|38 CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC|6 DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD|26
modelC are the device models that are communicating, stored in glorious plaintext. As you can see, I tested this using a OnePlus 6 (
ONEPLUS A6003), and an LG V20 (
LG-H990), specifically the H990DS model.
As for the remaining files,
files/packet contains 120 characters of base64-encoded binary data, which from a cursory grep’ing seems to come from a Bluetooth broadcast, maybe one of the temporary IDs? I haven’t looked into it much other than to confirm that it is base64, and decodes to 89 bytes:
~/code/covid $ base64 --decode data/data/au.gov.health.covidsafe/files/packet | wc -c 89
shared_prefs contains some user settings and UI related stuff. Examples are shown here, with sensitive/encrypted parts redacted.
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <int name="layout_fail_decor" value="-1" /> <boolean name="hwui" value="true" /> <string name="layout_name_main">au.gov.health.covidsafe:layout/activity_onboarding</string> <int name="layout_theme_main" value="2132017394" /> <int name="layout_fail_main" value="-1" /> <int name="layout_decor" value="17367271" /> <int name="layout_theme_decor" value="2132017394" /> <boolean name="layout_preload_main" value="true" /> <boolean name="layout_preload_decor" value="true" /> <string name="layout_name_decor">android:layout/screen_simple</string> <int name="layout_main" value="2131558430" /> </map>
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <long name="NEXT_FETCH_TIME" value="XXXXXXXXXXXXX" /> <long name="EXPIRY_TIME" value="XXXXXXXXXXXXX" /> <string name="__androidx_security_crypto_encrypted_prefs_key_keyset__">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string> <string name="POST_CODE">XXXX</string> <string name="__androidx_security_crypto_encrypted_prefs_value_keyset__">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string> <string name="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</string> <string name="PHONE_NUMBER">XXXXXXXXXXXX</string> <boolean name="IS_ONBOARDED" value="true" /> <string name="DEVICE_ID">XXXXXXXXXXXXXXXX</string> <string name="NAME">XXXXXXXXXXXXXXX</string> <string name="AGE">XX</string> </map>
And that’s it. I’ve only included the data that came from my own devices, but from the limited time that I ran this in my apartment I also saw model values of
ONEPLUS A5010 for a OnePlus 5T, a device I remember from previously scanning bluetooth when pairing devices. This appeared in 6 of the 76 records I collected, so it’s likely a neighbour’s signal is peeking through the walls of my building, hovering around the power threshold.
The app is set to delete these records after 22 days, but as an avid data hoarder I will likely dump it periodically for safe keeping. It will make for a nice log of devices I come within sneezing range of.