Writing in the front

Hello, I’m the light source.

This post was published on my personal public account and will be synchronized on my personal blog. If you have any suggestions, please go to the blog to comment, and if there are changes or corrections in the blog itself, it will be updated in the blog.

review

Obfuscation is undoubtedly one of the most important processes in the packaging process, and should be turned on in all apps for no particular reason.

First of all, the obfuscation here actually includes the optimization process of code compression, code obfuscation and resource compression. Using ProGuard, obfuscation processes remove unused classes, class members, methods, and attributes from the main project and dependency libraries, helping to circumvent the 64K method bottleneck. At the same time, renaming classes, class members, and methods to meaningless short names increases the difficulty of reverse engineering. With Gradle’s Android plugin, we can remove unused resources and reduce the size of the APK installation package.

This article is composed of two parts. The first part gives the best practices of obfuscation, and tries to make obfuscation available to beginners with zero foundation. The second part will introduce the whole of obfuscation, the syntax and practice of custom obfuscation rules, custom resource retention rules, and so on.

Android confuses best practices

1. Confuse the configuration

By default, the build.gradle file in the App Module has the following structure:

android {
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}Copy the code

Obfuscation should not be enabled in debug mode because it takes longer to compile. What we need to do is:

  1. Change minifyEnabled under release to true to turn on obfuscation;

  2. ShrinkResources true to turn on resource compression.

The modified file content is as follows:

android {
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}Copy the code

2. Customize obfuscation rules

Proguard-rules. pro, a custom obturation rule file for the project, is generated by default in app Module. After multiple investigations, the best practices of obturation rules applicable to most projects are as follows:

- optimizationpasses 5 # # specified compression level cannot skip class members of the non public library - the algorithm adopted in dontskipnonpubliclibraryclassmembers # confusion - optimizations. code/simplification/arithmetic,! field/*,! Class/done / * # confusion in the class, the name also confused - useuniqueclassmembernames # optimization there allowed to access and modify the modifier when members of the class and class - allowaccessmodification # will source file renamed "SourceFile string" - renamesourcefileattribute SourceFile # keep the line Numbers - keepattributes SourceFile, LineNumberTable # keep all implementation 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(); } #Fragment does not need to be registered in androidmanifest.xml, Need additional protection - keep public class * extends android. Support. The v4. App. The fragments - keep public class * extends android. The app. # fragments ** - DontNote junit.runner.** -dontwarn Android.test.** -dontwarn android.support.test.** -dontwarn org.junit.**Copy the code

These are the really generic ones that need to be added, but each project needs to add some obfuscation rules for its own needs:

  • Obfuscation rules required by third-party libraries. Formal third-party libraries generally write the obfuscation rules in the access document, which should be added when using.

  • Code that changes dynamically at run time, such as reflection. A good example is an entity class that converts to and from JSON. -keep public class **.* model *.** {*; -keep public class **. }

  • Classes invoked in JNI.

  • A method called by JavaScript in a WebView

  • Layout View constructor, Android :onClick, etc.

3. Check for confusion

Obfuscated packages must be checked to avoid bugs introduced through obfuscation.

On the one hand, you need to check at the code level. After using the above configuration for confusing packaging/build/outputs/mapping/release/directory will output the following files:

  • Dump.txt describes the internal structure of all classes in APK files

  • TXT Provides mapping tables of classes, methods, and class members before and after the confusion

  • Seeds.txt lists the classes and members that have not been confused

  • Usage.txt lists the code that was removed

We can check the unscrambled classes and members to see if they contain all the desired ones using the seeds.txt file, and then use the usage.txt file to see if any code was mistakenly removed.

On the other hand, you need to check from the testing side. The obfuscated packages are thoroughly tested for bugs.

4. Resolve the obfuscation stack

The confused class and method names are difficult to read, which, of course, will increase the difficulty of reverse engineering, but also cause obstacles to tracking online crash. After we get the stack information of crash, we will find it difficult to locate. In this case, we need to reverse the confusion.

Bat for Windows or ProGuardgui.sh for Mac or Linux. Procedure

The Window platform is used as an example. After you double-click proGuardgui.bat, you can see a menu on the left. Confused click ReTrace, select the package corresponding mapping file (after the confusion in the/build/outputs/mapping/release/path generates mapping. TXT file, its role is to provide the confusion before and after class, method, class member table), Then paste crash’s stack trace into the input box, click ReTrace in the lower right corner, and the confused stack information will be displayed.

The above is done using a GUI program. Another way is to use the retrace tool in this path to reverse the solution from the command line

