Threadtear - Multifunctional Java Deobfuscation Tool Suite
Threadtear is a multifunctional deobfuscation tool for java. Suitable for easier code analysis without worrying too much about obfuscation. Even the most expensive obfuscators like ZKM or Stringer are included. It also contains older deobfuscation tools from my github account, but it can also be useful for other stuff. Insert debug line numbers to better understand where exceptions originate, or add .printStackTrace() to try catch blocks without re-compiling your code. Reverse compatibility is also not a problem anymore (of course only when no version specific methods are used).
Executions
An "execution" is a task that is executed and modifies all loaded class files. There are multiple types of executions, varying from bytecode cleanup to string deobfuscation. Make sure to have them in the right order. Cleanup executions for example should be executed at last, but also can help other executions if executed first.
Warning
Use this tool at your own risk. Some executions use implemented ClassLoaders to run code from the jar file, an attacker could tweak the file so that malicious code would be executed. Affected executions use the class me.nov.threadtear.asm.vm.VM. These are mostly used for decrypting string or resource / access obfuscation.
How to compile
First, run gradle build, then gradle fatJar. In builds/libs a runnable jar file should then have been created.
Make your own execution
You can easily create your own execution task. Just extend 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 classes, boolean verbose) {
classes.values().stream().map(c -> c.node).forEach(c -> {
//transform the classes here using the tree-API of ASM
});
return false;
}
}To load ClassNodes at runtime, use the me.nov.threadtear.asm.vm.VM class and implement 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 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;
}
}Using the ConstantTracker (me.nov.threadtear.analysis.stack.ConstantTracker) you can analyze methods and keep track of non-variable stack values. If for example iconst_0 is pushed to the stack, the value itself isn't lost like in the basic ASM analyzer, and you can use it to predict things later on in the code.
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 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 a = new Analyzer(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[] frames = a.getFrames();
InsnList rewrittenCode = new InsnList();
Map labels = Instructions.cloneLabels(m.instructions);
// rewrite method instructions
for (int i = 0; i < m.instructions.size(); i++) {
AbstractInsnNode ain = m.instructions.get(i);
Frame 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.upda teInstructions(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
http://dlvr.it/RWVdv5
http://dlvr.it/RWVdv5
Comments
Post a Comment