2015-07-20 22 views
6

Sto usando ProGuard per offuscare il mio programma .jar. Tutto funziona bene, tranne per il fatto che ProGuard non offusca le variabili locali nei corpi dei metodi. Ecco un esempio:Perché proguard non offusca il corpo del metodo?

Raw:

enter image description here

offuscato:

enter image description here

I nomi delle variabili che sono evidenziati in giallo sho essere offuscato, ma non lo sono. Come posso nascondere anche loro (li faccio rinominati a, b, c, ecc?)

Ecco il mio ProGuard config: http://pastebin.com/sb3DMRcC (il metodo di cui sopra non è da una delle classi escluse).

+0

Il codice "offuscato" codice Java reale o codice Java che è stato decompilato? La mia comprensione è che un file bytecode semplicemente non registra i nomi dei parametri del metodo e delle variabili locali. (Se questo è il codice sorgente emesso da ProGuard prova a compilarlo, quindi decompila il file .class ... o guardandolo usando javap.) –

+0

@StephenC È il codice Java decompilato. Ho offuscato il file .jar (con proguard, dopo aver compilato il .jar), poi ho decompilato il .jar in precedenza offuscato. I file .jar (il bytecode) memorizzano quasi tutti i dati dal codice sorgente originale (tranne i commenti e la formattazione della sintassi). – Victor2748

risposta

11

Perché proguard non offusca il corpo del metodo?

Perché non può.
I nomi degli argomenti del metodo e le variabili locali non vengono semplicemente memorizzati durante la compilazione.
I nomi che stai vedendo sono generati dal tuo decompilatore.

