Scott Alexander-Bown (scottyab) Scott is a senior Android developer and co-author of the Android Security Cookbook. He is also the founder of SWmobile, a mobile developer-focused MeetUp group which runs tech talks, networking and hacking events.

1 Introduction

The recent rash of breaches and security alerts has heightened everyone's attention and raised the priority level of security: finally, it has become a must-have instead of a should-have requirement in mobile app development. Android tends to receive the bulk of the negative media attention due to its open nature and the ease with which users can install .apk files from unofficial or unknown sources.

Whether it's a genuine alternative to Google's Play Store, such as the Amazon App Store, or a questionable forum post on the XDA forum, this freedom from a central controlling app store is great for user choice — but it does comes with risks. Specifically, when Android users install apps from places that don't have the same behind-the-scenes security such as Android Bouncer in the Google Play store.

Android apps are compressed, packaged and distributed as .apk files, which are similar to .jar or .zip files. They generally contain all the compiled resources (compiled code, images, layouts, xml files, databases, etc.) the app needs; however, don't mistake this .apk file format for any kind of security. APKs can be extracted with simple archive software, and the compiled source code can be decompiled easily with free and open source tools such as APKTool and Dex2Jar.

We're not going to cover reverse engineering in this article, but I highly recommend trying projects like APKTool and Dex2Jar as they will help you understand the rationale for these and other security enhancements.

In this article we'll discuss 4 techniques you can implement to help protect your app's users and attempt to thwart reverse engineers and attackers. I should add a disclaimer: this isn't guaranteed stop your app from getting pirated. There is no such thing as 100% security, and a determined and skilled attacker with enough time could remove these checks from the code. The real objective here is to raise the bar out of reach of opportunist and automated attackers.

2 Verifying your app's signing certificate at runtime

Prerequisite: The Android developers blog has a great article on signing your app. I Recommend reading this first as this technique relies on that knowledge.

In a nutshell, developers must sign applications with their private key/certificate (contained in a .keystore file) before the app can be installed on user devices. The signing certificate must stay consistent throughout the life of the app, and typically have an expiry date of 25 years in the future.

The consistency of the developer signing certificate is relied upon by the Android system when dealing with app upgrades. For example, while I could create an app with the same App ID as Facebook, I couldn't trick users into upgrading to my version as it's not signed with Facebook's certificate. As developers, we must keep this certificate private otherwise we risk others being able to sign applications as us.

Tip: Keep your private key (.keystore file) out of source control and in a separately secured and backed-up system.

The app signature will be broken if the .apk is altered in any way — unsigned apps cannot typically be installed. We can imagine an attacker removing license-checking code to enable full app features without paying, for instance. A more dangerous example would be altering the .apk to include malware in a legitimate app to harvest sensitive user data. In order for the altered .apk to be installed, the attacker must resign it.

This technique details how to ensure that your .apk has been signed with your developer certificate, and leverages the fact that the certificate remains consistent and that only you have access to it.

We can break this technique into 3 simple steps:

Find your developer certificate signature. Embed your signature in a String constant in your app. Check that the signature at runtime matches our embedded developer signature.

Here's the code snippet:

