ANDROID : ProGaurd


What is ProGaurd ?


It is a free Java class file shrinker, optimizer, obfuscator, and preverifier. It detects and removes unused classes, fields, methods, and attributes. It optimizes bytecode and removes unused instructions. It renames the remaining classes, fields, and methods using short meaningless names. Finally, it preverifies the processed code for Java 6 or for Java Micro Edition.  The result is a smaller sized .apk file that is more difficult to reverse engineer. Because ProGuard makes your application harder to reverse engineer, it is important that you use it when your application utilizes features that are sensitive to security like when you are Licensing Your Applications.  Having ProGuard run is completely optional, but highly recommended.

Today, I will describe the usage, advantages, disadvantage, and steps of ProGaurd. I will also explain how to enable and configure ProGuard as well as use the retrace tool to decode obfuscated stack traces.

ProGaurd Internal Steps:


1.      Shrinking

Java source code (.java files) is typically compiled to bytecode (.class files). Bytecode is more compact than Java source code, but it may still contain a lot of unused code, especially if it includes libraries. Shrinking tools such as ProGuard can analyze bytecode and remove unused classes, fields, and methods. The application remains functionally equivalent, including the information given in exception stack traces.
For a realistic example, take the following code:

if (LogUtils.LOGGING)

{

TestClass test = new TestClass();

Log.d(TAG, “testClass=” + test);

}

The above code is a typical scenario during development. You create code like this to help debug and test your code. Before releasing the final product, though, you set LOGGING to false, so it doesn’t execute. The problem is, this code is still in your application. It makes it bigger, and may cause potential security issues by including code which should never be seen by a snooping hacker.

Shrinking the code solves this problem beautifully. The code is completely removed from the final product, leaving the final package safer and smaller.

2.     Optimization

 Apart from removing unused classes, fields, and methods in the shrinking step, ProGuard can also perform optimizations at the bytecode level, inside and across methods. Thanks to techniques like control flow analysis, data flow analysis, partial evaluation, static single assignment, global value numbering, and liveness analysis, ProGuard can do things such as perform over 200 peephole optimizations, like replacing x * 2 with x << 1. The positive effects of these optimizations will depend on your code and on the virtual machine on which the code is executed. Simple virtual machines may benefit more than advanced virtual machines with sophisticated JIT compilers. At the very least, your bytecode may become a bit smaller.

Following are some examples of optimization performed by ProGaurd :

  • Evaluate constant expressions.
  • Remove unnecessary field accesses and method calls.
  • Remove unnecessary branches.
  • Remove unnecessary comparisons and instanceof tests.
  • Remove unused code blocks.
  • Merge identical code blocks.
  • Reduce variable allocation.
  • Remove write-only fields and unused method parameters.
  • Inline constant fields, method parameters, and return values.
  • Inline methods that are short or only called once.
  • Simplify tail recursion calls.
  • Merge classes and interfaces.
  • Make methods private, static, and final when possible.
  • Make classes static and final when possible.
  • Replace interfaces that have single implementations.
  • Perform over 200 peephole optimizations, like replacing …*2 by …<<1.
  • Optionally remove logging code.

3.     Obfuscation

By default, compiled bytecode still contains a lot of debugging information: source file names, line numbers, field names, method names, argument names, variable names, etc. This information makes it straightforward to decompile the bytecode and reverse-engineer entire application. Sometimes, this is not desirable. Obfuscators such as ProGuard can remove the debugging information and replace all names by meaningless character sequences, making it much harder to reverse-engineer the code. It further compacts the code as a bonus. The application remains functionally equivalent, except for the class names, method names, and line numbers given in exception stack traces.

4.     Preverification

When loading class files, the class loader performs some sophisticated verification of the byte code. This analysis makes sure the code can’t accidentally or intentionally break out of the sandbox of the virtual machine. Java Micro Edition and Java 6 introduced split verification. This means that the JME preverifier and the Java 6 compiler add preverification information to the class files (StackMap and StackMapTable attributes, respectively), in order to simplify the actual verification step for the class loader. Class files can then be loaded faster and in a more memory-efficient way. ProGuard can perform the preverification step too, for instance allowing to retarget older class files at Java 6.

