Android Binary Protection Methods

The majority of Android applications we test, even critical apps, do not prevent an attacker from successfully analysing, reverse engineering or modifying the app’s binary code. Most Android apps can easily be decompiled into readable source code that resembles the original.

What can an attacker potentially do with an application’s binary? The following are a few examples:

  • Inject backdoor code which will steal sensitive user data. They can then distribute the application through third party App Stores or Internet forums.
  • Crack the application and freely distribute it on the Internet.
  • Bypass any binary protections in place widening the surface for further vulnerabilities and exploitation.

 

The impact for a business could vary depending on how the attacker exploits the app. It could be revenue loss because of piracy (for example a game with in-app purchases), intellectual property theft, such as a competitor stealing parts of source code, or even reputational damage if confidential client data such as personal identifiable information is accessed.

This article aims to demonstrate some techniques for securing the source code; which if implemented correctly, can slow down a reverse engineer and increase the cost in time in the hope it will deter or demotivate attackers from targeting the app.

To demonstrate some important binary protection techniques an intentionally vulnerable Android bank application (InsecureBank) was used, which is available at the following link:

https://github.com/dineshshetty/Android-InsecureBankv2

The following sections describe protection methods along with concepts of implementing them in a secure way; as quite often developers will implement binary protections which are easy for reversers to bypass.

Code Obfuscation

As previously stated, Android apps can be easily disassembled into readable source code that resembles the original. Code obfuscation aims to make the application’s code difficult to understand even if an attacker disassembles it, by replacing classes, fields and methods with random short names. The code will become less readable and hard to follow; hence increasing the time and resources needed by an attacker.

It is possible to disassemble the Android application package (APK) file into Dalvik bytecode, which is the code being executed in the Dalvik VM (Virtual Machine) – Android’s Java VM implementation.

An attacker could use one of the many available tools for disassembling the APK. The example shown below uses the apktool, which will retrieve the app’s manifest file (“AndroidManifest.xml”), resources and Smali code. Smali code is a human friendly representation of the actual bytecode that the Android Dalvik VM understands and executes. As shown below, running the apktool will extract the “.smali” files which come with the class names; this can provide hints about what is going on in each “.smali” file.

android application binary no obfuscation apktool decode

Below a code snippet is shown from the “PostLogin” class, where one can see that field names are not obfuscated and also some root detection checks occur.

# instance fields
.field changepasswd_button:Landroid/widget/Button;
.field root_status:Landroid/widget/TextView;
.field statement_button:Landroid/widget/Button;
.field transfer_button:Landroid/widget/Button;
.field uname:Ljava/lang/String;
[...]
    if-eqz v2, :cond_1

    :cond_0
    move v0, v1

    .line 88
    .local v0, "isrooted":Z
    :goto_0
    if-ne v0, v1, :cond_2

    .line 90
    iget-object v1, p0, Lcom/android/insecurebankv2/PostLogin;->root_status:Landroid/widget/TextView;

    const-string v2, "Rooted Device!!"

There are many solutions for obfuscation out there; ProGuard is a well-known tool that is now integrated in Android Studio.

You can see the result of using ProGuard to obfuscate the InsecureBank source code as follows. The disassembled files are shown below:

android obfuscation example using proguard code shrink tool

The following code output is from the same root detection code shown previously, however the field names are now obfuscated. This can be seen underneath, where the field names have been changed to “a”, “b” and so on, making the code harder to understand:

.class public Lcom/android/insecurebankv2/PostLogin;
.super Landroid/app/Activity;

# instance fields
.field a:Landroid/widget/Button;
.field b:Landroid/widget/TextView;
.field c:Landroid/widget/Button;
.field d:Landroid/widget/Button;
.field e:Ljava/lang/String;
[...]
    if-ne v0, v2, :cond_1

    move v0, v2

    :goto_0
    if-nez v0, :cond_0

    invoke-static {}, Lcom/android/insecurebankv2/PostLogin;->a()Z

    move-result v0

    if-eqz v0, :cond_2

    :cond_0
    move v0, v2

    :goto_1
    if-ne v0, v2, :cond_3

An important note to make, is that ProGuard will not obfuscate all the classes, methods and field names, as some of them need to retain their names so that Android can reference them. In the screenshot above, the “LoginActivity”, “PostLogin” classes for instance are not obfuscated. In this case, try to further configure ProGuard; this is dependent on how the application works. Additionally, further custom obfuscation techniques can be applied. Some of these are briefly mentioned below, which are quite effective as they are seen to be in use in the wild by sophisticated malware.

  • Very long class, method and field names that do not betray their purpose can be used.
  • Junk code insertion is a technique where complex code instructions which will not change the semantic of the application are added in classes or methods.
  • Encoding (such as Base64) and string concatenation can be used together to dynamically decode and concatenate partial strings at execution time, that all together form a sensitive string, such as an API key.
  • Encryption can be used to decrypt and load sensitive data or chunks of code during runtime; hence making the app harder to statically reverse.
  • Sensitive strings, such as encryption keys, can be injected into the binary data of Android assets (for example images) and be extracted during runtime. They could be also stored in various structures such as arrays, and then dynamically be converted back.

 

