From 6cac8c1cc930d44b1c765a77125513fa4e7cc681 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 13 Jun 2015 14:24:04 -0400 Subject: [PATCH] Move deobfuscation methods to their own files --- .../java/info/sigterm/deob/ClassGroup.java | 8 +- src/main/java/info/sigterm/deob/Deob.java | 317 +++--------------- src/main/java/info/sigterm/deob/Method.java | 7 +- .../sigterm/deob/deobfuscators/Jumps.java | 107 ++++++ .../deob/deobfuscators/RuntimeExceptions.java | 35 ++ .../deob/deobfuscators/UnusedBlocks.java | 43 +++ .../deob/deobfuscators/UnusedMethods.java | 34 ++ .../deob/deobfuscators/UnusedParameters.java | 96 ++++++ .../sigterm/deob/execution/Execution.java | 22 +- 9 files changed, 385 insertions(+), 284 deletions(-) create mode 100644 src/main/java/info/sigterm/deob/deobfuscators/Jumps.java create mode 100644 src/main/java/info/sigterm/deob/deobfuscators/RuntimeExceptions.java create mode 100644 src/main/java/info/sigterm/deob/deobfuscators/UnusedBlocks.java create mode 100644 src/main/java/info/sigterm/deob/deobfuscators/UnusedMethods.java create mode 100644 src/main/java/info/sigterm/deob/deobfuscators/UnusedParameters.java diff --git a/src/main/java/info/sigterm/deob/ClassGroup.java b/src/main/java/info/sigterm/deob/ClassGroup.java index da121b88ec..a8b5e4c51f 100644 --- a/src/main/java/info/sigterm/deob/ClassGroup.java +++ b/src/main/java/info/sigterm/deob/ClassGroup.java @@ -27,7 +27,7 @@ public class ClassGroup public ClassFile findClass(String name) { - // XXX handle arrays + // XXX handle arrays? for (ClassFile c : classes) if (c.getName().equals(name)) return c; @@ -48,6 +48,12 @@ public class ClassGroup public void buildCallGraph() { + for (ClassFile c : classes) + for (Method m : c.getMethods().getMethods()) + { + m.callsTo.clear(); + m.callsFrom.clear(); + } for (ClassFile c : classes) c.buildCallGraph(); } diff --git a/src/main/java/info/sigterm/deob/Deob.java b/src/main/java/info/sigterm/deob/Deob.java index a63ddb1401..65f2a1a451 100644 --- a/src/main/java/info/sigterm/deob/Deob.java +++ b/src/main/java/info/sigterm/deob/Deob.java @@ -1,5 +1,10 @@ package info.sigterm.deob; +import info.sigterm.deob.deobfuscators.Jumps; +import info.sigterm.deob.deobfuscators.RuntimeExceptions; +import info.sigterm.deob.deobfuscators.UnusedBlocks; +import info.sigterm.deob.deobfuscators.UnusedMethods; +import info.sigterm.deob.deobfuscators.UnusedParameters; import info.sigterm.deob.execution.Execution; import info.sigterm.deob.execution.Frame; import info.sigterm.deob.pool.NameAndType; @@ -29,10 +34,36 @@ import java.util.jar.Manifest; public class Deob { public static void main(String[] args) throws IOException + { + ClassGroup group = loadJar(args[0]); + + // remove except RuntimeException + new RuntimeExceptions().run(group); + + // remove code blocks that used to be the runtime exception handlers + new UnusedBlocks().run(group); + + // remove unused methods + new UnusedMethods().run(group); + + // remove unused parameters + new UnusedParameters().run(group); + + // remove jump obfuscation + new Jumps().run(group); + + //group.buildClassGraph(); + //group.buildInstructionGraph(); + //group.buildCallGraph(); + + saveJar(group, args[1]); + } + + private static ClassGroup loadJar(String jarfile) throws IOException { ClassGroup group = new ClassGroup(); - JarFile jar = new JarFile(args[0]); + JarFile jar = new JarFile(jarfile); for (Enumeration it = jar.entries(); it.hasMoreElements();) { JarEntry entry = it.nextElement(); @@ -44,21 +75,13 @@ public class Deob group.addClass(entry.getName(), new DataInputStream(is)); } jar.close(); - - group.buildClassGraph(); - group.buildInstructionGraph(); - group.buildCallGraph(); - checkCallGraph(group); - removeExceptionObfuscation(group); - checkBlockGraph(group); - - Execution e = execute(group); - - checkParameters(e, group); - checkBlockGraphJump(group); - - JarOutputStream jout = new JarOutputStream(new FileOutputStream(args[1]), new Manifest()); + return group; + } + + private static void saveJar(ClassGroup group, String jarfile) throws IOException + { + JarOutputStream jout = new JarOutputStream(new FileOutputStream(jarfile), new Manifest()); for (ClassFile cf : group.getClasses()) { @@ -74,268 +97,4 @@ public class Deob jout.close(); } - - private static Execution execute(ClassGroup group) throws IOException - { - Execution e = new Execution(group); - - int count = 0, fcount = 0; - for (ClassFile cf : group.getClasses()) - for (Method method : cf.getMethods().getMethods()) - { - if (method.getCode() == null) - continue; - Frame f = new Frame(e, method); - e.frames.add(f); - fcount += e.run(); - ++count; - } - - System.out.println("Processed " + count + " methods and " + fcount + " paths"); - return e; - } - - private static void checkCallGraph(ClassGroup group) - { - int i = 0; - for (ClassFile cf : group.getClasses()) - { - for (Method m : new ArrayList<>(cf.getMethods().getMethods())) - { - /* assume obfuscated names are <= 2 chars */ - if (m.getName().length() > 2) - continue; - - if (!m.isUsed()) - { - cf.getMethods().removeMethod(m); - ++i; - } - } - } - System.out.println("Removed " + i + " methods"); - } - - private static void removeExceptionObfuscation(ClassGroup group) - { - int i = 0; - for (ClassFile cf : group.getClasses()) - { - for (Method m : new ArrayList<>(cf.getMethods().getMethods())) - { - Code c = m.getCode(); - if (c == null) - continue; - - for (info.sigterm.deob.attributes.code.Exception e : new ArrayList<>(c.getExceptions().getExceptions())) - { - if (e.getCatchType() != null && e.getCatchType().getName().equals("java/lang/RuntimeException")) - { - c.getExceptions().remove(e); - ++i; - } - } - } - } - System.out.println("Removed " + i + " exception handlers"); - } - - private static void checkBlockGraph(ClassGroup group) - { - int i = 0; - for (ClassFile cf : group.getClasses()) - { - for (Method m : new ArrayList<>(cf.getMethods().getMethods())) - { - if (m.getCode() == null) - continue; - - Instructions ins = m.getCode().getInstructions(); - int count = 0; - for (Block b : new ArrayList<>(ins.getBlocks())) - { - // first block is the entrypoint, so its always used - if (count++ == 0) - continue; - - if (b.begin.from.isEmpty() && b.begin.exce.isEmpty()) - { - ins.remove(b); - ++i; - } - } - } - } - System.out.println("Removed " + i + " unused blocks"); - } - - private static int checkBlockGraphOnce(ClassGroup group) - { - int count = 0; - for (ClassFile cf : group.getClasses()) - { - for (Method m : new ArrayList<>(cf.getMethods().getMethods())) - { - if (m.getCode() == null) - continue; - - Instructions ins = m.getCode().getInstructions(); - ins.buildBlocks(); - ins.buildJumpGraph(); - List blocks = ins.getBlocks(); - for (int i = 0; i < blocks.size(); ++i) - { - Block block = blocks.get(i); - Block prev = i > 0 ? blocks.get(i - 1) : null; - - // only one thing jumps here - if (block.begin.from.size() == 1 && prev != null && prev.end.isTerminal()) - { - Instruction from = block.begin.from.get(0); // this instruction jumps to block - - if (from.block == block) - continue; - - if (from instanceof Goto || from instanceof GotoW) - { - ++count; - - List ilist = ins.getInstructions(); - - // remove instructions - for (Instruction in : block.instructions) - ilist.remove(in); - - int index = ilist.indexOf(from); - - assert from.block != block; - from.block = null; - - // move instructions which jump here to jump to block.begin - for (Instruction in : from.from) - { - assert in.jump.contains(from); - assert !in.jump.contains(block.begin); - - in.jump.remove(from); - - in.jump.add(block.begin); - block.begin.from.add(in); - } - from.from.clear(); - - // .replace ins - for (Instruction in : ilist) - in.replace(from, block.begin); - - for (info.sigterm.deob.attributes.code.Exception e : m.getCode().getExceptions().getExceptions()) - e.replace(from, block.begin); - - ins.remove(from); // remove jump - - // insert instructions from block where jump was - for (Instruction in : block.instructions) - ilist.add(index++, in); - } - } - } - } - } - return count; - } - - private static void checkBlockGraphJump(ClassGroup g) - { - int count = 0; - int passes = 0; - int i; - do - { - i = checkBlockGraphOnce(g); - count += i; - ++passes; - } - while (i > 0); - - System.out.println("Inlined " + count + " jumps in " + passes + " passes"); - } - - private static int[] checkParametersOnce(Execution execution, ClassGroup group) - { - // removing parameters shifts the others around which is annoying. - // if more than one is unused, we'll just remove the one - // and do the others on another pass - - int count = 0; - int collide = 0; - int overrides = 0; - for (ClassFile cf : group.getClasses()) - { - for (Method m : cf.getMethods().getMethods()) - { - int offset = m.isStatic() ? 0 : 1; - NameAndType nat = m.getNameAndType(); - Signature signature = nat.getDescriptor(); - - for (int variableIndex = 0, lvtIndex = offset; - variableIndex < signature.size(); - lvtIndex += signature.getTypeOfArg(variableIndex++).getSlots()) - { - List lv = m.findLVTInstructionsForVariable(lvtIndex); - - if (lv == null) - continue; - - // XXX instead of checking if the lvt index is never accessed, - // check execution frames and see if it is never read prior to being - // written to, and if so, then remove the parameter, but don't re index - // the lvt table. - if (!lv.isEmpty()) - continue; - - if (!m.getOverriddenMethods().isEmpty()) - { - ++overrides; - continue; - } - - Signature newSig = new Signature(m.getDescriptor()); - newSig.remove(variableIndex); - - Method otherMethod = cf.getMethods().findMethod(new NameAndType(m.getName(), newSig)); - if (otherMethod != null) - { - // sometimes removing an unused parameter will cause a signature collision with another function, - // just ignore it atm (there seems to be very few) - ++collide; - continue; - } - - m.removeParameter(execution, variableIndex, lvtIndex); - ++count; - break; - } - } - } - return new int[] { count, collide, overrides }; - } - - private static void checkParameters(Execution execution, ClassGroup group) - { - int count = 0; - int collide = 0; - int override = 0; - int[] i; - do - { - i = checkParametersOnce(execution, group); - - count += i[0]; - collide = i[1]; // the next pass may be able to reduce the collisions - override = i[2]; - } - while (i[0] > 0); - - System.out.println("Removed " + count + " unused parameters, unable to remove " + collide + " because of signature collisions and " + override + " due to overriding"); - } } \ No newline at end of file diff --git a/src/main/java/info/sigterm/deob/Method.java b/src/main/java/info/sigterm/deob/Method.java index 504e956ae9..fdb4f4805d 100644 --- a/src/main/java/info/sigterm/deob/Method.java +++ b/src/main/java/info/sigterm/deob/Method.java @@ -31,8 +31,8 @@ public class Method private String name; private Signature arguments; private Attributes attributes; - private List callsTo = new ArrayList<>(), - callsFrom = new ArrayList<>(); + List callsTo = new ArrayList<>(); + List callsFrom = new ArrayList<>(); Method(Methods methods) throws IOException { @@ -62,7 +62,7 @@ public class Method assert callsFrom.isEmpty(); } - protected void removeParameter(Execution execution, int paramIndex, int lvtIndex) + public void removeParameter(Execution execution, int paramIndex, int lvtIndex) { Set done = new HashSet<>(); for (Node n : callsFrom) @@ -197,6 +197,7 @@ public class Method public void addCallTo(Instruction ins, Method method) { + assert method != null; Node node = new Node(this, method, ins); callsTo.add(node); method.callsFrom.add(node); diff --git a/src/main/java/info/sigterm/deob/deobfuscators/Jumps.java b/src/main/java/info/sigterm/deob/deobfuscators/Jumps.java new file mode 100644 index 0000000000..f22e1dd7f0 --- /dev/null +++ b/src/main/java/info/sigterm/deob/deobfuscators/Jumps.java @@ -0,0 +1,107 @@ +package info.sigterm.deob.deobfuscators; + +import info.sigterm.deob.ClassFile; +import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; +import info.sigterm.deob.attributes.code.Block; +import info.sigterm.deob.attributes.code.Instruction; +import info.sigterm.deob.attributes.code.Instructions; +import info.sigterm.deob.attributes.code.instructions.Goto; +import info.sigterm.deob.attributes.code.instructions.GotoW; + +import java.util.ArrayList; +import java.util.List; + +public class Jumps +{ + private int checkBlockGraphOnce(ClassGroup group) + { + int count = 0; + for (ClassFile cf : group.getClasses()) + { + for (Method m : new ArrayList<>(cf.getMethods().getMethods())) + { + if (m.getCode() == null) + continue; + + Instructions ins = m.getCode().getInstructions(); + ins.buildBlocks(); + ins.buildJumpGraph(); + List blocks = ins.getBlocks(); + for (int i = 0; i < blocks.size(); ++i) + { + Block block = blocks.get(i); + Block prev = i > 0 ? blocks.get(i - 1) : null; + + // only one thing jumps here + if (block.begin.from.size() == 1 && prev != null && prev.end.isTerminal()) + { + Instruction from = block.begin.from.get(0); // this instruction jumps to block + + if (from.block == block) + continue; + + if (from instanceof Goto || from instanceof GotoW) + { + ++count; + + List ilist = ins.getInstructions(); + + // remove instructions + for (Instruction in : block.instructions) + ilist.remove(in); + + int index = ilist.indexOf(from); + + assert from.block != block; + from.block = null; + + // move instructions which jump here to jump to block.begin + for (Instruction in : from.from) + { + assert in.jump.contains(from); + assert !in.jump.contains(block.begin); + + in.jump.remove(from); + + in.jump.add(block.begin); + block.begin.from.add(in); + } + from.from.clear(); + + // .replace ins + for (Instruction in : ilist) + in.replace(from, block.begin); + + for (info.sigterm.deob.attributes.code.Exception e : m.getCode().getExceptions().getExceptions()) + e.replace(from, block.begin); + + ins.remove(from); // remove jump + + // insert instructions from block where jump was + for (Instruction in : block.instructions) + ilist.add(index++, in); + } + } + } + } + } + return count; + } + + public void run(ClassGroup g) + { + int count = 0; + int passes = 0; + int i; + do + { + i = checkBlockGraphOnce(g); + count += i; + ++passes; + } + while (i > 0); + + System.out.println("Inlined " + count + " jumps in " + passes + " passes"); + } +} diff --git a/src/main/java/info/sigterm/deob/deobfuscators/RuntimeExceptions.java b/src/main/java/info/sigterm/deob/deobfuscators/RuntimeExceptions.java new file mode 100644 index 0000000000..62dc6fd223 --- /dev/null +++ b/src/main/java/info/sigterm/deob/deobfuscators/RuntimeExceptions.java @@ -0,0 +1,35 @@ +package info.sigterm.deob.deobfuscators; + +import info.sigterm.deob.ClassFile; +import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; +import info.sigterm.deob.attributes.Code; + +import java.util.ArrayList; + +public class RuntimeExceptions +{ + public void run(ClassGroup group) + { + int i = 0; + for (ClassFile cf : group.getClasses()) + { + for (Method m : new ArrayList<>(cf.getMethods().getMethods())) + { + Code c = m.getCode(); + if (c == null) + continue; + + for (info.sigterm.deob.attributes.code.Exception e : new ArrayList<>(c.getExceptions().getExceptions())) + { + if (e.getCatchType() != null && e.getCatchType().getName().equals("java/lang/RuntimeException")) + { + c.getExceptions().remove(e); + ++i; + } + } + } + } + System.out.println("Removed " + i + " exception handlers"); + } +} diff --git a/src/main/java/info/sigterm/deob/deobfuscators/UnusedBlocks.java b/src/main/java/info/sigterm/deob/deobfuscators/UnusedBlocks.java new file mode 100644 index 0000000000..155c8cab7e --- /dev/null +++ b/src/main/java/info/sigterm/deob/deobfuscators/UnusedBlocks.java @@ -0,0 +1,43 @@ +package info.sigterm.deob.deobfuscators; + +import info.sigterm.deob.ClassFile; +import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; +import info.sigterm.deob.attributes.code.Block; +import info.sigterm.deob.attributes.code.Instructions; + +import java.util.ArrayList; + +public class UnusedBlocks +{ + public void run(ClassGroup group) + { + int i = 0; + for (ClassFile cf : group.getClasses()) + { + for (Method m : new ArrayList<>(cf.getMethods().getMethods())) + { + if (m.getCode() == null) + continue; + + Instructions ins = m.getCode().getInstructions(); + ins.buildBlocks(); + + int count = 0; + for (Block b : new ArrayList<>(ins.getBlocks())) + { + // first block is the entrypoint, so its always used + if (count++ == 0) + continue; + + if (b.begin.from.isEmpty() && b.begin.exce.isEmpty()) + { + ins.remove(b); + ++i; + } + } + } + } + System.out.println("Removed " + i + " unused blocks"); + } +} diff --git a/src/main/java/info/sigterm/deob/deobfuscators/UnusedMethods.java b/src/main/java/info/sigterm/deob/deobfuscators/UnusedMethods.java new file mode 100644 index 0000000000..7470cc2f8c --- /dev/null +++ b/src/main/java/info/sigterm/deob/deobfuscators/UnusedMethods.java @@ -0,0 +1,34 @@ +package info.sigterm.deob.deobfuscators; + +import info.sigterm.deob.ClassFile; +import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; + +import java.util.ArrayList; + +public class UnusedMethods +{ + public void run(ClassGroup group) + { + group.buildCallGraph(); + + int i = 0; + for (ClassFile cf : group.getClasses()) + { + for (Method m : new ArrayList<>(cf.getMethods().getMethods())) + { + /* assume obfuscated names are <= 2 chars */ + if (m.getName().length() > 2) + continue; + + if (!m.isUsed()) + { + cf.getMethods().removeMethod(m); + ++i; + } + } + } + + System.out.println("Removed " + i + " methods"); + } +} diff --git a/src/main/java/info/sigterm/deob/deobfuscators/UnusedParameters.java b/src/main/java/info/sigterm/deob/deobfuscators/UnusedParameters.java new file mode 100644 index 0000000000..13d88c3556 --- /dev/null +++ b/src/main/java/info/sigterm/deob/deobfuscators/UnusedParameters.java @@ -0,0 +1,96 @@ +package info.sigterm.deob.deobfuscators; + +import info.sigterm.deob.ClassFile; +import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; +import info.sigterm.deob.attributes.code.Instruction; +import info.sigterm.deob.execution.Execution; +import info.sigterm.deob.pool.NameAndType; +import info.sigterm.deob.signature.Signature; + +import java.util.List; + +public class UnusedParameters +{ + private int[] checkParametersOnce(Execution execution, ClassGroup group) + { + // removing parameters shifts the others around which is annoying. + // if more than one is unused, we'll just remove the one + // and do the others on another pass + + int count = 0; + int collide = 0; + int overrides = 0; + for (ClassFile cf : group.getClasses()) + { + for (Method m : cf.getMethods().getMethods()) + { + int offset = m.isStatic() ? 0 : 1; + NameAndType nat = m.getNameAndType(); + Signature signature = nat.getDescriptor(); + + for (int variableIndex = 0, lvtIndex = offset; + variableIndex < signature.size(); + lvtIndex += signature.getTypeOfArg(variableIndex++).getSlots()) + { + List lv = m.findLVTInstructionsForVariable(lvtIndex); + + if (lv == null) + continue; + + // XXX instead of checking if the lvt index is never accessed, + // check execution frames and see if it is never read prior to being + // written to, and if so, then remove the parameter, but don't re index + // the lvt table. + if (!lv.isEmpty()) + continue; + + if (!m.getOverriddenMethods().isEmpty()) + { + ++overrides; + continue; + } + + Signature newSig = new Signature(m.getDescriptor()); + newSig.remove(variableIndex); + + Method otherMethod = cf.getMethods().findMethod(new NameAndType(m.getName(), newSig)); + if (otherMethod != null) + { + // sometimes removing an unused parameter will cause a signature collision with another function, + // just ignore it atm (there seems to be very few) + ++collide; + continue; + } + + m.removeParameter(execution, variableIndex, lvtIndex); + ++count; + break; + } + } + } + return new int[] { count, collide, overrides }; + } + + public void run(ClassGroup group) + { + Execution execution = new Execution(group); + execution.run(); + + int count = 0; + int collide = 0; + int override = 0; + int[] i; + do + { + i = checkParametersOnce(execution, group); + + count += i[0]; + collide = i[1]; // the next pass may be able to reduce the collisions + override = i[2]; + } + while (i[0] > 0); + + System.out.println("Removed " + count + " unused parameters, unable to remove " + collide + " because of signature collisions and " + override + " due to overriding"); + } +} diff --git a/src/main/java/info/sigterm/deob/execution/Execution.java b/src/main/java/info/sigterm/deob/execution/Execution.java index e937cefa74..f2b471196f 100644 --- a/src/main/java/info/sigterm/deob/execution/Execution.java +++ b/src/main/java/info/sigterm/deob/execution/Execution.java @@ -1,6 +1,9 @@ package info.sigterm.deob.execution; +import info.sigterm.deob.ClassFile; import info.sigterm.deob.ClassGroup; +import info.sigterm.deob.Method; + import java.util.ArrayList; import java.util.List; @@ -15,7 +18,24 @@ public class Execution this.group = group; } - public int run() + public void run() + { + int count = 0, fcount = 0; + for (ClassFile cf : group.getClasses()) + for (Method method : cf.getMethods().getMethods()) + { + if (method.getCode() == null) + continue; + Frame f = new Frame(this, method); + frames.add(f); + fcount += this.runFrames(); + ++count; + } + + System.out.println("Processed " + count + " methods and " + fcount + " paths"); + } + + private int runFrames() { int fcount = 0;