
Suite di strumenti multifunzionale per la deofuscazione Java
Threadtear è uno strumento di deofuscazione multifunzionale per Java. Il supporto per applicazioni Android è in arrivo (attualmente sto lavorando a un convertitore da Dalvik a Java).
Adatto per un'analisi del codice più semplice senza preoccuparsi troppo dell'offuscamento.
Sono inclusi anche gli offuscatori più costosi come ZKM o Stringer. Per un debug più semplice sono inclusi altri strumenti.
Inserisci numeri di riga di debug per capire meglio da dove provengono le eccezioni, oppure aggiungi .printStackTrace() ai blocchi try-catch senza ricompilare il codice.
La retrocompatibilità non è più un problema, se non vengono utilizzati metodi specifici di una versione. Analizza il flusso del codice in un grafico, per comprendere meglio gli algoritmi.

Una "esecuzione" è un'attività che viene eseguita e modifica tutti i file di classe caricati. Esistono vari tipi di esecuzioni, che vanno dalla pulizia del bytecode alla deofuscazione delle stringhe. Assicurati di averle nell'ordine giusto. Le esecuzioni di pulizia, ad esempio, dovrebbero essere eseguite per ultime, ma possono anche aiutare altre esecuzioni se eseguite per prime. Se sei pronto, clicca sul pulsante "Run" e verranno eseguite in ordine.
Usa questo strumento a tuo rischio. Alcune esecuzioni utilizzano ClassLoader implementati per eseguire codice dal file jar. Un utente malintenzionato potrebbe modificare il bytecode in modo da eseguire codice dannoso.
Le esecuzioni interessate usano la classe me.nov.threadtear.asm.vm.VM.
Questo viene utilizzato principalmente per decifrare stringhe o offuscamento di risorse/accessi, poiché è molto più semplice eseguire i metodi di decifratura in remoto.
Threadtear fa del suo meglio per proteggerti da chiamate dannose (esecuzioni di codice arbitrario) utilizzando un proprio SecurityManager, ma non c'è garanzia.
Soprattutto con deoffuscatori come quelli per ZKM o Stringer bisogna stare molto attenti, poiché la riflessione deve essere consentita, altrimenti non funzionerebbero.
Se scopri un ACE, per favore apri una segnalazione. Cercherò di risolverli il prima possibile.
Prima, esegui gradle build, poi gradle fatJar. Nella cartella builds/libs dovrebbe essere stato creato un jar eseguibile. Se non vuoi scaricare il repository, puoi usare l'ultima release.
Puoi creare facilmente la tua attività di esecuzione. Basta estendere me.nov.threadtear.execution.Execution:
public class MyExecution extends Execution {
public MyExecution() {
super(ExecutionCategory.CLEANING /* categoria */, "My execution" /* nome */,
"Executes something" /* descrizione, può usare html */);
}
/**
* Questo metodo viene invocato quando l'utente clicca sul pulsante Run
* @return true se successo, false se fallimento
*/
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
//trasforma qui le classi usando l'API tree di ASM
});
return false;
}
}
Per caricare ClassNode a runtime, usa la classe me.nov.threadtear.asm.vm.VM e implementa me.nov.threadtear.asm.vm.IVMReferenceHandler:
public class MyExecution extends Execution implements IVMReferenceHandler {
public MyExecution() {
super(ExecutionCategory.GENERIC, "My execution", "Carica ClassNode a runtime");
}
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
VM vm = VM.constructVM(this);
//trasforma bytecode in java.lang.Class
Class<?> loadedClass = vm.loadClass(c.name.replace('/', '.'), true);
//fai cose con la tua classe qui
loadedClass.getMethods()[0].invoke(...);
return true;
});
}
/**
* Verrà invocato da VM, quando viene chiamato VM.loadClass
*/
@Override
public ClassNode tryClassLoad(String name) {
//prova a trovare la classe da caricare nell'archivio jar aperto
return classes.containsKey(name) ? classes.get(name).node : null;
}
}
Usando ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker) puoi analizzare i metodi e tenere traccia dei valori dello stack non variabili.
Se ad esempio iconst_0 viene spinto nello stack, il valore stesso non viene perso come nell'analizzatore ASM di base, e puoi usarlo per prevedere cose più avanti nel codice.
public class MyExecution extends Execution implements IConstantReferenceHandler {
public MyExecution() {
super(ExecutionCategory.GENERIC, "My execution", "Esegue analisi dello stack e sostituisce il codice.");
}
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(this::analyzeAndRewrite);
return true;
}
public void analyzeAndRewrite(ClassNode cn) {
cn.methods.forEach(m -> {
// questo analizzatore mantiene i valori dello stack noti, ad esempio utili per la previsione dei salti
Analyzer<ConstantValue> a = new Analyzer<ConstantValue>(new ConstantTracker(this, Access.isStatic(m.access), m.maxLocals, m.desc, new Object[0]));
try {
a.analyze(cn.name, m);
} catch (AnalyzerException e) {
logger.severe("Analisi dello stack fallita in " + cn.name + "." + m.name + ":" + e.getMessage());
return;
}
Frame<ConstantValue>[] frames = a.getFrames();
InsnList rewrittenCode = new InsnList();
Map<LabelNode, LabelNode> labels = Instructions.cloneLabels(m.instructions);
// riscrivi le istruzioni del metodo
for (int i = 0; i < m.instructions.size(); i++) {
AbstractInsnNode ain = m.instructions.get(i);
Frame<ConstantValue> frame = frames[i];
// sostituisci / modifica istruzioni, ecc...
if (frame.getStackSize() > 0) {
ConstantValue top = frame.getStack(frame.getStackSize() - 1);
if (top.isKnown() && top.isInteger()) {
int knownTopStackValue = top.getInteger();
// usa lo stack noto per rimuovere salti, semplificare codice, ecc...
// if(...) { rewrittenCode.add(...); }
continue;
}
}
rewrittenCode.add(ain.clone(labels));
}
// aggiorna istruzioni e correggi blocchi try-catch, variabili locali, ecc...
Instructions.updateInstructions(m, labels, rewrittenCode);
});
}
/**
* Usa questo metodo per prevedere i valori dello stack se vengono caricati i campi
*/
@Override
public Object getFieldValueOrNull(BasicValue v, String owner, String name, String desc) {
return null;
}
/**
* Usa questo metodo per prevedere i valori dello stack se vengono invocati metodi su oggetti noti
*/
@Override
public Object getMethodReturnOrNull(BasicValue v, String owner, String name, String desc, List<? extends ConstantValue> values) {
if (name.equals("toCharArray") && owner.equals("java/lang/String")) {
if (!values.get(0).isKnown()) {
// il target dell'invocazione non è noto, non possiamo calcolare il ritorno
return null;
}
return ((String) values.get(0).getValue()).toCharArray();
}
return null;
}
}
Non dimenticare di aggiungere la tua esecuzione all'albero in me.nov.threadtear.execution.ExecutionLink!
Ci sono alcuni trucchi che possono aiutarti a identificare e deoffuscare con successo i file jar. Prima di eseguire le esecuzioni, decompila il codice per scoprire cosa deve essere usato. Puoi usare il decompilatore integrato per questo.
L'ordine migliore per una deofuscazione è: esecuzioni generiche > deofuscazione degli accessi > deofuscazione delle stringhe > esecuzioni di pulizia.
Gli offuscatori mostrano schemi che puoi usare per identificarli. Il modo più semplice per identificare un offuscatore è scorrere il file META-INF/MANIFEST.MF.
È possibile che ci sia un attributo Obfuscated-By: XXX o Protected-By: XXX.
Codice estremamente offuscato (nel flusso), spesso riconoscibile da un metodo di decifratura delle stringhe nell'inizializzatore statico contenente switch,
o metodi di decifratura delle stringhe con un blocco switch molto lungo (circa 250 casi).
ZKM è uno dei migliori (e più vecchi) offuscatori per Java, ed è molto costoso. Antico quanto l'offuscatore è il loro sito web.

