Find further code and implementation here: https://github.com/browep/AndroidProguardTutorial
The following tutorial follows closely what’s in the code at https://github.com/browep/AndroidProguardTutorial . I suggest you clone that repo and follow along with it. It was created with the Android Studio IDE which is free and easy to use. It also uses Gradle to build the project which can be utilized without Android Studio.
What is Proguard?
Proguard minimizes your code by taking recurring method names, field names, and class names and renaming them with smaller names. It also removes any unused code from your APK.
Why use Proguard?
With names minimized and unused code removed your APK is much smaller. Users tend to like smaller APK sizes as they download quicker and eat up less data. Since proguard changes the names of your methods it is harder for someone to decompile your code to determine how it works and possibly find weaknesses. Change your code like this is called obsfuscation.
Where does it come in?
Since we only need these security features during distribution like in the Google Play store, proguard is only used for release builds. Luckily gradle takes care of this for us.
Using Proguard
This tutorial assumes you are using gradle to build your project. There are links to corresponding files in the sample project.
The first thing to do is tell gradle that we want to use proguard for our release buildType.
full file /app/build.gradle
android {
buildTypes {
release {
minifyEnabled true
}
}
}
You can run proguard now without any further configuration but lets tell proguard a little more about what we want. Proguard gets its configuration from a file in the project called proguard-rules.pro. Lets modify it to tell proguard we want to keep the line numbers in our stacktraces. Modify the contents to looks like this:
-keepattributes SourceFile,LineNumberTable
Now we are ready to build the sample project and see it run.
# in project base
./gradlew build
in the output you should see :app:proguardRelease
which happens after the Java is turned into .class files and before the .class files are turned into dex code.
Proguard has also created some text files for us. We will use these text files to turn the obsfuscated stacktraces into readable stacktraces. The created text files are found in app/build/outputs/mapping/release/
.
$ ls app/build/outputs/mapping/release/
dump.txt mapping.txt seeds.txt usage.txt
Install this APK and click the “Cause Crash” button. You should see a stacktrace that looks similar to this in your ADB output:
java.lang.IllegalStateException: Could not execute method of the activity
at android.view.View$1.onClick(View.java:3823)
at android.view.View.performClick(View.java:4438)
at android.view.View$PerformClick.run(View.java:18422)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at android.view.View$1.onClick(View.java:3818)
at android.view.View.performClick(View.java:4438)
at android.view.View$PerformClick.run(View.java:18422)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IndexOutOfBoundsException
at java.util.LinkedList.get(LinkedList.java:519)
at com.github.browep.proguard.MainActivity.j(MainActivity.java:56)
at com.github.browep.proguard.MainActivity.i(MainActivity.java:50)
at com.github.browep.proguard.MainActivity.onClick(MainActivity.java:46)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at android.view.View$1.onClick(View.java:3818)
at android.view.View.performClick(View.java:4438)
at android.view.View$PerformClick.run(View.java:18422)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
The interesting part is
Caused by: java.lang.IndexOutOfBoundsException
at java.util.LinkedList.get(LinkedList.java:519)
at com.github.browep.proguard.MainActivity.j(MainActivity.java:56)
at com.github.browep.proguard.MainActivity.i(MainActivity.java:50)
at com.github.browep.proguard.MainActivity.onClick(MainActivity.java:46)
The methods have been renamed from subMethod
& subSubMethod
to i
& j
by proguard. See the full file here. The onClick method is called when the button is tapped.
So now lets de-obsfuscate this stacktrace. Take the text from ADB and save it to a file. I have used stacktrace.txt
. The proguard tools are found in $ANDROID_HOME/tools/proguard/bin/
. We will be using the mapping.txt
file that proguard produced in the build.
$ retrace.sh mapping.txt stacktrace.txt
The outputted stacktrace will contain the de-obsfuscated stacktrace. In my example:
Caused by: java.lang.IndexOutOfBoundsException
at java.util.LinkedList.get(LinkedList.java:519)
at com.github.browep.proguard.MainActivity.void subSubMethod()(MainActivity.java:56)
at com.github.browep.proguard.MainActivity.void subMethod()(MainActivity.java:50)
at com.github.browep.proguard.MainActivity.void onClick(android.view.View)(MainActivity.java:46)
at java.lang.reflect.Method.invokeNative(Native Method)
We see that the method names have been filled in now.
Common pitfalls
- One common mistake is that proguard doesn’t know about any reflection you are doing in the code so it may rename some methods that you are invoking programmatically. You can tell proguard to skip file by adding it to
proguard-rules.pro
-keep class com.github.browep.MainActivity
- Save the mapping.txt file for each release build. Without it you cannot de-obsfuscate your code. Consider adding to your source repository.
Taking it further
Proguard has lots of options and can be customized heavily, see http://developer.android.com/tools/help/proguard.html for Google’s take on it and http://proguard.sourceforge.net/ for full docs.