Advantages of Proguard:


  • Creating more compact code, for smaller code archives, faster transfer across networks, faster loading, and smaller memory footprints.
  • Making apk and libraries harder to reverse-engineer.
  • Listing dead code, so it can be removed from the source code.
  • Retargeting and preverifying existing class files for Java 6, to take full advantage of Java 6’s faster class loading.

 

Limitations:


Some notable optimizations that aren’t supported yet:

  • Moving constant expressions out of loops.
  • Optimizations that require escape analysis (DexGuard does).

String values does not encrypt, it stays as is.

Decoding Obfuscated Stack Traces


When your obfuscated code outputs a stack trace, the method names are obfuscated, which makes debugging hard, if not impossible. Fortunately, whenever ProGuard runs, it outputs a <project_root>/bin/proguard/mapping.txt file, which shows you the original class, method, and field names mapped to their obfuscated names.

The retrace.bat script on Windows or the retrace.sh script on Linux or Mac OS X can convert an obfuscated stack trace to a readable one. It is located in the <sdk_root>/tools/proguard/ directory. The syntax for executing theretrace tool is:

retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]

For example:

retrace.bat -verbose mapping.txt obfuscated_trace.txt

If you do not specify a value for <stacktrace_file>, the retrace tool reads from standard input.

Debugging considerations for published application


Save the mapping.txt file for every release that you publish to your users. By retaining a copy of the mapping.txt file for each release build, you ensure that you can debug a problem if a user encounters a bug and submits an obfuscated stack trace. A project’s mapping.txt file is overwritten every time you do a release build, so you must be careful about saving the versions that you need.

For example, say you publish an application and continue developing new features of the application for a new version. You then do a release build using ProGuard soon after. The build overwrites the previous mapping.txt file. A user submits a bug report containing a stack trace from the application that is currently published. You no longer have a way of debugging the user’s stack trace, because the mapping.txt file associated with the version on the user’s device is gone. There are other situations where your mapping.txt file can be overwritten, so ensure that you save a copy for every release that you anticipate you have to debug.

How you save the mapping.txt file is your decision. For example, you can rename them to include a version or build number, or you can version control them along with your source code.

Example: A simple Android activity

These options shrink, optimize, and obfuscate the single Android activity mypackage.MyActivity:

-injars      bin/classes

-outjars     bin/classes-processed.jar

-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar

-dontpreverify

-repackageclasses ”

-allowaccessmodification

-optimizations !code/simplification/arithmetic

-keep public class mypackage.MyActivity

We’re targeting the Android run-time and keeping the activity as an entry point.

Preverification is irrelevant for the dex compiler and the Dalvik VM, so we can switch it off with the -dontpreverify option.

The -optimizations option disables some arithmetic simplifications that Dalvik 1.0 and 1.5 can’t handle. Note that the Dalvik VM also can’t handle aggressive overloading (of static fields).

If applicable, you should add options for processing native methodscallback methodsenumerationsannotations, and resource files.

Example: A complete Android application

These options shrink, optimize, and obfuscate all public activities, services, broadcast receivers, and content providers from the compiled classes and external libraries:

-injars      bin/classes

-injars      libs

-outjars     bin/classes-processed.jar

-libraryjars /usr/local/java/android-sdk/platforms/android-9/android.jar

-dontpreverify

-repackageclasses ”

-allowaccessmodification

-optimizations !code/simplification/arithmetic

-keepattributes *Annotation*

-keep public class * extends android.app.Activity

-keep public class * extends android.app.Application

-keep public class * extends android.app.Service

-keep public class * extends android.content.BroadcastReceiver

-keep public class * extends android.content.ContentProvider

-keep public class * extends android.view.View {

    public <init>(android.content.Context);

    public <init>(android.content.Context, android.util.AttributeSet);

    public <init>(android.content.Context, android.util.AttributeSet, int);

    public void set*(…);

}

-keepclasseswithmembers class * {

    public <init>(android.content.Context, android.util.AttributeSet);

}

-keepclasseswithmembers class * {

    public <init>(android.content.Context, android.util.AttributeSet, int);

}

-keepclassmembers class * extends android.content.Context {

   public void *(android.view.View);

   public void *(android.view.MenuItem);

}

-keepclassmembers class * implements android.os.Parcelable {

    static android.os.Parcelable$Creator CREATOR;

}

-keepclassmembers class **.R$* {

    public static <fields>;

}

 

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.