Home > java > Java Bytecode Obfuscator con ProGuard

Java Bytecode Obfuscator con ProGuard

22 novembre 2010

Translate in English with Google translate
Nell’articolo precedente ho parlato delle caratteristiche dell’offuscazione del codice mentre in questo articolo farò qualche esempio utilizzando il progetto open-source ProGuard rilasciato con licenza GPL.

Questo progetto non si occupa solo di offuscare il codice ma esegue eventualmente operazioni di riduzione ed ottimizzazione del codice.

ProGuard: step opzionali di processamento

ProGuard: step opzionali di processamento

Nell’immagine vengono esposti i passi di processamento eseguita da ProGuard:

  1. input jars: il jar del progetto da offuscare viene sottoposto all’elaborazione.
  2. shrunk code: il codice viene sottoposto alla prima attività di riduzione delle componenti non necessarie inserite nel bytecode (nome della classe, date ecc)
  3. optimize code: il codice viene sottoposto all’attività di ottimizzazione.
  4. obfuscate code: il codice viene sottoposto all’attività di offuscazione.
  5. preverify: viene effettuata una attività di verifica del codice
  6. output jars: viene prodotto il jar frutto delle attività di elaborazioni.

Partiamo da un esempio semplice semplice, come piace a me. Una classe vuota!

public class Prova {
}

Compiliamo la classe e decompiliamo il bytecode usando jad:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
// Source File Name:   Prova.java
public class Prova{
    public Prova(){
    }
}

Ovviamente è stato creato il costruttore Prova() automaticamente.
Disassembliamo il bytecode usando il disassemblatore Oracle javap:

%JAVA_HOME/bin/javap -c Prova
Compiled from "Prova.java"
public class Prova extends java.lang.Object{
public Prova();
  Code:
   0:	aload_0
   1:	invokespecial	#1; //Method java/lang/Object."":()V
   4:	return
}

Vediamo anche il contenuto del costruttore Prova() che invoca la classe padre Object, in poche parole esegue super().

Offuschiamo il codice usando Proguard.
In questo esempio attiviamo solo la funzione di offuscazione del codice e non attiviamo le altre funzionalità che, come detto precedentemente, sono opzionali in modo tale da non conforderci, pertanto disabilitiamo: riduzione(shrunk), ottimizzazione(optimaze) e preverifica(preverify) del codice.
Pacchettizziamo la nostra classe Prova.class in un file jar che chiamiamo prova.jar e creiamo il file di configurazione di Proguard che chiamiamo conf.pro:

cat conf.pro
-injars /articolo/pre-offuscazione/jar/prova.jar
-outjars /articolo/post-offuscazione/jar
-libraryjars /opt/jdk1.6.0_21/jre/lib/rt.jar
-dontskipnonpubliclibraryclassmembers
-dontshrink
-dontoptimize
-dontusemixedcaseclassnames
-dontpreverify
-dontnote
-dontwarn
# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
    native ;
}

Scarichiamo ProGuard e eseguiamolo indicando il file di configurazione:

%JAVA_HOME/bin/java -jar proguard.jar @conf.pro
ProGuard, version 4.5.1
Reading program jar [/articolo/pre-offuscazione/jar/prova.jar]
Reading library jar [/opt/jdk1.6.0_21/jre/lib/rt.jar]
Preparing output directory [/articolo/post-offuscazione/jar]
  Copying resources from program jar [/articolo/pre-offuscazione/jar/prova.jar]
Processing completed successfully

Otteniamo un nuovo file jar, creato nella cartella target /articolo/post-offuscazione/jar/ con lo stesso nome del nostro jar prova.jar. Scompattiamo il jar generato da ProGuard e vediamo cosa c’è dentro:

$ %JAVA_HOME/bin/jar -xvf /articolo/post-offuscazione/jar/prova.jar
decompresso: META-INF/MANIFEST.MF
decompresso: a.class

Nel jar non troviamo la classe Prova.class ma la classe a.class pertanto decompiliamo la classe a.class e vediamo il contenuto:

cat a.jad
// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
public class a{
    public a(){
    }
}

Disassembliamo il bytecode a.class:

