Obfuscation Example - RetroGuard Documentation
We'll now take an example obfuscation process from beginning to end, starting
with the internal default RetroGuard
script, working through the various issues that can arise during obfuscation,
and ending with a maximally obfuscated and compact jar.
Since RetroGuard is itself a Java application, we'll use it as the example
codebase. We begin with the unobfuscated jar (in.jar), which is around
242kb in size.
Default Obfuscation
As recommended in the quick start
section, we'll try the internal default script first:
java -cp retroguard.jar:ant.jar RetroGuard in.jar out.jar
Since no script file is specified as the third parameter to RetroGuard and
no file named 'script.rgs' exists in the current directory, RetroGuard uses
its internal default script.
Also, notice that ant.jar has been included in
the Java classpath. The software in.jar (which is just RetroGuard
itself) can be run under Apache Ant and so the
Ant library must be accessible during obfuscation, as mentioned in
access to libraries.
The result is out.jar which is 166kb as
well as the retroguard.log file. We now examine
retroguard.log and find the following lines:
# WARNING - Methods are called which may unavoidably break in obfuscated
# version at runtime. Please review your source code to ensure that these
# methods are not intended to act on classes within the obfuscated Jar file.
# Your class COM/rl/obf/Cl$ExtNameListUp uses '.class' or calls java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class;
# Your class COM/rl/obf/Cl$ExtNameListUp calls the java/lang/Class method getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;
# Your class COM/rl/obf/Cl$ExtNameListUp calls the java/lang/Class method getField(Ljava/lang/String;)Ljava/lang/reflect/Field;
# Your class COM/rl/obf/Cl uses '.class' or calls java/lang/Class.forName(Ljava/lang/String;)Ljava/lang/Class;
This is an example of RetroGuard discovering
use of reflection methods. Since
in.jar (which is RetroGuard here) uses reflection only to examine
classes outside of its own jar, it is safe to ignore these warnings in this
case.
[RetroGuard-v2.2.x only]
In future
runs, we can suppress the warnings
using the .nowarn <class> script entry.
If these reflection methods were used to reference classes, methods, or
fields inside in.jar we would have to preserve those classes,
methods, and fields from being obfuscated by listing them in the script file.
Notice that it needs an understanding of how the obfuscated software will be
used to correctly handle reflection. It is not even theoretically possible for
RetroGuard to handle reflection automatically in all cases.
The internal default script has done a good job of obfuscating RetroGuard and
reducing the application size. However, on examination of the log file, we see
that the Apache Ant library interfaces have not been preserved. In general,
it is necessary to preserve your software's library interfaces by listing them
in the script file. There is no way for RetroGuard to decide automatically that
those library entry points must be left unchanged.
Also, we see from the log that RetroGuard's internal default script is very
conservative and has left several names unobfuscated that could safely have
been changed. We will now construct an optimal script file specifically
for this obfuscation process.
Optimal Obfuscation
[RetroGuard-v2.2.x only]
To begin, we could use RGdefault
to output the internal default script:
java -cp retroguard.jar RGdefault > script.rgs
and then modify this as needed.
In this case, however, we'll build up the script.rgs file
step by step. The simplest useful script for our example is:
.option Application
.class ** public method extends org/apache/tools/ant/Task
.nowarn COM/rl/obf/Cl*
which preserves the various application main methods and their classes, as
well as the Apache Ant library methods. Note the use of the
extends clause to limit the scope of the .class **
statement to classes derived from Ant Task.
[RetroGuard-v2.2.x only]
The .nowarn line suppresses warnings about the reflection
methods.
This obfuscation run:
java -cp retroguard.jar:ant.jar RetroGuard in.jar out.jar script.rgs
results in an output jar 167kb in size.
[RetroGuard-v2.2.x only]
We now add the statement:
.option Repackage
to script.rgs which instructs RetroGuard to
repackage all classes into the top level
of the package hierarchy. This leads to a size reduction since the repackaged
names are usually much shorter and they are referenced many times in each class.
We now have an output jar 162kb in size.
[RetroGuard-v2.1.x and later]
Finally we add the statement:
.option Trim
which causes RetroGuard to trim any unused
methods, fields, and even whole classes from the output. RetroGuard doesn't
have much unused code; a few debugging statements. However, in an application
which includes a large library but uses only a small part of that library
the size reduction due to trimming would be considerable. Finally, then, we
have an output jar 159kb in size, a 34% reduction from the
original.
The size reduction seen during obfuscation depends very much on the
detailed content and identifier names used in your software, and will vary
greatly in other examples.
Null Obfuscation
Sometimes it can be useful to start with a null script; one that runs
the obfuscator but preserves all class, method, and field names and
keeps all class attributes. Here is such a script:
.class ** protected
.method;private ** *
.field;private ** *
.attribute SourceFile
.attribute LocalVariableTable
.attribute LineNumberTable
.attribute Deprecated
.attribute Signature
.attribute LocalVariableTypeTable
.attribute EnclosingMethod
.option Annotations
The various .attribute lines and .option Annotations
preserve all class attributes. The .class ** protected statement
preserves class names in all packages (the ** wildcard crosses package
boundaries) and preserves public, package, and protected access methods and
fields in all those classes. The private methods and fields, which are always
safe to obfuscate, are preserved here using the .field and
.method lines.
This obfuscation run strips the jar and its component classes apart, but
renames nothing. From this starting point, obfuscation can be switched on
statement-by-statement using the !class, !method,
!field
negation entries.
|