
Suíte de ferramentas multifuncional de desofuscação java
Threadtear é uma ferramenta de desofuscação multifuncional para Java. O suporte a aplicativos Android está a caminho (atualmente trabalhando em um conversor de dalvik para Java).
Adequado para análise de código mais fácil sem se preocupar muito com ofuscação.
Até os ofuscadores mais caros, como ZKM ou Stringer, estão incluídos. Para depuração mais fácil, outras ferramentas estão incluídas.
Insira números de linha de depuração para entender melhor de onde as exceções se originam, ou adicione .printStackTrace() a blocos try-catch sem recompilar seu código.
A compatibilidade reversa não é mais um problema, se nenhum método específico de versão for usado. Analise o fluxo de código em um gráfico para entender melhor os algoritmos.

Uma "execução" é uma tarefa que é executada e modifica todos os arquivos de classe carregados. Existem vários tipos de execuções, variando de limpeza de bytecode a desofuscação de strings. Certifique-se de colocá-las na ordem correta. Execuções de limpeza, por exemplo, devem ser executadas por último, mas também podem ajudar outras execuções se executadas primeiro. Se estiver pronto, clique no botão "Run" e elas serão executadas em ordem.
Use esta ferramenta por sua conta e risco. Algumas execuções usam ClassLoaders implementados para executar código do arquivo jar. Um invasor poderia ajustar o bytecode para que código malicioso seja executado.
Execuções afetadas usam a classe me.nov.threadtear.asm.vm.VM.
Elas são usadas principalmente para descriptografar strings ou ofuscação de recursos/acesso, pois é muito mais fácil executar os métodos de descriptografia remotamente.
O Threadtear faz o possível para protegê-lo de chamadas maliciosas (execuções de código arbitrário) usando seu próprio SecurityManager, mas não há garantia.
Especialmente com desofuscadores como para ZKM ou Stringer, você precisa ser muito cuidadoso, pois a reflexão precisa ser permitida, caso contrário, eles não funcionariam.
Se você descobrir um ACE (ACE), por favor, abra uma issue. Tentarei corrigi-los o mais rápido possível.
Primeiro, execute gradle build, depois gradle fatJar. Em builds/libs, um arquivo jar executável deve ter sido criado. Se você não quiser baixar o repositório, pode usar o lançamento mais recente.
Você pode criar facilmente sua própria tarefa de execução. Basta estender 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;
}
}
Para carregar ClassNodes em tempo de execução, use a classe me.nov.threadtear.asm.vm.VM e implemente 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;
}
}
Usando o ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker) você pode analisar métodos e manter o controle dos valores de pilha não variáveis.
Se, por exemplo, iconst_0 for empurrado para a pilha, o valor em si não é perdido como no analisador ASM básico, e você pode usá-lo para prever coisas mais adiante no 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 -> {
// 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);