Getting Started with ProGuard
In this Android tutorial, you’ll learn how to strip down your app size by making use of ProGuard – an app shrinking and obfuscation tool. By Kolin Stürt.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Getting Started with ProGuard
30 mins
- Getting Started
- Using the APK Analyzer
- Adding the BubblePicker Library
- Enabling ProGuard
- Adding “Don’t Warn” Rules
- Adding BubblePicker Code
- Debugging with ProGuard Output Files
- Adding Keep Rules
- Adding Data to the BubblePicker
- Introspection and Reflection
- Enabling Advanced Optimizations
- Understanding Obfuscation
- Adding Annotations
- Where To Go From Here
Understanding Obfuscation
Sloths hide themselves at the tops of trees to prevent predators finding them from below. Many have a symbiotic relationship with green algae. It grows on their backs so that they blend in with the green leaves. This tricks eagles and other flying predators. Sloths obfuscate themselves, which really comes down to trickery and hiding.
Obfuscation is not encryption. Throwing ProGuard on your release can deter the casual attacker to move on. However, it’s not a substitute for proper security measures. You should never store sensitive API keys, tokens or passwords anywhere in the APK. Instead, have those items sent to the app encrypted upon authentication.
That being said, obfuscation is sometimes used to hide or obscure proprietary logic or a secret algorithm. Sometimes developers apply manual obfuscation. Some examples are string splitting, dummy code, disguising the names of methods, or using reflection to muddy the app flow. You’ll add some reflection code that obfuscates your secret bubble-gradient formula. As of right now, it’s not impossible for an attacker to find the numbers used to make the gradient.
In the APK Analyzer, select the classes.dex file. Navigate to com ▸ raywenderlich ▸ android ▸ slothsanctuary. Right-click on MainActivity$b and choose Show Bytecode. Integers e, f and g are the multiplier
, modulus
and addition
variables of 2, 8 and 1 you added earlier.
When Android Studio compiles your app, it puts the code into that classes.dex DEX (Dalvik Executable) file. DEX files contain Bytecode – an intermediary set of instructions that a Java Virtual Machine (JVM) runs or ART (The Android Runtime) later converts to native code. With this being exposed, an attacker could potentially see values that your variables hold!
You’ll now make it much harder to find those gradient numbers. In the ProGuard rules file, add rules that allow the use of reflection:
-keep class kotlin.reflect.jvm.internal.** { *; }
-keep class kotlin.Metadata { *; }
Under the hood, Kotlin classes contain metadata about an object. You’ll need to keep that metadata in order to use reflection.
Open the BN.kt file. Notice the class was manually obfuscated by abbreviating the class name and methods. The comments explain each abbreviation. Comments don’t make it into the APK.
Add the following code to the sv()
method:
val kClass = Class.forName(ownerClassName).kotlin // 1
val instance = kClass.objectInstance ?: kClass.java.newInstance() // 2
val member = kClass.memberProperties.filterIsInstance<KMutableProperty<*>>()
.firstOrNull { it.name == fieldName } // 3
member?.setter?.call(instance, value) // 4
Wait, what? What is this magic?
While you don’t need to understand it, this code does the following:
- Gets the Kotlin class for the
ownerClassName
string provided. - Instantiates that class at runtime if not already instantiated.
- Dynamically gets the property referred to by
fieldName
for the instantiated class. - Calls a setter on that property, passing in
value
.
The real magic happens when the app invokes sn()
(setupNumbers), which looks for the com.raywenderlich.android.slothsanctuary.GO
class. It finds the fields named f1
–f3
and swaps the values out for something else at runtime.
To put this code to use, go back to the MainActivity.kt file and add this code to onCreate()
, right before the setupBubblePicker()
call:
val bn = BN() //BubbleNumbers
bn.sn() //bubbleNumbers.setupNumbers
Then find the lines that declare multiplier
, modulus
and addition
in setupBubblePicker()
and replace them with this:
val multiplier = GO.f1 //GradientObject.field1
val modulus = GO.f2 //GradientObject.field2
val addition = GO.f3 //GradientObject.field3
Note: Sometimes attackers also reverse engineer apps in hopes of patching or hooking security checks out of the code. A good example is when a feature is only available with a paid subscription or after a user achieves a level in a game. It’s recommended to do those types of checks on a server. ProGuard can still help by obfuscating the code that makes the request to the server.
Note: Sometimes attackers also reverse engineer apps in hopes of patching or hooking security checks out of the code. A good example is when a feature is only available with a paid subscription or after a user achieves a level in a game. It’s recommended to do those types of checks on a server. ProGuard can still help by obfuscating the code that makes the request to the server.
This is an example of using the Kotlin reflection library to call methods by name, but now ProGuard is changing those names. Build and run the app to get the ClassNotFoundException
once again.
You can use keep rules again to preserve the class, but this time around you’ll do it by using @Keep
annotations.
Adding Annotations
With the Annotations Support Library, you can add @Keep
to methods and classes you want to preserve. This is a great feature because it acts like documentation. The ProGuard information sits above your method as opposed to being in a separate file. Adding @Keep
on a class will preserve the entire class. Adding @Keep
on a method or field will keep the name of the method or field as-is.
In the BN.kt file, add a @Keep
annotation to the top of the GO
object.
Build and run the app. You should now see the bubbles again. This time, if you analyze the APK, you’ll see the secret gradient numbers are not so obvious:
Even if you follow those methods to the GO
companion object, the numbers that are apparently returned are not the real ones. You tricked em!
You’re becoming a pro at ProGuard!
Where To Go From Here
You survived a crash course on ProGuard. Download the final project using the Download Materials button at the top or bottom of this tutorial.
You looked at how ProGuard has problems with reflection. ProGuard also has issues when you call a method from JNI (Java Native Interface). If you’re working with JNI, check out solutions in the JNI training article.
An Android Library (AAR) is able to make use of a transparent method that retrieves published keep rules automatically. See consumerProguardFiles on how to take advantage of this.
The makers of ProGuard, GuardSquare, have a commercial solution called DexGuard. It offers more protection than obfuscation. DexGuard encrypts the classes and strings as well as assets and resource files. It also provides app and device integrity checking which is important for keeping spammers out of your app.
While Android Studio comes with ProGuard built in, Google has been developing R8, which aims to be a drop-in replacement. It’s a new code shrinking and obfuscation tool that could replace ProGuard in the future. Check out Google’s R8 page for more information.
If you have any questions or comments, please join the discussion below!