
Многофункциональный набор инструментов для деобфускации Java
Threadtear — это многофункциональный инструмент для деобфускации Java. Скоро появится поддержка приложений Android (сейчас ведётся разработка конвертера Dalvik в Java).
Подходит для упрощённого анализа кода без излишнего беспокойства об обфускации.
Поддерживаются даже самые дорогие обфускаторы, такие как ZKM или Stringer. Для упрощения отладки включены другие инструменты.
Вставьте номера строк отладки, чтобы лучше понять, откуда возникают исключения, или добавьте .printStackTrace() в блоки try-catch без перекомпиляции кода.
Обратная совместимость больше не проблема, если не используются версиезависимые методы. Анализируйте поток кода в графе, чтобы лучше понимать алгоритмы.

«Исполнение» — это задача, которая выполняется и изменяет все загруженные файлы классов. Существует несколько типов исполнений — от очистки байткода до деобфускации строк. Убедитесь, что они расположены в правильном порядке. Например, исполнения очистки должны выполняться последними, но также могут помочь другим исполнениям, если выполнить их первыми. Когда будете готовы, нажмите кнопку «Run», и они выполнятся по порядку.
Используйте этот инструмент на свой страх и риск. Некоторые исполнения используют реализованные ClassLoader для выполнения кода из JAR-файла. Злоумышленник может подстроить байткод так, чтобы выполнился вредоносный код.
Затронутые исполнения используют класс me.nov.threadtear.asm.vm.VM.
В основном они используются для дешифровки строк или деобфускации ресурсов/доступа, так как гораздо проще выполнять методы дешифровки удалённо.
Threadtear делает всё возможное, чтобы защитить вас от вредоносных вызовов (произвольного выполнения кода), используя собственный SecurityManager, но гарантий нет.
Особенно с деобфускаторами для ZKM или Stringer нужно быть очень осторожным, так как рефлексия должна быть разрешена, иначе они не будут работать.
Если вы обнаружите ACE, пожалуйста, откройте issue. Я постараюсь исправить их как можно скорее.
Сначала выполните gradle build, затем gradle fatJar. В папке builds/libs должен появиться исполняемый JAR-файл. Если вы не хотите скачивать репозиторий, можете использовать последний релиз.
Вы можете легко создать собственное исполнение. Просто расширьте класс me.nov.threadtear.execution.Execution:
public class MyExecution extends Execution {
public MyExecution() {
super(ExecutionCategory.CLEANING /* категория */, "My execution" /* имя */,
"Executes something" /* описание, можно использовать HTML */);
}
/**
* Этот метод вызывается, когда пользователь нажимает кнопку Run
* @return true при успехе, false при неудаче
*/
@Override
public boolean execute(Map<String, Clazz> classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
//преобразуйте классы здесь, используя tree-API ASM
});
return false;
}
}
Для загрузки ClassNodes во время выполнения используйте класс me.nov.threadtear.asm.vm.VM и реализуйте 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);
//преобразуйте байткод в java.lang.Class
Class<?> loadedClass = vm.loadClass(c.name.replace('/', '.'), true);
//работайте с этим классом здесь
loadedClass.getMethods()[0].invoke(...);
return true;
});
}
/**
* Будет вызван VM, когда вызывается VM.loadClass
*/
@Override
public ClassNode tryClassLoad(String name) {
//попробуйте найти загружаемый класс в открытом JAR-архиве
return classes.containsKey(name) ? classes.get(name).node : null;
}
}
Используя ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker), вы можете анализировать методы и отслеживать не переменные значения стека.
Например, если iconst_0 помещается в стек, само значение не теряется, как в базовом анализаторе ASM, и вы можете использовать его для предсказания дальнейших действий в коде.
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 -> {
// этот анализатор сохраняет известные значения стека, например, полезно для предсказания переходов
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);