Zelix KlassMaster - Java Obfuscator

Zelix KlassMaster™ Java Obfuscator and Obfuscation FAQ

Version 22.0

  1. How do I get started with Zelix KlassMaster™?
  2. Why wont Zelix KlassMaster™ run?
  3. Does Zelix KlassMaster™ support Java 8 through to Java 22?
  4. What is the difference between the evaluation and licensed versions of Zelix KlassMaster?
  5. Will Zelix KlassMaster™ run in Windows, Mac OS X or Linux?
  6. Does Zelix KlassMaster™ support JEE™, Spring, Hibernate™, Java ME™ and Google Android™?
  7. Why don't all names change when I obfuscate?
  8. Won't string encryption slow down my code and make it bigger?
  9. When I change a method name in one class, why do some methods in other classes also get renamed?
  10. When changing a method name, why do I get the message "New name clashes with a method in Class name" when the class mentioned doesn't seem to be related to the class that defines the method?
  11. After being obfuscated why does my code get the runtime errors "ClassFormatError", "VerifyError", "NoClassDefFoundError" or "Can't find class Classname"?
  12. Can Zelix KlassMaster™ automatically handle Java Reflection API calls?
  13. When I open some classes why do I get the "Class Open Warnings" window?
  14. When I open some classes why do I get the "Class Open Errors" window?
  15. What options should I use when obfuscating my classes?
  16. What can I do if I get an OutOfMemoryError or "Insufficient Memory to open classes" error?
  17. Why does Zelix KlassMaster™ run so slowly?
  18. How do I obfuscate classes with native methods?
  19. I used the Zelix KlassMaster™ ZKM Script Helper to create a ZKM Script file but when I run it I get warnings. Why?
  20. Can Zelix KlassMaster™ obfuscate JSPs?
  21. Won't flow obfuscation slow down my code and make it bigger?
  22. Can I tell Zelix KlassMaster™ not to flow obfuscate certain methods?
  23. Why aren't all my methods flow obfuscated?
  24. What is the difference between "normal", "aggressive", "flow obfuscate" and "enhanced" String encryption?
  25. After obfuscation, why don't my classes work properly?
  26. What does the message "WARNING: changeLogIn fileName: Method oldMethodName in class className1 is mapped to new name newMethodName1 but the corresponding method in class className2 is mapped to new name newMethodName2. Using mapping of newMethodName2 for both methods" mean?
  27. How does the Input Change Log functionality interact with the name exclusion functionality?
  28. If I exclude a class from being obfuscated will the class's methods still be obfuscated?
  29. Can I edit a change log and then use it as an input change log so that I can predetermine new class, field or method names?
  30. I have a number of applets in the default package that I obfuscate separately. How do I stop Zelix KlassMaster™ from giving them the same name (e.g."a.class")?
  31. What is the easiest way of excluding non-static, non-transient fields from being renamed in serializable classes?
  32. My application will consist of a client jar file and server jar file which share some common classes. How do I obfuscate it?
  33. Does Zelix KlassMaster™ support incremental obfuscation?
  34. Can Zelix KlassMaster™ be called from a build tool such as Apache Ant?
  35. Can I access environment variables from within a ZKM Script?
  36. Our application uses Java Serialization. How does this impact obfuscation?
  37. Does Zelix KlassMaster™ support ResourceBundles as used in Localization and Internationalization?
  38. Why do I get a ClassCastException when I try to obfuscate bytecode compiled with Java 5 (i.e. JDK 1.5)?
  39. How do I refer to inner classes in ZKM Script statements?
  40. I am getting "Proguard returned with error code 1". How do I find out what is wrong?
  41. What settings do I use for Android™ Apps?
  42. What are default exclusions?
  43. Won't the Reference Obfuscation function slow down my code and make it bigger?
  44. By how much will parallel processing reduce obfuscation time?
  45. What do the "API warnings detected during open" or "API calls detected that may not be handled automatically" messages mean?
  46. Does Zelix KlassMaster™ support Java bytecode generated from Kotlin™ source?

Answers

Q0. How do I get started with Zelix KlassMaster™?

It is recommended that you initially use the GUI Build Helper tool to get a feeling for the processing sequence and then to create your first ZKM Script. It is also highly recommended that you at least read the basic exclusions part of the ZKM Script Exclusions Tutorial before modifying your first ZKM Scripts.

See the Getting Started page of the documentation for more detail.

Q1. Why wont Zelix KlassMaster™ run?

There could be a number of problems.
  1. You cannot run Zelix KlassMaster™ using a Java 7 or earier JVM. You need Java 8 (JDK 1.8 or better).
  2. Zelix KlassMaster™ may not run on your operating system. See the answer to Q4.
If you are not getting a useful error message, try running with the -verify and -verbose switches of the JVM.

Q2. Does Zelix KlassMaster™ support Java 8 through to Java 22?

Yes. Although, Zelix KlassMaster™ requires only a recent Java 8 JVM to run, it can handle Java 9 through to Java 22 bytecode including modules.

Of course Zelix KlassMaster™ will also open and process Java 1.1 through to Java 8 bytecode. All you need to do is set the Zelix KlassMaster™ classpath to point to the appropriate bootstrap classes. For Java 2 through to Java 8 that is the corresponding rt.jar. For Java 9 to Java 22 it is the corresponding lib\jrt-fs.jar file system.

Q3. What is the difference between the evaluation and licensed versions of Zelix KlassMaster?

The major differences between the evaluation version and the commercial version are as follows:
  • The evaluation version will flow obfuscate only one or two methods in each class.
  • The evaluation version is time limited to thirty days.

Q4. Will Zelix KlassMaster™ run in Windows, Mac OS X or Linux?

Zelix KlassMaster™ is written entirely in Java 8 (i.e. JDK 1.8) and technically should run on any platform that supports a Java 8 or better Virtual Machine. However, note that Zelix KlassMaster™ version 10 or better requires a newer version of a Java 8 JVM to run. If you use an Oracle JVM earlier than 1.8.0_152 then you may get a JVM crash due to HotSpot bugs. Also note that Zelix KlassMaster™ version 22 currently may not run successfully on older versions (e.g. pre-Aug 2020) of the Eclipse OpenJ9 JVM due to a JVM bug.

