Android & AirTags (Part II)


Summer (and DFIR conference season) is in full swing, so things have been busy! However, an opportunity presented itself recently that I could not allow to pass: revisiting a subject I previously wrote about. Things change so quickly in DFIR. App developers add new functionality and change or remove existing functionality often. Threat actors change their methods. Also often. There is a good chance that by the time a researcher actually does the research, writes, edits, and publishes things have changed. It’s just the nature of our business, and, while that is really annoying, it does keep things exciting.

All the way back in January of 2022 I wrote about AirTags and Android and how the latter had minimal options when it came to detecting the former. Now, things have changed a bit with Google’s recent announcement, so I thought it would be great to look at those changes.

OS-Level? Not Quite.

Within the past week, Google released their long-awaited AirTag detection feature. Code for this detection feature has existed in some form in the Google Mobile Services apk since March of 2022, but was not released until last week when Google made its announcement and pushed the Play Services update. In the meantime, Apple announced they were partnering with Google to establish a standard for detecting unwanted trackers. The partnership includes accepting input “industry partners” so I can only assume that it will (hopefully) include third-party developers like Tile, Samsung, Chipolo, and Cube.

One thing I do want to explicitly point out is that detections are not exactly at the OS-level. Google states phones running Android Marshmallow (6) and newer will have the tracker detection feature so long as they have the latest version of Play Services installed. So, if an examiner does encounter a grapheneOS (or other non-GMS OS) phone that does not have Play Services installed, they will likely not find the data discussed below.

What The User Sees

Within Android detecting trackers is called “Unknown tracker alert” and can be found in Settings > Safety & emergency > Unknown tracker alerts. See Figure 1.

Figure 1. Top level of the Unknown tracker alert menu.

This is a basic interface. There are only two things a user can do here: see alerts for detected trackers or manually initiate a scan for nearby trackers. While this feature is on by default, a user has to opt in for alerts, which seems counter-intuitive. When I first navigated to that part of the menu I was immediately prompted to declare my intentions.

Starting a manual scan takes a user to a new screen. See Figure 2.

Figure 2. Manual scan for trackers.

Note the informational message at the top that indicates manual scans are not saved. While this is correct from the user’s point of view, it is not when it comes to the forensics. More on that later.

If a tracker is detected, a user can press on the alert seen in Figure 1 which takes them to a new screen seen in Figure 3.

Figure 3. Detection details.

Here a user can see information about a tracker that was automatically detected, including the location(s) where it was detected. A user can also trigger the tracker to play a noise or get instructions on how to disable it. See Figure 3a.

Figure 3a. Map of detected AirTag with additional options.

Notifications are standard Android notifications. Clicking on one will take a user to the view seen in Figure 3. See Figure 4.

Figure 4. A notification.

I bring this up to make a point: regardless of whether the phone being examined has thrown an alert for an unknown tracker, there is still a really good chance that an examiner will find tracker information if the phone ever detected one within a certain amount of time of the extraction.

Tracking The Trackers

Before the forensics, I want to address scanning and intervals. I only have anecdotal observations, but I think it is important to share them. The big feature of Google’s detection implementation is that it can passively scan without a user needing to do anything. During testing, I had two AirTags (both of which were physically far away from their respective owners) and a Pixel 5a and 6a. Both phones were up-to-date with their respective SPL, and, obviously, were running the latest version of Play Services. Here is what I observed:

  • Passive interval scanning did not start until there was a substantial amount of movement. This usually involved me traveling at least approximately 1.00 – 2.00 miles (1.60 to 3.21 kilometers) before the first scan; simply walking around my house for 30 minutes (I actually did that) would not trigger a scan. I found that this could vary depending on the rate (speed) of travel. For example, when walking, the distance traveled before the first scan was shorter than it was if I were driving. This may have had an affect on the next item.
  • Scanning typically started anywhere between 20 and 30 minutes after any substantial movement. I also observed longer first-scan times up to one (1) hour after continuous movement.
  • Scanning frequency after the first scan was higher during movement and decreased over time after movement stopped. After the first scan, I saw scanning intervals anywhere between 7 and 15 minutes with an average of around 10 minutes during movement. When movement stopped (e.g. I arrived at my intended destination) the scanning frequency decreased over time until they stopped until the next substantial movement was detected.
  • Even though a phone says it has not detected any tags does not mean it has not seen any tags. Remember, the intent of the feature is to detect unknown tags traveling with the phone, not detecting every tracker a phone may come in contact with. If Android were to alert the user to the latter, it could overwhelm the user and desensitize them to any alerts that may be thrown. Obviously, some logic is being applied with the detection process.
  • Time to first scan and scanning interval during movement varied between the Pixel 5a and 6a.

