
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);
// rewrite method instructions
for (int i = 0; i < m.instructions.size(); i++) {
AbstractInsnNode ain = m.instructions.get(i);
Frame<ConstantValue> frame = frames[i];
// replace / modify instructions, etc...
if (frame.getStackSize() > 0) {
ConstantValue top = frame.getStack(frame.getStackSize() - 1);
if (top.isKnown() && top.isInteger()) {
int knownTopStackValue = top.getInteger();
// use the known stack to remove jumps, simplify code, etc...
// if(...) { rewrittenCode.add(...); }
continue;
}
}
rewrittenCode.add(ain.clone(labels));
}
// update instructions and fix try catch blocks, local variables, etc...
Instructions.updateInstructions(m, labels, rewrittenCode);
});
}
/**
* Use this method to predict stack values if fields are loaded
*/
@Override
public Object getFieldValueOrNull(BasicValue v, String owner, String name, String desc) {
return null;
}
/**
* Use this method to predict stack values if methods are invoked on known objects
*/
@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()) {
// invocation target is not known, we can't compute the return
return null;
}
return ((String) values.get(0).getValue()).toCharArray();
}
return null;
}
}
Não se esqueça de adicionar sua execução à árvore em me.nov.threadtear.execution.ExecutionLink!
Existem alguns truques que podem ajudá-lo a identificar e desofuscar arquivos jar com sucesso. Antes de executar as execuções, descompilar o código para descobrir o que precisa ser usado. Você pode usar o descompilador implementado para isso.
A melhor ordem para uma desofuscação é execuções genéricas > desofuscação de acesso > desofuscação de strings > execuções de limpeza.
Ofuscadores exibem padrões que você pode usar para identificá-los. A maneira mais fácil de identificar um ofuscador é dar uma olhada no arquivo META-INF/MANIFEST.MF.
É possível que haja um atributo Obfuscated-By: XXX ou Protected-By: XXX.
Código extremamente (fluxo) ofuscado, frequentemente perceptível por um método de descriptografia de string no inicializador estático contendo switches,
ou métodos de descriptografia de string com um bloco de switch muito longo (cerca de 250 casos).
ZKM é um dos melhores (e mais antigos) ofuscadores para Java, e muito caro. Tão antigo quanto o ofuscador é o site deles.

Se seu arquivo jar contém algumas classes especiais com algoritmos de descriptografia enormes que são usados por ofuscação de strings e ofuscação de acesso, provavelmente é Stringer.
A proteção não é ruim e o Stringer é um dos ofuscadores mais caros. Ao contrário dos ofuscadores normais, ele não vem com ofuscação de nome.
É usado mais como "segunda camada". Provavelmente 90% das pessoas que usam este ofuscador estão usando uma crack, pois custa mais que um carro.
Se seu arquivo foi ofuscado com múltiplos ofuscadores, e o Stringer é um deles, você deve começar sua desofuscação pelo Stringer, pois a ofuscação do Stringer não pode ser sobrescrita.
(Devido à assinatura JAR personalizada e ao uso de nomes de métodos durante a descriptografia de strings)

Nomes de classe como IiIlIlIiIl ou aUx, cOn, PrX indicam ofuscação Allatori.
Allatori é muito comum entre arquivos jar ofuscados, porque oferece uma demonstração gratuita acessível em alguns cliques. A ofuscação não é tão difícil de reverter.

Paramorphism é como o irmão menor do Stringer, pois se parece com ele, mas não é tão bom. Também possui alguns recursos interessantes que visam quebrar ferramentas de engenharia reversa, que podem ser removidos facilmente.
A força da ofuscação é comparável ao Allatori.

Para outros ofuscadores, você pode tentar execuções genéricas ou abrir uma issue, e verei o que posso fazer.
Antes de selecionar uma execução, verifique os textos das dicas de ferramenta ao passar o mouse. Eles contêm uma pequena descrição sobre o que fazem, mas também tags que ajudam a entender como o comportamento do seu arquivo JAR será alterado.
Threadtear é licenciado sob a GNU General Public License 3.0
Esta ferramenta foi muito trabalho. Se eu economizei seu tempo e você quiser me comprar um café, pode fazê-lo aqui: Donate with Bitcoin
Use o Threadtear apenas para fins legais. O Threadtear não tem como objetivo ser uma ferramenta de cracking, mas sim um kit de ferramentas de análise de malware.
Por favor, abra uma issue ou me envie um e-mail se um transformador não funcionar corretamente e anexe o log.
Observe que os arquivos de saída provavelmente não são executáveis. Se ainda assim quiser tentar executá-los, use -noverify como argumento JVM!
Esta ferramenta destina-se a ser usada com Java 8, mas provavelmente também funcionará em versões superiores.