There are more advanced techniques which can be implemented, such as using Java’s reflection ability. Developers of very sensitive apps should research further obfuscation techniques.

Developers should attempt to decompile the app themselves after applying obfuscation and look for code chunks or sensitive strings that are exposed. Custom obfuscation techniques can then be applied to the relevant areas.

For security sensitive apps, commercial obfuscation products can be used to apply more advanced obfuscation, which adds an extra headache to a reverser, however “security by obscurity” will ultimately only slow down a determined attacker.

Root Detection

Important Android applications may have legitimate reasons for wanting to avoid running on a rooted device. User installed applications which are able to run with root privileges have complete control over the device, and can therefore access any resource on the device. For example, a malicious application could compromise user personal data stored in a financial mobile app.

Implementing root detection properly can be very tricky, since there are many ways in which a user or an application running on a device can obtain root access. The most common techniques for root detection are to check if the “su” binary exists on the system or look for the package names of well-known rooted apps. The following links discuss many root detection techniques in detail:

https://blog.netspi.com/android-root-detection-techniques
https://www.owasp.org/index.php/Mobile_Top_10_2014-M10

If root detection is crucial for an app, then multiple techniques for detecting if a device is rooted should be implemented. This should be done in a way to make it difficult for an attacker to bypass these checks by modifying the application. The InsecureBank app does some basic root detection and displays a message to the screen as shown below.

android root detection methods reverse engineer

An attacker can then easily search through all the “.smali” files for this message and spot the part of code that implements the root detection checks, even if the code has been obfuscated using ProGuard for instance. Using the grep tool, the attacker can pin-point the exact “.smali” file and line of code where the “Rooted Device!!” message originates from, as shown below.

android bypass root detection reversing

App messages can sometimes render a protection mechanism useless by betraying its exact location in the source code. An attacker can then alter the code’s logic to return the desired result. For example, to bypass the root checks done in the PostLogin class, an attacker can alter the method’s code to always return false. Therefore, applying string obfuscation techniques in cases like that can hinder reversers.

SSL Pinning

In Android, an attacker is able to bypass SSL pinning if their device is rooted. This is done by hooking runtime methods responsible for SSL pinning. That is why all the binary protections are stronger when combined together. In this case, root detection and obfuscation will place extra obstacles in the way of an attacker trying to intercept traffic between the app and the server. In addition, the following article discusses advanced ways in which an application can detect whether methods are being hooked:

https://d3adend.org/blog/?p=589

SSL pinning should be also done without exposing helpful information to attackers. For instance, when Twitter’s app detects another certificate to be in place it stops updating and displays “Cannot retrieve Tweets at this time. Please try again later”. This is a very generic error message which could be the cause of many issues and will not allow an attacker to quickly spot the chunk of code implementing SSL pinning. Furthermore, an attacker can attempt to search through all the “.smali” files for keywords relating to SSL pinning, such as “certificate” and “X509”. In this case, obfuscation should help; as well as implementing further custom defensive techniques.

Tamper Detection

An app can be designed not to run if any signs of tampering with its code are detected; meaning that if an attacker has altered the APK file, to bypass other binary protections for example, the app will not run. This can be implemented by checking the app’s signature against a trusted certificate. Extra checksum and validation mechanisms can be used to add further annoyance to potential reversers. The following website provides more information on this, as well as a snippet of code implementing tamper detection:

https://www.nowsecure.com/resources/secure-mobile-development/coding-practices/anti-temper-techniques

Ensure that classes, methods and field names relating to this are properly obfuscated. You can also transmit app errors through the API, with the user’s consent, of course. These could include errors relating to rooted devices, certificate pinning and security check bypasses, which may help with hardening the app further.

Final Words

To sum up, keep in mind that there are more binary protections an app may leverage such as debugger, emulator and hook detections. All these techniques further impede an attacker depending on how the techniques are implemented and also how determined and skilful the attacker is.

Depending on the sensitivity and the risk of the app, developers should consider hardening the app binary. Lastly, ensure the SDLC (Software Development Lifecycle) process in place caters for introducing security protections in applications and these are adequately tested.


Find out how we can help with your cyber challenge

Please enter your contact details using the form below for a free, no obligation, quote and we will get back to you as soon as possible. Alternatively, you can email us directly at [email protected]