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 3 of 4 of this article. Click here to view the first page.

Introspection and Reflection

Sloths hang out in trees for hours and hours, not seeming to do very much. It’s probably because they’re busy introspecting and reflecting on life. In Kotlin, introspection and reflection are features of the language that inspect objects and call methods dynamically at runtime.

ProGuard looks at the static version of your app, but doesn’t actually run your app, so it cannot know which methods are reachable using introspection and reflection. Remember that ProGuard takes long class names and replaces them with smaller names. If something tries to reference a name at runtime with a constant string, the name will have changed. You can often tell this is happening with the NoSuchMethodException.

You need to tell ProGuard to keep the sections that use reflection, like the @param code you added. You’ll use the implements keyword to keep all classes that extend or implement Parameter. Add the following to the end of the ProGuard rules file:

-keep class * implements org.simpleframework.xml.core.Parameter { public *; }

While you’re at it, go ahead and add a bit more functionality to the app. In the MainActivity.kt file, add this to the onBubbleSelected() method of the BubblePickerListener:

val showDetailsIntent = Intent(picker.context, SlothDetailActivity::class.java)
val pet = map[item.title]
showDetailsIntent.putExtra(SLOTH_KEY, pet)
startActivity(showDetailsIntent)

item.isSelected = false

Build and run the app. You should see the bubbles populated with the different species of sloths.
Final project
Tap on a bubble to reveal more information about each species.
Details view

Congratulations! You’ve set up a cool looking app using the default ProGuard settings.
Happy face
You could stop here, but if you’re interested in how the app size can be further reduced, continue reading.

Enabling Advanced Optimizations

ProGuard provides an advanced optimization profile. It’s not used as the default because it can cause further build and runtime errors. You’ll enable the advanced optimizations by swapping the default proguard-android.txt file out with proguard-android-optimize.txt.

In the build.gradle app module file, replace the proguardFiles line in the debug section with the following:

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'),
          'proguard-rules.pro'

Sync Gradle, then build and run the app. The build time will be much longer because ProGuard is performing more analysis and optimizations inside and across methods. It will seem very slow, much like a sloth climbing a tree.

Your app should crash with the following error:
Crash error message
Here, the @ symbol is in reference to an annotated class – in this case, classes annotated with org.simpleframework.xml.*. Add a rule to the end of the ProGuard rules file to keep methods annotated with org.simpleframework.xml.*:

-keepclassmembers class * { @org.simpleframework.xml.* *; }

This time, the error mentions a constructor instead of a missing class, which is why this code uses keepclassmembers to keep class members only.

Build and run the app. You should see your app back up and running again.

Note: This might be a good time to stop and check the APK Analyzer again to see how the size is doing.

You’ve set up a fancy ProGuard optimized app for your Sloth Sanctuary. The Sleuth Zoo is very competitive – they’ve been taking all your ideas and making a profit off of them. How evil! You think that when you release your app, they’re going to analyze your APK and steal your “secret sauce”. Since ProGuard performs obfuscation, this is yet another place that it can help you out.

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.

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.