Zelix KlassMaster - Documentation

Reference Obfuscation Tutorial

This tutorial is divided into the following sections:

Introduction

A field or method reference is a field access or a method call. The following line of Java™ code represents references to the field System.out and to the method PrintStream.println(String).

System.out.println("Hello world");

A field or method reference can be to a Java™ API as in the example above or it can be to your own fields or methods. Zelix KlassMaster™'s Reference Obfuscation functionality allows you to obfuscate field or method references by replacing them with Reflection API or invokedynamic calls. In the case of static field and method references, it also obscures the identity of the class containing the static member. Zelix KlassMaster™ then encrypts the names of the fields, methods and classes that are used by the Reflection API calls.

The Reference Obfuscation functionality is intended to be used to obscure references in and to sensitive parts of your application. It can make decompiled code quite incomprehensible. It can also obscure key API calls which otherwise would allow a hacker to "zero in" on a particular part of your bytecode.

As mentioned above, Zelix KlassMaster™ will obfuscate a reference by replacing it with a Reflection API or invokedynamic call. If the bytecode being opened for obfuscation is Java 8 or better then invokedynamic calls will be used. You can change this behaviour by setting the ZKM_OBFUSCATE_REFERENCES_INDY configuration option to false. Note that earlier versions of the Oracle and IBM Java 8 JVM's have a bug and may crash when running reference obfuscated bytecode using invokedynamic. Versions of the Oracle Java 8 JVM that are 1.8.0_161 or better have had the bug fixed.

Although you could obfuscate almost ALL of the field and method references in your application, it would have a dramatic and probably unacceptable impact on your application's performance. Reflection API calls are much slower than direct references. Invokedynamic calls are much faster than Reflection API calls but they are still slower than direct references. As a very rough guide, on a Java 8 JVM, Reflection API calls are about 15 times slower and invokedynamic calls are about 2.5 times slower than direct references. On a Java 9 JVM, Reflection API calls are about 7.5 times slower and invokedynamic calls are about 1.5 times slower than direct references.

When you use the Reference Obfuscation functionality to obscure sensitive parts of your application you should be careful to also obscure a significant number of non-sensitive parts. Otherwise you will be drawing attention to the sensitive parts which would be counter productive. However, you should be careful not to unnecessarily obfuscate references in performance sensitive parts of your code such as within deeply nested loops.

Example

Method decompiled 

public static void method0() {
   System.out.println("HelloWorld");
}

Method Reference Obfuscated and String Encrypted with Method Parameter Changing then decompiled 

private static final String c;
public static void method0(final long n4) {
   /*invokedynamic(j:(Ljava/io/PrintStream;Ljava/lang/String;JJ)V,
                   invokedynamic(\u00ee:(JJ)Ljava/io/PrintStream;, 
                   -7703503747166308699L, n4), 
                   MyClass.c, 
                   -7703568321494008229L, n4)*/
}

How to use Reference Obfuscation functionality

The full Reference Obfuscation functionality is only available through the ZKM Script interface. The first step is to "switch on" the Reference Obfuscation functionality. You switch on the Reference Obfuscation functionality by setting the obfuscateReferences parameter of the obfuscate statement to normal. It will default to none.

obfuscateReferences=normal

The obfuscate statement has two other relevant parameters. Before discussing these parameters, it should be understood that the Reference Obfuscation functionality requires the creation of some special fields and methods. These fields and methods are used to decrypt the strings used in the Reflection API calls and for caching Class, Field and Method objects.

The first of the two parameters is the obfuscateReferenceStructures parameter which specifies where the special fields and methods should be put. The obfuscateReferenceStructures parameter can be set to inSpecialClass or inReferencingClasses. It defaults to inSpecialClass. The inSpecialClass setting tells Zelix KlassMaster™ to put the special fields and methods into a single special class created just for this purpose. This setting typically results in smaller overall code size and better performance because there is one cache for the entire application. The weaknesses with this setting are that
  1. it limits the number of references that can be obfuscated because there is a limit to size of a class's constant pool and
  2. it creates a new interdependencies between the classes in your application and the new special class which can cause problems in certain scenarios.
The inReferencingClasses setting the obfuscateReferenceStructures parameter tells Zelix KlassMaster™ to put the special fields and methods into the classes that makes the original reference. This can result in duplication with the fields, methods and caches being private to each referencing class. However, no new inter-class dependencies are created.

The second of the two parameters is the obfuscateReferencesPackage parameter. It only has an effect if you specify obfuscateReferenceStructures=inSpecialClass. The obfuscateReferencesPackage allows you to specify which already existing package you would like the special class to be put into.

obfuscateReferencesPackage="com.mycompany.mypackage0"

After switching the Reference Obfuscation functionality on, you still need to specify which field and method references you wish to have obfuscated. How you do this is explained in the next section.

How to specify field and method references

You specify which fields and/or method references you want obfuscated by using the obfuscateReferencesInclude and/or obfuscateReferencesExclude statements. The obfuscateReferencesInclude specifies the set of references which references are to be obfuscated. If a obfuscateReferencesExclude is used in association with a obfuscateReferencesInclude statement, then the obfuscateReferencesExclude removes references from the set specified by the obfuscateReferencesInclude. If a obfuscateReferencesExclude is used by itself then it removes references from the set of all references.

Both statements have a similar structure. The main part of both statements is a field or method specification. For example, the statement below specifies that all references to any method declared in the class com.mycompany.MyClass0 should be obfuscated.

obfuscateReferencesInclude com.mycompany.MyClass0 *(*);

Both statements also have an optional containedIn clause which specifies the methods in which a reference must exist before it will be obfuscated. The statement below specifies again that that references to any method declared in the class com.mycompany.MyClass0 should be obfuscated but only where those references occur in a public method in the class com.mycompany.MyClass1.

obfuscateReferencesInclude containedIn{com.mycompany.MyClass1 public *(*)} com.mycompany.MyClass0 *(*);

So, let's say that you would like obfuscate (i.e. hide) calls to the method com.mycompany.MyClass1 mySensitiveMethod() no matter where they occur. You could do that with the following statement.

obfuscateReferencesInclude com.mycompany.MyClass1 mySensitiveMethod();

Alternatively, let's say that you would like all obfuscate all method calls made from within the method com.mycompany.MyClass1 mySensitiveMethod(). You could do that with the following statement.

obfuscateReferencesInclude containedIn{com.mycompany.MyClass1 mySensitiveMethod()} *.* *(*);

As a final example, let's say that you would like all obfuscate all calls to java.lang.System getenv(String) but only where they are made from within the method com.mycompany.MyClass1 mySensitiveMethod(). You could do that with the following statement.

obfuscateReferencesInclude containedIn{com.mycompany.MyClass1 mySensitiveMethod()} java.lang.System getenv(java.lang.String);

Interaction with Method Parameter Changes

If you use Zelix KlassMaster™'s Method Parameter Changes functionality then it can significantly "harden" any Reference Obfuscation from attack. The downside is that the Method Parameter Changes functionality can interlink your classes such that it can become impractical to release changes to your obfuscated classes in the form of patches which are just a subset of your classes. It is a trade-off between protection and flexibility.

Qualifications

There are a small number of references which cannot be obfuscated. These include the following
  • References to class initializer or constructor methods,
  • References to certain methods in java.lang.Object,
  • References made through the super keyword.
Also, if you specify obfuscateReferenceStructures=inReferencingClasses then references made in pre-Java 8 interfaces cannot be obfuscated.
 
Documentation Table of Contents
Zelix KlassMaster - Java Obfuscator