Se il tuo file jar contiene alcune classi speciali con enormi algoritmi di decifratura utilizzati per l'offuscamento delle stringhe e degli accessi, probabilmente è Stringer.
La protezione non è male e Stringer è uno degli offuscatori più costosi. A differenza degli offuscatori normali, non include l'offuscamento dei nomi.
Viene piuttosto usato come "secondo strato". Probabilmente il 90% delle persone che usano questo offuscatore utilizza una crack, poiché costa più di un'auto.
Se il tuo file è stato offuscato con più offuscatori, e Stringer è uno di questi, dovresti iniziare la deofuscazione con Stringer, poiché l'offuscamento di Stringer non può essere sovrascritto.
(A causa della firma JAR personalizzata e dell'uso dei nomi dei metodi durante la decifratura delle stringhe)

Nomi di classe come IiIlIlIiIl o aUx, cOn, PrX indicano l'offuscamento con Allatori.
Allatori è molto comune tra i file jar offuscati, perché offre una demo gratuita accessibile con pochi clic. L'offuscamento non è così difficile da invertire.

Paramorphism è come il fratello minore di Stringer, poiché sembra simile, ma non è altrettanto buono. Ha anche alcune caratteristiche interessanti che mirano a far crashare gli strumenti di reverse engineering, ma possono essere rimosse facilmente.
La forza dell'offuscamento è paragonabile ad Allatori.

Per altri offuscatori puoi provare esecuzioni generiche o aprire una segnalazione, e vedrò cosa posso fare.
Prima di selezionare un'esecuzione, controlla i testi dei suggerimenti che appaiono al passaggio del mouse. Contengono una breve descrizione di ciò che fanno, ma anche tag che ti aiutano a capire come il comportamento del tuo file JAR verrà modificato.
Threadtear è concesso in licenza sotto la GNU General Public License 3.0
Questo strumento è stato un'enorme mole di lavoro. Se ti ho fatto risparmiare tempo e vuoi offrirmi un caffè, puoi farlo qui: Dona con Bitcoin
Utilizza Threadtear solo per scopi legali. Threadtear non mira ad essere uno strumento di cracking, ma piuttosto un toolkit per l'analisi di malware.
Per favore apri una segnalazione o inviami un'email se un trasformatore non funziona correttamente e allega il log.
Nota che i file di output molto probabilmente non sono eseguibili. Se vuoi comunque provare ad eseguirli, usa -noverify come argomento JVM!
Questo strumento è pensato per essere utilizzato con Java 8, ma probabilmente funzionerà anche su versioni successive.