
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);