Inside SafetyNet - part 3

SafetyNet: Google's tamper detection for Android - part 3

This post is part of a series:

It’s been more than 8 months since my last blog post on Android’s SafetyNet. In that post I was describing an end-of-2015 version of the system (version code 2495818). As expected, there have been several updates since then; I thought I should write one more post in this series, probably the last. I’ll briefly describe the differences of that last reviewed version versus version 10000700; this is second-to-last version. The latest version I’ve seen is 10000801, but it doesn’t have many functional differences.

For a more complete overview of the client-side checks inside the SafetyNet system and its usage please read through my previous posts.

SNET version codes

I thought I’d just list some of the SNET versions I’ve observed in the wild; I’m sure there are many more.

  • 1626247 (December 2014)
  • 1839652 (April 2015)
  • 2097462 (July 2015)
  • 2296032 (September 2015)
  • 2495818 (December 2015)
  • 10000700 (August 2016)
  • 10000801 (September 2016)

Design changes

SafetyNet got partially integrated into the Chimera system as a dynamite module. Chimera is a sort-of package management system for Google Play Services components, allowing Google to flexibly and independently upgrade each one. As Chimera manages part of the download and provisioning process, this change made the snet package lighter and more flexible. Chimera also has a nice interface, found by going into Google Settings and enabling “debug items”. You’ll see an “[internal]” menu item called “Chimera Modules”. The SafetyNet module is called, currently version 1. You’ll also find that DroidGuard is another dynamite module.

Chimera modules list


At least since last July, Attestation responses objects contain a new boolean flag called basicIntegrity. This new field does not appear in Google’s published documentation yet. I’m sure it will soon.

    "nonce": "R2Rra24fVm5xa2Mg",
    "timestampMs": 9860437986543,
    "apkPackageName": "",
    "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the certificate used to sign requesting app"],
    "apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK",
    "ctsProfileMatch": true,
    "basicIntegrity": true,

But what is this field? Here are some thoughts, the way I understand it:

In September 2016, Google decided to introduce more aggressive checks into ctsProfileMatch, e.g. acting on VerifiedBoot status. Due to these changes, devices that are not “rooted” but may only use a different bootloader will cause ctsProfileMatch to be set to false.

In such cases, basicIntegrity will still remain true. It seems that this is set to false only if an su binary is placed in expected locations. The basicIntegrity field currently seems to behave like ctsProfileMatch did before the recent changes. One could see it as a way to maintain the previous ctsProfileMatch behavior, so that 3rd party apps can choose the level of checks they want to base their decisions on. I have yet to see any case where basicIntegrity is true while ctsProfileMatch is false; let me know in the comments if you do.

I also updated Cigital’s SafetyNet Playground app in Google Play. It now checks and reports the value of basicIntegrity. Check it out!

SafetyNet Playground


It’s safe to say that DroidGuard plays a bigger role into attestation than I have previously described. It is (and has always been) a packed native library, designed to somewhat withstand reverse engineering, which makes dealing with it more interesting. Another blogpost on this may come in the future.

Safe Browsing

SafetyNet - from a 3rd party app perspective, used to initially be only about Attestation and CTS compatibility.

It now offers another flavor: A lookupUri() API that allows apps to check if a given URI is classified as Potentially Harmful App by Google’s threat intelligence systems. More details on this here and here.

I’ve not described Safe Browsing in any of my blog posts yet, this may come in the future - just like Verify Apps & DroidGuard.


As of version 10000801 SafetyNet is making use of ProGuard-style minification (some call it obfuscation). This is an interesting change of heart. Initially Google seemed to leave things unobfuscated on purpose in order to increase transparency, however this appears to have changed.

It is important to note that this change happened at the same time as the cat-and-mouse game between SafetyNet and various “bypasses” intensified after the recent device integrity change. I am sure that SafetyNet re-implementations like this might have affected this decision.

SafetyNet module changes

A few but important new modules have been added in recent versions and some older ones were restructured. As mentioned above, SafetyNet is configured by Google at runtime or via play services updates; the snet module itself updated independently less often.

What follows is the list of snet modules that are currently enabled by default. Note that a few extra modules have been enabled compared to a year ago. But the real differences are of course not here; the real differences are in the way data from each module affects backend attestation decisions.

Idle mode modules

Idle mode checks appear to run every 12 hours. The last time an idle mode scan run is stored in snet’s shared preferences inside the Play Services app private files. The following modules are now always enabled by default in idle mode.

  • gmscore
  • system_partition_files
  • system_ca_cert_store
  • setuid_files
  • dalvik_cache_monitor
  • device_state
  • locale
  • selinux_status
  • logcat
  • event_log

The gmscore, locale and selinux_status modules have now been turned ON comared to a year ago. The gmscore module retrieves info about the package installed on the device via PackageManager APIs, including versionCode, hashes, signatures etc. The locale module obviously retrieves the current locale, including country code. The selinux_status modules will be described in a moment.

Normal mode modules

The following modules are now always enabled by default in normal mode. Normal mode ‘checks’ appear to run when a 3rd party app requests an attestation or at a maximum every 24 hours.

  • default_packages
  • su_files
  • settings
  • locale
  • ssl_redirect
  • ssl_handshake
  • proxy
  • selinux_status
  • sd_card_test
  • google_page_info
  • captive_portal_test
  • attest
  • gmscore
  • device_state
  • carrier_info
  • logcat
  • event_log

The following non-default modules are also currently enabled by play services config:

  • mx_record
  • sslv3_fallback

Compared to a year ago, the only differences appear to be that the following two modules are now turned ON: device_state and carrier_info. The device_state module is of course very important and is directly related to the recent verified boot blocks. It’s described in more detail below.

SU files module