The last two notes here are probably the most important. First, examiners should evaluate the artifacts discussed below. If the feature is active and the phone has seen any trackers, location data for the phone will be available. Second, there are going to be differences between phones, so the mileage will vary on the above points.

Since Unknown Tracker Detection is part of Google Play Services, the data related to it is found in the /data/data/ directory path. The first, and arguably the most important, file is personalsafety_db which is found in ~/databases/. The first table of interest is DeviceData. See Figure 5.

Figure 5. Table DeviceData

This table tracks detected devices which have an associated alert, and holds its data for approximately 48 hours. The columns are straight forward. The column macAddress is the Bluetooth MAC address for the AirTag that has been identified as “unwanted.” The column creationTimestampMillis represents when the AirTag’s entry was created in this table. The lastUpdatedTimestampMillis represents when the record was last updated, and the column alertStatus represents the status of the unwanted tracker alert. I have observed two (2) status types, SENT and STAGED, both of which are seen in Figure 5. The former indicates the notification has been delivered to the user and the latter indicates the user has yet to be notified about the tracker. So far, I have observed that both timestamps are the same when the alertStatus is STAGED. When the alert is listed as SENT, the timestamp in lastUpdatedTimestampMillis is upated. See Figures 6 and 6a.

Figure 6. A staged notification.
Figure 6a. A sent notification.

Figure 6b shows the notification as it appeared to the user.

Figure 6b. The SENT notification.

The other field that is interesting in this table is deviceType. I could not locate anything in the apk that indicated what “1” represented, but each entry I have seen here has had a value of “1.” I suspect that once Google is able to scan for devices from other vendors, other values will appear here.

The other table in the database is Scan. See Figure 7. Note that the timestamps are stored in Unix Epoch timestamp. I converted them to human-readble for purposes of this post.

Figure 7. Table Scan.

This table, like DeviceScan, holds its data for approximately 48 hours. It contains all of the AirTags that have been within range of the phone during both passive and manual scans. It is no secret that AirTags rotate their Bluetooth MAC addresses periodically, so it is possible that the same physical tag will appear in this table multiple times using different MAC addresses. The data seen in Figure 6 represents only two AirTags, but there are multiple MAC addresses in the column macAddress. The timestamps seen in the columns creationTimestampMillis and lastUpdatedTimestampMillis seem to always be the same; I have not yet seen them be different. Regardless, they are the time when the AirTag was detected by the phone.

The column state contains two values: STATE_STILL and STATE_MOVING. The former is indicative of when a tag is detected using a manual scan, while the latter is indicative of when an AirTag is detected passively; the phone may or may not necessarily be “in motion.” The columns bleScan and locationScan contain protobuf blob data. bleScan contains some duplicative data and an interesting data point. See Figure 8.

Figure 8. bleScan protobuf data.

The data in the orange and red boxes in Figure 8 are the Bluetooth MAC address and the time the AirTag was scanned, respectively, and are repeats of what is seen in other columns. The data in the blue is box is unique. While I have not been able to confirm this within the apk, it is likely this is the Received Signal Strength Indicator (RSSI) value, which is a measure of signal strength as measured by the phone. It may, and I mean may, be indicative of proximity of the AirTag to the phone (i.e. a better RSSI value could mean the AirTag may be physically closer to the phone). Obviously, environmental factors such as physical barriers, other radio signals, and AirTag battery levels can affect signal levels, so I would not say this is an outright indicator of proxmimity but it can give you a general idea. You can learn about Bluetooth RSSI here.

The last column in Figure 7 is the key. See Figure 9.

Figure 9. locationScan protobuf data.

