Using Android's tamper detection securely in your app

Using SafetyNet attestation in a way that is not trivially bypassable is not straight-forward. Learn how to use it properly.

In a previous blogpost, I described how Google Play’s SafetyNet service is structured, from a technical perspective, diving deep into details and the checks it perfoms on the device.

Recap: Google Play’s SafetyNet service allows your application to gain information about the ‘CTS compatibility’ status of the device you are running on. You can think of CTS compatibility as a mix of rooting detection, device tampering detection and active MitM detection.

Many applications use commercial ‘protection suites’ to do some of these tasks, or roll their own solution - which is often trivially broken.

Google Play’s SafetyNet service can provide your app with similar information for free - and, although the checks are basic, it is harder to bypass than rolling your own solution. I believe using this API is worth a shot if you really want to do tamper detection but not invest in a specialized product or consultancy services.

However, using the SafetyNet API ‘properly’ is not straight-forward for non security-aware developers.

Using SafetyNet insecurely

For example, this sample app (source) and this app implement the API in a client-side-only way. These applications get the attestation result and check the signature and the CTS compatibility field locally using the getBoolean() method on the ctsProfileMatch and isValidSignature fields.

The problem with this approach is that an attacker, who already has root access on the device, can hook the getBoolean() method and make it return always true - tricking your app into believing that the device is indeed CTS compatible, while the real SafetyNet response says it is not. The same problem exists if you are locally checking the signature of the JWS AttestationResult object.

An Xposed module that does exactly this sort of hooking has been published already - allowing for easy bypass.

Alternatively, an attacker could just repackage your application and strip out all these checks, achieving the same results.

Avoid client-side checks

This is hardly a new best-practice advice: avoiding client-side checks is good for you.

Me and Georgi Boiko, fellow Cigital consultant, created SafetyNet Playground - a sample open-source Android application that attempts to tackle these ‘trivial to bypass’ issues. It uses the SafetyNet API much in the same way Android Pay does.

The application is designed so that checks are done on the server side. The idea is that your server will not return any useful data unless the SafetyNet service responds that your device is CTS compatible.

With such a solution, an attacker can no longer trivially hook things in your application. He needs to invest time and effort playing catch up and understanding the constantly-changing SafetyNet service, attempting to hook everything it collects from the device and figuring out what would constitute an ‘acceptable’ state for each check.

Of course, an attacker can still repackage the application after striping out the Attestation API. Depending on how smart the attacker is this could be defeated as well, because JWS objects include the signature of the package that did the request… An attacker would have to fake that signature so that Google Services think that a different app did the request.

This blog post has more details about the design of SafetyNet Playground. The Android app and web-service are open source, so that you can reuse parts of the code or study it.


mobile security, static & dynamic analysis, automation, payments

London, UK