The SU finding modules has been partially redesigned since it was first introduced. It reports back information about two sets of files:

  • files explicitly requested by Google
  • su binaries

The first category of files comes from snet configuration options, shipped separately from snet itself as part of Google play services. It currently includes a single file: /proc/sunxi_debug/sunxi_debug. This file is a well known kernel backdoor allowing easy root on some devices.

The second category has been split out into a new rooting file finder submodule. This works as follows:

  1. SNET assembles a combined list of two types of directories to search into:

    • interesting directories: These by default include /system/bin and /system/xbin and all directories specified in PATH, if such environment variable exists.

    • systemless root directories: SNET attempts to identify directories that might be bind-mounted by parsing /proc/self/mountinfo and figuring out if /bin or /xbin are mounted using via /dev/block/loop. I will not go over the details of how systemless root works in this post.

  2. SNET attempts to find if the su binary exists in any of these directories. If it does, it gathers information about it, including its SHA256 hash, if it’s a symlink and its target, ownership/permissions/selinux info (lstat) etc. It now also checks if the file is executable - this is done via, X_OK).

Settings module

This module is used to retrieve various pieces of information about system settings and is run as part of the ‘normal-mode’ checks. It has changed to also retrieve two more pieces of info:

  • Storage Encryption Status

    This is retrieved via the getStorageEncryptionStatus() method of the device_policy system service if SDK >= 11.

  • Fingerprint Status

    This is retrieved via the isHardwareDetected() and hasEnrolledFingerprints() methods of the fingerprint system service if SDK >= 23. This can return the following values:


Device State module

This module used to gather the following data:

  • Verified Boot state via ro.boot.verifiedbootstate
  • Verity mode via ro.boot.veritymode
  • Security Patch Level via
  • Unlock Support via ro.oem_unlock_supported
  • State of Flash Lock (oemLocked): via ro.boot.flash.locked

Now the following have been added:

  • Device Brand via ro.product.brand
  • Device Model via ro.product.model
  • Kernel Version from /proc/version
  • List of System properties explicitly specified by Google via play services.
    • currently just

On API>23, flash lock state is now retrieved via PersistentDataBlockManager.getFlashLockState() which is a new wrapper API to the ro.boot.flash.locked property.

Possible values are:

  • FLASH_LOCK_STATE_UNKNOWN = -1 (0xffffffff)

Some people were surprised back in September this year, when Google started blocking devices that were not rooted but had unlocked their bootloader. First of all, unlocked bootloaders cause verified boot to fail - that’s what’s triggering snet. Not much changed in client-side code: SafetyNet was retrieving ro.boot.verifiedbootstate and ro.boot.veritymode all along, gathering metrics, until someone made the decision to make these indicators influence the ctsProfileMatch boolean flag (but not basicIntegrity).

dm-verity correction info checks

For idle-mode checks, the Device State communicated back to Google now also includes dm-verity correction information for SDK > 23. Some of you may know tha Android N introduced Verified Boot with Error Correction. This is all described here and here and here. Forward Error Correction is of course used to recover from filesystem corruption. An interesting security “side-effect” is that the definition of ‘curruption’ extends to ‘tampering’, so this feature can effectively repair rooted filesystems)

SafetyNet searches all device mapper directories (e.g. /sys/block/dm-0, /sys/block/dm-1) and looks for a directory named fec, signalling that a partition is using Forward Error Correction. If fec is found, SNET retrieves the partition name (/sys/block/dm-X/dm/name) and the fec record file (/sys/block/dm-X/fec/corrected) and sends these back, allowing Google to track how this new feature is used.

System Partition Files module

In my previous blogpost I briefly described this system performs integrity measurements, retrieving hash trees over the /system directory and reporting them back, along with other info, to a System Integrity service separate from SNET.

A year ago the SIC Server URL was empty but this has now been filled in. It is: This appears to be an undocumented part of the Safe Browsing system.

SELinux checking module

Previously this module only retrieved:

  • whether selinux is supported
  • if it’s in enforcing mode via /sys/fs/selinux/enforce.

Now, it also retrieves:

  • the version via /selinux_version
  • the SHA256 hash of the policy file (/sepolicy)

SSL Handshake module

As discussed before, this module attempts to figure out if communications can be intercepted in a number of ways, such as via having an SSL-Kill-Switch app installed. It basically attempts to find malicious TrustManagers. In the last few versions it has been significantly refactored.

Like before, the code attempts to contact and The third host is, replacing

For each host, the module attempts to do an SSL handshake (sslContext.getSocketFactory().createSocket(hostname, 443)) and now, separately, an HTTPS connection (new URL("https", hostname, "").openConnection()) Both use a custom all-trusting X509TrustManager to establish a secure connection and retrieve the server’s SSL certificates. Then the module finds the system’s first X509TrustManager (instead of all of them like it did before).

It then uses this TrustManager (via checkServerTrusted() to verify the server’s chain. It is interesting to note that now, on API>=21, the module uses X509TrustManagerExtensions.checkServerTrusted() instead of checkServerTrusted(), in order to retrieve the validated certificate chain. This is good news; bad things happen when these differ, as seen in CVE-2016-2402. In a change since previous versions, even on API<21 there's code to re-create the validate certificate chain - great stuff. As discussed before this module also checks if certificates are valid because they have been added by a user or not; these methods now, for API >=21 use X509TrustManagerExtensions.isUserAddedCertificate() instead of manually checking /data/misc/keychain/cacerts-added.

SDCard analyzer module

This module now also retrieves the last modification time of JPEG file it attempts to store on the SD card.

Carrier Info module

One new module was introduced into SafetyNet: “Carrier Info” This just retrieves the Name of the current carrier by using the getNetworkOperatorName() method of the phone system service.

mobile security, static & dynamic analysis, automation, payments

London, UK