retrace.bat|retrace.sh [-verbose] mapping.txt []Copy the code

Such as:

retrace.bat -verbose mapping.txt obfuscated_trace.txtCopy the code

Matters needing attention:

1) All classes involved in androidmanifest.xml are automatically retained, so there is no need to add this obfuscating rule. (Used in many old obfuscations, no longer necessary)

2) There are some default obturation rules in proguard-android. TXT. There is no need to add them in proguard-rules.pro repeatedly.

Two, confuse the introduction

“Obfuscation” in Android can be divided into two parts. One is optimization and obfuscation of Java code, which is realized by proGuard obfuscation. The other part is resource compression, which removes unused resources from projects and dependent libraries (resource compression is not strictly related to confusion, but is generally covered together).

1. Code compression

Code obfuscation is a process involving code compression, optimization and obfuscation. As shown in the figure above, the obfuscation process has the following functions:

  1. Compression. Remove invalid classes, class members, methods, attributes, etc.

  2. Optimization. Analyze and optimize method binary code; As described in Proguard-Android-optimize.txt, optimizations pose some potential risks and are not guaranteed to work on all versions of Dalvik.

  3. Confusion. Replace class, attribute, and method names with short, meaningless names;

  4. Preverification. Add preverification information. This prevalidation works on The Java platform, not on Android, and can be removed to speed up obfuscation.

These four processes are enabled by default.

In an Android project, you can turn optimizations and prevalidations off by using -dontoptimize and -dontpreverify. (Of course, the default proGuard-Android. TXT file already contains these obturations, so you don’t need to configure them.)

2. Resource compression

Resource compression removes unused resources from projects and dependent libraries. This can be useful in reducing the size of APK packages and is generally recommended. Set shrinkResources to true in the build.grade file. Note that resource compression will only take effect if code compression is enabled with minifyEnabled True.

Resource compression consists of merging resources and removing resources.

In the Merge Resource process, resources with the same name are regarded as duplicate resources and merged. Note that this process is not controlled by the shrinkResources property and cannot be disabled. Gradle must do this because it will cause an error if there are resources with the same name in different projects. Gradle looks for duplicate resources everywhere:

  • SRC/main/res/path

  • Different build types (Debug, Release, and so on)

  • Different build channels

  • Third-party libraries that the project depends on

Resources are merged in the following sequence:

Dependencies -> Main -> Channels -> Build typeCopy the code

For example, if a duplicate resource exists in the main folder and in different channels, Gradle chooses to keep the resource in the channel.

At the same time, if duplicate resources occur at the same level, such as SRC /main/res/ and SRC /main/res/, Gradle cannot complete the resource merge and reports a resource merge error.

The “Remove resource” process is known by name, but it is important to note that, like code, obfuscating resource removal can also define which resources need to be retained, as described below.

3. Customize confusion rules

There is a line like this in the “obfuscation configuration” section above

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'Copy the code

This line of code defines that the obturation rule consists of two parts: the contents of proguard-Android. TXT in the tools/proguard/ folder of the SDK and the contents of ProGuard-rules.pro placed by default in the module root directory. The former is the default obfuscation file provided by the SDK (see Appendix 1), while the latter is where developers customize obfuscation rules.

1. Common confusion commands:

  • optimizationpasses

  • dontoptimize

  • dontusemixedcaseclassnames

  • dontskipnonpubliclibraryclasses

  • dontpreverify

  • dontwarn

  • verbose

  • optimizations

  • keep

  • keepnames

  • keepclassmembers

  • keepclassmembernames

  • keepclasseswithmembers

  • keepclasseswithmembernames

The obfuscation commands used in part 1 of Android obfuscation Best Practices will not be described here, but can be found on the official website for details. In particular, there are several commands related to the rule of keeping related elements out of confusion:

The command role
-keep Prevents classes and members from being removed or renamed
-keepnames Prevents classes and members from being renamed
-keepclassmembers Prevents members from being removed or renamed
-keepnames Prevents members from being renamed
-keepclasseswithmembers To preventHaving the memberClasses and members are removed or renamed
-keepclasseswithmembernames To preventHaving the memberClasses and members are renamed

2. Keep elements from participating in obfuscation rules

Like:

[Hold command] [class] {[member]}Copy the code

“Class” represents a class-related qualification that will eventually locate some classes that meet that qualification. Its contents can be used:

  • Concrete classes

  • Access modifiers (public, protected, private)

  • Wildcard *, matches any length of character, but does not contain the package name delimiter (.)

  • The wildcard character ** matches characters of any length and contains the package name delimiter (.)

  • Extends, which is the base class of a class that you can specify

  • Implement, matches a class that implements an interface

  • $, inner class