%JAVA_HOME/bin/javap -c a
public class a extends java.lang.Object{
public a();
  Code:
   0:	aload_0
   1:	invokespecial	#3; //Method java/lang/Object."":()V
   4:	return
}

Vediamo che sono state effettuate 2 operazioni:

  1. E’ stato modificato il nome della classe e del suo costruttore rinominato in a
  2. E’ stato eliminato il riferimento al file sorgente originario Prova.java

Aggiungiamo metodi e proprietà alla classe Prova
Arricchiamo la classe Prova.java ed inseriamo un metodo ed una proprietà:

public class Prova {
     int index = 15;
    public void metodo() {
        String key = "prova";
    }
}

Decompiliamo il bytecode con jad:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
// Source File Name:   Prova.java
public class Prova{
    public Prova(){
        index = 15;
    }
    public void metodo(){
        String s = "prova";
    }
    int index;
}

Offuschiamo e decompiliamo la classe generata:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
public class a{
    public a(){
        a = 15;
    }
    public void a(){
        String s = "prova";
    }
    int a;
}

Sono state effettuate le seguenti modifiche dei nomi:

  1. nome classe: Prova -> a
  2. nome metodo: metodo() ->a()
  3. nome costruttore: Prova() -> a()
  4. nome proprità: int index -> int a

Che fantasia!! Certo, adesso è più facile confondersi.
Questa tecnica viene chiamata Name Mangling o anche Name Decoration, possiamo tradurla in “storpiatura dei nomi” o più finemente “decorazione dei nomi” o “distorsione dei nomi”. Consiste nel confondere il lettore del codice propoendo dei nomi senza significato e simili.

Nel caso in cui esistano più metodi o proprietà verranno utilizzate le altre lettere dell’alfabeto b, c, d ecc, ma è sempre possibile caricare un proporio dizionario personale di offuscazione passando il parametro -obfuscationdictionary al file di configurazione.

Mentre invece per sapere il mapping NomeClasseOriginaria->NomeClasseOffuscata è possibile utilizzare il parametro -printmapping che visualizza la corrispondenza tra le classi originarie e quelle offuscate, come segue:

Obfuscating...
Printing mapping to [standard output]...
Prova -> a:
    int index -> a
    void metodo() -> a
Writing output...

Utilizziamo altre funzionalità ProGuard: Optimaze e Shrunk
Divertiamoci con altre modifiche previste da ProGuard, ossia Optimize e Shrunk.
Iniziamo con Optimaze e modifichiamo il file conf.pro:

cat conf.pro
-injars /articolo/pre-offuscazione/jar/prova.jar
-outjars /articolo/post-offuscazione/jar
-libraryjars /opt/jdk1.6.0_21/jre/lib/rt.jar
-dontskipnonpubliclibraryclassmembers
-dontshrink
-dontusemixedcaseclassnames
-dontpreverify
-dontnote
-dontwarn
# Keep names - _class method names. Keep all .class method names. This may be
# useful for libraries that will be obfuscated again with different obfuscators.
-keepclassmembers,allowshrinking class * {
    java.lang.Class class$(java.lang.String);
    java.lang.Class class$(java.lang.String,boolean);
}

Eseguiamo ProGuard e decompiliamo il bytecode a.class con jad:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
public final class a{
    public a(){
    }
    private static void a(){
    }
    private int a;
}

Cosa è successo:

  1. la classe adesso è final perchè nessuno la eredita
  2. il metodo a() e la proprietà a adesso sono diventati private perchè nessuno li invoca
  3. il contenuto del metodo e della proprietà sono vuoti per non sprecare spazio di memoria inutili

Vediamo cosa succede se invochiamo la classe Prova ad opera di un’altra classe che chiameremo Invocazione.
Pertanto creiamo la classe Invocazione.java:

public class Invocazione {
    public void metodo() {
        new Prova().metodo();
    }
}

Offuschiamo e decompiliamo la classe Prova.class adesso rinominata in  b.class:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
public final class b{
    public b(){
    }
    public static void a(){
    }
    private int a;
}

Notiamo che:

  1. il modificatore del metodo è diventato public in quanto la classe Invocazione invoca metodo() della classe Prova.
  2. il contenuto del metodo è ancora vuoto perchè la stringa non è utilizzata nel metodo pertanto non viene neanche allocata in memoria perchè è inutile. Per essere “popolato” il metodo deve utilizzare l’oggetto stringa creato.

