To give you some context: in the last months, I've been developing a high-profile app for a governmental institution, during which I needed to do quite some R&D on the do's-and-don'ts of mobile development. So I took my Android phone and connected it to my computer, and found to my surprise that some of the apps where spawning results from REST services to their logcat, which included confidential information.
Astonished I looked a bit further, how easy would it be to fetch the application from a non-rooted device. Quite easy so it seemed.
Would I be able access the "internals" of the applications? Yes I was! Some of the hybrid solutions (Cordova) were even easier to look at! I had direct access to Javascript and HTML source files. Wow! So it should be more difficult for Native Android applications, right? Wrong!
"WTF?!" was about the only thing that keept going through my mind.
How could we be so sloppy in developing Android applications!
Are we ignorant of the available tools?
Or are we just lazy?
Or are we not specialised enough?
Catching my drift here? So it was quite difficult to write an intro here.
This post will try to provide some insights in decompiling an APK file and securing your app using proguard.
PS/ This is not the Holy Grail of app security. It helps you in delivering higher quality apps. But it is still up to you to use your common sense. If it needs to be secure, or the information is sensitive: don't store it. Period. That's my advice.
An insight on the APK
When you install an Android application from Google Play, you download an APK file. This APK contains the assets (images, layouts, etc) and code (DEX-file) required for your application to run on your Android Smartphone.An APK is "no more" than a ZIP file constructed using your favourite IDE (e.g. Android Studio & Gradle) which respects a certain structure. Some believe that you need a Jailbroken or Rooted device to download the APK, but that's actually not the case. The Android SDK is shipped with the required tools to download the APK from your mobile device.
One does not simply download an APK
Actually, you do.- First, connect your smart device to your computer, and make sure that you've enabled the developer option on the smartphone (no, that's not rooting)
- Once connected, list the installed packages on your phone
- Look for the package you need and check the install location
- "Pull" the APK from the install location
Show me the packages!
Start a Terminal window and go to the "platform-tools" folder (if it's not in your path, that is). Connect to your smartphone using the "adb shell" command.
$ adb shell pm list packages
The result will be a list of all the installed packages on your smart device, e.g:
package:com.imdb.mobile ... package:com.google.android.apps.books package:com.google.android.videos package:com.lge.update package:be.acuzio.foobar package:com.google.android.inputmethod.pinyin ... package:com.google.android.syncadapters.contacts package:com.google.android.backup
O Package, where art thou?
Let's say that we are interested in the package "be.acuzio.foobar" and we want to know the path:
$ adb shell pm path be.acuzio.foobar package:/data/app/be.acuzio.foobar-1.apk
Excellent! Next, let's pull the APK from the location /data/app/be.acuzio.foobar-1.apk
Download the APK
Download the APK
$ adb pull /data/app/be.acuzio.foobar-1.apk 4551 KB/s (6484838 bytes in 1.391s) $ ls -alh total 16920 drwxr-xr-x 9 vandekr staff 306B Feb 10 20:05 . drwxr-x---@ 14 vandekr staff 476B Jan 21 10:24 .. ... -rw-r--r-- 1 vandekr staff 6.2M Feb 10 20:05 be.acuzio.foobar-1.apk ...
Eh, voila, you are now the proud owner of the zip APK file.
Dex-de-compilation
You can use the dex2jar tool to extract the DEX File from the APK and convert it to a JAR (which we need for JD-GUI)
Look at the code
Start the JD-GUI tool and open the created "output_jar.jar" from your desktop.
Now that looks like readable code to me. Let's have a look at the actual code.
Even without much effort, you can read and understand the decompiled code. It's not even that much different from the actual code.
So let us talk about what we can do to make it more difficult to decompile and understand.
Note: it's a way to optimize your code and avoid data leaks via logging. By no means what so ever is it an encryption of values. A stubborn developer with enough time on his/her hands will still be able to figure your code, even when it is obfuscated.
When you develop an Android application, you might want to consider the following:
Although not perfect, Proguard will help you in the above points, resulting in an app that is:
Mastering the ZIP
Unzipping the APK is one thing you can do to check the internals of the app. If the app was developed as a native app, you can use the dex2jar and JD-GUI tools to have a look at the code.Dex-de-compilation
You can use the dex2jar tool to extract the DEX File from the APK and convert it to a JAR (which we need for JD-GUI)
$ d2j-dex2jar.sh -f -o ~/Desktop/output_jar.jar ~/Desktop/be.acuzio.foobar-1.apk
Look at the code
Start the JD-GUI tool and open the created "output_jar.jar" from your desktop.
Now that looks like readable code to me. Let's have a look at the actual code.
package be.acuzio.foobar; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; public class MainActivity extends ActionBarActivity { private final static String TAG = MainActivity.class.getSimpleName(); private Button btnLogin = null; private EditText etUsername = null; private EditText etPassword = null; @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate was called"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.etPassword = (EditText) this.findViewById(R.id.et_password); this.etUsername = (EditText) this.findViewById(R.id.et_username); this.btnLogin = (Button) this.findViewById(R.id.btn_login); this.btnLogin.setOnClickListener(new LoginOnClickListener()); } class LoginOnClickListener implements View.OnClickListener { @Override public void onClick(View v) { Log.d(TAG, "onClick was triggered for Login"); String username = etUsername.getText().toString(); String password = etPassword.getText().toString(); Log.d(TAG, String.format(" > Username: %1$s", username)); Log.d(TAG, String.format(" > hashing password: %1$s", password)); } } }
Even without much effort, you can read and understand the decompiled code. It's not even that much different from the actual code.
So let us talk about what we can do to make it more difficult to decompile and understand.
Note: it's a way to optimize your code and avoid data leaks via logging. By no means what so ever is it an encryption of values. A stubborn developer with enough time on his/her hands will still be able to figure your code, even when it is obfuscated.
Proguard
Proguard was chosen by Google/Android as the preferred framework for code obfuscation and mimification. Proguard is not the only framework available for obfuscation: a more complex (and commercial) version is DexGuard.When you develop an Android application, you might want to consider the following:
- During development and (unit) testing, you added logging to your application. This logging might have confidential information: data consumed from remote services, usernames, contents of arrays, etc. This data dumping is the primary source of information leaks! You just need to connect your Android device to your computer and look at the rendered logcat to see what is spawned by the different applications (PS/ we assume the usage of the standard Android Logging);
- You use libraries to facilitate your development. Sometimes you just need a small portions (e.g. Bouncy Castle encryption, Apache Commons libs, Custom JAR files, etc) of the provided libraries.
- During development, you might have URL's, passwords, hashes etc stored clear text in your application to secure the communication between the app and the remote service.
- You've worked very hard (for a client), and you're not snappy on the idea of sharing your code with the world.
Although not perfect, Proguard will help you in the above points, resulting in an app that is:
- Not leaking information via Logging and cleaning up your code;
- Packaging the code you use and removing the libraries or classes that are solely there as dead-weight;
- Obfuscate your code, making it harder to decompile and understand what you did
A Proguard example
Proguard is not the easiest framework to master. So a generic proguard file is already provided by your Android SDK. It might resemble a set-up like this one:
# This is a configuration file for ProGuard. # http://proguard.sourceforge.net/index.html#manual/usage.html -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -verbose # Keep a fixed source file attribute and all line number tables to get line # numbers in the stack traces. # You can comment this out if you're not interested in stack traces. -renamesourcefileattribute SourceFile -keepattributes SourceFile,LineNumberTable # keep annotations -keepattributes *Annotation* # keep the licencing service classes from Google -keep public class com.google.vending.licensing.ILicensingService -keep public class com.android.vending.licensing.ILicensingService # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native -keepclasseswithmembernames class * { native <methods>; } # keep setters in Views so that animations can still work. # see http://proguard.sourceforge.net/manual/examples.html#beans -keepclassmembers public class * extends android.view.View { void set*(***); *** get*(); } # We want to keep methods in Activity that could be used in the XML attribute onClick -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } -keepclassmembers class **.R$* { public static <fields>; } ############################################################################# # The support library contains references to newer platform versions. # Don't warn about those in case this app is linking against an older # platform version. We know about them, and they are safe. -dontwarn android.support.** # an example if you don't want to be warned about missing libraries # -dontwarn javax.naming.** ############################################################################# # remove logging, note that this removes ALL logging, including the # Log.e statements -assumenosideeffects class android.util.Log { *; } # an example on how to keep an entire package # -keep class com.google.zxing.** ############################################################################# # repackage and optimize -repackageclasses "" -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/* -optimizationpasses 5 -allowaccessmodification -dontpreverify
Update the Gradle build file
Having a proguard file is just one thing, you need to activate it during your build. I do suggest that you add a proguard file to your debug assembly: because of its intrusive nature, you really need to test your app after the obfuscation.
apply plugin: 'android' android { compileSdkVersion 19 buildToolsVersion "19.0.3" defaultConfig { minSdkVersion 8 targetSdkVersion 19 versionCode 1 versionName "1.0" } buildTypes { debug { runProguard true proguardFiles 'proguard-rules.txt' } } } dependencies { compile 'com.android.support:appcompat-v7:19.+' compile fileTree(dir: 'libs', include: ['*.jar']) }
If you deploy the app to a device or run the assembleDebug command, a new APK will be built using the proguard settings.
Assembling a debug APK using gradle is a simple as running:
The .apk will be stored in the "build/apk" folder of your app project.
Assembling a debug APK using gradle is a simple as running:
$ ./gradlew assembleDebug
The .apk will be stored in the "build/apk" folder of your app project.
Have a look now
Now that already looks a bit better!
- The logging is removed from the class;
- The names are obfuscated, but the methods and classes that are required by the AndroidManifest or the Android ecosystem are kept (e.g. onCreate).
- The packages are flattened and renamed, so happy hunting if you have a complex project!