Saturday, February 7, 2015

Code Obfuscation and Optimization using Proguard tool



I have been thinking of writing on Code optimization and obfuscation from quiet a long time as I got chance to dive into obfuscation in my recent project, the basic mistakes we make while coding and how small things help us optimize. Also will cover the bullet points to keep in mind while coding.

Code optimization is a set of methods of code modification to improve code quality and efficiency.
A program may be optimized so that it becomes of a smaller size, consumes less memory, executes more rapidly, or performs fewer input/output operations.

Obfuscation is the deliberate act of creating obfuscated code, i.e. source or machine code that is difficult for humans to understand. Programmers may deliberately obfuscate code to conceal its purpose (security through obscurity) or its logic, in order to prevent tampering, deter reverse engineering, or as a puzzle or recreational challenge for someone reading the source code.

Proguard most popular open source code optimization and obfuscation tool provided as part of the Android SDK, can be a double edge sword -- it presents bootstrapping challenges but when applied correctly, provides tremendous benefits!


The ProGuard tool shrinks, optimizes, and obfuscates your code by removing unused code and renaming classes, fields, and methods with semantically obscure names. 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.


ProGuard is integrated into the Android build system, so you do not have to invoke it manually. ProGuard runs only when you build your application in release mode, so you do not have to deal with obfuscated code when you build your application in debug mode. Having ProGuard run is completely optional, but highly recommended.

This document describes how to enable and configure ProGuard as well as use the retrace tool to decode obfuscated stack traces.

Steps to use proguard tool properly



  1. Create android library project
  2. In Terminal go to sdk/tools and run below command to generate build.xml at the root folder of library project. build.xml basically has all the steps to build the .apk file and has property to use local project.properties file 
    <loadproperties srcFile="project.properties" />
    .
    /android update project -p /root path of your android library project
  3. To enable proguard, please check the predefined proguard.config refered in project.properties and uncomment it proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt

    1. In proguard-project.txt add below lines


    This would actually take the original jar generated in bin->proguard folder as an input to generate obfuscated jar generated at the path provided under -outjars

    The methods that needs to be accessed from another project or the code that we don't want to obfuscate can be kept without obfuscation using, similar thing can be done with class members also.

    -keepclassmembernames class *
    -injars       Path-to-android-project/bin/proguard/original.jar
    -outjars      Path-to-android-project/bin/samplelibraryout.jar
    -libraryjars  <java.home>/lib/rt.jar
    -printmapping out.map
    
    -keepparameternames
    -renamesourcefileattribute SourceFile
    -keepattributes Exceptions,InnerClasses,Signature,Deprecated,
                    SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
    
    -keep public class * {
        public protected *;
    }
    
    -keepclassmembernames class * {
        java.lang.Class class$(java.lang.String);
        java.lang.Class class$(java.lang.String, boolean);
    }
    
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    
    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }
    
    -keepclassmembers class * implements java.io.Serializable {
        static final long serialVersionUID;
        private static final java.io.ObjectStreamField[] serialPersistentFields;
        private void writeObject(java.io.ObjectOutputStream);
        private void readObject(java.io.ObjectInputStream);
        java.lang.Object writeReplace();
        java.lang.Object readResolve();
    }

    5. Similarly, to optimize add line in project.properties
    proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt

    6. Now in Terminal go to library project path and run below command, obfuscation or optimization can only be applied to a release build, hence the below commands.
    ant clean
    ant release


    Thats it an obfuscated and optimized jar is generated. 



    Obfuscated jar can be tested using any of the decompilers available, the one I use is JD-Decompiler.


    The basic requirement optimization methods should comply with is that an optimized program must have the same output and side effects as its non-optimized version. This requirement, however, may be ignored in case the benefit from optimization is estimated to be more important than probable consequences of a change in the program behavior.

    Also, few other things that should be kept in mind while optimizing the code.
    • Only optimize code if you need to
    • Only optimize where it counts
    • Use the profiler to see where to optimize
    • The profiler won't help you on the device, so use the System timer on the hardware
    • Always study your code and try to improve the algorithms before using low-level techniques
    • Drawing is slow, so use the Graphics calls as sparingly as possible
    • Use setClip() where possible to minimize the drawing area
    • Keep as much stuff as possible out of loops
    • Pre-calculate and cache like crazy
    • Strings create garbage and garbage is bad so use StringBuffers instead
    • Assume nothing
    • Use static final methods where possible and avoid the synchronized modifier
    • Pass as few parameters as possible into frequently-called methods
    • Where possible, remove method calls altogether
    • Unroll loops
    • Use bit shift operators instead of division or multiplication by a power of two
    • You can use bit operators to implement circular loops instead of modulo
    • Try to compare to zero instead of any other number
    • Array access is slower, so cache array elements
    • Eliminate common sub-expressions
    • Local variables are faster than instance variables
    • Don't wait() if you can callSerially()
    • Use small, close constants in switch() statements
    • Look inside your Fixed Point math library and optimize it
    • Unravel nested FP calls to reduce casting
    • Division is slower than multiplication, so multiply by the inverse instead of dividing
    • Use tried and tested algorithms
    • Use proprietary high-performance APIs with care to preserve portability 

    0 comments:

    Post a Comment