“Member” represents a qualification associated with a class member, which will eventually locate some class member that meets that qualification. Its contents can be used:

  • Matches all constructors
  • Match all domains
  • Match all methods
  • Wildcard *, matches any length of character, but does not contain the package name delimiter (.)

  • The wildcard character ** matches characters of any length and contains the package name delimiter (.)

  • The wildcard character *** matches any parameter type

  • … Matches any type of argument of any length. Such as void test (…). I can match any void test(String a) or void test(int A, String b).

  • Access modifiers (public, protected, private)

For example, if you need to hold all public classes and constructors that inherit from the Activity in the name.huihui.test package, you can write:

-keep public class name.huihui.test.** extends Android.app.Activity {
    
}Copy the code

3. Common custom obfuscation rules

  • Don’t mess with a class

    -keep public class name.huihui.example.Test { *; }Copy the code
  • Do not confuse all classes of a package

    -keep class name.huihui.test.** { *; }Copy the code
  • Do not confuse subclasses of a class

    -keep public class * extends name.huihui.example.Test { *; }Copy the code
  • Do not confuse all classes and their members whose class names contain “Model”

    -keep public class **.*model*.** {*; }Copy the code
  • Do not confuse the implementation of an interface

    -keep class * implements name.huihui.example.TestInterface { *; }Copy the code
  • Do not obfuscate the constructor of a class

    -keepclassmembers class name.huihui.example.Test { 
      public (); 
    }Copy the code
  • Do not confuse specific methods of a class

    -keepclassmembers class name.huihui.example.Test { 
      public void test(java.lang.String); 
    }Copy the code

4. Customize resource retention rules

1. keep.xml

With shrinkResources true enabled, all unused resources are removed by default. If you need to define which resources must be reserved, create an XML file in the res/raw/ path, such as keep.xml.

You can define resource retention requirements by setting the following properties:

  • Tools :keep Defines which resources need to be reserved (separated by commas).

  • Tools :discard Defines which resources need to be removed (separated by commas)

  • Tools :shrinkMode Enables the strict mode

Normal resource reference checking can be problematic when Resources are retrieved and used in code using dynamic strings via resources.getidentifier (). For example, the following code causes all resources starting with “img_” to be marked as used.

String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());Copy the code

We can set Tools :shrinkMode to strict to enable strict mode so that only resources that are actually used are reserved.

The above is the configuration related to custom resource retention rules, as an example:


Copy the code

2. Remove alternative resources

Some alternative resources, such as strings. XML with multi-language support and layout. XML with multi-resolution support, can be removed by resource compression when they are not needed and do not want to be deleted.

We use the resConfig property to specify the properties that need to be supported, for example

android {
    defaultConfig {
        ...
        resConfigs "en", "fr"
    }
}Copy the code

Other language resources that are not explicitly declared are removed.

The resources

  • Shrink Your Code and Resources

  • proguard

  • Android security Attack and Defense, decompilation and obfuscation technology

  • Android confuses the transition from beginner to master

  • Android code confusion ProGuard

The appendix

# # package name not mixed cases - dontusemixedcaseclassnames don't skip the public library class - dontskipnonpubliclibraryclasses # # confusion when logging - verbose close preverification - DontPreVerify # do not optimize incoming class files -dontoptimize # Protect annotations - KeepAttributes *Annotation* # Keep all class names that have local methods and local method names -keepclasseswithmembernames class * { native ; } # keepClassMembers public class extends Android.view. View {void set*(***); *** get*(); } # keepClassMembers class * extends Android.app. Activity {public void *(android.view.view); } # enum - keepClassMembers enum * {**[] $VALUES; public *; } #Parcelable -keepclassmembers class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator CREATOR; } #R file static members - keepClassMembers class **.R$* {public static; } - dontwarn android. Support. * * # keep related annotation - keep class. Android support. The annotation. Keep - keep @android.support.annotation.Keep class * {*; } -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @android.support.annotation.Keep ; } -keepclasseswithmembers class * { @android.support.annotation.Keep (...) ; }Copy the code

Write in the last

Thank you very much for seeing the end, because my level is limited, if there is any mistake, please do not hesitate to comment, you can directly reply to the public number or go to the blog under the comment, thank you in advance.

At the same time, if you have any questions or discussions about this aspect, please feel free to contact me.