martedì 28 agosto 2012

Offuscare codice Java con PROGUARD

Utilizzando PROGUARD è possibile effettuare un efficace offuscamento del codice e anche comprimere nel caso sia necessario ottimizzare spazio.
Si utilizza da riga di comando.
Per usarlo ho per prima cosa scritto un progetto di prova, un semplice java project che effettua una criptazione utilizzando l'algoritmo AES con chiave a 16 bit.

Testato il programma ho creato il jar.
Quindi ho esportato il jar dentro la cartella lib di PROGUARD.
Ho inserito sempre qui sotto la lib di PROGUARD anche il jar commons-codec-1.3.jar che è utilizzato dal mio codice.
Quindi ho creato (sempre sotto la lib) un file denominato parametri.pro così definito

-injars  cipher.jar
-outjars cipher_obs.jar

-libraryjars C:\Programmi\Java\jdk1.6.0_29\jre\lib\rt.jar;C:\Programmi\Java\jdk1.6.0_29\jre\lib\jce.jar;commons-codec-1.3.jar

-keep public class it.oasi.cipher.Cypher {
    public static void main(java.lang.String[]);
}




injars e outjars rappresentano rispettivamente il jar da offuscare e il jar prodotto dall'offuscamento

libraryjars indica tutte le librerie (comprese quelle generali della jdk come si può vedere) utilizzate dal jar. Si noti che ho onserito anche il jce.jar per avere il supporto della classe javax.crypto.Cipher

keep serve a mantenere l'entry point del codice (in questo caso il codice main)

Per generare il jar offuscato posizionarsi via shell sulla directory lib e digitare il seguente comando

 java -jar proguard.jar @parametri.pro


Il risultato è il seguente

Classe originale

import java.io.IOException;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Cypher
{
  private static Cypher istanza;


public static Cypher getInstance(){
      if(istanza==null){
          istanza=new Cypher();
      }
      return istanza;
  }

  private  Key getKey()
  {
    Date data = getDateForKey();
    SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy");
    String chiave = sdf.format(data) + "abcdre";
   
    byte[] rawdata = chiave.getBytes();
   
    SecretKeySpec skeySpec = new SecretKeySpec(rawdata, "AES");
    return skeySpec; }

  private  Date getDateForKey() {
    Calendar c = Calendar.getInstance();
    c.set(1, 1978);
    c.set(2, 3);
    c.set(5, 20);
    return c.getTime(); }

  private  byte[] encrypt(String valore) throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(1, getKey());
    return cipher.doFinal(valore.getBytes());
  }

  private  byte[] decrypt(byte[] valore) throws Exception {
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(2, getKey());
    return cipher.doFinal(valore);
  }

  public  String cifra(String valore) throws Exception {
    return toBase64(encrypt(valore));
  }

  public  String decifra(String valore) throws Exception {
        return new String(decrypt(fromBase64(valore)));
      }
  private  String toBase64(byte[] valore)
  {
    return new String(new Base64().encode(valore));
  }

  private  byte[] fromBase64(String valore)
    throws IOException
  {
    return new Base64().decode(valore.getBytes());
  }

  public static void main(String[] args)
    throws Exception
  {
      Cypher c=Cypher.getInstance();
     
   String cifratoB64 = c.cifra("abilitatoDrillDown");
   String decifrato = c.decifra(cifratoB64);
    System.out.println("Cifrato: " + cifratoB64);
    System.out.println("Decifrato: " + decifrato);
  }
}


Se provo a decompilare il jar offuscato ottengo qualcosa di vagamente incomprensibile:


import java.io.PrintStream;
import java.security.Key;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;

public class Cypher
{
  private static Cypher a;

  private Key a()
  {
    (localObject = Calendar.getInstance()).set(1, 1978);
    ((Calendar)localObject).set(2, 3);
    ((Calendar)localObject).set(5, 20);
    Object localObject = ((Calendar)localObject).getTime();
    SimpleDateFormat localSimpleDateFormat = new SimpleDateFormat("dd-MM-yyyy");
    localObject = (localObject = localSimpleDateFormat.format((Date)localObject) + "abcdre").getBytes();
    return (Key)(localObject = new SecretKeySpec(localObject, "AES"));
  }

  public static void main(String[] paramArrayOfString)
  {
    if (a == null)
      a = new Cypher();
    Object localObject2 = "abilitatoDrillDown";
    localObject2 = localObject2;
    Object localObject1 = localObject1 = paramArrayOfString = a;
    Cipher localCipher;
    (localCipher = Cipher.getInstance("AES")).init(1, ((Cypher)localObject1).a());
    localObject1 = localCipher.doFinal(((String)localObject2).getBytes());
    String str = new String(new Base64().encode(localObject1));
    localObject2 = str;
    localObject1 = paramArrayOfString;
    localObject1 = localObject2;
    localObject2 = new Base64().decode(((String)localObject1).getBytes());
    localObject1 = localObject1;
    (localCipher = Cipher.getInstance("AES")).init(2, ((Cypher)localObject1).a());
    paramArrayOfString = new String(localCipher.doFinal(localObject2));
    System.out.println("Cifrato: " + str);
    System.out.println("Decifrato: " + paramArrayOfString);
  }
}


NB: in questo caso anche importando il jar su un  nostro progetto sarà visibile solo il metodo main, per fare in modo che lo siano anche altri metodi occorre specificarli qui.

Esempio:

-keep public class it.oasi.cipher.Cypher {
    public static void main(java.lang.String[]);
    public static it.oasi.cipher.Cypher getInstance();
    public java.lang.String decifra(java.lang.String );
}


Da notare come le variabili di input non vanno specificati così come i tipi, se disgraziatamente doveste farlo (ad esempio nel decifra dopo il tipo di input definire la variabile valore) si incappa nel seguente messaggio di errore

Error: Expecting separating ',' or closing ')' before 'valore' in line 8 of file
 'parametri.pro',
  included from argument number 1

Nessun commento:

Posta un commento