private static final int VALID = 0; private static final int INVALID = 1; public static int checkAppSignature(Context context) { try { PackageInfo packageInfo = context.getPackageManager() .getPackageInfo(context.getPackageName(), PackageManager.GET\_SIGNATURES); for (Signature signature : packageInfo.signatures) { byte[] signatureBytes = signature.toByteArray(); MessageDigest md = MessageDigest.getInstance("SHA"); md.update(signature.toByteArray()); final String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT); Log.d("REMOVE\_ME", "Include this string as a value for SIGNATURE:" + currentSignature); //compare signatures if (SIGNATURE.equals(currentSignature)){ return VALID; }; } } catch (Exception e) { //assumes an issue in checking signature., but we let the caller decide on what to do. } return INVALID; }

First, we need to find the signature of our certificate so that we can embed it in the app. I've included a line to calculate and log this to the system log — it should go without saying this should be removed once your have a copy.

Check your logcat output for a message similar to this:

10-10 17:37:11.483: D/REMOVE\_ME:(111): 478yYkKAQF+KST8y4ATKvHkYibo=

Make a note of the encoded signature and replace the value of the static constant SIGNATURE:

private static final String SIGNATURE = "478yYkKAQF+KST8y4ATKvHkYibo=";

At runtime, the PackageManager allows us to query the signatures of our application. We iterate through this array of signatures and compare it against our signature.

Now, when you run checkAppSignature on your app — when signed with your release developer certificate — you should see that it returns 0 , i.e. valid. This hardcoded signature is an ideal candidate for DexGuard's String Encryption.

3 Verifying the installer

Each app contains the identifier of the app which installed it. Therefore, with a very simple check you could have your app verify the installer ID.

This example assumes you are only distributing via the Google Play Store:

private static final String PLAY\_STORE\_APP\_ID = "com.android.vending"; public static boolean verifyInstaller(final Context context) { final String installer = context.getPackageManager() .getInstallerPackageName(context.getPackageName()); return installer != null && installer.startsWith(PLAY\_STORE\_APP\_ID); }

The snippet shows getting the InstallerPackageName from the PackageManager and verifying against the known value of the Google Play Store, com.android.vending .

4 Environment checks

The final two checks are aimed at assessing the environment the app is running in. Outside of development, it's unlikely that your app should be running on an emulator, and releasing apps with debuggable enabled is discouraged as it allows connected computers to access and debug the app via the Android Debug Bridge.

4.1 Emulator

If your app is being run on an emulator outside the development process, it gives an indication that someone other than you is trying to analyze the app.

These emulator checks look for private or hidden system properties that give a strong indication the system is an emulator. It's not fool-proof — with custom ROMs and rooted devices, these values could be modified.

First we define a helper method which uses Java Reflection to allow us to access these hidden system properties:

private static String getSystemProperty(String name) throws Exception { Class systemPropertyClazz = Class .forName("android.os.SystemProperties"); return (String) systemPropertyClazz.getMethod("get", new Class[] { String.class }) .invoke(systemPropertyClazz, new Object[] { name });

Now we check ro.hardware , ro.kernel.qemu and ro.product.model properties for known values that emulators use.

public static boolean checkEmulator() { try { boolean goldfish = getSystemProperty("ro.hardware").contains("goldfish"); boolean emu = getSystemProperty("ro.kernel.qemu").length() > 0; boolean sdk = getSystemProperty("ro.product.model").equals("sdk"); if (emu || goldfish || sdk) { return true; } } catch (Exception e) { } return false; }

My personal favorite is to check whether or not ro.hardware contains "goldfish," I must admit that I don't know the story behind the goldfish value, apart from that it's commonly used on emulators and not real devices.

There are additional checks that don't require accessing hidden system properties noted in this StackOverflow answer.

4.2 Debuggable

Allowing apps to be debugged when installed on an Android device is something that, as developers, we only enable during the development process. Therefore, if debugging occurs on a live build of your app, it's likely that someone other than you is trying to analyze the app.

It's a common first step for attackers to decompile the app, enable the debuggable flag (in the Android Manifest file) and recompile, allowing them to attach the debugger to help better understand how the app works.

Here's a snippet to check for the debuggable flag at runtime:

public static boolean checkDebuggable(Context context){ return (context.getApplicationInfo().flags & ApplicationInfo.FLAG\_DEBUGGABLE) != 0; }

The debuggable flag is accessible as one of the flags on the ApplicationInfo object, which is accessible from context. You will likely want to wrap the checkDebuggable() method in if statement to only check on live builds.

5 Summary

To help your app detect tampering, we looked at identifying telltale signs of emulation and third-party debugging with environment checks. We introduced a quick and easy way to confirm the installer of your app, and — perhaps most importantly — how to verify that your app is still signed with your developer signature.

One thing we didn't talk about was deciding on the appropriate course of action when tampering is detected. This is very subjective and varies from app to app; reporting these situations to a server is a good idea so that you can analyze the scale of the issue.

In the past I've used Flurry and/or Crashlytics to log custom events and displayed "your app has been tampered with" warning pages to users, with details on how to contact the developer. You might decide to wipe the user's data or disable selected functionality; ultimately it's down to you to figure out how to handle it.

As I mentioned in the introduction, reverse engineering and decompiling Android apps is easy. So what is to stop an attacker removing your tampering checks?

Well, nothing. That's why I recommend using them in conjunction with code obfuscation, with tools such as ProGuard, which is included in the SDK. Ideally, you could use a tool designed to actively protect compiled code, such as DexGuard.

If you have any questions or want to talk more about Android security, be sure to book me for an AirPair!