Per il codice compilato, ci sono due modi per memorizzare dati a livello locale (ad esempio all'interno di un metodo):

  • Sulla pila operando
  • In variabili locali

Lo stack operando è davvero solo una pila.
Vedere Table 7.2 dalla specifica VM Java per gli operatori di stack.
È possibile pop valori (pop), duplicare il valore superiore (dup), scambiare le prime due valori (swap) e la stessa cosa con un comportamento leggermente diverso (pop2, dup_x1, dup_x2, dup2, dup2_x1, dup2_x2).
E la maggior parte, se non tutte le istruzioni che producono un valore di ritorno, lasceranno cadere il valore in pila.

La cosa importante per questa domanda è come le cose nello stack sono indicati, che è come con qualsiasi altro stack:
rispetto alla posizione superiore, e sulla base delle istruzioni utilizzate.
Non ci sono numeri o nomi assegnati, è solo quello che c'è attualmente.

Ora, per le cosiddette "variabili locali":

pensare a loro più come un ArrayList di variabili in Java.
Perché è esattamente come accedervi: per indice.
Per le variabili da 0 a 3, ci sono istruzioni speciali (ad es.byte singolo) perché vengono utilizzati così spesso, tutte le altre variabili sono accessibili solo tramite un'istruzione a due byte, in cui il secondo byte è l'indice.
Vedere Table 7.2 di nuovo, "Carichi" e "Negozi".
I primi cinque voci di entrambe le tabelle sono ampie (due byte) istruzioni memorizzano/carico per ogni tipo di dati (si noti che, per valori singoli, boolean, char, byte e short sono tutti convertiti in int, lasciando solo int, float e come valori a slot singolo e long e double come quelli a doppio slot, le venti istruzioni successive sono le istruzioni per l'accesso diretto ai registri da 0 a 3 e le ultime otto istruzioni sono per accedere agli indici di array (notare che all'interno degli array , boolean, byte, char e short sono non convertito in int, per non sprecare spazio, ecco perché ci sono altre tre istruzioni (non quattro, dal byte e dallo char hanno le stesse dimensioni)).

Sia la dimensione massima dello stack e il numero di variabili locali sono limitate, e deve essere data nell'intestazione dell'attributo Code di ciascun metodo, come definito nella Section 4.7.3 (max_stack e max_locals).

La cosa interessante di variabili locali, però, è che raddoppiano come argomenti di metodo, il che significa che il numero di variabili locali non può mai essere inferiore al numero di argomenti di metodo.
noti che quando il conteggio valori per il Java VM, le variabili del tipo long e double sono considerate due valori, e necessitano di due "slot" di conseguenza.
noti inoltre che per i metodi non statici, l'argomento sarà 0 this, che richiede un altro "slot" per sé.

Detto questo, diamo un'occhiata ad un codice!

Esempio:

class Test 
{ 
    public static void main(String[] myArgs) throws NumberFormatException 
    { 
     String myString = "42"; 
     int myInt = Integer.parseInt(myString); 
     double myDouble = (double)myInt * 42.0d; 
     System.out.println(myDouble); 
    } 
} 

Qui abbiamo tre variabili locali myString, myInt e myDouble, oltre a un argomento myArgs.
Inoltre, abbiamo due costanti "42" e 42.0d, e un sacco di riferimenti esterni:

  • java.lang.String[] - classe
  • java.lang.NumberFormatException - classe
  • java.lang.String - classe
  • java.lang.Integer.parseInt - metodo
  • java.lang.System.out - campo
  • java.io.PrintStream.println - Metodo

E alcune esportazioni: Test e main, più il costruttore di default che il compilatore genera per noi.

Tutte le costanti, i riferimenti e le esportazioni saranno esportati al Constant Pool - le variabili locali e nomi degli argomenti non.

Compilazione e smontaggio della classe (utilizzando javap -c Test) rendimenti:

Compiled from "Test.java" 
class Test { 
    Test(); 
    Code: 
     0: aload_0 
     1: invokespecial #1     // Method java/lang/Object."<init>":()V 
     4: return 

    public static void main(java.lang.String[]) throws java.lang.NumberFormatException; 
    Code: 
     0: ldc   #2     // String 42 
     2: astore_1 
     3: aload_1 
     4: invokestatic #3     // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 
     7: istore_2 
     8: iload_2 
     9: i2d 
     10: ldc2_w  #4     // double 42.0d 
     13: dmul 
     14: dstore_3 
     15: getstatic  #6     // Field java/lang/System.out:Ljava/io/PrintStream; 
     18: dload_3 
     19: invokevirtual #7     // Method java/io/PrintStream.println:(D)V 
     22: return 
} 

Oltre il costruttore di default, possiamo vedere la nostra main metodo, passo dopo passo.
Si noti come myString si accede con astore_1 e aload_1, myInt con istore_2 e iload_2, e myDouble con dstore_3 e dload_3.
non è accessibile da nessuna parte, quindi non esiste alcun bytecode che lo tratti, ma all'inizio del metodo, un riferimento all'array di stringhe sarà nella variabile locale 1, che verrà presto sovrascritta da un riferimento a "42".

javap visualizzerà anche il Pool costante se si passa il flag -v, ma in realtà non aggiunge alcun valore all'output, poiché tutte le informazioni rilevanti del Pool costante vengono comunque visualizzate nei commenti.

Ma ora, diamo un'occhiata a ciò che producono i decompilatori!

JD-GUI 0.3.5 (JD-Core 0.6.2): ​​

import java.io.PrintStream; 

class Test 
{ 
    public static void main(String[] paramArrayOfString) 
    throws NumberFormatException 
    { 
    String str = "42"; 
    int i = Integer.parseInt(str); 
    double d = i * 42.0D; 
    System.out.println(d); 
    } 
} 

Procyon 0.5.28:

class Test 
{ 
    public static void main(final String[] array) throws NumberFormatException { 
     System.out.println(Integer.parseInt("42") * 42.0); 
    } 
} 

Nota come tutto ciò che è stato esportato al Pool Constant persiste, mentre JD-GUI seleziona semplicemente alcuni nomi per le variabili locali e Procyon li ottimizza completamente.
Il nome dell'argomento - paramArrayOfString vs array (rispetto all'originale myArgs) - è un esempio perfetto, tuttavia, per dimostrare che non esiste più un nome "corretto" e che i decompilatori devono semplicemente fare affidamento su alcuni schemi di selezione di un nome.

Non so da dove vengano i nomi "veri" nel codice decompilato, ma sono abbastanza certo che non siano contenuti nel file jar.
Caratteristica del tuo IDE forse?

+0

Bella risposta, ben fatto. –

+0

Grazie! La migliore risposta di sempre! – Victor2748