
Multifunktionales Java-Deobfuskierungs-Toolset
Threadtear ist ein multifunktionales Deobfuskierungswerkzeug für Java. Unterstützung für Android-Apps kommt bald (Derzeit arbeite ich an einem Dalvik-zu-Java-Konverter).
Geeignet für eine einfachere Code-Analyse, ohne sich zu sehr um die Verschleierung kümmern zu müssen.
Selbst die teuersten Obfuskatoren wie ZKM oder Stringer sind enthalten. Für einfacheres Debugging sind weitere Werkzeuge enthalten.
Fügen Sie Debug-Zeilennummern ein, um besser zu verstehen, wo Ausnahmen entstehen, oder fügen Sie .printStackTrace() zu try-catch-Blöcken hinzu, ohne Ihren Code neu zu kompilieren.
Rückwärtskompatibilität ist kein Problem mehr, wenn keine versionsspezifischen Methoden verwendet werden. Analysieren Sie den Codefluss in einem Diagramm, um Algorithmen besser zu verstehen.

Eine „Ausführung“ ist eine Aufgabe, die ausgeführt wird und alle geladenen Klassendateien modifiziert. Es gibt mehrere Arten von Ausführungen, die von Bytecode-Bereinigung bis zur String-Entschleierung reichen. Stellen Sie sicher, dass sie in der richtigen Reihenfolge sind. Bereinigungsausführungen sollten zum Beispiel zuletzt ausgeführt werden, können aber auch anderen Ausführungen helfen, wenn sie zuerst ausgeführt werden. Wenn Sie bereit sind, klicken Sie auf die Schaltfläche „Ausführen“, und sie werden der Reihe nach ausgeführt.
Verwenden Sie dieses Tool auf eigenes Risiko. Einige Ausführungen verwenden implementierte ClassLoader, um Code aus der JAR-Datei auszuführen. Ein Angreifer könnte den Bytecode so manipulieren, dass schädlicher Code ausgeführt werden kann.
Betroffene Ausführungen verwenden die Klasse me.nov.threadtear.asm.vm.VM.
Diese werden hauptsächlich zum Entschlüsseln von String- oder Ressourcen-/Zugriffsverschleierung verwendet, da es viel einfacher ist, die Entschlüsselungsmethoden remote auszuführen.
Threadtear gibt sein Bestes, um Sie mit einem eigenen SecurityManager vor bösartigen Aufrufen (beliebige Codeausführungen) zu schützen, aber es gibt keine Garantie.
Besonders bei Deobfuskatoren wie für ZKM oder Stringer müssen Sie sehr vorsichtig sein, da Reflection erlaubt sein muss, sonst würden sie nicht funktionieren.
Wenn Sie eine ACE entdecken, eröffnen Sie bitte ein Issue. Ich werde versuchen, sie so schnell wie möglich zu beheben.
Führen Sie zuerst gradle build aus, dann gradle fatJar. In builds/libs sollte dann eine ausführbare JAR-Datei erstellt worden sein. Wenn Sie das Repository nicht herunterladen möchten, können Sie die neueste Version verwenden.
Sie können ganz einfach Ihre eigene Ausführungsaufgabe erstellen. Erweitern Sie einfach me.nov.threadtear.execution.Execution:
public class MyExecution extends Execution {
public MyExecution() {
super(ExecutionCategory.CLEANING /* category */, "My execution" /* name */,
"Executes something" /* description, can use html */);
}
/**
* This method is invoked when the user clicks on the Run button
* @return true if success, false if failure
*/
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
//transform the classes here using the tree-API of ASM
});
return false;
}
}
Um ClassNodes zur Laufzeit zu laden, verwenden Sie die Klasse me.nov.threadtear.asm.vm.VM und implementieren Sie me.nov.threadtear.asm.vm.IVMReferenceHandler:
public class MyExecution extends Execution implements IVMReferenceHandler {
public MyExecution() {
super(ExecutionCategory.GENERIC, "My execution", "Loads ClassNodes at 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);
//transform bytecode to java.lang.Class
Class<?> loadedClass = vm.loadClass(c.name.replace('/', '.'), true);
//do stuff with your class here
loadedClass.getMethods()[0].invoke(...);
return true;
});
}
/**
* Will get invoked by VM, when VM.loadClass is called
*/
@Override
public ClassNode tryClassLoad(String name) {
//try to find the class to be loaded in open jar archive
return classes.containsKey(name) ? classes.get(name).node : null;
}
}
Mit dem ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker) können Sie Methoden analysieren und den Überblick über nicht-variable Stack-Werte behalten.
Wenn beispielsweise iconst_0 auf den Stack gelegt wird, geht der Wert selbst nicht verloren wie im einfachen ASM-Analyzer, und Sie können ihn verwenden, um später im Code Dinge vorherzusagen.
public class MyExecution extends Execution implements IConstantReferenceHandler {
public MyExecution() {
super(ExecutionCategory.GENERIC, "My execution", "Performs stack analysis and replaces code.");
}
@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 -> {
// this analyzer keeps known stack values, e.g. can be useful for jump prediction
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("Failed stack analysis 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);