Figure 9 has two important data points. First, in the red box is the GPS coordinates for the phone. I have found the coordinates to be relatively accurate, but there are times where it isn’t 100% accurate, but that has been when I was inside a building or in a more remote part of my area where cellular service is spotty. I suspect one of the two remaining, non-highlighted values are likely horizontal accuracy but I have not been able to confirm this as the values fluctuate quite a bit. Regardless, make sure you corroborate your data. See Figure 10 for the GPS coordinates on a map.

Figure 10. Mapped coordinates. I was there at that time!

I can confirm that I was in Smashed Burgers & Cocktails at this time, picking up food, and I had two AirTags with me. The green box in Figure 9 has the timestamp of the GPS coordinates. I have observed there are times when there is a slight delay between when the AirTag is detected and when the information is created in this table (again, the column creationTimestampMillis). In this example, there is an approximate delay of one minute and a half, but have seen exact mathces between timestamps and I have seen timestamps that are off by a few seconds. The best timestamp, IMHO, is the timestamp created in the protobuf blob.

There are two additional files within the GMS sandbox I want to mention, both of which are in the ~/files/personalsafety/shared directory path. Both files contain protobuf data. The first is personalsafety_info.pb. See Figures 11 and 12.

Figure 11. Last scanned timestamp.

The file has a single Unix Epoch timestamp in it. It represents the last time the phone conducted a scan. This timestamp holds true even if a user opts to turn off the passive scan feature. The timestamp can be seen within the UI in Figure 12.

Figure 12. Last scanned time within the UI (UTC -0400).

Speaking of turning passive scanning on/off, the other file is personalsafety_optin.pb. This file represents the state of passive scan feature. Users have the option to turn off passive scanning, and they are warned about the implications if they do so. See Figure 13.

Figure 13. Warning window.

I confirmed that once this feature was turned off, personalsafety_db was completely empty. If an examiner encounters an empty database, they should check personalsafety_optin.pb to determine whether passive scanning is on or off. I have observed only two values: 1 or 2. The former indicates passive scanning is on, and the latter indicates passive scanning is off. See Figures 14 and 15.

Figure 14. Passive scanning is on.
Figure 15. Passive scanning is off.

If an examiner is interested in determining the last time the state was changed, they can check the last modification timestamp of personalsafety_optin.pb. See Figure 16 as an example; I had turned passive scanning off at 14:36 local time (also seen in Figure 13).

Figure 16. Last modification timestamp of personalsafety_option.pb.

The last location with data related to detections is /data/system_ce/%PROFILE_NUMBER%/notification_history/history/. Here, the path is /data/system_ce/0/notification_history/history/. Based on my testing, this data only appears if a user has Notification History turned on. If history is not turned on, then the directory notification_history is absent.

See Figure 17.

Figure 17. notification_history.

Each file in this directory path contains protobuf data related to notifications. Specifically, the package name that sent the notification, the time the notification was sent, and the contents of the notification. There could be multiple notifications per file, so there is a bit of hunting that has to occur. The highlighted file above (1691968225042) has a notification for an unknown tracker. See Figures 18 and 19.

Figure 18. Unknown tracker notification with timestamp.
Figure 19. Converted timestamp.

In Figure 18 the timestamp of the notification is highlighted in the blue box. Above and below that is the information about the notification, and, in this case, it is a notification for an unknown tracker. Figure 19 shows the converted timestamp. Figure 20 shows the notification as it appeared to me during testing. Note the time seen in the upper left corner, which is UTC -0400.

Figure 20. User-facing notification (UTC -0400).

Wrapping Up (Again).

It took a while, but Google finally got around to releasing their almost-native AirTag detection feature. It is much better than Apple’s offering, and just about as good as the other 3rd party app, AirGuard, with the major differences being that AirGuard already has supprot for additional trackers from Samsung and Chipolo, and it retains data for a bit longer. Regardless, it is great that Android users now have a third option for detecting unknown and unwanted trackers.

With AirTag use exploding, examiners should pay attention to the files discussed in this post and understand what they can offer and their limitations. As support for more 3rd party (and likely home-grown) trackers is implemented, these files will become more important. They can come in handy in examinations involving stalking, and can generally serve as an extra source of location data for the phone.

3 thoughts on “Android & AirTags (Part II)

Leave a Reply