More generally, differences in the file systems and GUIs can also cause problems. Further, Zelix KlassMaster's flow obfuscation technology can expose bugs in some Just in Time (JIT) compilers. Zelix KlassMaster™ has been tested successfully using the following environments.

  • Windows 11™ 64bit using
    • java version "1.8.0_401" Java HotSpot(TM) 64-Bit Server VM (build 25.401-b10, mixed mode)
    • openjdk version "1.8.0_402" OpenJDK 64-Bit Server VM (Temurin)(build 25.402-b06, mixed mode)
    • java version "11.0.22" 2024-01-16 LTS Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.22+9-LTS-219, mixed mode)
    • openjdk version "11.0.22" 2024-01-16 OpenJDK 64-Bit Server VM Temurin-11.0.22+7 (build 11.0.22+7, mixed mode)
    • openjdk version "17.0.10" 2024-01-16 OpenJDK 64-Bit Server VM Temurin-17.0.10+7 (build 17.0.10+7, mixed mode)
    • openjdk version "21.0.2" 2024-01-16 LTS OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode)
    • java version "21.0.2" 2024-01-16 LTS Java HotSpot(TM) 64-Bit Server VM (build 21.0.2+13-LTS-58, mixed mode, sharing)
    • openjdk version "22" 2024-03-19 OpenJDK 64-Bit Server VM (build 22+36-2370, mixed mode, sharing)
    • IBM Semeru Runtime Open Edition (build 1.8.0_402-b06) Eclipse OpenJ9 VM (build openj9-0.43.0, JRE 1.8.0x86-32-Bit 20240131_847 (JIT enabled, AOT enabled)
    • IBM Semeru Runtime Open Edition (build 11.0.22+7) Eclipse OpenJ9 VM 11.0.22.0 (build openj9-0.43.0, amd64-64-Bit 20240131_835 (JIT enabled, AOT enabled)
    • IBM Semeru Runtime Open Edition 17.0.10.0 (build 17.0.10+7) Eclipse OpenJ9 VM 17.0.10.0 (build openj9-0.43.0, amd64-64-Bit 20240116_624 (JIT enabled, AOT enabled)
    • IBM Semeru Runtime Open Edition 21.0.2.0 (build 21.0.2+13-LTS) Eclipse OpenJ9 VM 21.0.2.0 (build openj9-0.43.0, amd64-64-Bit 20240116_95 (JIT enabled, AOT enabled)
  • Linux (Xubuntu 22.04) using
    • java version "1.8.0_401" Java HotSpot(TM) 64-Bit Server VM (build 25.401-b10, mixed mode)
    • java version "11.0.22" 2024-01-16 LTS Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.22+9-LTS-219, mixed mode)
    • java version "17.0.10" 2024-01-16 LTS Java HotSpot(TM) 64-Bit Server VM (build 17.0.10+11-LTS-240, mixed mode, sharing))
    • java version "21.0.2" 2024-01-16 LTS Java HotSpot(TM) 64-Bit Server VM (build 21.0.2+13-LTS-58, mixed mode, sharing)
    • openjdk version "22" 2024-03-19 OpenJDK 64-Bit Server VM (build 22+36-2370, mixed mode, sharing)
    • IBM Semeru Runtime Open Edition 17.0.10.0 (build 17.0.10+7) Eclipse OpenJ9 VM 17.0.10.0 (build openj9-0.43.0,amd64-64-Bit 20240116_670 (JIT enabled, AOT enabled)
    • IBM Semeru Runtime Open Edition 21.0.2.0 (build 21.0.2+13-LTS Eclipse OpenJ9 VM 21.0.2.0 (build openj9-0.43.0, amd64-64-Bit 20240116_94 (JIT enabled, AOT enabled)

Does Zelix KlassMaster™ support JEE™, Spring, Hibernate™, Java ME™ and Google Android™?

Yes. Zelix KlassMaster™ supports JEE. Its default exclusions and its SmartSave™ technology provide automatic support for servlets, the EJB 1 specification and the EJB 3 specification to the extent that you have used annotations rather than XML. If your application is an EJB 2 application that uses advanced features such as container managed persistence then we recommend that, for all classes directly related to the EJBs, you exclude
  • public class names and their containing package names,
  • public field names,
  • public method names.
If your EJB related classes are in specific packages then it should be quite easy to specify these exclusions in ZKM Script. Flow obfuscation and String encryption should cause no JEE problems.

Yes. Zelix KlassMaster™ supports Spring and Hibernate™ by automatically updating the XML to reflect any obfuscated names. However, there are some minor qualifiactions.

Yes. Zelix KlassMaster™ supports Java ME. There is a plugin for the Sun Java ME Wireless Toolkit. If you are not using the Sun Java ME Wireless Toolkit then see the Java ME obfuscation tutorial for more detail on the steps invloved.

Yes. Zelix KlassMaster™ supports Google Android™ by obfuscating the Java class files before they are converted into an APK file. Depending on your build environment you may need to use of Zelix KlassMaster™'s ProGuard compatibility.

Q6. Why don't all names change when I obfuscate?

There are a number of reasons why all class, field or method names don't change when you obfuscate.
  • The name is public or protected and your obfuscate options say that public or protected names shouldn't be changed.
  • The name of a package scoped method is overridden to be protected or public in a subclass. If your obfuscate options say that protected or public method names shouldn't be changed then the overridden method name can't be changed without breaking the polymorphic link.
  • The name of a method overrides a method in a superclass or implements a method in an interface but the superclass or interface hasn't been opened inside Zelix KlassMaster. In such cases the method name cannot be changed without breaking the polymorphic link.

Q7. Won't string encryption slow down my code and make it bigger?

Encrypted strings are decrypted at runtime using code that has been added to your classes. This means that the classes will run slightly slower and will be bigger. How much slower and bigger depends on how many String literals there are in your bytecode and the option selected. Typically the performance impact is insignificant. The bytecode size increase if all Strings are encrypted is typically in the range of 10% to 30%. The enhanced setting will result in the biggest increase in bytecode size but it will also provide the best protection. It is recommended that you measure the impact on your classes.

Q8. When I change a method name in one class, why do some methods in other classes also get renamed?

When you change a method name, Zelix KlassMaster™ automatically changes all method names that are polymorphically linked to that method. So overridden methods in superclasses and overriding methods in subclasses must also be renamed. If the method is an implementation of an interface method then the method in the interface and the corresponding methods in all classes that implement that interface must also be renamed. So the change can ripple across to different branches of the class hierarchy.

Q9. When changing a method name, why do I get the message "New name clashes with a method in Class name" when the class mentioned doesn't seem to be related to the class that defines the method?

The answer to Q8 explains how a method name change can ripple across to different branches of the class hierarchy. So Zelix KlassMaster™ must check that the new name wont clash with existing names in these other classes. A name clash occurs if the proposed new signature
  • already exists in a class
  • already exists in a superclass or subclass so that a new polymorphic relationship would be created.

Q10. After being obfuscated why does my code get the runtime errors "ClassFormatError", "VerifyError", "NoClassDefFoundError" or "Can't find class Classname"?

This will happen if you obfuscate your bytecode using Java illegal identifiers and you have run you code with verification switched on. Verification is on:
  • in JDK 1.1.7 or less, for applications run with the -verify switch
  • in Java 2 or better by default unless you use the -Xverify:none switch
  • is always switched on for Applets.
You cannot use illegal identifiers if your classes must run after being verified.

Alternatively, you will get such messages even when not runing with verification if you have renamed your highest level package qualifiers so that class names are Java illegal when fully qualified. For example a class named com.1.0 will cause no problems when running without verification but a class named 1.1.0 will cause problems.

Q11. Can Zelix KlassMaster™ automatically handle Java Reflection API calls?

The short answer is yes. Zelix KlassMaster™'s AutoReflection™ functionality can automatically handle Java Reflection API calls. However, it is highly recommended that you also explicitly specify the subset of classes, fields and methods that are accessed by unresolved Reflection API calls by using the accessedByReflection and/or accessedByReflectionExclude statements.

If you choose not to use the AutoReflection™ functionality, Zelix KlassMaster™ can still automatically handle many simple Java Reflection API calls. When Zelix KlassMaster™ opens your classes, it detects Java Reflection API calls that access classes, fields or methods and it automatically looks for the source of the class, field or method name which is being passed to the Reflection API method. If Zelix KlassMaster™ can find a String literal which contains the name of the class, field or method then it is said to have "resolved" that call. Zelix KlassMaster™ automatically handles such resolved Reflection API calls by updating the String literal that holds the class, field or method name to reflect the corresponding obfuscated name.

Regardless of whether or not you use the the AutoReflection™ functionality, if Zelix KlassMaster™ cannot fully resolve a Reflection API call then it lists the unresolved call in the Class Open Warnings window if you are using the GUI or in the Zelix KlassMaster™ log (which is named "ZKM_log.txt" by default) under the heading "API calls detected that may not be handled automatically...".

Zelix KlassMaster™ may still be able to automatically process classes that appear in the Class Warnings list (even if not you don't use the AutoReflection™ functionality). For example, calls to Class.getName() are often made purely for debugging purposes. Also, in certain situations, Zelix KlassMaster™ can change the contents of a string containing a class name to match the new class name. In any case, the user must look at the way the call is used to determine if the class can be reliably processed automatically.

The bottom line is that
  • If you choose not to use the AutoReflection™ functionality and
  • If a particular Reflection API call is not handled automatically by Zelix KlassMaster™ and
  • That will break the obfuscated application
then the package, class, field or method that is accessed by name by that call must be explicitly excluded from being renamed.

Q12. When I open some classes why do I get the "Class Open Warnings" window?

When Zelix KlassMaster™ opens a class that it might not be able to trim or obfuscate reliably it reports the fact using the "Class Open Warnings" window. You may get the Class Open Warnings window when an opened class contains a Java Reflection API call to a method like one of the following:
  • Class.forName(String)
  • Class.getName()
  • Class.getField(String)
  • Class.getMethod(String)
  • Class.getDeclaredField(String)
  • Class.getDeclaredMethod(String)
  • Field.getName()
  • Method.getName()
  • Constructor.getName()
  • ClassLoader.loadClass(String,boolean)
  • ClassLoader.defineClass()
  • ClassLoader.findSystemClass(String)
  • ClassLoader.findLoadedClass(String)
  • RMIClassLoader.loadClass(String)
  • RMIClassLoader.loadClass(URL, String)
  • LoaderHandler.loadClass(String)
  • LoaderHandler.loadClass(URL, String)
  • EventSetDescriptor(Class, String, Class, String)
  • EventSetDescriptor(Class, String, Class, String[], String, String)
  • EventSetDescriptor(Class, String, Class, String[], String, String, String)
Note that the Class Open Warnings window lists
  • the class in which the Reflection API method call appears
  • the signature of the method in which the Reflection API method call appears
  • the signature of the Reflection API method being called.
Note that it does not name the class, field or method that is being accessed by name. Of course it cannot do that because the Reflection API call could not be fully analyzed. Please see the answer to Q11 for detail on what to do if you get "Class Open Warnings".

Q13. When I open some classes why do I get the "Class Open Errors" window?

Some obfuscators add corrupt information to bytecode files in the hope of confusing or crashing decompilers. Zelix KlassMaster™ detects this corrupt information and reports the fact using the Class Open Errors window? Corrupt Local Variable Table and Inner Class information is automatically removed.

Zelix KlassMaster™ will also report the existence of duplicate classes if the classes
  • appear in the same archive or
  • are not absolutely identical.

Q14. What options should I use when obfuscating my classes?

Obfuscation of even a mildly complicated application is not trivial. For this reason it is highly recommended that you start by using minimal obfuscation. Once you have your minimally obfuscated application working satisfactorily then you can iteratively increase the level of obfuscation. Please see the Getting Started for some preliminary advice.

Trim function

It is highly recommended that you do not use the Trim function until you have your obfuscated application working. Once your obfuscated application is working you will find that your ZKM Script exclude statement exclusion parameters will provide an excellent guide to the necessary ZKM Script trimExclude statement parameters.

Inner classes

Generally, you don't need to keep the inner class information so you select "false" in the "Keep inner class information" list. This will reduce the size of your bytecode. Typically, JVMs make no use of inner class information at runtime.

However, compilers do make use of inner class information at compile time. So, if your classes form a library which third party classes will be compiled against and if some of your inner classes are exposed to the user then you will need to select either "true" or "if name not obfuscated" from the "Keep inner class information" list. Selecting "true" will preserve all inner class information.

Generics

Similarly, you generally don't need to keep the generics information of your classes so you select "false" in the "Keep generics information" list. Typically, JVMs make no use of generics information at runtime and removing the generics information will reduce the size of your bytecode.

However, compilers do make use of generics information at compile time. So, if your classes form a library which third party classes will be compiled against then you should select "true" from the "Keep generics information" list.

String Encryption

The String Encryption "encrypt string literals" option is generally safe. However, use of the "aggressive", "flowObfuscate" or "enhanced" settings can, in certain unusual circumstances, result in the contents of public static final String fields of some interfaces not being accessible to classes outside of the set that you have obfuscated. If this happens, you will get a warning message in the Zelix KlassMaster™ log. Also, note that String encryption does make your bytecode larger and slightly slower (see the answer to Q7).

For maximum resistance to deobfuscation, it is recommended that you use the "enhanced" setting along with the Method Parameter Changes functionality.

Flow Obfuscation

If you are obfuscating an applet note that there are known bugs in the JITs of the early browsers that can be exposed by flow obfuscation. If you flow obfuscate using the "light" option then the risk of JIT problems will be greatly reduced. Nonetheless, if you flow obfuscate your applet then test it thoroughly.

Reference Obfuscation

The Reference Obfuscation functionality when used in conjunction with the the Method Parameter Changes functionality can make decompiled bytecode very difficult to understand. However, its use of the Reflection API can slow your application down. The performance impact can be quite dramatic in the case of pre-Java 8 bytecode where the JVM's invokedynamic instruction is not available and the Reflection API must be used instead. It is recommended that you obfuscate only those references that are not accessed many times and which do not appear in performance critical areas of your code. For example, as a rule of thumb, we would suggest that you do not obfuscate a reference which will be used more than 75,000 times (1,000 times for pre-Java 8) within a short spell of processing. Typically you could safely obfuscate those "high level" references which occur only a limited number of times as a user enters sensitive parts of your application.

Otherwise, it is generally recommended that you do not use Reference Obfuscation functionality until after you have your obfuscated application working satisfactorily using the more straightforward features.

Method Parameter Obfuscation

The Method Parameter Obfuscation functionality obfuscates method parameter lists by replacing them with a single Object[] parameter. Note that only methods that will have their names obfuscated will be eligible for method parameter obfuscation. The combination of method Name Obfuscation and Method Parameter Obfuscation reduces the comprehensibility of the Java source code produced by a decompiler. Method Parameter Obfuscation also obscures the changes made by the Method Parameter Changes functionality.

Note that the Method Parameter Obfuscation functionality can have a significant performance impact. For this reason it is recommended that you exclude any performance critical methods from having their parameter lists obfuscated.

Method Parameter Changes

The Method Parameter Changes functionality is essential if you want maximum protection against deobfuscation. As mentioned above, the functionality significantly hardens the String Encryption and Reference Obfuscation functions against being reversed.

You could consider using the flowObfuscate setting which results in a special kind of Flow Obfuscation which is more difficult to reverse than the classic Flow Obfuscation.

Application types

If your classes make up a self-contained application then it should be safe to change all package, class, field and method names other than those of the public static main(String[]) method and the class that contains it. You can achieve this by selecting the "Self contained application or applet" application type and by selecting your entry paint class in the "Don't change main class name" list.

If you are obfuscating a set of classes that will be used by third parties as a non-extensible class library then you should
  • not obfuscate public class names
  • not obfuscate public field or method names
You can achieve this is the GUI by selecting the "Non-extensible class library" application type.

If you are obfuscating a set of classes that will be used by third parties as an extensible framework then you should
  • not obfuscate public class names
  • not obfuscate public or protected field or method names
You can achieve this is the GUI by selecting the "Extensible framework" application type.

In either case you should
  • limit String encryption by either
    • using the "normal" setting or
    • using the "aggressive", "flowObfuscate" or "enhanced" settings but excluding all interfaces from String encryption using the ZKM Script stringEncryptionExclude statement. (e.g. "stringEncryptionExclude interface *.*;")
  • consider choosing the line numbers "scramble" option

If your classes form a complex EJB 2 application or (an EJB 3 application where you have used XML rather than annotations) then the easiest solution is to exclude all public classes, fields and methods that are directly related to the EJBs. Otherwise it should be safe to use light flow obfuscation and String encryption.

Line numbers

If the size of your bytecode is critical then select the "delete" line numbers option. However, if you are obfuscating for a beta release or if the size of your code isn't critical then you can select the "scramble" line numbers option. The code will be larger but you will find crashes much easier to track down.

Local variables

By default, Zelix KlassMaster™ deletes all local variable information. This information is primary used for debugging purposes and is not essential at runtime. However, the localVariables parameter of the ZKM Script obfuscate statement allows you to keep all or some of the local variable information or to obfuscate that information.

When using ZKM Script, you generally should use the "delete" local variable setting (which is the default). This is particularly the case if the size of your bytecode is important. However, if you are obfuscating a class library and need the method signatures to be previewable in IDEs then you should use the "keepVisibleMethodParameters" or "keepVisibleMethodParametersIfNotObfuscated" settings. (If you are using Java 8 then see the "Method parameters" section below.) See the documentation for the obfuscate functionality for more detail.

You can use the "obfuscate" setting to slightly complicate any attempts at decompilation but this will result in increased bytecode size when compared to the use of the "delete" setting.

Generally it is recommended that you never use the "keep" setting outside of your development environment.

Method parameters

Java 8 introduced the optional MethodParameters attribute which stores the names of your method parameters and some access flag information. These attributes are created only if you specify the -parameters compiler option.

When using ZKM Script, you generally should use the "delete" local variable setting (which is the default). This is particularly the case if the size of your bytecode is important. However, if you are obfuscating a class library and need the method signatures to be previewable in IDEs then you should use the "keepVisible" or "keepVisibleIfNotObfuscated" settings. See the documentation for the obfuscate statement for more detail.

Legal identifiers

It is highly recommended that you always use the "legal identifiers" option. This option is now available only in ZKM Script and only for the purposes of backwards compatibility. If you are obfuscating an application that doesn't have to run with verification switched on then you can get extra protection with this option but they will cause problems with modern JVMs.

The bottom line

Please note that whatever options you choose it remains your responsibility to fully test or retest your classes after they have been processed by Zelix KlassMaster™.

Q15. What can I do if I get an OutOfMemoryError or "Insufficient Memory to open classes" error?

Zelix KlassMaster™ holds a lot of information in memory for each class that it opens. So, the more classes you open and the more complicated those classes are the more memory you need. If Zelix KlassMaster™ tells you that there was insufficient memory to open your classes or if it crashes with an OutOfMemoryError then you can do one of the following:
  1. Give Java more memory to work with by using the -Xmx??m option where ?? represents the number of megabytes. (For example, the option -Xmx512m gives Java 512MB of heap space.)
  2. When you are running Zelix KlassMaster™ via Ant then you need to give more memory to Ant itself. An easy way of doing this is by setting the "ANT_OPTS" environment variable.
    e.g. set ANT_OPTS=-Xmx512m
  3. Use the "Tools/Garbage Collect" menu option or the gc ZKM Script statement after opening your classes and before obfuscating.
  4. Install more physical memory.
It is not recommended that you make use of virtual memory (by giving Java more memory to work with than is physically installed.) Performance will be unreasonably degraded as memory is swapped to and from the disk.

Q16. Why does Zelix KlassMaster™ run so slowly?

There are a number of reasons Zelix KlassMaster™ may run slowly.
  1. Your computer may have insufficient real memory installed. If your computer has to use virtual memory performance will be very poor. You will notice that the hard disk is being very heavily utilized.
  2. You are obfuscating while viewing a very large class or method. This results in reduced performance due to the overhead of updating the class or method details in the View Panel.

Q17. How do I obfuscate classes with native methods?

Zelix KlassMaster's default exclusions will automatically handle any native methods by ensuring that
  1. the names of the native methods themselves and
  2. the fully qualified names of the classes that contain them
will not be changed.

However, Zelix KlassMaster™ has no way of knowing if your native code accesses your Java fields or methods. If your native code does access any of your Java fields or methods then you will need to explicitly exclude the accessed member names and the fully qualified names of the classes that contain them from obfuscation.

Q18. I used the Zelix KlassMaster™ ZKM Script Helper to create a ZKM Script file but when I run it I get warnings. Why?

The current version of the ZKM Script Helper does not validate all of your choices to ensure they are not contradictory. For example, it will allow you to exclude final abstract methods even though such methods cannot exist. The parser will give you a warning in this case.

Q19. Can Zelix KlassMaster™ obfuscate JSPs?

Zelix KlassMaster™ cannot obfuscate JSP (Java Server Page) source but it can obfuscate the servlet that is generated from a JSP. However, problems can arise if a JSP servlet is automatically regenerated at runtime replacing the class produced by Zelix KlassMaster. If this can happen in your environment then you should specify your JSP servlet classes as "fixed" by using the ZKM Script fixedClasses statement. For example you could say:

fixedClasses *.* implements javax.servlet.jsp.JspPage;

Q20. Won't flow obfuscation slow down my code and make it bigger?

Generally, Zelix KlassMaster's flow obfuscation will slightly increase the size of your bytecode and it will reduce its performance. It is a trade off between the degree of protection against decompilation and bytecode size and speed. The table below shows the approximate bytecode size increase in a compressed JAR file that could be expected for typical applications. Note that the size increase will vary from application to application and you should measure the impact on your bytecode.
flow obfuscation
option used
approximate
size increase
light 2%
normal 3%
aggressive 7%
extraAggressive 10%

The performance impact of flow obfuscation varies depending upon
  • the bytecode that is being obfuscated,
  • the JVM used to execute the bytecode,
  • the flow obfuscation option used.
When the HotSpot™ 1.8.0 JVMs are used, the performance impact for general purpose applications is insignificant. However, the performance impact on extremely CPU intensive applications will be greater. Also, the the impact when other JVMs (e.g IBM) using JIT compiler technology will be greater. Again, it is recommended that you measure the impact on your classes. If you experience an unacceptable performance impact then you should
  • use "light" flow obfuscation,
  • use the ZKM Script obfuscateFlowExclude statement to exclude performance critical methods from flow obfuscation.

Q21. Can I tell Zelix KlassMaster™ not to flow obfuscate certain methods?

You can use the ZKM Script "obfuscateFlowExclude" statement to specify methods that you don't want flow obfuscated. This functionality is not yet available in the GUI interface. (Note that the name exclusion functionality has no impact at all on flow obfuscation.)

Q22. Why aren't all my methods flow obfuscated?

Zelix KlassMaster™ only attempts to obfuscate the flow of methods that already have some control flow complexity. So methods without if, switch, while or for constructs will not be flow obfuscated. Also there are small percentage of methods with some control flow complexity that Zelix KlassMaster™ cannot yet flow obfuscate. Finally, note that the evaluation version will only flow obfuscate one or two methods in each opened class.

Q23.What is the difference between "normal", "aggressive", "flow obfuscate" and "enhanced" String encryption?

"Normal" String encryption encrypts all String literals that are accessed by methods in the defining class. This can mean that the values of some final String fields were not obfuscated. For example, String constants in interfaces would typically not be encrypted.

"Aggressive" String encryption uses a more sophisticated approach that can normally encrypt Strings even in interfaces.

The "flow obfuscate" option is the same as the "aggressive" option but causes the runtime decryption instructions, that is inserted into your bytecode, to be flow obfuscated. This makes the decrypt instructions harder to decompile but it could cause problems with some JITs. Note that the limitations mentioned above for the "aggressive" option also apply for the "flow obfuscate" option. Note also that the String Encryption "flow obfuscate" option is quite independent of the general Flow Obfuscation functionality. You can choose to have one without the other.

The "enhanced" option is the same as the "flow obfuscate" option but uses an extra decrypt key. This makes the String encryption harder to reverse. For maximum resistance against deobfuscation, it is recommended that you use the "enhanced" setting along with the Method Parameter Changes functionality.

Q24. After obfuscation, why don't my classes work properly?

If you get any of the runtime errors "ClassFormatError", "VerifyError", "NoClassDefFoundError" or "Can't find class Classname" see the answer to Q10.

Another reasons for errors such as "NoClassDefFoundError", "Can't find class Classname" or "NoSuchMethodError" are:
  • You didn't open all your classes inside Zelix KlassMaster™ before obfuscating
  • Your application
    • Uses the Java™ Reflection API and
    • Zelix KlassMaster™ has reported that there are some Reflection API calls in your bytecode that it may not be able to automatically handle and
    • You didn't exclude the names of the classes, fields or methods that are accessed by those Reflection API calls.
    Please see the answer to Q12 for more detail.

Finally, if you used Flow Obfuscation and your classes are causing the VM to crash (typically with a memory access error) or if you are getting an unexpected NullPointerException, you may be running foul of a bug in the JIT. To test this, run your application with the JIT switched off. If the problems occur only when the JIT is being used you should:
  • Upgrade to a more recent JIT. Of course it isn't always practical to ask your users to upgrade their JITs. JITs that are known to cause bugs with flow obfuscated bytecode are:
    • Microsoft VMs earlier than 5.00.3167
    • Symantec JITs earlier than 3.00.072b.
    • some of the IBM 1.3 JVMs
  • If you obfuscated with "aggressive" flow obfuscation, try the "normal" setting.
  • If you obfuscated with "normal" flow obfuscation, try the "light" setting. The "light" setting should eliminate most problems.
  • If you can identify the flow obfuscated method that is triggering the JIT bug then you can use the ZKM Script obfuscateFlowExclude statement to exclude that method from flow obfuscation.
  • As a last resort you can switch off flow obfuscation altogether by using the "none" option.

Q25. What does the message "WARNING: changeLogIn fileName: Method oldMethodName in class className1 is mapped to new name newMethodName1 but the corresponding method in class className2 is mapped to new name newMethodName2. Using mapping of newMethodName2 for both methods" mean?

The warning message indicates that the method name mappings in the Input Change Log are inconsistent for the opened classes. This can happen if
  • you have directly edited the change log file and make some mistakes or
  • your application has changed so that there are now polymorphic relationships between two or more methods that where previously unrelated.
In either case, Zelix KlassMaster™ handles the situation automatically to ensure the obfuscated bytecode is OK.

Q26. How does the Input Change Log functionality interact with the name exclusion functionality?

If you are using the GUI or if you specified an input change log using the changeLogFileIn parameter of the ZKM Script obfuscate statement then Zelix KlassMaster™ will always try to rename packages, classes, fields and methods to the names specified by the input change log. All the other statements such as the exclude and obfuscate statements remain fully active but generally they will only have impact on the names of packages, classes, fields and methods that do not already appear in the input change log.

So, for example, if the input change log clashes with the exclusions that have been set (or haven't been set) then warnings will be generated but the input change log will be given priority. The only exceptions are where:
  • the input change log contains inconsistent or conflicting information. For example if it
    • contains duplicate new package, class, field or method names
    • contains fully qualified new class names that conflict with the corresponding new package name
  • the input change log contains information that cannot be validly applied to the opened classes. For example if it
    • would incorrectly make or break a polymorphic relationship between methods.
However, if you specified an input change log using the looseChangeLogFileIn parameter of the ZKM Script obfuscate statement then your exclusions will take precedence over the change log mappings. However, this could mean that the newly obfuscated classes will not be fully consistent with your previous release.

Q27. If I exclude a class from being obfuscated will the class's methods still be obfuscated?

Yes. If you specify a class in the exclude statement or using the GUI's Obfuscate Advanced Name Exclusion Options Dialog, you are excluding only the class name from obfuscation. Excluding a class name from obfuscation has no effect
  1. on the obfuscation of that class's field and method names or
  2. on the flow obfuscation or String encryption of any of its methods. To exclude methods from flow obfuscation you must use the ZKM Script obfuscateFlowExclude statement. To exclude String literals from encryption you must use the ZKM Script stringEncryptionExclude statement.

Q28. Can I edit a change log and then use it as an input change log so that I can predetermine new class, field or method names?

Yes but be careful. If you are using a change log that was generated when you obfuscated a release of your application or applet then make sure you edit a copy. You may still need the original to interpret stack dumps from the released bytecode.

Also, be careful when you choose you predetermined names. It's a complicated business - especially if you are predetermining method names. Zelix KlassMaster™ will perform a large number of cross checks on your input change log and on your opened classes but it may not check for every possible conflict. If Zelix KlassMaster™ gives you warnings then read them carefully.

Note that Zelix KlassMaster's ZKM Script language allows you to specify more than one input change log. So you could specify an small, edited input change log that specifies only your changes followed by the full, original input change log. This can be a safer approach.

Q29. I have a number of applets in the default package that I obfuscate separately. How do I stop Zelix KlassMaster™ from giving them the same name (e.g."a.class")?

It causes problems when different applets (or different classes used by different applets) are given the same name. Browsers and proxy servers can get confused. The recommended solution is to put your applets in different packages that are unique to your organisation and to exclude those package names from obfuscation. If you then obfuscate the package as a whole, then each class will be given a unique name within the name space of the package.

However, it is recognized that, for various reasons, not everyone wants to do this. The best alternative way of handling this situation is to use Zelix KlassMaster's Input Change Log functionality. Perform a preliminary obfuscation with all of your applet classes opened inside Zelix KlassMaster. Don't bother saving the obfuscated classes. All you want is the "cross applet" change log with each class being given a unique name. Now obfuscate each of the individual applets using the "cross applet" change log as an input change log so that each class again gets its unique name. Retain the "cross applet" change log for use as an input change log in any subsequent releases of your applets. You only need save the individual applet change logs if you have used line number scrambling.

Q30. What is the easiest way of excluding non-static, non-transient fields from being renamed in serializable classes?

Using the ZKM Script interface you can accomplish this easily with a statement like:

exclude *.* implements java.io.Serializable !static !transient *;

Using the GUI interface's Advanced Exclusion Options dialog, you can select "!static" and "!transient" from the drop down lists.

Q31. My application will consist of a client jar file and server jar file which share some common classes. How do I obfuscate it?

Obfuscate your application as a whole in one pass. Zelix KlassMaster™ version 3 or better automatically handles issues relating to obfuscating multiple JAR files.

If you will sometimes need to release just a changed client or server jar file then you should use Zelix KlassMaster's Input Change Log functionality to ensure consistent renaming across your releases. The steps are
  1. Initially obfuscate your client and server jar files together in one pass generating an overall change log.
  2. If you make changes to only one of the jar files then obfuscate both your client and server jar files together in one pass once more using the change log generated in the step above as an input change log. Release just the changed jar.
  3. Use the change log generated in the second step as the new overall change log.

Q32. Does Zelix KlassMaster™ support incremental obfuscation?

Yes. Zelix KlassMaster's Input Change Log functionality ensures
  1. consistent package, class, field and method renaming and
  2. consistent flow obfuscation
across releases. It is generally recommended that you always open ALL the classes of the application inside Zelix KlassMaster. This gives Zelix KlassMaster™ the opportunity to ensure that all references are consistent. This is the case even if you are planning to distribute just the changed class subset in the form of some kind of patch.

Zelix KlassMaster™ does provide some limited ability to obfuscate a subset of your application provided the mappings for the full application appears in an input change log. To do this you should set the allClassesOpened parameter of the obfuscate statement to false and set the classpath so that the unobfuscated versions of all the unopened classes of the application can been located.

However, if you are using the Trim function then you must still open your application as a whole.

Q33. Can Zelix KlassMaster™ be called from a build tool such as Apache Ant?

Yes. Zelix KlassMaster™ exposes a simple, generic Java API for use by build tools. It also provides an Ant task called ZKMTask. See the online documentation for a full description of the API and for extra detail on the Ant task definition.

Q34. Can I access environment variables from within a ZKM Script?

To access an enviroment variable such as PATH from within your ZKM script you should firstly add the environment variable to the System properties when you start Zelix KlassMaster. Use this syntax for Windows:

java -D"PATH=%PATH%" -jar ZKM.jar

and this syntax for Unix

java -DPATH=$PATH -jar ZKM.jar

If you running Zelix KlassMaster™ through Apache Ant then you can either add the environment variable to the Ant project properties or use the Zelix KlassMaster™ Ant task (ZKMTask) to add the environment variable to the System propeties. (See the online documentation for more detail.)

Then access the variable in your ZKM Script by using the %...% delimiters. For example %PATH%. The Zelix KlassMaster™ preprocessor will perform the substitution.

Q35. Our application uses Java Serialization. How does this impact obfuscation?

If you never have to deserialize a non-obfuscated class with a obfuscated class then Serialization presents no problems. Zelix KlassMaster™ will automatically handle the necessary exclusions using its defaults and its Input Change Log functionality can be used to ensure consistent renaming across releases. However, if you need to deserialize already existing (ie. unobfuscated) serialized instances using your obfuscated classes then your obfuscated classes need to be compatible for Serialization purposes with your unobfuscated classes.

To achieve this compatibility you need to use the ZKM Script existingSerializedClasses statement to specify the classes that have existing serialized instances. For each class matched by the statement, Zelix KlassMaster™ will
  • add name exclusions for
    • the fully qualified name of the class
    • the name of every field contained or inherited by the class
    • the fully qualified class name of the type of each non-primitive field contained or inherited by the class
  • add a serialVersionUID field to the class (if it doesn't already exist) set to a value based upon the non-obfuscated class definition.

Q36. Does Zelix KlassMaster™ support ResourceBundles as used in Localization and Internationalization?

The issue here is whether Zelix KlassMaster's obfuscation of class names will effect ResourceBundle.getBundle(String, Locale) calls.

PropertyResourceBundles

In the case of PropertyResourceBundles, because they are backed by properties files and don't involve Java class files, they are generally unaffected by obfuscation. For example, a properties file with the file name "pack1/pack2/dir3/MyProperties.properties" will be moved to be "a/a/dir3/MyProperties.properties" if the packages "pack1.pack2" are obfuscated to be "a.a".

However, if a property file backing a PropertyResourceBundle
  1. Has a name containing an unqualified class name (e.g. "pack1.pack2.MyClass0.properties") and
  2. The name of the class is used to get the name of the resource bundle (e.g. "ResourceBundle.getBundle(getClass().getName())")
then the code will be broken if the unqualified class name is obfuscated. This because the properties file will be moved to reflect obfuscated package names but it will not be renamed to reflect an obfuscated unqualified class name. In such a case you must explicitly exclude the unqualified class name from being obfuscated.

ListResourceBundles

On the other hand, ListResourceBundles are backed by class files. If you create your ListResourceBundles by specifying the fully qualified base class name in a String literal and if the base class is within a package then Zelix KlassMaster™ will automatically change the value of the String literal to match the new obfuscated base class name. So the following code would be automatically handled (as far as the base class is concerned).

ResourceBundle myBundle = ResourceBundle.getBundle("package1.Class1", myLocale);

However, some additional work would still have to be done to handle the subsidiary ListResourceBundle classes such as
  • package1.Class1_de,
  • package1.Class1_de_DE,
  • package1.Class1_fr and
  • package1.Class1_fr_CH
Basically, a class suffix exclusion statement must be used for each language code, country code and locale variant combination to ensure that the subsidiary classes are renamed in synchronization with the base class with the suffixes being retained. An example ZKM Script exclusion statement would be:

exclude *.<link>_de and
        *.<link>_de_DE and
        *.<link>_fr and
        *.<link>_fr_CH;
If your organization uses a standard set of localizations, you could consider adding such an exclusion to your defaultExclude.txt file so that it is automatically applied.

Otherwise, if your ListResourceBundle class names are not stored as fully qualified names in String literals or if the classes are not within packages then you would have to explicitly exclude the base class and subsidiary class names from being obfuscated.

Another issue with ListResourceBundles is that they must be explicitly from being trimmed if you use the trim function. This can be done as follows.

trimExclude *.* extends java.util.ListResourceBundle

Q37. Why do I get a ClassCastException when I try to obfuscate bytecode compiled with Java 5 (i.e. JDK 1.5)?

A ClassCastException can occur if you obfuscate bytecode compiled with the JDK 1.5 or better with Zelix KlassMaster™ 4.2 or earlier. It is due to the fact that the JDK 1.5 changed the JVM specification for the loading of constants in a fundamental way.

Zelix KlassMaster™ (version 4.3 and better) has been enhanced to support bytecode created with the JDK 1.5. However, if you compile with the JDK 1.5 using the "-source 1.4 -target 1.4" parameters then you may still be able to use earlier versions of Zelix KlassMaster™ without further difficulties.

Q38. How do I refer to inner classes in ZKM Script statements?

Inner classes are compiled to be top level classes. If you have an inner class Inner within the class com.mycompany.Outer then that inner class Outer.Inner will be compiled to the class file com/mycompany/Outer$Inner.class. At the level of the bytecode, the name of that inner class will be com.mycompany.Outer$Inner. Note the presence of the '$' character in the unqualified class name.

When you refer to inner classes in ZKM Script statements, you need to use the bytecode level name. So you would use the name com.mycompany.Outer$Inner and not com.mycompany.Outer.Inner.

Because all inner classes will contain the '$' character in their unqualified class name, you could exclude all inner classes with the following ZKM Script exclude statement. However, the statement would also match any non-inner classes that have been given a name including an embedded '$' character.

exclude *.*$*;

Q39. I am getting "Proguard returned with error code 1". How do I find out what is wrong?

You can get this error when you use Zelix KlassMaster™ to take the place of the ProGuard obfuscator in the Google Android Eclipse plugin. You should first look in the ZKM_PG_log.txt file. This is a special log file which Zelix KlassMaster™ uses to write messages relating to the translation of a ProGuard configuration file.

The ZKM_PG_log.txt file will also give you the location of the Zelix KlassMaster log file which is named ZKM_log.txt by default. You should look in the Zelix KlassMaster log file for any warnings or errors. See the ProGuard Configuration File Translation Tutorial Troubleshooting section for more detail.

Q40. What settings do I use for Android™ Apps?

Note that Android™ Studio 3.6 no longer allows you to use ProGuard. You must use R8. So unfortunately you cannot use Zelix KlassMaster™ in place of ProGuard in this version. Everything set out below assumes that you are using a version of Android™ Studio prior to 3.6.

Below is an example ZKM Script for the obfuscation of Java classes before they are compiled into an APK. It should be adequate for most Android™ Apps. Obviously it would have to be modified for your environment.
classpath   "/usr/local/java/android-sdk/platforms/android-9/android.jar"
            ;

open        "bin/classes\*"
            "libs\*"
            ;

trimExclude 
   public *.^*^ extends android.app.Activity and 
   public *.^*^ extends android.app.Application and 
   public *.^*^ extends android.app.Service and 
   public *.^*^ extends android.content.BroadcastReceiver and 
   public *.^*^ extends android.content.ContentProvider and 
   public *.^*^ extends android.view.View public <init>(android.content.Context) and 
   public *.^*^ extends android.view.View public <init>(android.content.Context,android.util.AttributeSet) and 
   public *.^*^ extends android.view.View public <init>(android.content.Context,android.util.AttributeSet,int) and 
   public *.^*^ extends android.view.View public set*(*) and 
   *.^*^ containing {public <init>(android.content.Context,android.util.AttributeSet)} 
      public <init>(android.content.Context,android.util.AttributeSet) and 
   *.^*^ containing {public <init>(android.content.Context,android.util.AttributeSet,int)} 
      public <init>(android.content.Context,android.util.AttributeSet,int) and 
   *.* extends android.content.Context public *(android.view.View) and 
   *.* extends android.content.Context public *(android.view.MenuItem) and 
   *.* implements android.os.Parcelable static android.os.Parcelable$Creator CREATOR and 
   *.R$* public static * and 
   *.* @android.webkit.JavascriptInterface *(*)
   ;

trim        deleteDeprecatedAttributes=true
            deleteSourceFileAttributes=true
            deleteAnnotationAttributes=false
            deleteExceptionAttributes=true
            //deleteUnknownAttributes=true
            ;

exclude     
   public *.^*^ extends android.app.Activity and 
   public *.^*^ extends android.app.Application and 
   public *.^*^ extends android.app.Service and 
   public *.^*^ extends android.content.BroadcastReceiver and 
   public *.^*^ extends android.content.ContentProvider and 
   public *.^*^ extends android.view.View public <init>(android.content.Context) and 
   public *.^*^ extends android.view.View public <init>(android.content.Context,android.util.AttributeSet) and 
   public *.^*^ extends android.view.View public <init>(android.content.Context,android.util.AttributeSet,int) and 
   public *.^*^ extends android.view.View public set*(*) and 
   *.^*^ containing {public <init>(android.content.Context,android.util.AttributeSet)} 
      public <init>(android.content.Context,android.util.AttributeSet) and 
   *.^*^ containing {public <init>(android.content.Context,android.util.AttributeSet,int)} 
      public <init>(android.content.Context,android.util.AttributeSet,int) and 
   *.* extends android.content.Context public *(android.view.View) and 
   *.* extends android.content.Context public *(android.view.MenuItem) and 
   *.* implements android.os.Parcelable static android.os.Parcelable$Creator CREATOR and 
   *.R$* public static * and 
   *.* @android.webkit.JavascriptInterface *(*)
   ;

obfuscate   keepGenericsInfo=false
            keepInnerClassInfo=false
            obfuscateFlow=none //can change from none to light, normal or aggressive
            encryptStringLiterals=none //can change from none to normal, aggressive, flowObfuscate or enhanced
            exceptionObfuscation=none //can change from none to light or heavy
            autoReflectionHandling=none //can change from none to normal
            randomize=false //can change from false to true
            collapsePackagesWithDefault=""
            preverify=false //don't need to preverify if only used for Android
            mixedCaseClassNames=ifInArchive
            keepBalancedLocks=true //ensures compatibility with ART verifier
            ;

saveAll     archiveCompression=all
            "bin/classes-processed.jar"
            ;

Q41. What are default exclusions?

By default, Zelix KlassMaster™ will exclude certain classes, fields and methods from being trimmed by the Trim function or renamed by the Name Obfuscation function. However, you may override these defaults with your own.

In the case of the Trim function, Zelix KlassMaster™ looks for a file in the current user directory named "defaultTrimExclude.txt". If it finds the file then it will use its contents as the default trim exclusions. You can tell Zelix KlassMaster™ to use a different file for default trim exclusions by using the -dte command line option.

In the case of Name Obfuscation, Zelix KlassMaster™ looks for a file in the current user directory named "defaultExclude.txt". If it finds the file then it will use its contents as the default exclusions. You can tell Zelix KlassMaster™ to use a different file for default exclusions by using the -de command line option.

If the default exclusion file cannot be found then Zelix KlassMaster™ applies its predefined internal default exclusions. If the defaultExclude.txt file exists but is is empty then there there will be no default exclusions. The same applies for the default trim exclusion file.

Q42. Won't the Reference Obfuscation function slow down my code and make it bigger?

Yes it will. Although you could obfuscate ALL of the field and method references in your application, it would have a dramatic and unacceptable impact on your application's performance. Reflection API calls are much slower than direct references.

The Reference Obfuscation functionality is intended to be used sparingly 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.

When you use the Reference Obfuscation functionality to obscure sensitive parts of your application you should be careful to also obscure a significant number 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.

For maximum resistance against deobfuscation, it is recommended that Reference Obfuscation be used along with the Method Parameter Changes functionality.

Q43. By how much will parallel processing reduce obfuscation time?

Zelix KlassMaster™ will perform some processing in parallel by default and where possible to reduce obfuscation time. The size of the reduction in obfuscation time could be more than 30% but it will very much depend upon
  • The obfuscation options that have been selected,
  • The number of processors that are available and
  • The amount of heap space that is available.
Parallel processing can be disabled by setting the ZKM_USE_PARALLEL special configuration option to false.

Q44. What do the "API warnings detected during open" or "API calls detected that may not be handled automatically" messages mean?

Note that these Reflection API call warnings are just warnings rather than errors. Zelix KlassMaster detects Reflection API calls and attempts to automatically resolve the object that is being accessed by name. If it isn't able to 100% resolve an access, then it outputs a warning to the ZKM_log.txt file (if you are using ZKM Script) which starts with the line "API calls detected that may not be handled automatically...". (If you are using the "-v" parameter, then resolved accesses are also listed but are marked as "RESOLVED".) These warnings indicate
  1. The name of the class being obfuscated which contains the unresolved Reflection API call,
  2. The name of the method being obfuscated which contains the unresolved Reflection API call and
  3. The signature the Reflection API call.
Note that the warning cannot specify the name of the class, field or method being accessed by the Reflection API call if it is unresolved. If you get such an unresolved warning, you should go to the method concerned and satisfy yourself that the Reflection API call will not be broken by name obfuscation. So you need to work out which class, field or method is being accessed by the Reflection API call and then determine whether the accessed class, field or method is being renamed. If a Reflection API call would be broken by the obfuscation of a class, field or method name then that class, field or method name should be explicitly excluded from name obfuscation. If you decide to use Zelix KlassMaster's Trim functionality then the same exclusions should also appear in a ZKM Script trimExclude statement so that Zelix KlassMaster's can satisfactorily trace all accesses.

However, also note that any warnings pertaining to java.lang.Class.getName() calls will only be a problem if
  1. The String returned by java.lang.Class.getName() is compared to another String containing a class name AND
  2. The second String is not effectively a String literal containing a full class name AND
  3. The class which has its named stored in the second String will have its name obfuscated.
So, if the value returned by java.lang.Class.getName() is simply output for debugging purposes (which is normally the case) then there will be no problem. However, if the returned value is used in a way that satisfies the above three conditions then the name of the class or classes that the return value is being compared to will need to be excluded from obfuscation.

You can suppress these warnings by setting the ZKM_REFLECTION_WARNINGS system property to "false". One way of doing this is to use a command line like the following.

java -D"ZKM_REFLECTION_WARNINGS=false" -jar ZKM.jar MyScript.txt

Note that you can also use Zelix KlassMaster™'s AutoReflection™ functionality to automatically handle Java Reflection API calls. See the answer to FAQ Question 11 above.

Q45. Does Zelix KlassMaster™ support Java bytecode generated from Kotlin™ source?

Zelix KlassMaster by default provides only limited support for the obfuscation of constructs that the Kotlin™ compiler puts into the Java bytecode that it generates. Java bytecode generated from relatively simple Kotlin code might be obfuscated safely by adding ZKM Script removeMethodCallsInclude and removeMethodCalls statements and an exclusion like the following.
//Specify removal of non-essential validity check methods
removeMethodCallsInclude 
   kotlin.jvm.internal.Intrinsics throwUninitializedPropertyAccessException(java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkExpressionValueIsNotNull(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkFieldIsNotNull(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkNotNull(java.lang.Object) and
   kotlin.jvm.internal.Intrinsics checkNotNull(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkNotNullExpressionValue(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkNotNullParameter(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkParameterIsNotNull(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkReturnedValueIsNotNull(java.lang.Object, java.lang.String) and
   kotlin.jvm.internal.Intrinsics checkReturnedValueIsNotNull(java.lang.Object,java.lang.String,java.lang.String)
   ;

//"removeMethodCalls" statement should precede any "trim" or "obfuscate" statements.
removeMethodCalls;

exclude kotlin.* + and //exclude all classes, fields and methods in kotlin package
        kotlin.*.* + and //exclude all classes, fields and methods in kotlin sub-packages
        org.jetbrains.kotlin.* + and //exclude all classes, fields and methods in package
        org.jetbrains.kotlin.*.* + //exclude all classes, fields and methods in packages
        ;
If your Java bytecode is generated from very simple Kotlin code you might even be able to safely remove the Kotlin annotations entirely using the ZKM Script trim statement with the deleteAnnotationAttributes=true setting. However by default this setting would remove ALL annotations. So you may need to specify annotation exclusions on your trimExclude statement to pevent some annotations from being removed as a result of this setting.