Adesso passiamo a vedere le funzionalità di Shrunk che serve per eliminare classi e metodi che non vengono utilizzati. Quindi mentre Optimaze serve per assegnare i modificatori corretti in base all’utilizzo che viene effettivamente fatto, Shrunk invece serve per eliminare tutto ciò che è inutilizzato. Vediamo un esempio.
Modifichiamo il metodo della classe Invocazione da public a private:

public  class Invocazione {
    <strong>private</strong> void metodo() {
        new Prova().metodo();
    }
}

Modifichiamo il file di configurazione di ProGuard conf.pro

-injars /articolo/pre-offuscazione/jar/prova.jar
-outjars /articolo/post-offuscazione/jar
-libraryjars /opt/jdk1.6.0_21/jre/lib/rt.jar
-dontskipnonpubliclibraryclassmembers
-dontoptimize
-dontusemixedcaseclassnames
-dontpreverify
-verbose
-dontnote
-dontwarn
# Keep - Library. Keep all public and protected classes, fields, and methods.
-keep public class * {
    public protected ;
    public protected ;
}

Avremo questo output:

ProGuard, version 4.5.1
Reading input...
Reading program jar [/articolo/pre-offuscazione/jar/prova.jar]
Reading library jar [/opt/jdk1.6.0_21/jre/lib/rt.jar]
Initializing...
Ignoring unused library classes...
  Original number of library classes: 17410
  Final number of library classes:    9
Shrinking...
Removing unused program classes and class elements...
  Original number of program classes: 2
  Final number of program classes:    2
Obfuscating...
Writing output...
Preparing output directory [/articolo/post-offuscazione/jar]
  Copying resources from program jar [/articolo/pre-offuscazione/jar/prova.jar]
Processing completed successfully

Vediamo il contenuto decompilato di Invocazione.jad:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3)
public class Invocazione{
    public Invocazione(){
    }
}

Come vediamo il metodo private è stato eliminato e questo perchè non è invocabile dall’esterno della classe.
Vediamo invece il caso in cui eliminiamo la definizione public della classe in modo tale da essere:

class Invocazione {
    public void metodo() {
        new Prova().metodo();
    }
}

Facciamo le stesse operazioni di prima ed abbiamo questo output da Proguard:

ProGuard, version 4.5.1
Reading input...
Reading program jar [/media/_/ARCHIVIO/software/projectsDir/netBeans/prove/articolo/pre-offuscazione/jar/prova.jar]
Reading library jar [/opt/jdk1.6.0_21/jre/lib/rt.jar]
Initializing...
Ignoring unused library classes...
  Original number of library classes: 17410
  Final number of library classes:    9
Shrinking...
Removing unused program classes and class elements...
Original number of program classes: 2
Final number of program classes:    1
Obfuscating...
Writing output...
Preparing output directory [/media/_/ARCHIVIO/software/projectsDir/netBeans/prove/articolo/post-offuscazione/jar]
  Copying resources from program jar [/media/_/ARCHIVIO/software/projectsDir/netBeans/prove/articolo/pre-offuscazione/jar/prova.jar]
Processing completed successfully

In questo caso notiamo che una classe non è stata caricata nel jar di output e guarda caso si tratta della classe Invocazione che non è public:
Original number of program classes: 2
Final number of program classes: 1
Questa operazione di shrunk è stata effettuata per eliminare una classe “inutile”.

Categorie:java
  1. 7 giugno 2012 alle 3:42 PM

    Complimenti per l’articolo e per il blog! Sto scrivendo proprio ora un articolo sul mio blog su come proteggere le applicazioni Java prima della distribuzione (offuscamento, TrueLicense, …). Metto un riferimento alla tua pagina!

    ciao

    • 10 giugno 2012 alle 12:25 AM

      Grazie!🙂 Ho letto anche io il tuo blog e l’ho trovato molto interessante. Se trovo argomenti in comune metterò anch’io un riferimento alla tua pagina.

  1. 13 dicembre 2010 alle 10:17 AM
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: