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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 4 of 4 of this article. Click here to view the first page.

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 comraywenderlichandroidslothsanctuary. 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.

Bytecode

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:

  1. Gets the Kotlin class for the ownerClassName string provided.
  2. Instantiates that class at runtime if not already instantiated.
  3. Dynamically gets the property referred to by fieldName for the instantiated class.
  4. 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 f1f3 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.
Crash error message
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:

Obfuscated code
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!
Obfuscated code

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!