
Suite de herramientas multifuncional de desofuscación Java
Threadtear es una herramienta multifuncional de desofuscación para Java. Próximamente se añadirá soporte para aplicaciones Android (actualmente trabajando en un conversor de dalvik a Java).
Adecuada para un análisis de código más sencillo sin preocuparse demasiado por la ofuscación.
Incluso los ofuscadores más caros como ZKM o Stringer están incluidos. Para una depuración más fácil, se incluyen otras herramientas.
Inserta números de línea de depuración para entender mejor de dónde provienen las excepciones, o añade .printStackTrace() a los bloques try catch sin recompilar tu código.
La compatibilidad inversa ya no es un problema, si no se utilizan métodos específicos de versión. Analiza el flujo de código en un gráfico para comprender mejor los algoritmos.

Una "ejecución" es una tarea que se ejecuta y modifica todos los archivos de clase cargados. Hay múltiples tipos de ejecuciones, que varían desde limpieza de bytecode hasta desofuscación de cadenas. Asegúrate de tenerlas en el orden correcto. Por ejemplo, las ejecuciones de limpieza deberían ejecutarse al final, pero también pueden ayudar a otras ejecuciones si se ejecutan primero. Si estás listo, haz clic en el botón "Run", y se ejecutarán en orden.
Utiliza esta herramienta bajo tu propio riesgo. Algunas ejecuciones utilizan ClassLoaders implementados para ejecutar código del archivo jar. Un atacante podría modificar el bytecode para que se ejecute código malicioso.
Las ejecuciones afectadas utilizan la clase me.nov.threadtear.asm.vm.VM.
Estas se utilizan principalmente para descifrar cadenas o recursos/ofuscación de acceso, ya que es mucho más fácil ejecutar los métodos de descifrado de forma remota.
Threadtear hace todo lo posible para protegerte de llamadas maliciosas (ejecuciones de código arbitrario) usando su propio SecurityManager, pero no hay garantía.
Especialmente con desofuscadores como los de ZKM o Stringer, debes tener mucho cuidado, ya que la reflexión debe estar permitida, de lo contrario no funcionarían.
Si descubres un ACE, por favor abre un issue. Intentaré solucionarlos lo antes posible.
Primero, ejecuta gradle build, luego gradle fatJar. En builds/libs debería haberse creado un archivo jar ejecutable. Si no deseas descargar el repositorio, puedes usar la última versión publicada.
Puedes crear fácilmente tu propia tarea de ejecución. Solo extiende me.nov.threadtear.execution.Execution:
public class MyExecution extends Execution {
public MyExecution() {
super(ExecutionCategory.CLEANING /* categoría */, "My execution" /* nombre */,
"Executes something" /* descripción, puede usar html */);
}
/**
* Este método se invoca cuando el usuario hace clic en el botón Run
* @return true si éxito, false si fallo
*/
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
//transforma las clases aquí usando la API de árbol de ASM
});
return false;
}
}
Para cargar ClassNodes en tiempo de ejecución, usa la clase 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", "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);
//transforma bytecode a java.lang.Class
Class<?> loadedClass = vm.loadClass(c.name.replace('/', '.'), true);
//haz cosas con tu clase aquí
loadedClass.getMethods()[0].invoke(...);
return true;
});
}
/**
* Será invocado por VM, cuando se llame a VM.loadClass
*/
@Override
public ClassNode tryClassLoad(String name) {
//intenta encontrar la clase a cargar en el archivo jar abierto
return classes.containsKey(name) ? classes.get(name).node : null;
}
}
Usando ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker) puedes analizar métodos y rastrear valores de pila no variables.
Si, por ejemplo, se empuja iconst_0 a la pila, el valor en sí no se pierde como en el analizador ASM básico, y puedes usarlo para predecir cosas más adelante en el código.
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 -> {
// este analizador mantiene los valores conocidos de la pila, p. ej., puede ser útil para la predicción de saltos
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);