Android security

đź”’ Protect your Android app code

Make app safer against decompilation with obfuscation, c++, etc

Jonathan Mercandalli
6 min readMar 20, 2023
DALL.E 2 generated image: smartphone in a japan painter style

As an Android developer, you will face an issue.
Your code is available to everyone.

If someone wants to know how your app works, no matter what you do, he will succeed. In security, there is no perfect protection.

The goal of this article is to provide you with some tools and tips to protect your app. Please note that decompiling an app to copy is not legal. You should only decompile your own app.

In this article, we will cover the following topics:
1. How to obtain the APK file
2. How to decompile the code
3. How attackers can obtain information
4. How to protect against decompilation

1. How to obtain the APK file

From your own app from android project

  • If you are the developer of the application, the command ./gradlew assembleDebug and ./gradlew assembleRelease will generate the APK files in the folder app/build/outputs/apk .
  • If you release your app via app bundle, you can generate app bundle via ./gradlew bundleDebug or ./gradlew bundleRelease .
    The app bundle can be transformed into an APK via bundletool.

From your own app from the PlayStore

  • On the Google Play Console, the section App Bundle Explorer allows developers to download AAB or APK.

From any installed app

  • Android stores APK from installed app. Note that installed AAB will make multiple APKs on the device. In order to pull APKs from the device to your computer, you can do it via ADB. adb pull /data/app/...
  • Tips: You can download this launcher, long press on the app drawer button and click on the “adb pull every apk button”. This will give you the ADB pull command to get every APK.

2. How to decompile the code

My own app decompiled, obfuscated class picked at random.

From Android Studio

  • Drag and drop the APK or AAB file into Android Studio. Click on .dex files to a sneak peek of the bytecode.

From jadx

  • Download the latest version and run the GUI. This will show you the full bytecode in a “java view”.

From apktool

  • You can use apktool to decompile the code either in command-line or via your own java app.

From online decompiler

  • There are multiple decompilers available online to decompile APKs. However, as I do not know the code behind these tools and they are not open source, I recommand using this option as a last resort.

3. How attackers can obtain information

Multiple open doors are available for attackers. Read the decompiled app code. Read the UI displayed. Read the RAM. Read network traffic.

Non minified codebase
Some apps do not minify the code. Minification makes it harder to read decompiled by replacing methods and variables names with “a”, “b”, “c”, etc.

Resources
Android resources like strings and layout are open doors for attackers. With strings, it’s sometimes possible to know what the code is doing. Raw folder is also a great entry point.

Strings
Any string constant in the code will be fully readable.

Classes that are never minified like Activity, View, @Keep…
To have the view inflate working, the views will never be minified. So, the name and package name you give to your views and activities will be fully readable.

Assets folder
Keep in mind the assets folder is accessible without any protection. Anything you put in this folder is public.

Kotlin Intrinsics
Kotlin automatically checks nullability of variables, fields and parameters. To do that, Kotlin uses the Intrinsics class. Intrinsics check every parameter and add the name of the variable in the error message inside a readable string. This is another entry point of attack.

Kotlin data class
A Kotlin data class automatically generates the toString(). The “issue” is that the data class name and field name will be visible in the string inside the toString() method.

View dump
On a non-debug APK, the layout inspector does not work. However, you can dump the UI using uiautomator by running the command adb shell uiautomator dump. This will retrieve the entire get UI DOM of the screen displayed on your device.

Rooted device
With a rooted device, you can both, analyse the RAM at runtime, and pull the internal storage.

Network
To analyse network calls, you can use a proxy or tools like Wireshark or Charles.

AndroidManifest
The AndroidManifest is one other open door that the system will use. Indeed, the manifest lists every activity, receiver, services… By knowing the first Activity of an app, attackers can follow the call flow that the Android OS will do. So, attackers will be able to follow step by step from the Application.onCreate to every Activity.onCreate.

4. How to protect against decompilation

Minify, shrink and Obfuscate
The first step to protect against decompilation is to use obfuscation. Enable it via minifyEnabled in your app/build.gradle.kts. This will reduce the size of the code and make reverse engineer harder. R8 will be use using proguard files for configuration. More information can be found on the Android official documentation.

Never put a key in clear in string
There are robots on internet which crawl the PlayStore and decompile apps searching for secrets via static code analysis. The main target of these robots are non-protected strings with API keys inside. To protect information inside an application, there are multiple solutions depending on the level of security you want to have. Here are some solutions:

Base64, Zip, Gzip
This is the “easier” and less robust way to protect against basic code analysis. By base64 encoding your secret, it will be more difficult to know that there is a secret in the first place.

Native code via jni
This approach is a little bit more secure than Base64 approach. But keep in mind, this still possible to decompile the .so file and read your secret.

RSA & AES
Using RSA to exchange the key and AES to encrypt provides better better protection (although not perfect as there is no perfect solution).

Remove Kotlin Intrinsics to not provide your field and variable name
There is a way to remove these Kotlin nullability checks. However, keep in mind that in a fail-fast approach, this is not a good practice. At least, you will give less information and reduce a your bytecode. Great article here and by Jake Wharton.

Kotlin data class - toString
To hide class name and field names, you can override your data class’s toString() method.

R8 option
R8 has an option to make attacks harder. For more information, check out this great article. With this option, all your minified classes will be put on the same package.

-repackageclasses 'defpackage'

Compose
With the use of Jetpack Compose, there is no need for layouts, views, fragments. You may even be tempted to have a mono activity. By doing this, you will reduce the surface of attack.

PlayStore anti tamper protection
Here is a great article about it. Otherwise the PlayStore hase released its own anti-tamper protection.

AabResGuard
Tool from bytedance that I kept up-to date with latest version of BundleTool. This tool obfuscate resources: layouts, strings...

Conclusion

Thank you for reading. Please remember that there is no foolproof way to protect your app. Combine the techniques of this article to avoid other developers to steal your code. Don’t hesitate to comment and provide feedback.
You can learn more about me on mercandalli.com or on linkedin.

DALL.E.2 generated image : mobile phone impressionism

--

--