This week, an assumption I made about the Android Gradle plugin method
proguardFiles nearly resulted in a minor security slip. Let’s all learn from my mistake.
The client codebase I’m currently working on has three build types:
beta build type is a minor variation of the
debug build type, so it’s configured using the Android Gradle plugin’s
initWith method as shown below:
My tasks today included enabling ProGuard for every single build type. Here’s the code I initially wrote to accomplish this:
The common ProGuard rules defined in the
proguard-rules.pro files are applied to all three build types. The extra ProGuard rules defined in the
proguard-debug.pro file are applied to the
debug build type only.
proguard-debug.pro file contains exactly one line:
Obfuscation is a useful (but certainly not impenetrable) defense against reverse engineering of a compiled application. However, I have found in the past that it interferes with Android Studio’s debugger, so I like to disable it for the non-production build variants I actively develop with.
As described above, my intention was to disable obfuscation for the
debug build type only, leaving obfuscation enabled for the
release build types. To test that this was working as expected, I assembled a
beta build and inspected the APK contents using ClassyShark. Here’s what our
Parcelable utility class looked like in ClassyShark:
Those method names are definitely not obfuscated.
Confused, I jumped to the definition of the
proguardFiles method commonly used to apply ProGuard configuration to a build type (remember that in Android Studio you can do this using the “Go To Declaration” shortcut):
for loop, each configuration file is passed to the
proguardFiles method adds to an internal list of configuration files rather than specifying a new list of configuration files. In my opinion this is not obvious from the method name. At least the documented behavior is clear, though confusingly the name
proguardFiles may also be used to refer to a getter defined on the same type!
Armed with this knowledge, we can now walk through exactly what happens (with respect to ProGuard rules) when the
beta build type is configured:
initWith(buildTypes.debug)is called, which results in three ProGuard configuration files (
proguard-debug.pro) being added to the
betabuild type’s internal list;
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'is called, which results in duplicates of the
proguard-rules.probeing added to the
betabuild type’s internal list.
proguard-debug.pro is unintentionally included in the
beta build type’s internal list of configuration files, and the
-dontobfuscate ProGuard rule is therefore applied when packaging our application. This is consistent with what we found in the decompiled APK.
setProguardFiles to the rescue
The most obvious solution to this problem might be to avoid initializing the
beta build type using the
debug build type. However, this would have lead to a lot more duplication in our build.gradle file. Luckily there’s a better way.
BuildType class exposes a
setProguardFiles method, defined as follows:
This is exactly how I originally assumed the
proguardFiles method worked! Any existing configuration files are explicitly replaced by those contained in the argument passed to
setProguardFiles. So, here’s a fixed version of our build.gradle file:
To check that obfuscation was now properly enabled for
beta builds, I assembled a new APK and navigated to our
Parcelable utility class using ClassyShark:
The Android Gradle plugin source code is accessible - utilize it;
Proactively seek ways to validate your assumptions, no matter how obviously-correct they may seem/feel;
Double-triple-check changes that potentially impact application security.