diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/executor/MethodExecutor.java b/src/main/java/com/javadeobfuscator/deobfuscator/executor/MethodExecutor.java index 83029b12..3b3fc987 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/executor/MethodExecutor.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/executor/MethodExecutor.java @@ -1007,6 +1007,18 @@ private static T execute(ClassNode classNode, MethodNode method, AbstractIns Type type = Type.getType(cast.desc); Class clazz = PrimitiveUtils.getPrimitiveByName(type.getClassName()); Object provided = context.provider.getField(cast.owner, cast.name, cast.desc, null, context); + if(provided == null && type.getSort() != Type.OBJECT && type.getSort() != Type.ARRAY) + { + ClassNode fieldClass = null; + for(ClassNode cn : context.dictionary.values()) + if(cn.name.equals(cast.owner)) + { + fieldClass = cn; + break; + } + provided = fieldClass.fields.stream().filter(f -> f.name.equals(cast.name) && f.desc.equals(cast.desc)).findFirst().orElse(null).value; + context.provider.setField(cast.owner, cast.name, cast.desc, null, provided, context); + } switch (type.getSort()) { case Type.BOOLEAN: @@ -1075,6 +1087,19 @@ else if(obj instanceof JavaObject) Type type = Type.getType(cast.desc); Class clazz = PrimitiveUtils.getPrimitiveByName(type.getClassName()); Object provided = context.provider.getField(cast.owner, cast.name, cast.desc, obj, context); + if(provided == null && type.getSort() != Type.OBJECT && type.getSort() != Type.ARRAY) + { + ClassNode fieldClass = null; + for(ClassNode cn : context.dictionary.values()) + if(cn.name.equals(cast.owner)) + { + fieldClass = cn; + break; + } + provided = fieldClass.fields.stream().filter(f -> f.name.equals(cast.name) && f.desc.equals(cast.desc)).findFirst().orElse(null).value; + context.provider.setField(cast.owner, cast.name, cast.desc, obj, provided, context); + } + switch (type.getSort()) { case Type.BOOLEAN: stack.add(0, new JavaBoolean((Boolean) provided)); @@ -1216,10 +1241,13 @@ else if(obj instanceof JavaObject) break; } } else { - throw new NoSuchMethodHandlerException("Could not find invoker for " + args.get(args.size() - 1).type() + " " + cast.name + cast.desc); + throw new NoSuchMethodHandlerException("Could not find invoker for " + args.get(args.size() - 1).type() + " " + cast.name + cast.desc).setThrownFromInvoke(true); } break; } catch (NoSuchMethodHandlerException | IllegalArgumentException t) { + if(t instanceof NoSuchMethodHandlerException + && !((NoSuchMethodHandlerException)t).isThrownFromInvoke()) + throw t; ClassNode ownerClass = context.dictionary.get(owner); if (ownerClass != null) { if (ownerClass.superName != null) { @@ -1311,10 +1339,13 @@ else if(obj instanceof JavaObject) break; } } else { - throw new NoSuchMethodHandlerException("Could not find invoker for " + cast.owner + " " + cast.name + cast.desc); + throw new NoSuchMethodHandlerException("Could not find invoker for " + cast.owner + " " + cast.name + cast.desc).setThrownFromInvoke(true); } break; } catch (NoSuchMethodHandlerException | IllegalArgumentException t) { + if(t instanceof NoSuchMethodHandlerException + && !((NoSuchMethodHandlerException)t).isThrownFromInvoke()) + throw t; ClassNode ownerClass = context.dictionary.get(owner); if (ownerClass != null) { if (ownerClass.superName != null) { @@ -1404,7 +1435,7 @@ else if(obj instanceof JavaObject) break; } } else { - throw new NoSuchMethodHandlerException("Could not find invoker for " + cast.owner + " " + cast.name + cast.desc); + throw new NoSuchMethodHandlerException("Could not find invoker for " + cast.owner + " " + cast.name + cast.desc).setThrownFromInvoke(true); } break; } @@ -1534,7 +1565,7 @@ else if(obj instanceof JavaObject) break; } }else { - throw new NoSuchMethodHandlerException("Could not find invoker for " + args.get(args.size() - 1).type() + " " + cast.name + cast.desc); + throw new NoSuchMethodHandlerException("Could not find invoker for " + args.get(args.size() - 1).type() + " " + cast.name + cast.desc).setThrownFromInvoke(true); } break; } @@ -1796,6 +1827,8 @@ else if(argumentTypes[i + 3].getSort() == Type.DOUBLE) e.clazz = classNode.name; e.method = method.name + method.desc; } + if(e instanceof NoSuchMethodHandlerException) + ((NoSuchMethodHandlerException)e).setThrownFromInvoke(false); throw e; } catch (Throwable t) { if (DEBUG_PRINT_EXCEPTIONS) { diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/executor/defined/JVMMethodProvider.java b/src/main/java/com/javadeobfuscator/deobfuscator/executor/defined/JVMMethodProvider.java index 958c4bf9..f59a6d68 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/executor/defined/JVMMethodProvider.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/executor/defined/JVMMethodProvider.java @@ -24,6 +24,7 @@ import java.math.BigInteger; import java.net.URI; import java.net.URL; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.security.CodeSource; import java.security.Key; @@ -109,6 +110,10 @@ public class JVMMethodProvider extends MethodProvider { put("java/nio/charset/Charset", new HashMap, Context, Object>>() {{ put("availableCharsets()Ljava/util/SortedMap;", (targetObject, args, context) -> Charset.availableCharsets()); }}); + put("java/nio/ByteBuffer", new HashMap, Context, Object>>() {{ + put("wrap([B)Ljava/nio/ByteBuffer;", (targetObject, args, context) -> ByteBuffer.wrap(args.get(0).as(byte[].class))); + put("getDouble()D", (targetObject, args, context) -> targetObject.as(ByteBuffer.class).getDouble()); + }}); put("java/util/SortedMap", new HashMap, Context, Object>>() {{ put("keySet()Ljava/util/Set;", (targetObject, args, context) -> targetObject.as(SortedMap.class).keySet()); }}); @@ -241,6 +246,11 @@ public class JVMMethodProvider extends MethodProvider { targetObject.initialize(new String(args.get(0).as(byte[].class), args.get(1).intValue())); return null; }); + put("([BII)V", (targetObject, args, context) -> { + expect(targetObject, "java/lang/String"); + targetObject.initialize(new String(args.get(0).as(byte[].class), args.get(1).intValue(), args.get(2).intValue())); + return null; + }); put("([BLjava/lang/String;)V", (targetObject, args, context) -> { expect(targetObject, "java/lang/String"); targetObject.initialize(new String(args.get(0).as(byte[].class), args.get(1).as(String.class))); @@ -636,6 +646,14 @@ public class JVMMethodProvider extends MethodProvider { put("getMethodName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StackTraceElement.class).getMethodName()); put("getFileName()Ljava/lang/String;", (targetObject, args, context) -> targetObject.as(StackTraceElement.class).getFileName()); }}); + put("java/lang/Float", new HashMap, Context, Object>>() {{ + put("intBitsToFloat(I)F", (targetObject, args, context) -> Float.intBitsToFloat(args.get(0).intValue())); + put("valueOf(F)Ljava/lang/Float;", (targetObject, args, context) -> Float.valueOf(args.get(0).floatValue())); + }}); + put("java/lang/Double", new HashMap, Context, Object>>() {{ + put("longBitsToDouble(J)D", (targetObject, args, context) -> Double.longBitsToDouble(args.get(0).longValue())); + put("valueOf(D)Ljava/lang/Double;", (targetObject, args, context) -> Double.valueOf(args.get(0).doubleValue())); + }}); put("java/lang/Long", new HashMap, Context, Object>>() {{ put("(J)V", (targetObject, args, context) -> { expect(targetObject, "java/lang/Long"); @@ -672,6 +690,14 @@ public class JVMMethodProvider extends MethodProvider { put("java/util/regex/Pattern", new HashMap, Context, Object>>() {{ put("compile(Ljava/lang/String;)Ljava/util/regex/Pattern;", (targetObject, args, context) -> Pattern.compile(args.get(0).as(String.class))); }}); + put("java/util/Random", new HashMap, Context, Object>>() {{ + put("(J)V", (targetObject, args, context) -> { + expect(targetObject, "java/util/Random"); + targetObject.initialize(new Random(args.get(0).longValue())); + return null; + }); + put("nextDouble()D", (targetObject, args, context) -> targetObject.as(Random.class).nextDouble()); + }}); put("java/lang/BootstrapMethodError", new HashMap, Context, Object>>() {{ put("()V", (targetObject, args, context) -> { expect(targetObject, "java/lang/BootstrapMethodError"); @@ -697,7 +723,7 @@ public class JVMMethodProvider extends MethodProvider { targetObject.initialize(new HashMap<>(args.get(0).intValue())); return null; }); - put("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(HashMap.class).put(args.get(0), args.get(1))); + put("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(HashMap.class).put(args.get(0).value(), args.get(1).value())); put("get(Ljava/lang/Object;)Ljava/lang/Object;", (targetObject, args, context) -> targetObject.as(HashMap.class).get(args.get(0).value())); put("containsKey(Ljava/lang/Object;)Z", (targetObject, args, context) -> targetObject.as(HashMap.class).containsKey(args.get(0).value())); put("isEmpty()Z", (targetObject, args, context) -> targetObject.as(HashMap.class).isEmpty()); @@ -746,6 +772,7 @@ public class JVMMethodProvider extends MethodProvider { }}); put("java/lang/Math", new HashMap, Context, Object>>() {{ put("abs(J)J", (targetObject, args, context) -> Math.abs(args.get(0).longValue())); + put("round(D)J", (targetObject, args, context) -> Math.round(args.get(0).doubleValue())); }}); put("java/math/BigInteger", new HashMap, Context, Object>>() {{ put("(Ljava/lang/String;I)V", (targetObject, args, context) -> { diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/executor/exceptions/NoSuchMethodHandlerException.java b/src/main/java/com/javadeobfuscator/deobfuscator/executor/exceptions/NoSuchMethodHandlerException.java index ad55f1c0..0e108e8e 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/executor/exceptions/NoSuchMethodHandlerException.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/executor/exceptions/NoSuchMethodHandlerException.java @@ -17,7 +17,18 @@ package com.javadeobfuscator.deobfuscator.executor.exceptions; public class NoSuchMethodHandlerException extends NoSuchHandlerException { + private boolean isThrownFromInvoke; + public NoSuchMethodHandlerException(String msg) { super(msg); } + + public NoSuchMethodHandlerException setThrownFromInvoke(boolean isThrownFromExe) { + this.isThrownFromInvoke = isThrownFromExe; + return this; + } + + public boolean isThrownFromInvoke() { + return isThrownFromInvoke; + } } diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/executor/values/JavaValue.java b/src/main/java/com/javadeobfuscator/deobfuscator/executor/values/JavaValue.java index 4dd1daba..1eaa41ca 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/executor/values/JavaValue.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/executor/values/JavaValue.java @@ -24,9 +24,9 @@ import com.javadeobfuscator.deobfuscator.executor.exceptions.ExecutionException; public abstract class JavaValue { - - public boolean booleanValue() { - throw new ExecutionException(new UnsupportedOperationException()); + + public boolean booleanValue() { + throw new ExecutionException(new UnsupportedOperationException()); } public int intValue() { diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/dasho/FlowObfuscationTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/dasho/FlowObfuscationTransformer.java index 9f45dc75..68963f4f 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/dasho/FlowObfuscationTransformer.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/dasho/FlowObfuscationTransformer.java @@ -2,167 +2,306 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.commons.lang3.tuple.Triple; import org.assertj.core.internal.asm.Opcodes; import org.objectweb.asm.tree.*; +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer; +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer.JumpData; import com.javadeobfuscator.deobfuscator.config.TransformerConfig; import com.javadeobfuscator.deobfuscator.transformers.Transformer; import com.javadeobfuscator.deobfuscator.utils.Utils; +/** + * This transformer can be VERY slow. + */ public class FlowObfuscationTransformer extends Transformer { - @Override - public boolean transform() throws Throwable { - System.out.println("[DashO] [FlowObfuscationTransformer] Starting"); - AtomicInteger counter = new AtomicInteger(); - - for(ClassNode classNode : classNodes()) - for(MethodNode method : classNode.methods) - loop: - for(AbstractInsnNode ain : method.instructions.toArray()) - if(Utils.getIntValue(ain) == -1 && ain.getNext() != null - && ain.getNext().getOpcode() == Opcodes.ISTORE - && ain.getNext().getNext() != null - && ain.getNext().getNext().getOpcode() == Opcodes.LDC - && ((LdcInsnNode)ain.getNext().getNext()).cst.equals("0") - && ain.getNext().getNext().getNext() != null - && ain.getNext().getNext().getNext().getOpcode() == Opcodes.IINC - && ((IincInsnNode)ain.getNext().getNext().getNext()).incr == 1 - && ((IincInsnNode)ain.getNext().getNext().getNext()).var == ((VarInsnNode)ain.getNext()).var - && ain.getNext().getNext().getNext().getNext() != null - && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.ASTORE) - { - List remove = new ArrayList<>(); - Map replace = new HashMap<>(); - remove.add(ain); - remove.add(ain.getNext()); - remove.add(ain.getNext().getNext()); - remove.add(ain.getNext().getNext().getNext()); - remove.add(ain.getNext().getNext().getNext().getNext()); - int var1Index = ((VarInsnNode)ain.getNext()).var; - int var1Value = 0; - int var2Index = ((VarInsnNode)ain.getNext().getNext().getNext().getNext()).var; - int var2Value = 0; - - AbstractInsnNode next = ain.getNext().getNext().getNext().getNext(); - int count = 0; - while(true) - { - boolean found = false; - LabelNode lbl = null; - while(next != null && !(next instanceof LabelNode)) - { - if(Utils.getIntValue(next) == -1 && ain.getNext() != null - && next.getNext().getOpcode() == Opcodes.ISTORE - && next.getNext().getNext() != null - && next.getNext().getNext().getOpcode() == Opcodes.LDC - && ((LdcInsnNode)next.getNext().getNext()).cst.equals("0") - && next.getNext().getNext().getNext() != null - && next.getNext().getNext().getNext().getOpcode() == Opcodes.IINC - && ((IincInsnNode)next.getNext().getNext().getNext()).incr == 1 - && ((IincInsnNode)next.getNext().getNext().getNext()).var == ((VarInsnNode)next.getNext()).var - && next.getNext().getNext().getNext().getNext() != null - && next.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.ASTORE) - break; - if(next.getOpcode() == Opcodes.ALOAD && ((VarInsnNode)next).var == var2Index - && next.getNext() != null && next.getNext().getOpcode() == Opcodes.INVOKESTATIC - && ((MethodInsnNode)next.getNext()).name.equals("parseInt") - && ((MethodInsnNode)next.getNext()).owner.equals("java/lang/Integer") - && next.getNext().getNext() != null - && next.getNext().getNext().getOpcode() == Opcodes.TABLESWITCH - && ((TableSwitchInsnNode)next.getNext().getNext()).min == 0 - && ((TableSwitchInsnNode)next.getNext().getNext()).labels.size() == 1) - { - remove.add(next); - remove.add(next.getNext()); - found = true; - lbl = var2Value == 0 ? ((TableSwitchInsnNode)next.getNext().getNext()).labels.get(0) : - ((TableSwitchInsnNode)next.getNext().getNext()).dflt; - replace.put(next.getNext().getNext(), new JumpInsnNode(Opcodes.GOTO, lbl)); - next = lbl.getNext(); - break; - }else if(next.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)next).var == var1Index - && next.getNext() != null && next.getNext().getOpcode() == Opcodes.TABLESWITCH - && ((TableSwitchInsnNode)next.getNext()).min == 0 - && ((TableSwitchInsnNode)next.getNext()).labels.size() == 1) - { - remove.add(next); - found = true; - lbl = var1Value == 0 ? ((TableSwitchInsnNode)next.getNext()).labels.get(0) : - ((TableSwitchInsnNode)next.getNext()).dflt; - replace.put(next.getNext(), new JumpInsnNode(Opcodes.GOTO, lbl)); - next = lbl.getNext(); - break; - } - next = next.getNext(); - } - if(!found) - break; - count++; - boolean found2 = false; - while(next != null && !(next instanceof LabelNode)) - { - if(next.getOpcode() == Opcodes.IINC && ((IincInsnNode)next).var == var1Index - && next.getNext() != null && next.getNext().getOpcode() == Opcodes.LDC - && next.getNext().getNext() != null - && next.getNext().getNext().getOpcode() == Opcodes.ASTORE - && ((VarInsnNode)next.getNext().getNext()).var == var2Index) - { - remove.add(next); - remove.add(next.getNext()); - remove.add(next.getNext().getNext()); - found2 = true; - var1Value += ((IincInsnNode)next).incr; - var2Value = Integer.parseInt((String)((LdcInsnNode)next.getNext()).cst); - if(next.getNext().getNext().getNext().getOpcode() == Opcodes.GOTO) - next = ((JumpInsnNode)next.getNext().getNext().getNext()).label.getNext(); - else if(next.getNext().getNext().getNext() instanceof LabelNode) - next = next.getNext().getNext().getNext().getNext(); - break; - }else if(Utils.isInteger(next) && next.getNext() != null - && next.getNext().getOpcode() == Opcodes.ISTORE - && ((VarInsnNode)next.getNext()).var == var1Index - && next.getNext().getNext() != null - && next.getNext().getNext().getOpcode() == Opcodes.LDC - && next.getNext().getNext().getNext() != null - && next.getNext().getNext().getNext().getOpcode() == Opcodes.ASTORE - && ((VarInsnNode)next.getNext().getNext().getNext()).var == var2Index) - { - remove.add(next); - remove.add(next.getNext()); - remove.add(next.getNext().getNext()); - remove.add(next.getNext().getNext().getNext()); - found2 = true; - var1Value = Utils.getIntValue(next); - var2Value = Integer.parseInt((String)((LdcInsnNode)next.getNext().getNext()).cst); - if(next.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.GOTO) - next = ((JumpInsnNode)next.getNext().getNext().getNext().getNext()).label.getNext(); - else if(next.getNext().getNext().getNext().getNext() instanceof LabelNode) - next = next.getNext().getNext().getNext().getNext().getNext(); - break; - } - next = next.getNext(); - } - if(!found2) - continue loop; - } - - if(count > 0) - { - for(AbstractInsnNode a : remove) - method.instructions.remove(a); - for(Entry en : replace.entrySet()) - method.instructions.set(en.getKey(), en.getValue()); - counter.incrementAndGet(); - } - } + private LabelNode NO_INSN = new LabelNode(); + + @Override + public boolean transform() throws Throwable + { + System.out.println("[DashO] [FlowObfuscationTransformer] Starting"); + AtomicInteger suspect = new AtomicInteger(); + AtomicInteger counter = new AtomicInteger(); + + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + if(method.instructions == null || method.instructions.size() == 0) + continue; + FlowAnalyzer analyzer = new FlowAnalyzer(method); + LinkedHashMap> result = analyzer.analyze(method.instructions.getFirst(), + new ArrayList<>(), new HashMap<>(), false, true); + FlowAnalyzer.Result jumpAnalysis = analyzer.analyze(); + List ordered = new ArrayList<>(); + for(Entry> e : result.entrySet()) + { + if(e.getKey() != FlowAnalyzer.ABSENT) + ordered.add(e.getKey()); + ordered.addAll(e.getValue()); + for(Triple entry : jumpAnalysis.labels.get(e.getKey()).getValue()) + if(entry.getMiddle().cause == FlowAnalyzer.JumpCause.NEXT) + ordered.add(new JumpInsnNode(Opcodes.GOTO, entry.getLeft())); + } + loop: + for(AbstractInsnNode ain : ordered) + { + if(Utils.getIntValue(ain) == -1 + && getNext(ordered, result, jumpAnalysis, ain, 1).getOpcode() == Opcodes.ISTORE + && getNext(ordered, result, jumpAnalysis, ain, 2).getOpcode() == Opcodes.LDC + && ((LdcInsnNode)getNext(ordered, result, jumpAnalysis, ain, 2)).cst.equals("0") + && getNext(ordered, result, jumpAnalysis, ain, 3).getOpcode() == Opcodes.IINC + && ((IincInsnNode)getNext(ordered, result, jumpAnalysis, ain, 3)).incr == 1 + && ((IincInsnNode)getNext(ordered, result, jumpAnalysis, ain, 3)).var == ((VarInsnNode)getNext(ordered, result, jumpAnalysis, ain, 1)).var + && getNext(ordered, result, jumpAnalysis, ain, 4).getOpcode() == Opcodes.ASTORE) + { + suspect.incrementAndGet(); + List remove = new ArrayList<>(); + Map replace = new HashMap<>(); + remove.add(ain); + remove.add(getNext(ordered, result, jumpAnalysis, ain, 1)); + remove.add(getNext(ordered, result, jumpAnalysis, ain, 2)); + remove.add(getNext(ordered, result, jumpAnalysis, ain, 3)); + remove.add(getNext(ordered, result, jumpAnalysis, ain, 4)); + int var1Index = ((VarInsnNode)getNext(ordered, result, jumpAnalysis, ain, 1)).var; + int var1Value = 0; + int var2Index = ((VarInsnNode)getNext(ordered, result, jumpAnalysis, ain, 4)).var; + int var2Value = 0; + + AbstractInsnNode next = getNext(ordered, result, jumpAnalysis, ain, 4); + int count = 0; + search: + while(true) + { + boolean found = false; + LabelNode lbl = null; + while(next != NO_INSN) + { + if(next instanceof LabelNode) + { + int jumpCount = 0; + outer: + for(Entry, List>>> entry + : jumpAnalysis.labels.entrySet()) + for(Triple triple : entry.getValue().getValue()) + if(triple.getLeft() == next) + { + jumpCount++; + if(jumpCount > 1) + break outer; + } + if(jumpCount > 1) + break; + } + if(Utils.getIntValue(next) == -1 + && getNext(ordered, result, jumpAnalysis, next, 1).getOpcode() == Opcodes.ISTORE + && getNext(ordered, result, jumpAnalysis, next, 2).getOpcode() == Opcodes.LDC + && ((LdcInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).cst.equals("0") + && getNext(ordered, result, jumpAnalysis, next, 3).getOpcode() == Opcodes.IINC + && ((IincInsnNode)getNext(ordered, result, jumpAnalysis, next, 3)).incr == 1 + && ((IincInsnNode)getNext(ordered, result, jumpAnalysis, next, 3)).var == ((VarInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).var + && getNext(ordered, result, jumpAnalysis, next, 4).getOpcode() == Opcodes.ASTORE) + break; + if(next.getOpcode() == Opcodes.ALOAD && ((VarInsnNode)next).var == var2Index + && getNext(ordered, result, jumpAnalysis, next, 1).getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).name.equals("parseInt") + && ((MethodInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).owner.equals("java/lang/Integer") + && getNext(ordered, result, jumpAnalysis, next, 2) != null + && getNext(ordered, result, jumpAnalysis, next, 2).getOpcode() == Opcodes.TABLESWITCH + && ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).min == 0 + && ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).labels.size() == 1) + { + if(!method.instructions.contains(next)) + break search; + if(remove.contains(next)) + break search; + remove.add(next); + remove.add(getNext(ordered, result, jumpAnalysis, next, 1)); + found = true; + lbl = var2Value == 0 ? ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).labels.get(0) : + ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).dflt; + replace.put(getNext(ordered, result, jumpAnalysis, next, 2), new JumpInsnNode(Opcodes.GOTO, lbl)); + next = getNext(ordered, result, jumpAnalysis, lbl, 1, false); + break; + }else if(next.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)next).var == var1Index + && getNext(ordered, result, jumpAnalysis, next, 1).getOpcode() == Opcodes.TABLESWITCH + && ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).min == 0 + && ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).labels.size() == 1) + { + if(!method.instructions.contains(next)) + break search; + if(remove.contains(next)) + break search; + remove.add(next); + found = true; + lbl = var1Value == 0 ? ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).labels.get(0) : + ((TableSwitchInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).dflt; + replace.put(getNext(ordered, result, jumpAnalysis, next, 1), new JumpInsnNode(Opcodes.GOTO, lbl)); + next = getNext(ordered, result, jumpAnalysis, lbl, 1, false); + break; + } + next = getNext(ordered, result, jumpAnalysis, next, 1, false); + } + if(!found) + break; + count++; + boolean found2 = false; + while(next != NO_INSN) + { + if(next instanceof LabelNode) + { + int jumpCount = 0; + outer: + for(Entry, List>>> entry + : jumpAnalysis.labels.entrySet()) + if(ordered.contains(entry.getKey())) + for(Triple triple : entry.getValue().getValue()) + if(triple.getLeft() == next) + { + jumpCount++; + if(jumpCount > 1) + break outer; + } + if(jumpCount > 1) + break; + } + if(next.getOpcode() == Opcodes.IINC && ((IincInsnNode)next).var == var1Index + && getNext(ordered, result, jumpAnalysis, next, 1).getOpcode() == Opcodes.LDC + && getNext(ordered, result, jumpAnalysis, next, 2).getOpcode() == Opcodes.ASTORE + && ((VarInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).var == var2Index) + { + if(!method.instructions.contains(next)) + break search; + if(remove.contains(next)) + break search; + remove.add(next); + remove.add(getNext(ordered, result, jumpAnalysis, next, 1)); + remove.add(getNext(ordered, result, jumpAnalysis, next, 2)); + found2 = true; + var1Value += ((IincInsnNode)next).incr; + var2Value = Integer.parseInt((String)((LdcInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).cst); + if(getNext(ordered, result, jumpAnalysis, next, 3, false).getOpcode() == Opcodes.GOTO) + { + LabelNode lb = ((JumpInsnNode)getNext(ordered, result, jumpAnalysis, next, 3, false)).label; + next = getNext(ordered, result, jumpAnalysis, lb, 1, false); + }else if(getNext(ordered, result, jumpAnalysis, next, 3, false) instanceof LabelNode) + next = getNext(ordered, result, jumpAnalysis, next, 4, false); + break; + }else if(Utils.isInteger(next) + && getNext(ordered, result, jumpAnalysis, next, 1).getOpcode() == Opcodes.ISTORE + && ((VarInsnNode)getNext(ordered, result, jumpAnalysis, next, 1)).var == var1Index + && getNext(ordered, result, jumpAnalysis, next, 2).getOpcode() == Opcodes.LDC + && getNext(ordered, result, jumpAnalysis, next, 3).getOpcode() == Opcodes.ASTORE + && ((VarInsnNode)getNext(ordered, result, jumpAnalysis, next, 3)).var == var2Index) + { + if(!method.instructions.contains(next)) + break search; + if(remove.contains(next)) + break search; + remove.add(next); + remove.add(getNext(ordered, result, jumpAnalysis, next, 1)); + remove.add(getNext(ordered, result, jumpAnalysis, next, 2)); + remove.add(getNext(ordered, result, jumpAnalysis, next, 3)); + found2 = true; + var1Value = Utils.getIntValue(next); + var2Value = Integer.parseInt((String)((LdcInsnNode)getNext(ordered, result, jumpAnalysis, next, 2)).cst); + AbstractInsnNode varPoint = getNext(ordered, result, jumpAnalysis, next, 3); + if(getNext(ordered, result, jumpAnalysis, varPoint, 1, false).getOpcode() == Opcodes.GOTO) + { + LabelNode lb = ((JumpInsnNode)getNext(ordered, result, jumpAnalysis, varPoint, 1, false)).label; + next = getNext(ordered, result, jumpAnalysis, lb, 1, false); + }else if(getNext(ordered, result, jumpAnalysis, varPoint, 1, false) instanceof LabelNode) + next = getNext(ordered, result, jumpAnalysis, varPoint, 2, false); + break; + } + next = getNext(ordered, result, jumpAnalysis, next, 1, false); + } + if(!found2) + continue loop; + } + + if(count > 0) + { + for(AbstractInsnNode a : remove) + method.instructions.remove(a); + for(Entry en : replace.entrySet()) + method.instructions.set(en.getKey(), en.getValue()); + counter.incrementAndGet(); + } + } + } + } + System.out.println("[DashO] [FlowObfuscationTransformer] Suspected " + suspect.get() + " flow obfuscated chunks"); System.out.println("[DashO] [FlowObfuscationTransformer] Removed " + counter.get() + " flow obfuscated chunks"); System.out.println("[DashO] [FlowObfuscationTransformer] Done"); return counter.get() > 0; - } + } + + private AbstractInsnNode getNext(List list, + LinkedHashMap> flow, FlowAnalyzer.Result result, AbstractInsnNode ain, int count) + { + return getNext(list, flow, result, ain, count, true); + } + + private AbstractInsnNode getNext(List list, + LinkedHashMap> flow, FlowAnalyzer.Result result, AbstractInsnNode ain, int count, boolean doJump) + { + int index = list.indexOf(ain); + for(int i = index + 1; i < list.size();) + { + AbstractInsnNode here = list.get(i); + if(here.getOpcode() == Opcodes.GOTO && doJump) + { + if(i + 1 >= list.size()) + return NO_INSN; + //Note: Should be safe if there is one jump (will be caught otherwise) + LabelNode next = ((JumpInsnNode)here).label; + int idx = list.indexOf(next); + if(idx == -1) + return NO_INSN; + int diff = idx - i; + here = next; + index += diff; + i = idx; + } + if(here instanceof LabelNode && doJump) + { + if(!verifyJumps(flow, result, here)) + return NO_INSN; + index++; + i++; + continue; + } + if(i == index + count) + return here; + else + i++; + } + return NO_INSN; + } + + private boolean verifyJumps(LinkedHashMap> flow, FlowAnalyzer.Result result, AbstractInsnNode lbl) + { + int jumpCount = 0; + outer: + for(Entry, List>>> entry + : result.labels.entrySet()) + if(flow.containsKey(entry.getKey())) + for(Triple triple : entry.getValue().getValue()) + if(triple.getLeft() == lbl) + { + jumpCount++; + if(jumpCount > 1) + break outer; + } + return jumpCount <= 1; + } } diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/peephole/LabelRearranger.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/peephole/LabelRearranger.java new file mode 100644 index 00000000..59120096 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/peephole/LabelRearranger.java @@ -0,0 +1,948 @@ +package com.javadeobfuscator.deobfuscator.transformers.general.peephole; + +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiFunction; + +import org.apache.commons.lang3.tuple.Triple; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer; +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer.JumpData; +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.transformers.special.TryCatchFixer.TryCatchChain; + +public class LabelRearranger extends Transformer +{ + public static BiFunction matchingFunc = null; + + @Override + public boolean transform() throws Throwable + { + AtomicInteger counter = new AtomicInteger(); + for(ClassNode classNode : classNodes()) + methodloop: + for(MethodNode method : classNode.methods) + { + if(matchingFunc != null && !matchingFunc.apply(classNode, method)) + continue; + if(method.localVariables != null && method.localVariables.size() > 0) + continue; + if(method.instructions.size() == 0) + continue; + FlowAnalyzer analyzer = new FlowAnalyzer(method); + FlowAnalyzer.Result result = analyzer.analyze(); + LinkedHashMap> initialflowAnalysis = analyzer.analyze(method.instructions.getFirst(), + new ArrayList<>(), new HashMap<>(), true, true); + List addGotos = new ArrayList<>(); + int index = 0; + for(Entry, List>>> res : result.labels.entrySet()) + { + boolean sureJumpReached = false; + for(AbstractInsnNode ain : res.getValue().getKey()) + if(ain.getOpcode() == Opcodes.GOTO || ain.getOpcode() == Opcodes.TABLESWITCH + || ain.getOpcode() == Opcodes.LOOKUPSWITCH + || (ain.getOpcode() >= Opcodes.IRETURN && ain.getOpcode() <= Opcodes.RETURN) + || ain.getOpcode() == Opcodes.ATHROW) + { + sureJumpReached = true; + break; + } + if(!sureJumpReached) + { + if(index == result.labels.size() - 1) + { + if(initialflowAnalysis.containsKey(res.getKey())) + //Falling off code? + continue methodloop; + }else + addGotos.add(res.getKey()); + } + index++; + } + + List, List>>>> reverse = + new ArrayList<>(result.labels.entrySet()); + Collections.reverse(reverse); + LabelNode previous = null; + for(Entry, List>>> res : reverse) + { + if(addGotos.contains(res.getKey())) + { + if(previous == null) + throw new RuntimeException("No label after?"); + JumpInsnNode gotoJump = new JumpInsnNode(Opcodes.GOTO, previous); + res.getValue().getKey().add(res.getValue().getKey().size(), gotoJump); + if(res.getValue().getKey().size() > 1) + method.instructions.insert(res.getValue().getKey().get(res.getValue().getKey().size() - 2), + gotoJump); + else if(res.getKey() != FlowAnalyzer.ABSENT) + method.instructions.insert(res.getKey(), gotoJump); + else + method.instructions.insert(gotoJump); + } + previous = res.getKey(); + } + LinkedHashMap> flowAnalysis = analyzer.analyze(method.instructions.getFirst(), + Arrays.asList(), new HashMap<>(), false, true); + result = analyzer.analyze(); + + List blocks = new ArrayList<>(); + List orderedBlocks = new ArrayList<>(); + Map blockToLabel = new HashMap<>(); + Map labelToBlock = new HashMap<>(); + + for(Entry, List>>> res : result.labels.entrySet()) + { + Block block = new Block(res.getKey(), res.getValue().getKey()); + blockToLabel.put(block, res.getKey()); + labelToBlock.put(res.getKey(), block); + blocks.add(block); + } + + //Begin with first block + List> jumpQueue = new ArrayList<>(); + //The LabelNodes that can be written in the try-catch handler + List excluded = new ArrayList<>(); + AbstractInsnNode lastGoto = null; + LabelNode next = null; + + Block first = blocks.get(0); + orderedBlocks.add(first); + for(AbstractInsnNode ain : first.instructions) + { + if(ain instanceof TableSwitchInsnNode) + { + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((TableSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((TableSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof LookupSwitchInsnNode) + { + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((LookupSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((LookupSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof JumpInsnNode && ain.getOpcode() != Opcodes.GOTO) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((JumpInsnNode)ain).label, ain)); + else if((ain.getOpcode() >= Opcodes.IRETURN && ain.getOpcode() <= Opcodes.RETURN) + || ain.getOpcode() == Opcodes.ATHROW) + break; + else if(ain.getOpcode() == Opcodes.GOTO) + { + next = ((JumpInsnNode)ain).label; + lastGoto = ain; + break; + } + } + blocks.remove(0); + Block lastPlaced = first; + while(next != null || !jumpQueue.isEmpty()) + { + boolean placeGoto = false; + //Determine exceptions + if(next != null) + { + //Place block if it's unreachable when all jump statements removed + Map> switchBreaks = new HashMap<>(); + List breaks = new ArrayList<>(); + LabelNode label = FlowAnalyzer.ABSENT; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof LabelNode) + label = (LabelNode)ain; + else if(orderedBlocks.contains(labelToBlock.get(label))) + if(ain instanceof JumpInsnNode && ((JumpInsnNode)ain).label == next) + breaks.add(ain); + else if((ain instanceof TableSwitchInsnNode + && (((TableSwitchInsnNode)ain).labels.contains(next) + || ((TableSwitchInsnNode)ain).dflt == next)) + || (ain instanceof LookupSwitchInsnNode + && (((LookupSwitchInsnNode)ain).labels.contains(next) + || ((LookupSwitchInsnNode)ain).dflt == next))) + { + switchBreaks.putIfAbsent(ain, new ArrayList<>()); + switchBreaks.get(ain).add(label); + } + breaks.add(next); + boolean unreachable = true; + LinkedHashMap> reached = + analyzer.analyze(method.instructions.getFirst(), breaks, switchBreaks, true, false); + if(reached.containsKey(next)) + unreachable = false; + if(!unreachable) + tcbn: + for(TryCatchBlockNode tcbn : method.tryCatchBlocks) + { + LinkedHashMap> trycatchAnalysis = + analyzer.analyze(tcbn.handler, Arrays.asList(), new HashMap<>(), false, false); + for(Entry> en : trycatchAnalysis.entrySet()) + if(en.getKey() == next) + { + excluded.add(next); + break tcbn; + } + } + else + placeGoto = true; + int jumpQueueIndex = -1; + for(int i = 0; i < jumpQueue.size(); i++) + { + AbstractInsnNode cause = jumpQueue.get(i).getValue(); + if(cause.getOpcode() == Opcodes.GOTO) + continue; + jumpQueueIndex = i; + break; + } + if(placeGoto && jumpQueueIndex >= 0 && jumpQueue.get(jumpQueueIndex).getValue() instanceof JumpInsnNode) + { + //If jump site is a loop, we put the conditional block first + LinkedHashMap> reached1 = + analyzer.analyze(jumpQueue.get(jumpQueueIndex).getKey(), Arrays.asList(lastGoto, next), new HashMap<>(), true, true); + if(!reached1.containsKey(next) && reached1.get(lastPlaced.label) != null + && reached1.get(lastPlaced.label).contains(lastGoto)) + { + LinkedHashMap> res2 = analyzer.analyze(method.instructions.getFirst(), + Arrays.asList(lastGoto), new HashMap<>(), false, true); + boolean intersects = false; + for(LabelNode reach : reached1.keySet()) + if(reach != lastPlaced.label && res2.containsKey(reach)) + { + intersects = true; + break; + } + if(!intersects) + { + placeGoto = false; + jumpQueue.add(jumpQueueIndex + 1, new AbstractMap.SimpleEntry<>(next, lastGoto)); + } + } + //Check to see if jumpQueue intersects with jump + if(placeGoto && reached1.containsKey(next)) + { + LinkedHashMap> reachedWithoutJump = + analyzer.analyze(method.instructions.getFirst(), Arrays.asList(jumpQueue.get(jumpQueueIndex).getKey()), + new HashMap<>(), false, true); + boolean found = false; + find: + for(Entry> entry1 : reachedWithoutJump.entrySet()) + for(AbstractInsnNode a1 : entry1.getValue()) + { + for(Entry> entry2 : reached1.entrySet()) + for(AbstractInsnNode a2 : entry2.getValue()) + if(a2 == a1) + { + found = true; + break find; + } + } + if(!found) + { + placeGoto = false; + jumpQueue.add(jumpQueueIndex + 1, new AbstractMap.SimpleEntry<>(next, lastGoto)); + jumpQueue.add(0, jumpQueue.remove(jumpQueueIndex)); + } + } + } + } + + List> placeAfter = new ArrayList<>(); + while(!placeGoto && !jumpQueue.isEmpty() && jumpQueue.get(0).getValue() instanceof JumpInsnNode) + { + //Place jump if it's unreachable when all jump statements removed + AbstractInsnNode jump = jumpQueue.get(0).getKey(); + Map> switchBreaks = new HashMap<>(); + List breaks = new ArrayList<>(); + LabelNode label = FlowAnalyzer.ABSENT; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof LabelNode) + label = (LabelNode)ain; + else if(orderedBlocks.contains(labelToBlock.get(label))) + if(ain instanceof JumpInsnNode && ((JumpInsnNode)ain).label == jump) + breaks.add(ain); + else if((ain instanceof TableSwitchInsnNode + && (((TableSwitchInsnNode)ain).labels.contains(jump) + || ((TableSwitchInsnNode)ain).dflt == jump)) + || (ain instanceof LookupSwitchInsnNode + && (((LookupSwitchInsnNode)ain).labels.contains(jump) + || ((LookupSwitchInsnNode)ain).dflt == jump))) + { + switchBreaks.putIfAbsent(ain, new ArrayList<>()); + switchBreaks.get(ain).add(label); + } + breaks.add(jump); + boolean unreachable = true; + LinkedHashMap> reached = + analyzer.analyze(method.instructions.getFirst(), breaks, switchBreaks, true, false); + if(reached.containsKey(jump)) + unreachable = false; + if(!unreachable) + placeAfter.add(jumpQueue.remove(0)); + else + break; + } + if(!placeGoto && jumpQueue.isEmpty()) + { + if(excluded.contains(next)) + break; + else + { + if(next == null) + { + boolean shouldBreak = true; + for(Entry entry : placeAfter) + if(!orderedBlocks.contains(labelToBlock.get(entry.getKey()))) + { + shouldBreak = false; + break; + } + if(shouldBreak) + break; + } + System.out.println("Unknown pattern at method " + method.name + method.desc + ", class " + classNode.name); + continue methodloop; + } + } + if(!placeGoto) + jumpQueue.addAll(1, placeAfter); + Block block; + if(placeGoto) + block = labelToBlock.get(next); + else + block = labelToBlock.get(jumpQueue.get(0).getKey()); + next = null; + jumpQueue.removeIf((lbl) -> block.label == lbl.getKey()); + if(orderedBlocks.contains(block)) + continue; + orderedBlocks.add(block); + blocks.remove(block); + lastPlaced = block; + for(AbstractInsnNode ain : block.instructions) + { + if(ain instanceof TableSwitchInsnNode) + { + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((TableSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((TableSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof LookupSwitchInsnNode) + { + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((LookupSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((LookupSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof JumpInsnNode && ain.getOpcode() != Opcodes.GOTO) + jumpQueue.add(0, new AbstractMap.SimpleEntry<>(((JumpInsnNode)ain).label, ain)); + else if((ain.getOpcode() >= Opcodes.IRETURN && ain.getOpcode() <= Opcodes.RETURN) + || ain.getOpcode() == Opcodes.ATHROW) + break; + else if(ain.getOpcode() == Opcodes.GOTO) + { + next = ((JumpInsnNode)ain).label; + lastGoto = ain; + break; + } + } + } + //Write exception handlers + boolean modified; + do + { + modified = false; + List inlined = new ArrayList<>(); + for(int i = orderedBlocks.size() - 1; i >= 0; i--) + { + Block blockNow = orderedBlocks.get(i); + LabelNode now = blockNow.label; + if(!result.trycatchMap.containsKey(now)) + continue; + List exceptions = new ArrayList<>(result.trycatchMap.get(now)); + List addAfter = new ArrayList<>(); + Collections.reverse(exceptions); + for(TryCatchBlockNode exception : exceptions) + if(!inlined.contains(exception)) + { + inlined.add(exception); + Block handler = labelToBlock.get(exception.handler); + if(!orderedBlocks.contains(handler) && !addAfter.contains(handler)) + { + modified = true; + List> jumpQueue1 = new ArrayList<>(); + AbstractInsnNode lastGoto1 = null; + LabelNode next1 = null; + + addAfter.add(handler); + for(AbstractInsnNode ain : handler.instructions) + { + if(ain instanceof TableSwitchInsnNode) + { + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((TableSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((TableSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof LookupSwitchInsnNode) + { + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((LookupSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((LookupSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof JumpInsnNode && ain.getOpcode() != Opcodes.GOTO) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((JumpInsnNode)ain).label, ain)); + else if((ain.getOpcode() >= Opcodes.IRETURN && ain.getOpcode() <= Opcodes.RETURN) + || ain.getOpcode() == Opcodes.ATHROW) + break; + else if(ain.getOpcode() == Opcodes.GOTO) + { + next1 = ((JumpInsnNode)ain).label; + lastGoto1 = ain; + break; + } + } + blocks.remove(handler); + Block lastPlaced1 = first; + while(next1 != null || !jumpQueue1.isEmpty()) + { + boolean placeGoto = next1 != null; + //Determine exceptions + if(next1 != null) + { + //Place block if it's unreachable when all jump statements removed + Map> switchBreaks = new HashMap<>(); + List breaks = new ArrayList<>(); + LabelNode label = FlowAnalyzer.ABSENT; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof LabelNode) + label = (LabelNode)ain; + else if(orderedBlocks.contains(labelToBlock.get(label))) + if(ain instanceof JumpInsnNode && ((JumpInsnNode)ain).label == next1) + breaks.add(ain); + else if((ain instanceof TableSwitchInsnNode + && (((TableSwitchInsnNode)ain).labels.contains(next1) + || ((TableSwitchInsnNode)ain).dflt == next1)) + || (ain instanceof LookupSwitchInsnNode + && (((LookupSwitchInsnNode)ain).labels.contains(next1) + || ((LookupSwitchInsnNode)ain).dflt == next1))) + { + switchBreaks.putIfAbsent(ain, new ArrayList<>()); + switchBreaks.get(ain).add(label); + } + breaks.add(next1); + boolean unreachable = true; + LinkedHashMap> reached = + analyzer.analyze(method.instructions.getFirst(), breaks, switchBreaks, true, false); + if(reached.containsKey(next1)) + unreachable = false; + if(unreachable) + placeGoto = true; + + int jumpQueueIndex = -1; + for(int i1 = 0; i1 < jumpQueue.size(); i1++) + { + AbstractInsnNode cause = jumpQueue.get(i1).getValue(); + if(cause.getOpcode() == Opcodes.GOTO) + continue; + jumpQueueIndex = i1; + break; + } + if(placeGoto && jumpQueueIndex >= 0 && jumpQueue1.get(jumpQueueIndex).getValue() instanceof JumpInsnNode) + { + //If jump site is a loop, we put the conditional block first + LinkedHashMap> reached1 = + analyzer.analyze(jumpQueue1.get(jumpQueueIndex).getKey(), Arrays.asList(lastGoto1, next1), + new HashMap<>(), true, true); + if(!reached1.containsKey(next1) && reached1.get(lastPlaced1.label) != null + && reached1.get(lastPlaced1.label).contains(lastGoto1)) + { + LinkedHashMap> res2 = + analyzer.analyze(method.instructions.getFirst(), + Arrays.asList(lastGoto1), new HashMap<>(), false, true); + boolean intersects = false; + for(LabelNode reach : reached1.keySet()) + if(reach != lastPlaced1.label && res2.containsKey(reach)) + { + intersects = true; + break; + } + if(!intersects) + { + placeGoto = false; + jumpQueue1.add(1, new AbstractMap.SimpleEntry<>(next1, lastGoto1)); + } + } + //Check to see if jumpQueue intersects with jump + if(placeGoto && reached1.containsKey(next)) + { + placeGoto = false; + jumpQueue1.add(jumpQueueIndex + 1, new AbstractMap.SimpleEntry<>(next1, lastGoto1)); + jumpQueue1.add(0, jumpQueue1.remove(jumpQueueIndex)); + } + } + } + + List> placeAfter = new ArrayList<>(); + while(!placeGoto && !jumpQueue1.isEmpty() && jumpQueue1.get(0).getValue() instanceof JumpInsnNode) + { + //Place jump if it's unreachable when all jump statements removed + AbstractInsnNode jump = jumpQueue1.get(0).getKey(); + Map> switchBreaks = new HashMap<>(); + List breaks = new ArrayList<>(); + LabelNode label = FlowAnalyzer.ABSENT; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof LabelNode) + label = (LabelNode)ain; + else if(orderedBlocks.contains(labelToBlock.get(label))) + if(ain instanceof JumpInsnNode && ((JumpInsnNode)ain).label == jump) + breaks.add(ain); + else if((ain instanceof TableSwitchInsnNode + && (((TableSwitchInsnNode)ain).labels.contains(jump) + || ((TableSwitchInsnNode)ain).dflt == jump)) + || (ain instanceof LookupSwitchInsnNode + && (((LookupSwitchInsnNode)ain).labels.contains(jump) + || ((LookupSwitchInsnNode)ain).dflt == jump))) + { + switchBreaks.putIfAbsent(ain, new ArrayList<>()); + switchBreaks.get(ain).add(label); + } + breaks.add(jump); + boolean unreachable = true; + LinkedHashMap> reached = + analyzer.analyze(method.instructions.getFirst(), breaks, switchBreaks, true, false); + if(reached.containsKey(jump)) + unreachable = false; + if(!unreachable) + placeAfter.add(jumpQueue1.remove(0)); + else + break; + } + if(!placeGoto && jumpQueue1.isEmpty()) + { + if(next1 == null) + { + boolean shouldBreak = true; + for(Entry entry : placeAfter) + if(orderedBlocks.contains(labelToBlock.get(entry.getKey()))) + { + shouldBreak = false; + break; + } + if(shouldBreak) + break; + } + System.out.println("Unknown pattern at method " + method.name + method.desc + ", class " + classNode.name); + continue methodloop; + } + if(!placeGoto) + jumpQueue1.addAll(1, placeAfter); + Block block; + if(placeGoto) + block = labelToBlock.get(next1); + else + block = labelToBlock.get(jumpQueue1.get(0).getKey()); + next1 = null; + jumpQueue1.removeIf((lbl) -> block.label == lbl.getKey()); + if(orderedBlocks.contains(block) || addAfter.contains(block)) + continue; + addAfter.add(block); + blocks.remove(block); + lastPlaced1 = block; + for(AbstractInsnNode ain : block.instructions) + { + if(ain instanceof TableSwitchInsnNode) + { + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((TableSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((TableSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof LookupSwitchInsnNode) + { + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((LookupSwitchInsnNode)ain).dflt, ain)); + List switches = new ArrayList<>(((LookupSwitchInsnNode)ain).labels); + Collections.reverse(switches); + for(LabelNode label : switches) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(label, ain)); + break; + }else if(ain instanceof JumpInsnNode && ain.getOpcode() != Opcodes.GOTO) + jumpQueue1.add(0, new AbstractMap.SimpleEntry<>(((JumpInsnNode)ain).label, ain)); + else if((ain.getOpcode() >= Opcodes.IRETURN && ain.getOpcode() <= Opcodes.RETURN) + || ain.getOpcode() == Opcodes.ATHROW) + break; + else if(ain.getOpcode() == Opcodes.GOTO) + { + next1 = ((JumpInsnNode)ain).label; + lastGoto1 = ain; + break; + } + } + } + } + } + if(orderedBlocks.indexOf(blockNow) + 2 > orderedBlocks.size()) + orderedBlocks.add(new Block(new LabelNode(), new ArrayList<>())); + orderedBlocks.addAll(orderedBlocks.indexOf(blockNow) + 2, addAfter); + } + }while(modified); + //Write + for(AbstractInsnNode ain : method.instructions.toArray()) + method.instructions.remove(ain); + for(Block block : orderedBlocks) + { + LabelNode label = block.label; + if(label != FlowAnalyzer.ABSENT) + method.instructions.add(label); + for(AbstractInsnNode ain : block.instructions) + method.instructions.add(ain); + } + //Recalcuate exception blocks + LinkedHashMap> resNow = new LinkedHashMap<>(); + for(Block b : orderedBlocks) + if(result.trycatchMap.containsKey(b.label)) + resNow.put(b.label, result.trycatchMap.get(b.label)); + else + resNow.put(b.label, new ArrayList<>()); + List chains = new ArrayList<>(); + Map> pass = new HashMap<>(); + List labels = new ArrayList<>(resNow.keySet()); + for(Entry> entry : resNow.entrySet()) + for(TryCatchBlockNode tcbn : entry.getValue()) + if(!pass.containsKey(entry.getKey()) || !pass.get(entry.getKey()).contains(tcbn)) + { + TryCatchChain chain = new TryCatchChain(tcbn.handler, tcbn.type, + tcbn.visibleTypeAnnotations, tcbn.visibleTypeAnnotations); + chains.add(chain); + chain.covered.add(entry.getKey()); + if(labels.indexOf(entry.getKey()) + 1 >= labels.size()) + { + LabelNode lbl = new LabelNode(); + labels.add(lbl); + method.instructions.add(lbl); + } + chain.end = labels.get(labels.indexOf(entry.getKey()) + 1); + pass.putIfAbsent(entry.getKey(), new ArrayList<>()); + pass.get(entry.getKey()).add(tcbn); + for(int i = labels.indexOf(entry.getKey()) + 1; i < labels.size(); i++) + { + List list = resNow.get(labels.get(i)); + boolean found = false; + for(TryCatchBlockNode tcbn2 : list) + if(tcbn.handler.equals(tcbn2.handler) + && ((tcbn.type == null && tcbn2.type == null) || tcbn.type.equals(tcbn2.type)) + && ((tcbn.visibleTypeAnnotations == null && tcbn2.visibleTypeAnnotations == null) + || tcbn.visibleTypeAnnotations.equals(tcbn2.visibleTypeAnnotations)) + && ((tcbn.invisibleTypeAnnotations == null && tcbn2.invisibleTypeAnnotations == null) + || tcbn.invisibleTypeAnnotations.equals(tcbn2.invisibleTypeAnnotations))) + { + chain.covered.add(labels.get(i)); + if(i + 1 >= labels.size()) + { + LabelNode lbl = new LabelNode(); + labels.add(lbl); + method.instructions.add(lbl); + } + chain.end = labels.get(i + 1); + pass.putIfAbsent(labels.get(i), new ArrayList<>()); + pass.get(labels.get(i)).add(tcbn2); + found = true; + break; + } + if(!found) + break; + } + } + Map> splits = new HashMap<>(); + for(int i = 0; i < chains.size(); i++) + for(int i2 = i + 1; i2 < chains.size(); i2++) + { + TryCatchChain chain1 = chains.get(i); + TryCatchChain chain2 = chains.get(i2); + LabelNode start = labels.indexOf(chain1.covered.get(0)) > labels.indexOf(chain2.covered.get(0)) + ? chain1.covered.get(0) : chain2.covered.get(0); + LabelNode end = labels.indexOf(chain1.end) > labels.indexOf(chain2.end) + ? chain2.end : chain1.end; + if(labels.indexOf(start) >= labels.indexOf(end)) + continue; + int index1 = -1; + for(int ii = 0; ii < resNow.get(start).size(); ii++) + { + TryCatchBlockNode tcbn = resNow.get(start).get(ii); + if(tcbn.handler.equals(chain1.handler) + && ((tcbn.type == null && chain1.type == null) || tcbn.type.equals(chain1.type)) + && ((tcbn.visibleTypeAnnotations == null && chain1.visibleTypeAnnotations == null) + || tcbn.visibleTypeAnnotations.equals(chain1.visibleTypeAnnotations)) + && ((tcbn.invisibleTypeAnnotations == null && chain1.invisibleTypeAnnotations == null) + || tcbn.invisibleTypeAnnotations.equals(chain1.invisibleTypeAnnotations))) + { + index1 = ii; + break; + } + } + int index2 = -1; + for(int ii = 0; ii < resNow.get(start).size(); ii++) + { + TryCatchBlockNode tcbn = resNow.get(start).get(ii); + if(tcbn.handler.equals(chain2.handler) + && ((tcbn.type == null && chain2.type == null) || tcbn.type.equals(chain2.type)) + && ((tcbn.visibleTypeAnnotations == null && chain2.visibleTypeAnnotations == null) + || tcbn.visibleTypeAnnotations.equals(chain2.visibleTypeAnnotations)) + && ((tcbn.invisibleTypeAnnotations == null && chain2.invisibleTypeAnnotations == null) + || tcbn.invisibleTypeAnnotations.equals(chain2.invisibleTypeAnnotations))) + { + index2 = ii; + break; + } + } + boolean oneOnTop = index1 > index2; + index1 = -1; + index2 = -1; + for(int ii = labels.indexOf(start); ii < labels.indexOf(end); ii++) + { + LabelNode now = labels.get(ii); + for(int iii = 0; iii < resNow.get(now).size(); iii++) + { + TryCatchBlockNode tcbn = resNow.get(now).get(iii); + if(tcbn.handler.equals(chain1.handler) + && ((tcbn.type == null && chain1.type == null) || tcbn.type.equals(chain1.type)) + && ((tcbn.visibleTypeAnnotations == null && chain1.visibleTypeAnnotations == null) + || tcbn.visibleTypeAnnotations.equals(chain1.visibleTypeAnnotations)) + && ((tcbn.invisibleTypeAnnotations == null && chain1.invisibleTypeAnnotations == null) + || tcbn.invisibleTypeAnnotations.equals(chain1.invisibleTypeAnnotations))) + { + index1 = iii; + break; + } + } + for(int iii = 0; iii < resNow.get(now).size(); iii++) + { + TryCatchBlockNode tcbn = resNow.get(now).get(iii); + if(tcbn.handler.equals(chain2.handler) + && ((tcbn.type == null && chain2.type == null) || tcbn.type.equals(chain2.type)) + && ((tcbn.visibleTypeAnnotations == null && chain2.visibleTypeAnnotations == null) + || tcbn.visibleTypeAnnotations.equals(chain2.visibleTypeAnnotations)) + && ((tcbn.invisibleTypeAnnotations == null && chain2.invisibleTypeAnnotations == null) + || tcbn.invisibleTypeAnnotations.equals(chain2.invisibleTypeAnnotations))) + { + index2 = iii; + break; + } + } + boolean oneOnTopTemp = index1 > index2; + if(oneOnTop != oneOnTopTemp) + { + splits.putIfAbsent(chain1, new HashSet<>()); + splits.get(chain1).add(now); + oneOnTop = oneOnTopTemp; + } + } + } + if(splits.size() > 0) + System.out.println("Irregular exception table at " + classNode.name + ", " + method.name + method.desc); + for(Entry> entry : splits.entrySet()) + { + List orderedSplits = new ArrayList<>(entry.getValue()); + orderedSplits.sort(new Comparator() + { + @Override + public int compare(LabelNode l1, LabelNode l2) + { + return Integer.valueOf(labels.indexOf(l1)).compareTo(labels.indexOf(l2)); + } + }); + List replacements = new ArrayList<>(); + replacements.add(entry.getKey()); + for(LabelNode l : orderedSplits) + { + int lIndex = labels.indexOf(l); + TryCatchChain toModify = null; + for(TryCatchChain ch : replacements) + if(labels.indexOf(ch.covered.get(0)) <= lIndex + && labels.indexOf(ch.covered.get(ch.covered.size() - 1)) >= lIndex) + { + toModify = ch; + break; + } + TryCatchChain split1 = new TryCatchChain(toModify.handler, + toModify.type, toModify.visibleTypeAnnotations, + toModify.invisibleTypeAnnotations); + for(LabelNode lbl : toModify.covered) + { + if(lbl == l) + break; + split1.covered.add(lbl); + } + split1.end = l; + TryCatchChain split2 = new TryCatchChain(toModify.handler, + toModify.type, toModify.visibleTypeAnnotations, + toModify.invisibleTypeAnnotations); + for(int iii = toModify.covered.indexOf(l); iii < toModify.covered.size(); iii++) + split2.covered.add(toModify.covered.get(iii)); + split2.end = toModify.end; + int toModifyIndex = replacements.indexOf(toModify); + replacements.set(toModifyIndex, split2); + replacements.add(toModifyIndex, split1); + } + int chainIndex = chains.indexOf(entry.getKey()); + chains.set(chainIndex, replacements.get(replacements.size() - 1)); + replacements.remove(replacements.size() - 1); + chains.addAll(chainIndex, replacements); + } + List exceptions = new ArrayList<>(); + boolean modified1; + do + { + modified1 = false; + TryCatchChain remove = null; + for(TryCatchChain chain : chains) + { + boolean failed = false; + for(LabelNode lbl : chain.covered) + { + List list = resNow.get(lbl); + if(!(!list.isEmpty() && list.get(0).handler.equals(chain.handler) + && ((list.get(0).type == null && chain.type == null) || list.get(0).type.equals(chain.type)) + && ((list.get(0).visibleTypeAnnotations == null && chain.visibleTypeAnnotations == null) + || list.get(0).visibleTypeAnnotations.equals(chain.visibleTypeAnnotations)) + && ((list.get(0).invisibleTypeAnnotations == null && chain.invisibleTypeAnnotations == null) + || list.get(0).invisibleTypeAnnotations.equals(chain.invisibleTypeAnnotations)))) + { + failed = true; + break; + } + } + if(!failed) + { + TryCatchBlockNode tcbn = new TryCatchBlockNode(chain.covered.get(0), chain.end, + chain.handler, chain.type); + tcbn.visibleTypeAnnotations = chain.visibleTypeAnnotations; + tcbn.invisibleTypeAnnotations = tcbn.invisibleTypeAnnotations; + exceptions.add(tcbn); + remove = chain; + for(LabelNode lbl : chain.covered) + resNow.get(lbl).remove(0); + break; + } + } + if(remove != null) + { + modified1 = true; + chains.remove(remove); + } + }while(modified1); + if(chains.size() > 0) + throw new IllegalStateException("Impossible exception table at " + classNode.name + ", " + method.name + method.desc); + + boolean same = true; + if(method.tryCatchBlocks.size() != exceptions.size()) + same = false; + if(same) + for(int i = 0; i < method.tryCatchBlocks.size(); i++) + { + TryCatchBlockNode tcbn1 = method.tryCatchBlocks.get(i); + TryCatchBlockNode tcbn2 = exceptions.get(i); + if(tcbn1.start != tcbn2.start) + same = false; + else if(tcbn1.end != tcbn2.end) + same = false; + else if(tcbn1.handler != tcbn2.handler) + same = false; + else if(!((tcbn1.type == null && tcbn2.type == null) || tcbn1.type.equals(tcbn2.type))) + same = false; + else if(!((tcbn1.invisibleTypeAnnotations == null && tcbn2.invisibleTypeAnnotations == null) + || tcbn1.invisibleTypeAnnotations.equals(tcbn2.invisibleTypeAnnotations))) + same = false; + else if(!((tcbn1.visibleTypeAnnotations == null && tcbn2.visibleTypeAnnotations == null) + || tcbn1.visibleTypeAnnotations.equals(tcbn2.visibleTypeAnnotations))) + same = false; + if(!same) + break; + } + if(!same) + counter.incrementAndGet(); + method.tryCatchBlocks = exceptions; + FlowAnalyzer.Result resAfter = new FlowAnalyzer(method).analyze(); + List notFound = new ArrayList<>(); + for(Entry, List>>> entry + : result.labels.entrySet()) + if(!flowAnalysis.containsKey(entry.getKey())) + notFound.add(entry.getKey()); + for(LabelNode l : notFound) + { + result.labels.remove(l); + resAfter.labels.remove(l); + } + for(Entry, List>>> entry : + result.labels.entrySet()) + { + List> toRemove = new ArrayList<>(); + for(Triple tri : entry.getValue().getValue()) + if(notFound.contains(tri.getLeft())) + toRemove.add(tri); + entry.getValue().getValue().removeAll(toRemove); + } + for(Entry, List>>> entry : + resAfter.labels.entrySet()) + { + List> toRemove = new ArrayList<>(); + for(Triple tri : entry.getValue().getValue()) + if(notFound.contains(tri.getLeft())) + toRemove.add(tri); + entry.getValue().getValue().removeAll(toRemove); + } + int verify = 0; + for(Entry, List>>> entry : + result.labels.entrySet()) + { + if(!resAfter.labels.containsKey(entry.getKey())) + { + verify = 1; + break; + } + List> resultList = entry.getValue().getValue(); + List> resultAfterList = + resAfter.labels.get(entry.getKey()).getValue(); + List one = new ArrayList<>(); + List two = new ArrayList<>(); + for(Triple tri : resultList) + one.add(tri.getLeft()); + for(Triple tri : resultAfterList) + two.add(tri.getLeft()); + if(!new HashSet<>(one).equals(new HashSet<>(two))) + { + verify = 2; + break; + } + } + if(verify == 1) + System.out.println("Lost label while rearranging: " + classNode.name + " " + method.name + method.desc); + else if(verify == 2) + System.out.println("Lost flow while rearranging: " + classNode.name + " " + method.name + method.desc); + } + System.out.println("Rearranged " + counter.get() + " methods"); + return counter.get() > 0; + } + + private class Block + { + public final LabelNode label; + public final List instructions; + + public Block(LabelNode label, List instructions) + { + this.label = label; + this.instructions = instructions; + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/removers/IllegalSignatureRemover.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/removers/IllegalSignatureRemover.java index 6bafc0de..0c2bb58f 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/removers/IllegalSignatureRemover.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/general/removers/IllegalSignatureRemover.java @@ -27,7 +27,7 @@ public boolean transform() throws Throwable { if (classNode.signature != null) { try { CheckClassAdapter.checkClassSignature(classNode.signature); - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ignored) { classNode.signature = null; } } @@ -35,7 +35,7 @@ public boolean transform() throws Throwable { if (methodNode.signature != null) { try { CheckClassAdapter.checkMethodSignature(methodNode.signature); - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ignored) { methodNode.signature = null; } } @@ -44,7 +44,7 @@ public boolean transform() throws Throwable { if (fieldNode.signature != null) { try { CheckClassAdapter.checkFieldSignature(fieldNode.signature); - } catch (IllegalArgumentException ignored) { + } catch (IllegalArgumentException | StringIndexOutOfBoundsException ignored) { fieldNode.signature = null; } } diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/IntermediaryToYarnTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/IntermediaryToYarnTransformer.java new file mode 100644 index 00000000..85425c69 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/IntermediaryToYarnTransformer.java @@ -0,0 +1,666 @@ +package com.javadeobfuscator.deobfuscator.transformers.minecraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.filefilter.RegexFileFilter; +import org.apache.commons.io.filefilter.TrueFileFilter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.exceptions.NoClassInPathException; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.AbstractNormalizer; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.CustomRemapper; + +@TransformerConfig.ConfigOptions(configClass = IntermediaryToYarnTransformer.Config.class) +public class IntermediaryToYarnTransformer extends AbstractNormalizer +{ + @Override + public void remap(CustomRemapper remapper) + { + try + { + getDeobfuscator().assureLoaded("net/minecraft/client/main/Main"); + }catch(NoClassInPathException e) + { + System.out.println("[IntermediaryToYarnTransformer] Obfuscated Minecraft jar not detected, put it as a library for best results!"); + } + File dir = new File(System.getProperty("user.dir")); + File mappings = new File(dir, "mappings"); + if(!mappings.exists()) + { + System.out.println("[NotchToIntermediaryTransformer] You must put the mappings folder in the same directory as deobfuscator.jar!"); + return; + } + + File tiny = new File(dir, "mappings.tiny"); + if(!tiny.exists()) + { + System.out.println("[NotchToIntermediaryTransformer] You must put mappings.tiny in the same folder as deobfuscator.jar!"); + return; + } + + Map notchToIntClass = new HashMap<>(); + + try(BufferedReader reader = new BufferedReader(new FileReader(tiny))) + { + reader.readLine();//Skip first line + String line; + while((line = reader.readLine()) != null) + { + if(line.startsWith("#")) + continue; + String[] map = line.split("\\s+"); + if(map.length < 3) + throw new RuntimeException("Unexpected mapping"); + switch(map[0]) + { + case "CLASS": + if(map.length != 3) + throw new RuntimeException("Unexpected class mapping"); + if(!map[1].equals(map[2])) + notchToIntClass.put(map[1], map[2]); + break; + case "METHOD": + case "FIELD": + break; + default: + throw new RuntimeException("Unexpected mapping " + map[0]); + } + } + }catch(IOException e) + { + System.out.println("[NotchToIntermediaryTransformer] File read failed, are you sure the .tiny file has not been tampered?"); + e.printStackTrace(); + return; + } + + Map classMappings = new HashMap<>(); + Map> fieldMappings = new HashMap<>(); + Map> methodMappings = new HashMap<>(); + + boolean rev = getConfig().isReverse(); + Collection files = FileUtils.listFiles(mappings, new RegexFileFilter("^(.*?)"), + TrueFileFilter.INSTANCE); + for(File file : files) + try(BufferedReader reader = new BufferedReader(new FileReader(file))) + { + String firstLine = reader.readLine(); + String[] firstLineSplit = firstLine.split("\\s+"); + if(firstLineSplit.length != 2 && firstLineSplit.length != 3) + throw new RuntimeException("Invaild class mapping"); + if(!firstLineSplit[0].equals("CLASS")) + throw new RuntimeException("Invaild class mapping"); + String obfName = firstLineSplit[1]; + String deobfName = firstLineSplit.length == 2 ? firstLineSplit[1] : firstLineSplit[2]; + if(!deobfName.equals(obfName)) + classMappings.put(rev ? deobfName : obfName, rev ? obfName : deobfName); + String line; + int prevTab = 0; + while((line = reader.readLine()) != null) + { + int tabs = line.length(); + line = line.trim(); + tabs = tabs - line.length(); + String[] split = line.split("\\s+"); + switch(split[0]) + { + case "METHOD": + if(split.length != 3 && split.length != 4) + throw new RuntimeException("Invaild method mapping"); + if(tabs != prevTab + 1) + throw new RuntimeException("Invaild whitespace"); + if(split.length == 4) + { + String name = rev ? deobfName : obfName; + methodMappings.putIfAbsent(name, new HashMap<>()); + methodMappings.get(name).put(new NodeWrapper(rev ? split[2] : split[1], split[3]), + rev ? split[1] : split[2]); + } + break; + case "FIELD": + if(split.length != 3 && split.length != 4) + throw new RuntimeException("Invaild method mapping"); + if(tabs != prevTab + 1) + throw new RuntimeException("Invaild whitespace"); + if(split.length == 4) + { + String name = rev ? deobfName : obfName; + String desc = split[3]; + if(rev) + { + Type type = Type.getType(desc); + desc = getMappedType(type, classMappings).toString(); + } + fieldMappings.putIfAbsent(name, new HashMap<>()); + fieldMappings.get(name).put(new NodeWrapper(rev ? split[2] : split[1], desc), + rev ? split[1] : split[2]); + } + break; + case "CLASS": + //Handle subclasses + if(split.length != 2 && split.length != 3) + throw new RuntimeException("Invaild class mapping"); + if(prevTab < tabs - 1) + throw new RuntimeException("Invaild class whitespace"); + int indexesBack = prevTab - tabs + 1; + obfName = obfName.substring(0, lastIndexPos(indexesBack, obfName)); + deobfName = deobfName.substring(0, lastIndexPos(indexesBack, deobfName)); + String obfInnerName = split[1]; + String deobfInnerName = split.length == 2 ? split[1] : split[2]; + obfName += "$" + obfInnerName; + deobfName += "$" + deobfInnerName; + if(!obfName.equals(deobfName)) + classMappings.put(rev ? deobfName : obfName, rev ? obfName : deobfName); + prevTab = tabs; + break; + case "ARG": + case "COMMENT": + break; + default: + throw new RuntimeException("Unexpected mapping type " + split[0]); + } + } + }catch(IOException e) + { + System.out.println("[IntermediaryToYarnTransformer] File read failed, are you sure the mapping files have not been tampered?"); + e.printStackTrace(); + return; + } + //Fix descs when we are reversing + if(rev) + { + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + { + String desc = entry2.getKey().desc; + Type[] args = Type.getArgumentTypes(desc); + Type[] newArgs = new Type[args.length]; + Type returnArg = Type.getReturnType(desc); + for(int i = 0; i < args.length; i++) + { + Type t = args[i]; + newArgs[i] = getMappedType(t, classMappings); + } + returnArg = getMappedType(returnArg, classMappings); + entry2.getKey().desc = Type.getMethodDescriptor(returnArg, newArgs); + } + for(Entry> entry : fieldMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + entry2.getKey().desc = getMappedType(Type.getType(entry2.getKey().desc), classMappings).toString(); + } + //This is only useful when we are trying to map classes across packages + remapper.setIgnorePackages(true); + //Remap mixin shadow methods/fields + for(ClassNode classNode : classNodes()) + { + List mixinClasses = new ArrayList<>(); + if(classNode.invisibleAnnotations != null) + for(AnnotationNode annot : classNode.invisibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Mixin;") && annot.values.size() >= 2) + { + int index = -1; + for(int i = 0; i < annot.values.size(); i++) + { + Object o = annot.values.get(i); + if("value".equals(o)) + { + index = i + 1; + break; + } + } + List list = (List)annot.values.get(index); + for(Object o : list) + { + if(!(o instanceof Type)) + continue; + mixinClasses.add(((Type)o).getInternalName()); + } + } + + if(mixinClasses.isEmpty()) + continue; + + List superclasses = new ArrayList<>(); + for(String mixinClass : mixinClasses) + { + //Convert back to intermediate name (rev) + if(rev && classMappings.containsKey(mixinClass)) + mixinClass = classMappings.get(mixinClass); + //Convert back to Notch name + for(Entry entry : notchToIntClass.entrySet()) + if(entry.getValue().equals(mixinClass)) + { + mixinClass = entry.getKey(); + break; + } + + ClassNode mixinClassNode = null; + try + { + mixinClassNode = getDeobfuscator().assureLoaded(mixinClass); + }catch(NoClassInPathException e) + { + System.out.println("[IntermediaryToYarnTransformer] Class not found: " + mixinClasses); + } + + for(ClassNode cn : getSuperClasses(mixinClassNode, rev, classMappings, notchToIntClass)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + if(!superclasses.contains(mixinClassNode)) + superclasses.add(mixinClassNode); + } + + for(MethodNode method : classNode.methods) + { + if(method.name.startsWith("<")) + continue; + boolean isShadow = false; + if(method.visibleAnnotations != null) + for(AnnotationNode annot : method.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(notchToIntClass.containsKey(name)) + name = notchToIntClass.get(name); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(method.name) && entry.getKey().desc.equals(method.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(classNode.name, new HashMap<>()); + methodMappings.get(classNode.name).put(new NodeWrapper(method.name, method.desc), mapped); + } + } + } + for(FieldNode field : classNode.fields) + { + if(field.name.startsWith("<")) + continue; + boolean isShadow = false; + if(field.visibleAnnotations != null) + for(AnnotationNode annot : field.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(notchToIntClass.containsKey(name)) + name = notchToIntClass.get(name); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(field.name) && entry.getKey().desc.equals(field.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(classNode.name, new HashMap<>()); + fieldMappings.get(classNode.name).put(new NodeWrapper(field.name, field.desc), mapped); + } + } + } + } + //Remap subclass calls + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof FieldInsnNode) + { + boolean hasReobfOwner = false; + FieldInsnNode fieldInsn = (FieldInsnNode)ain; + String reobfOwner = fieldInsn.owner; + //Convert back to intermediate name (rev) + if(rev && classMappings.containsKey(reobfOwner)) + reobfOwner = classMappings.get(reobfOwner); + //Convert back to Notch name + for(Entry entry : notchToIntClass.entrySet()) + if(entry.getValue().equals(reobfOwner)) + { + hasReobfOwner = true; + reobfOwner = entry.getKey(); + break; + } + boolean hasMapping = false; + if(fieldMappings.containsKey(fieldInsn.owner)) + for(Entry entry : fieldMappings.get(fieldInsn.owner).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + owner = getDeobfuscator().assureLoaded(reobfOwner); + }catch(NoClassInPathException e) + { + if(hasReobfOwner) + System.out.println("[IntermediaryToYarnTransformer] Class not found: " + reobfOwner); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings, notchToIntClass); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(notchToIntClass.containsKey(name)) + name = notchToIntClass.get(name); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(fieldInsn.owner, new HashMap<>()); + fieldMappings.get(fieldInsn.owner).put(new NodeWrapper(fieldInsn.name, fieldInsn.desc), mapped); + } + } + }else if(ain instanceof MethodInsnNode && !((MethodInsnNode)ain).name.startsWith("<")) + { + boolean hasReobfOwner = false; + MethodInsnNode methodInsn = (MethodInsnNode)ain; + String reobfOwner = methodInsn.owner; + //Convert back to intermediate name (rev) + if(rev && classMappings.containsKey(reobfOwner)) + reobfOwner = classMappings.get(reobfOwner); + //Convert back to Notch name + for(Entry entry : notchToIntClass.entrySet()) + if(entry.getValue().equals(reobfOwner)) + { + hasReobfOwner = true; + reobfOwner = entry.getKey(); + break; + } + boolean hasMapping = false; + if(methodMappings.containsKey(methodInsn.owner)) + for(Entry entry : methodMappings.get(methodInsn.owner).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + owner = getDeobfuscator().assureLoaded(reobfOwner); + }catch(NoClassInPathException e) + { + if(hasReobfOwner) + System.out.println("[IntermediaryToYarnTransformer] Class not found: " + reobfOwner); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings, notchToIntClass); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(notchToIntClass.containsKey(name)) + name = notchToIntClass.get(name); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(methodInsn.owner, new HashMap<>()); + methodMappings.get(methodInsn.owner).put(new NodeWrapper(methodInsn.name, methodInsn.desc), mapped); + } + } + } + for(Entry entry : classMappings.entrySet()) + remapper.map(entry.getKey(), entry.getValue()); + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapMethodName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + for(Entry> entry : fieldMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapFieldName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + //Remap overrides + for(ClassNode classNode : classNodes()) + { + List superclasses = getSuperClasses(classNode, rev, classMappings, notchToIntClass); + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(notchToIntClass.containsKey(name)) + name = notchToIntClass.get(name); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + for(MethodNode method : classNode.methods) + if(method.name.equals(entry.getKey().name) && method.desc.equals(entry.getKey().desc)) + remapper.mapMethodName(classNode.name, method.name, method.desc, entry.getValue(), true); + } + } + } + + private int lastIndexPos(int pos, String s) + { + if(pos <= 0) + return s.length(); + return lastIndexPos(--pos, s.substring(0, s.lastIndexOf("$"))); + } + + /** + * Returns the accessible superclasses. + */ + private List getSuperClasses(ClassNode classNode, boolean rev, Map classMappings, + Map notchToIntClass) + { + List list = new ArrayList<>(); + if(classNode == null) + return list; + if(classNode.superName != null) + { + try + { + String superName = classNode.superName; + //Convert back to intermediate name (rev) + if(rev && classMappings.containsKey(superName)) + superName = classMappings.get(superName); + //Convert back to Notch name + for(Entry entry : notchToIntClass.entrySet()) + if(entry.getValue().equals(superName)) + { + superName = entry.getKey(); + break; + } + ClassNode superClass = getDeobfuscator().assureLoaded(superName); + if(superClass != null) + { + for(ClassNode cn : getSuperClasses(superClass, rev, classMappings, notchToIntClass)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superClass)) + list.add(superClass); + } + }catch(NoClassInPathException e) + { + return list; + } + } + for(String inf : classNode.interfaces) + { + //Convert back to intermediate name (rev) + if(rev && classMappings.containsKey(inf)) + inf = classMappings.get(inf); + //Convert back to Notch name + for(Entry entry : notchToIntClass.entrySet()) + if(entry.getValue().equals(inf)) + { + inf = entry.getKey(); + break; + } + try + { + ClassNode superInterface = getDeobfuscator().assureLoaded(inf); + if(superInterface != null) + { + for(ClassNode cn : getSuperClasses(superInterface, rev, classMappings, notchToIntClass)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superInterface)) + list.add(superInterface); + } + }catch(NoClassInPathException e) + { + return list; + } + } + return list; + } + + private Type getMappedType(Type t, Map classMappings) + { + if(t.getSort() == Type.OBJECT) + { + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(t.getInternalName())) + return Type.getObjectType(mapping.getKey()); + }else if(t.getSort() == Type.ARRAY) + { + int layers = 1; + Type element = t.getElementType(); + while(element.getSort() == Type.ARRAY) + { + element = element.getElementType(); + layers++; + } + if(element.getSort() == Type.OBJECT) + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(element.getInternalName())) + { + String beginning = ""; + for(int i = 0; i < layers; i++) + beginning += "["; + beginning += "L"; + return Type.getType(beginning + mapping.getKey() + ";"); + } + } + return t; + } + + private class NodeWrapper + { + public String name; + public String desc; + + public NodeWrapper(String name, String desc) + { + this.name = name; + this.desc = desc; + } + } + + public static class Config extends AbstractNormalizer.Config + { + /** + * If false, we map Intermediary to Yarn. + * If true, we map Yarn to Intermediary. + */ + private boolean reverse = false; + + public Config() + { + super(IntermediaryToYarnTransformer.class); + } + + public boolean isReverse() + { + return reverse; + } + + public void setReverse(boolean reverse) + { + this.reverse = reverse; + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToIntermediaryTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToIntermediaryTransformer.java new file mode 100644 index 00000000..bf604edb --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToIntermediaryTransformer.java @@ -0,0 +1,565 @@ +package com.javadeobfuscator.deobfuscator.transformers.minecraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.exceptions.NoClassInPathException; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.AbstractNormalizer; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.CustomRemapper; + +@TransformerConfig.ConfigOptions(configClass = NotchToIntermediaryTransformer.Config.class) +public class NotchToIntermediaryTransformer extends AbstractNormalizer +{ + @Override + public void remap(CustomRemapper remapper) + { + try + { + getDeobfuscator().assureLoaded("net/minecraft/client/main/Main"); + }catch(NoClassInPathException e) + { + System.out.println("[NotchToIntermediaryTransformer] Obfuscated Minecraft jar not detected, put it as a library for best results!"); + } + File dir = new File(System.getProperty("user.dir")); + File tiny = new File(dir, "mappings.tiny"); + if(!tiny.exists()) + { + System.out.println("[NotchToIntermediaryTransformer] You must put mappings.tiny in the same folder as deobfuscator.jar!"); + return; + } + + Map classMappings = new HashMap<>(); + Map> fieldMappings = new HashMap<>(); + Map> methodMappings = new HashMap<>(); + + boolean rev = getConfig().isReverse(); + //Read classes first + try(BufferedReader reader = new BufferedReader(new FileReader(tiny))) + { + reader.readLine();//Skip first line + String line; + while((line = reader.readLine()) != null) + { + if(line.startsWith("#")) + continue; + String[] map = line.split("\\s+"); + if(map.length < 3) + throw new RuntimeException("Unexpected mapping"); + if(map[0].equals("CLASS")) + { + if(map.length != 3) + throw new RuntimeException("Unexpected class mapping"); + if(!map[1].equals(map[2])) + classMappings.put(rev ? map[2] : map[1], rev ? map[1] : map[2]); + } + } + }catch(IOException e) + { + System.out.println("[NotchToIntermediaryTransformer] File read failed, are you sure the .tiny file has not been tampered?"); + e.printStackTrace(); + return; + } + try(BufferedReader reader = new BufferedReader(new FileReader(tiny))) + { + reader.readLine();//Skip first line + String line; + while((line = reader.readLine()) != null) + { + if(line.startsWith("#")) + continue; + String[] map = line.split("\\s+"); + if(map.length < 3) + throw new RuntimeException("Unexpected mapping"); + String className; + switch(map[0]) + { + case "CLASS": + break; + case "METHOD": + if(map.length != 5) + throw new RuntimeException("Unexpected method mapping"); + className = map[1]; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(className)) + { + className = entry.getKey(); + break; + } + String methodName = map[3]; + String mappedMethod = map[4]; + if(!methodName.equals(mappedMethod)) + { + methodMappings.putIfAbsent(className, new HashMap<>()); + String desc = map[2]; + if(rev) + { + Type[] args = Type.getArgumentTypes(desc); + Type[] newArgs = new Type[args.length]; + Type returnArg = Type.getReturnType(desc); + for(int i = 0; i < args.length; i++) + { + Type t = args[i]; + newArgs[i] = getMappedType(t, classMappings); + } + returnArg = getMappedType(returnArg, classMappings); + desc = Type.getMethodDescriptor(returnArg, newArgs); + } + methodMappings.get(className).put(new NodeWrapper(rev ? mappedMethod : methodName, + desc), rev ? methodName : mappedMethod); + } + break; + case "FIELD": + if(map.length != 5) + throw new RuntimeException("Unexpected field mapping"); + className = map[1]; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(className)) + { + className = entry.getKey(); + break; + } + String fieldName = map[3]; + String mappedField = map[4]; + String desc = map[2]; + if(rev) + desc = getMappedType(Type.getType(desc), classMappings).toString(); + if(!fieldName.equals(mappedField)) + { + fieldMappings.putIfAbsent(className, new HashMap<>()); + fieldMappings.get(className).put(new NodeWrapper(rev ? mappedField : fieldName, + desc), rev ? fieldName : mappedField); + } + break; + default: + throw new RuntimeException("Unexpected mapping " + map[0]); + } + } + }catch(IOException e) + { + System.out.println("[NotchToIntermediaryTransformer] File read failed, are you sure the .tiny file has not been tampered?"); + e.printStackTrace(); + return; + } + //This is only useful when we are trying to map classes across packages + remapper.setIgnorePackages(true); + //Remap mixin shadow methods/fields + for(ClassNode classNode : classNodes()) + { + List mixinClasses = new ArrayList<>(); + if(classNode.invisibleAnnotations != null) + for(AnnotationNode annot : classNode.invisibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Mixin;") && annot.values.size() >= 2) + { + int index = -1; + for(int i = 0; i < annot.values.size(); i++) + { + Object o = annot.values.get(i); + if("value".equals(o)) + { + index = i + 1; + break; + } + } + List list = (List)annot.values.get(index); + for(Object o : list) + { + if(!(o instanceof Type)) + continue; + mixinClasses.add(((Type)o).getInternalName()); + } + } + + if(mixinClasses.isEmpty()) + continue; + + List superclasses = new ArrayList<>(); + for(String mixinClass : mixinClasses) + { + //Convert back to Notch name + if(rev && classMappings.containsKey(mixinClass)) + mixinClass = classMappings.get(mixinClass); + + ClassNode mixinClassNode = null; + try + { + mixinClassNode = getDeobfuscator().assureLoaded(mixinClass); + }catch(NoClassInPathException e) + { + System.out.println("[NotchToIntermediaryTransformer] Class not found: " + mixinClasses); + } + + for(ClassNode cn : getSuperClasses(mixinClassNode, rev, classMappings)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + if(!superclasses.contains(mixinClassNode)) + superclasses.add(mixinClassNode); + } + + for(ClassNode cn : getSuperClasses(classNode, rev, classMappings)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + + for(MethodNode method : classNode.methods) + { + if(method.name.startsWith("<")) + continue; + boolean isShadow = false; + if(method.visibleAnnotations != null) + for(AnnotationNode annot : method.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(method.name) && entry.getKey().desc.equals(method.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(classNode.name, new HashMap<>()); + methodMappings.get(classNode.name).put(new NodeWrapper(method.name, method.desc), mapped); + } + } + } + for(FieldNode field : classNode.fields) + { + if(field.name.startsWith("<")) + continue; + boolean isShadow = false; + if(field.visibleAnnotations != null) + for(AnnotationNode annot : field.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(field.name) && entry.getKey().desc.equals(field.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(classNode.name, new HashMap<>()); + fieldMappings.get(classNode.name).put(new NodeWrapper(field.name, field.desc), mapped); + } + } + } + } + //Remap subclass calls + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof FieldInsnNode) + { + FieldInsnNode fieldInsn = (FieldInsnNode)ain; + boolean hasMapping = false; + if(fieldMappings.containsKey(fieldInsn.owner)) + for(Entry entry : fieldMappings.get(fieldInsn.owner).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + if(rev && classMappings.containsKey(fieldInsn.owner)) + owner = getDeobfuscator().assureLoaded(classMappings.get(fieldInsn.owner)); + else + owner = getDeobfuscator().assureLoaded(fieldInsn.owner); + }catch(NoClassInPathException e) + { + String ownerName = rev ? classMappings.get(fieldInsn.owner) : fieldInsn.owner; + if(classMappings.containsKey(fieldInsn.owner)) + System.out.println("[NotchToIntermediaryTransformer] Class not found: " + ownerName); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(fieldInsn.owner, new HashMap<>()); + fieldMappings.get(fieldInsn.owner).put(new NodeWrapper(fieldInsn.name, fieldInsn.desc), mapped); + } + } + }else if(ain instanceof MethodInsnNode && !((MethodInsnNode)ain).name.startsWith("<")) + { + MethodInsnNode methodInsn = (MethodInsnNode)ain; + boolean hasMapping = false; + if(methodMappings.containsKey(methodInsn.owner)) + for(Entry entry : methodMappings.get(methodInsn.owner).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + if(rev && classMappings.containsKey(methodInsn.owner)) + owner = getDeobfuscator().assureLoaded(classMappings.get(methodInsn.owner)); + else + owner = getDeobfuscator().assureLoaded(methodInsn.owner); + }catch(NoClassInPathException e) + { + String ownerName = rev ? classMappings.get(methodInsn.owner) : methodInsn.owner; + if(classMappings.containsKey(methodInsn.owner)) + System.out.println("[NotchToIntermediaryTransformer] Class not found: " + ownerName); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(methodInsn.owner, new HashMap<>()); + methodMappings.get(methodInsn.owner).put(new NodeWrapper(methodInsn.name, methodInsn.desc), mapped); + } + } + } + for(Entry entry : classMappings.entrySet()) + remapper.map(entry.getKey(), entry.getValue()); + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapMethodName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + for(Entry> entry : fieldMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapFieldName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + //Remap overrides + for(ClassNode classNode : classNodes()) + { + List superclasses = getSuperClasses(classNode, rev, classMappings); + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + for(MethodNode method : classNode.methods) + if(method.name.equals(entry.getKey().name) && method.desc.equals(entry.getKey().desc)) + remapper.mapMethodName(classNode.name, method.name, method.desc, entry.getValue(), true); + } + } + } + + /** + * Returns the accessible superclasses. + */ + private List getSuperClasses(ClassNode classNode, boolean rev, Map classMappings) + { + List list = new ArrayList<>(); + if(classNode == null) + return list; + if(classNode.superName != null) + { + String superName = classNode.superName; + if(rev && classMappings.containsKey(superName)) + superName = classMappings.get(superName); + try + { + ClassNode superClass = getDeobfuscator().assureLoaded(superName); + if(superClass != null) + { + for(ClassNode cn : getSuperClasses(superClass, rev, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superClass)) + list.add(superClass); + } + }catch(NoClassInPathException e) + { + return list; + } + } + for(String inf : classNode.interfaces) + { + if(rev && classMappings.containsKey(inf)) + inf = classMappings.get(inf); + try + { + ClassNode superInterface = getDeobfuscator().assureLoaded(inf); + if(superInterface != null) + { + for(ClassNode cn : getSuperClasses(superInterface, rev, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superInterface)) + list.add(superInterface); + } + }catch(NoClassInPathException e) + { + return list; + } + } + return list; + } + + private Type getMappedType(Type t, Map classMappings) + { + if(t.getSort() == Type.OBJECT) + { + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(t.getInternalName())) + return Type.getObjectType(mapping.getKey()); + }else if(t.getSort() == Type.ARRAY) + { + int layers = 1; + Type element = t.getElementType(); + while(element.getSort() == Type.ARRAY) + { + element = element.getElementType(); + layers++; + } + if(element.getSort() == Type.OBJECT) + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(element.getInternalName())) + { + String beginning = ""; + for(int i = 0; i < layers; i++) + beginning += "["; + beginning += "L"; + return Type.getType(beginning + mapping.getKey() + ";"); + } + } + return t; + } + + private class NodeWrapper + { + public String name; + public String desc; + + public NodeWrapper(String name, String desc) + { + this.name = name; + this.desc = desc; + } + } + + public static class Config extends AbstractNormalizer.Config + { + /** + * If false, we map Notch names to Intermediary. + * If true, we map Intermediary to Notch names. + */ + private boolean reverse = true; + + public Config() + { + super(NotchToIntermediaryTransformer.class); + } + + public boolean isReverse() + { + return reverse; + } + + public void setReverse(boolean reverse) + { + this.reverse = reverse; + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToSrgTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToSrgTransformer.java new file mode 100644 index 00000000..f6dac665 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/NotchToSrgTransformer.java @@ -0,0 +1,627 @@ +package com.javadeobfuscator.deobfuscator.transformers.minecraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.exceptions.NoClassInPathException; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.AbstractNormalizer; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.CustomRemapper; + +@TransformerConfig.ConfigOptions(configClass = NotchToSrgTransformer.Config.class) +public class NotchToSrgTransformer extends AbstractNormalizer +{ + @Override + public void remap(CustomRemapper remapper) + { + try + { + getDeobfuscator().assureLoaded("net/minecraft/client/main/Main"); + }catch(NoClassInPathException e) + { + System.out.println("[NotchToSrgTransformer] Obfuscated Minecraft jar not detected, put it as a library for best results!"); + } + File dir = new File(System.getProperty("user.dir")); + boolean tsrg = true; + File srg = new File(dir, "joined.tsrg"); + if(!srg.exists()) + { + srg = new File(dir, "joined.srg"); + tsrg = false; + } + if(!srg.exists()) + { + System.out.println("[NotchToSrgTransformer] You must put joined.srg or joined.tsrg in the same folder as deobfuscator.jar!"); + return; + } + + Map classMappings = new HashMap<>(); + Map> fieldMappings = new HashMap<>(); + Map> methodMappings = new HashMap<>(); + + boolean rev = getConfig().isReverse(); + try(BufferedReader reader = new BufferedReader(new FileReader(srg))) + { + String currentClass = null; + String line; + while((line = reader.readLine()) != null) + { + if(tsrg) + if(!line.startsWith("\t")) + { + String[] map = line.split(" "); + if(map.length != 2) + throw new RuntimeException("Unexpected class mapping"); + currentClass = rev ? map[1] : map[0]; + if(!map[0].equals(map[1])) + classMappings.put(rev ? map[1] : map[0], rev ? map[0] : map[1]); + }else + { + String[] map = line.replace("\t", "").split(" "); + if(currentClass == null) + throw new RuntimeException("Method/field with unknown class found!"); + if(map.length == 3) + { + if(!map[0].equals(map[2])) + { + methodMappings.putIfAbsent(currentClass, new HashMap<>()); + methodMappings.get(currentClass).put(new NodeWrapper(rev ? map[2] : map[0], map[1]), + rev ? map[0] : map[2]); + } + }else if(map.length == 2) + { + if(!map[0].equals(map[1])) + { + fieldMappings.putIfAbsent(currentClass, new HashMap<>()); + fieldMappings.get(currentClass).put(new NodeWrapper(rev ? map[1] : map[0], null), + rev ? map[0] : map[1]); + } + }else + throw new RuntimeException("Unexpected node mapping"); + } + else + { + String[] beginning = line.split(" "); + if(beginning.length != 2) + throw new RuntimeException("Unexpected srg mapping"); + line = beginning[1]; + String[] map = line.split(" "); + int idx; + String className; + switch(beginning[0]) + { + case "CL:": + if(map.length != 2) + throw new RuntimeException("Unexpected class mapping"); + if(!map[0].equals(map[1])) + classMappings.put(rev ? map[1] : map[0], rev ? map[0] : map[1]); + break; + case "MD:": + if(map.length != 4) + throw new RuntimeException("Unexpected method mapping"); + idx = map[0].lastIndexOf('/'); + className = map[0].substring(0, idx); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(className)) + { + className = entry.getKey(); + break; + } + String methodName = map[0].substring(idx + 1); + String mappedMethod = map[2].substring(map[2].lastIndexOf('/') + 1); + if(!methodName.equals(mappedMethod)) + { + methodMappings.putIfAbsent(className, new HashMap<>()); + methodMappings.get(className).put(new NodeWrapper(rev ? mappedMethod : methodName, + rev ? map[3] : map[1]), + rev ? methodName : mappedMethod); + } + break; + case "FD:": + if(map.length != 2) + throw new RuntimeException("Unexpected field mapping"); + idx = map[0].lastIndexOf('/'); + className = map[0].substring(0, idx); + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(className)) + { + className = entry.getKey(); + break; + } + String fieldName = map[0].substring(idx + 1); + String mappedField = map[1].substring(map[1].lastIndexOf('/') + 1); + if(!fieldName.equals(mappedField)) + { + fieldMappings.putIfAbsent(className, new HashMap<>()); + fieldMappings.get(className).put(new NodeWrapper(rev ? mappedField : fieldName, null), + rev ? fieldName : mappedField); + } + break; + case "PK:": + //Package mappings are ignored + break; + default: + throw new RuntimeException("Unexpected srg mapping " + beginning[0]); + } + } + } + }catch(IOException e) + { + System.out.println("[NotchToSrgTransformer] File read failed, are you sure the (T)SRG file has not been tampered?"); + e.printStackTrace(); + return; + } + //Fix descs for tsrg + if(tsrg && rev) + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + { + String desc = entry2.getKey().desc; + Type[] args = Type.getArgumentTypes(desc); + Type[] newArgs = new Type[args.length]; + Type returnArg = Type.getReturnType(desc); + for(int i = 0; i < args.length; i++) + { + Type t = args[i]; + newArgs[i] = getMappedType(t, classMappings); + } + returnArg = getMappedType(returnArg, classMappings); + entry2.getKey().desc = Type.getMethodDescriptor(returnArg, newArgs); + } + //We are not provided field descs, so we need to search them + for(Iterator>> itr = fieldMappings.entrySet().iterator(); itr.hasNext();) + { + Entry> entry = itr.next(); + ClassNode classNode; + String className = entry.getKey(); + try + { + if(rev && classMappings.containsKey(entry.getKey())) + className = classMappings.get(entry.getKey()); + classNode = getDeobfuscator().assureLoaded(className); + }catch(NoClassInPathException e) + { + System.out.println("[NotchToSrgTransformer] Class not found: " + className); + itr.remove(); + continue; + } + for(Iterator> itr2 = entry.getValue().entrySet().iterator(); itr2.hasNext();) + { + //Note: There are NO duplicate fields, so this is safe + Entry node = itr2.next(); + FieldNode field = rev ? classNode.fields.stream().filter(f -> f.name.equals(node.getValue())).findFirst().orElse(null) + : classNode.fields.stream().filter(f -> f.name.equals(node.getKey().name)).findFirst().orElse(null); + if(field == null) + { + System.out.println("[NotchToSrgTransformer] Field not found: " + node.getKey().name + " @ " + entry.getKey()); + itr2.remove(); + } + if(rev) + node.getKey().desc = getMappedType(Type.getType(field.desc), classMappings).toString(); + else + node.getKey().desc = field.desc; + } + } + //This is only useful when we are trying to "collapse" the classes into one package (reobf) + remapper.setIgnorePackages(true); + //Remap mixin shadow methods/fields + for(ClassNode classNode : classNodes()) + { + List mixinClasses = new ArrayList<>(); + if(classNode.invisibleAnnotations != null) + for(AnnotationNode annot : classNode.invisibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Mixin;") && annot.values.size() >= 2) + { + int index = -1; + for(int i = 0; i < annot.values.size(); i++) + { + Object o = annot.values.get(i); + if("value".equals(o)) + { + index = i + 1; + break; + } + } + List list = (List)annot.values.get(index); + for(Object o : list) + { + if(!(o instanceof Type)) + continue; + mixinClasses.add(((Type)o).getInternalName()); + } + } + + if(mixinClasses.isEmpty()) + continue; + + List superclasses = new ArrayList<>(); + for(String mixinClass : mixinClasses) + { + //Convert back to Notch name + if(rev && classMappings.containsKey(mixinClass)) + mixinClass = classMappings.get(mixinClass); + + ClassNode mixinClassNode = null; + try + { + mixinClassNode = getDeobfuscator().assureLoaded(mixinClass); + }catch(NoClassInPathException e) + { + System.out.println("[NotchToSrgTransformer] Class not found: " + mixinClasses); + } + + for(ClassNode cn : getSuperClasses(mixinClassNode, rev, classMappings)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + if(!superclasses.contains(mixinClassNode)) + superclasses.add(mixinClassNode); + } + + for(ClassNode cn : getSuperClasses(classNode, rev, classMappings)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + + for(MethodNode method : classNode.methods) + { + if(method.name.startsWith("<")) + continue; + boolean isShadow = false; + if(method.visibleAnnotations != null) + for(AnnotationNode annot : method.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(method.name) && entry.getKey().desc.equals(method.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(classNode.name, new HashMap<>()); + methodMappings.get(classNode.name).put(new NodeWrapper(method.name, method.desc), mapped); + } + } + } + for(FieldNode field : classNode.fields) + { + if(field.name.startsWith("<")) + continue; + boolean isShadow = false; + if(field.visibleAnnotations != null) + for(AnnotationNode annot : field.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(field.name) && entry.getKey().desc.equals(field.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(classNode.name, new HashMap<>()); + fieldMappings.get(classNode.name).put(new NodeWrapper(field.name, field.desc), mapped); + } + } + } + } + //Remap subclass calls + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof FieldInsnNode) + { + FieldInsnNode fieldInsn = (FieldInsnNode)ain; + boolean hasMapping = false; + if(fieldMappings.containsKey(fieldInsn.owner)) + for(Entry entry : fieldMappings.get(fieldInsn.owner).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + if(rev) + owner = getDeobfuscator().assureLoaded(classMappings.get(fieldInsn.owner)); + else + owner = getDeobfuscator().assureLoaded(fieldInsn.owner); + }catch(NoClassInPathException e) + { + String ownerName = rev ? classMappings.get(fieldInsn.owner) : fieldInsn.owner; + if(classMappings.containsKey(fieldInsn.owner)) + System.out.println("[NotchToSrgTransformer] Class not found: " + ownerName); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(fieldInsn.owner, new HashMap<>()); + fieldMappings.get(fieldInsn.owner).put(new NodeWrapper(fieldInsn.name, fieldInsn.desc), mapped); + } + } + }else if(ain instanceof MethodInsnNode && !((MethodInsnNode)ain).name.startsWith("<")) + { + MethodInsnNode methodInsn = (MethodInsnNode)ain; + boolean hasMapping = false; + if(methodMappings.containsKey(methodInsn.owner)) + for(Entry entry : methodMappings.get(methodInsn.owner).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + if(rev) + owner = getDeobfuscator().assureLoaded(classMappings.get(methodInsn.owner)); + else + owner = getDeobfuscator().assureLoaded(methodInsn.owner); + }catch(NoClassInPathException e) + { + String ownerName = rev ? classMappings.get(methodInsn.owner) : methodInsn.owner; + if(classMappings.containsKey(methodInsn.owner)) + System.out.println("[NotchToSrgTransformer] Class not found: " + ownerName); + continue; + } + List superclasses = getSuperClasses(owner, rev, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(methodInsn.owner, new HashMap<>()); + methodMappings.get(methodInsn.owner).put(new NodeWrapper(methodInsn.name, methodInsn.desc), mapped); + } + } + } + for(Entry entry : classMappings.entrySet()) + remapper.map(entry.getKey(), entry.getValue()); + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapMethodName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + for(Entry> entry : fieldMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapFieldName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + //Remap overrides + for(ClassNode classNode : classNodes()) + { + List superclasses = getSuperClasses(classNode, rev, classMappings); + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(rev) + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(name)) + { + name = entry.getKey(); + break; + } + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + for(MethodNode method : classNode.methods) + if(method.name.equals(entry.getKey().name) && method.desc.equals(entry.getKey().desc)) + remapper.mapMethodName(classNode.name, method.name, method.desc, entry.getValue(), true); + } + } + } + + /** + * Returns the accessible superclasses. + */ + private List getSuperClasses(ClassNode classNode, boolean rev, Map classMappings) + { + List list = new ArrayList<>(); + if(classNode == null) + return list; + if(classNode.superName != null) + { + String superName = classNode.superName; + if(rev && classMappings.containsKey(superName)) + superName = classMappings.get(superName); + try + { + ClassNode superClass = getDeobfuscator().assureLoaded(superName); + if(superClass != null) + { + for(ClassNode cn : getSuperClasses(superClass, rev, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superClass)) + list.add(superClass); + } + }catch(NoClassInPathException e) + { + return list; + } + } + for(String inf : classNode.interfaces) + { + if(rev && classMappings.containsKey(inf)) + inf = classMappings.get(inf); + try + { + ClassNode superInterface = getDeobfuscator().assureLoaded(inf); + if(superInterface != null) + { + for(ClassNode cn : getSuperClasses(superInterface, rev, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superInterface)) + list.add(superInterface); + } + }catch(NoClassInPathException e) + { + return list; + } + } + return list; + } + + private Type getMappedType(Type t, Map classMappings) + { + if(t.getSort() == Type.OBJECT) + { + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(t.getInternalName())) + return Type.getObjectType(mapping.getKey()); + }else if(t.getSort() == Type.ARRAY) + { + int layers = 1; + Type element = t.getElementType(); + while(element.getSort() == Type.ARRAY) + { + element = element.getElementType(); + layers++; + } + if(element.getSort() == Type.OBJECT) + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(element.getInternalName())) + { + String beginning = ""; + for(int i = 0; i < layers; i++) + beginning += "["; + beginning += "L"; + return Type.getType(beginning + mapping.getKey() + ";"); + } + } + return t; + } + + private class NodeWrapper + { + public String name; + public String desc; + + public NodeWrapper(String name, String desc) + { + this.name = name; + this.desc = desc; + } + } + + public static class Config extends AbstractNormalizer.Config + { + /** + * If false, we map Notch names to SRG. + * If true, we map SRG to Notch names. + */ + private boolean reverse = false; + + public Config() + { + super(NotchToSrgTransformer.class); + } + + public boolean isReverse() + { + return reverse; + } + + public void setReverse(boolean reverse) + { + this.reverse = reverse; + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/SrgToMCPTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/SrgToMCPTransformer.java new file mode 100644 index 00000000..560db8c7 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/minecraft/SrgToMCPTransformer.java @@ -0,0 +1,659 @@ +package com.javadeobfuscator.deobfuscator.transformers.minecraft; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.exceptions.NoClassInPathException; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.AbstractNormalizer; +import com.javadeobfuscator.deobfuscator.transformers.normalizer.CustomRemapper; + +@TransformerConfig.ConfigOptions(configClass = SrgToMCPTransformer.Config.class) +public class SrgToMCPTransformer extends AbstractNormalizer +{ + @Override + public void remap(CustomRemapper remapper) + { + try + { + getDeobfuscator().assureLoaded("net/minecraft/client/main/Main"); + }catch(NoClassInPathException e) + { + System.out.println("[SrgToMCPTransformer] Obfuscated Minecraft jar not detected, put it as a library for best results!"); + } + File dir = new File(System.getProperty("user.dir")); + boolean tsrg = true; + File srg = new File(dir, "joined.tsrg"); + if(!srg.exists()) + { + srg = new File(dir, "joined.srg"); + tsrg = false; + } + if(!srg.exists()) + { + System.out.println("[SrgToMCPTransformer] You must put joined.srg or joined.tsrg in the same folder as deobfuscator.jar!"); + return; + } + + File methods = new File(dir, "methods.csv"); + File fields = new File(dir, "fields.csv"); + + if(!methods.exists() || !fields.exists()) + { + System.out.println("[SrgToMCPTransformer] You must put methods.csv and fields.csv in the same folder as deobfuscator.jar!"); + return; + } + + Map deobfFieldMappings = new HashMap<>(); + Map deobfMethodMappings = new HashMap<>(); + + try(BufferedReader reader = new BufferedReader(new FileReader(fields))) + { + reader.readLine();//Skip first line + String line; + while((line = reader.readLine()) != null) + { + String[] split = line.split(","); + if(split.length < 3) + throw new RuntimeException("Invaild pattern in fields.csv"); + deobfFieldMappings.put(split[0], split[1]); + } + }catch(IOException e) + { + System.out.println("[SrgToMCPTransformer] File read failed, are you sure fields.csv has not been tampered?"); + e.printStackTrace(); + return; + } + try(BufferedReader reader = new BufferedReader(new FileReader(methods))) + { + reader.readLine();//Skip first line + String line; + while((line = reader.readLine()) != null) + { + String[] split = line.split(","); + if(split.length < 3) + throw new RuntimeException("Invaild pattern in methods.csv"); + deobfMethodMappings.put(split[0], split[1]); + } + }catch(IOException e) + { + System.out.println("[SrgToMCPTransformer] File read failed, are you sure methods.csv has not been tampered?"); + e.printStackTrace(); + return; + } + + Map classMappings = new HashMap<>(); + Map> fieldMappings = new HashMap<>(); + Map> methodMappings = new HashMap<>(); + + Map> fieldObfMappings = new HashMap<>(); + + boolean rev = getConfig().isReverse(); + try(BufferedReader reader = new BufferedReader(new FileReader(srg))) + { + String currentClass = null; + String line; + while((line = reader.readLine()) != null) + { + if(tsrg) + if(!line.startsWith("\t")) + { + String[] map = line.split(" "); + if(map.length != 2) + throw new RuntimeException("Unexpected class mapping"); + currentClass = map[1]; + if(!map[0].equals(map[1])) + classMappings.put(map[0], map[1]); + }else + { + String[] map = line.replace("\t", "").split(" "); + if(currentClass == null) + throw new RuntimeException("Method/field with unknown class found!"); + if(map.length == 3) + { + if(deobfMethodMappings.containsKey(map[2])) + { + methodMappings.putIfAbsent(currentClass, new HashMap<>()); + methodMappings.get(currentClass).put(new NodeWrapper(rev ? deobfMethodMappings.get(map[2]) : map[2], + map[1]), rev ? map[2] : deobfMethodMappings.get(map[2])); + } + }else if(map.length == 2) + { + if(deobfFieldMappings.containsKey(map[1])) + { + fieldMappings.putIfAbsent(currentClass, new HashMap<>()); + fieldMappings.get(currentClass).put(new NodeWrapper(rev ? deobfFieldMappings.get(map[1]) : map[1], null), + rev ? map[1] : deobfFieldMappings.get(map[1])); + } + if(!map[0].equals(map[1])) + { + fieldObfMappings.putIfAbsent(currentClass, new HashMap<>()); + fieldObfMappings.get(currentClass).put(map[1], map[0]); + } + }else + throw new RuntimeException("Unexpected node mapping"); + } + else + { + String[] beginning = line.split(" "); + if(beginning.length != 2) + throw new RuntimeException("Unexpected srg mapping"); + line = beginning[1]; + String[] map = line.split(" "); + String className; + switch(beginning[0]) + { + case "CL:": + if(map.length != 2) + throw new RuntimeException("Unexpected class mapping"); + if(!map[0].equals(map[1])) + classMappings.put(map[0], map[1]); + break; + case "MD:": + if(map.length != 4) + throw new RuntimeException("Unexpected method mapping"); + className = map[0].substring(0, map[0].lastIndexOf('/')); + if(classMappings.containsKey(className)) + className = classMappings.get(className); + String methodName = map[2].substring(map[2].lastIndexOf('/') + 1); + if(deobfMethodMappings.containsKey(methodName)) + { + methodMappings.putIfAbsent(className, new HashMap<>()); + methodMappings.get(className).put(new NodeWrapper(rev ? deobfMethodMappings.get(methodName) : methodName, + map[3]), rev ? methodName : deobfMethodMappings.get(methodName)); + } + break; + case "FD:": + if(map.length != 2) + throw new RuntimeException("Unexpected field mapping"); + className = map[0].substring(0, map[0].lastIndexOf('/')); + if(classMappings.containsKey(className)) + className = classMappings.get(className); + String fieldName = map[1].substring(map[1].lastIndexOf('/') + 1); + if(deobfFieldMappings.containsKey(fieldName)) + { + fieldMappings.putIfAbsent(className, new HashMap<>()); + fieldMappings.get(className).put(new NodeWrapper(rev ? deobfFieldMappings.get(fieldName) : fieldName, + null), rev ? fieldName : deobfFieldMappings.get(fieldName)); + } + String notchFieldName = map[0].substring(map[0].lastIndexOf('/') + 1); + if(!notchFieldName.equals(fieldName)) + { + fieldObfMappings.putIfAbsent(className, new HashMap<>()); + fieldObfMappings.get(className).put(fieldName, notchFieldName); + } + break; + case "PK:": + //Package mappings are ignored + break; + default: + throw new RuntimeException("Unexpected srg mapping " + beginning[0]); + } + } + } + }catch(IOException e) + { + System.out.println("[SrgToMCPTransformer] File read failed, are you sure the (T)SRG file has not been tampered?"); + e.printStackTrace(); + return; + } + //Fix descs for tsrg + if(tsrg) + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + { + String desc = entry2.getKey().desc; + Type[] args = Type.getArgumentTypes(desc); + Type[] newArgs = new Type[args.length]; + Type returnArg = Type.getReturnType(desc); + for(int i = 0; i < args.length; i++) + { + Type t = args[i]; + newArgs[i] = getMappedType(t, classMappings); + } + returnArg = getMappedType(returnArg, classMappings); + entry2.getKey().desc = Type.getMethodDescriptor(returnArg, newArgs); + } + //We are not provided field descs, so we need to search them + for(Iterator>> itr = fieldMappings.entrySet().iterator(); itr.hasNext();) + { + Entry> entry = itr.next(); + ClassNode classNode; + String className = entry.getKey(); + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(className)) + { + className = mapping.getKey(); + break; + } + try + { + classNode = getDeobfuscator().assureLoaded(className); + }catch(NoClassInPathException e) + { + System.out.println("[SrgToMCPTransformer] Class not found: " + className); + itr.remove(); + continue; + } + for(Iterator> itr2 = entry.getValue().entrySet().iterator(); itr2.hasNext();) + { + //Note: There are NO duplicate fields, so this is safe + Entry node = itr2.next(); + String srgName = rev ? node.getValue() : node.getKey().name; + String notchName = fieldObfMappings.get(entry.getKey()).get(srgName); + if(notchName == null) + throw new RuntimeException("Could not find Notch name for SRG!"); + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(notchName)).findFirst().orElse(null); + if(field == null) + { + System.out.println("[SrgToMCPTransformer] Field not found: " + notchName + " @ " + classNode.name); + itr2.remove(); + } + node.getKey().desc = getMappedType(Type.getType(field.desc), classMappings).toString(); + } + } + //Remap mixin shadow methods/fields + for(ClassNode classNode : classNodes()) + { + List mixinClasses = new ArrayList<>(); + if(classNode.invisibleAnnotations != null) + for(AnnotationNode annot : classNode.invisibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Mixin;") && annot.values.size() >= 2) + { + int index = -1; + for(int i = 0; i < annot.values.size(); i++) + { + Object o = annot.values.get(i); + if("value".equals(o)) + { + index = i + 1; + break; + } + } + List list = (List)annot.values.get(index); + for(Object o : list) + { + if(!(o instanceof Type)) + continue; + mixinClasses.add(((Type)o).getInternalName()); + } + } + + if(mixinClasses.isEmpty()) + continue; + + List superclasses = new ArrayList<>(); + for(String mixinClass : mixinClasses) + { + //Convert back to Notch name + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(mixinClass)) + { + mixinClass = entry.getKey(); + break; + } + + ClassNode mixinClassNode = null; + try + { + mixinClassNode = getDeobfuscator().assureLoaded(mixinClass); + }catch(NoClassInPathException e) + { + System.out.println("[SrgToMCPTransformer] Class not found: " + mixinClasses); + } + + for(ClassNode cn : getSuperClasses(mixinClassNode, classMappings)) + if(!superclasses.contains(cn)) + superclasses.add(cn); + if(!superclasses.contains(mixinClassNode)) + superclasses.add(mixinClassNode); + } + + for(MethodNode method : classNode.methods) + { + if(method.name.startsWith("<")) + continue; + boolean isShadow = false; + if(method.visibleAnnotations != null) + for(AnnotationNode annot : method.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(classMappings.containsKey(name)) + name = classMappings.get(name); + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(method.name) && entry.getKey().desc.equals(method.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(classNode.name, new HashMap<>()); + methodMappings.get(classNode.name).put(new NodeWrapper(method.name, method.desc), mapped); + } + } + } + for(FieldNode field : classNode.fields) + { + if(field.name.startsWith("<")) + continue; + boolean isShadow = false; + if(field.visibleAnnotations != null) + for(AnnotationNode annot : field.visibleAnnotations) + if(annot.desc.equals("Lorg/spongepowered/asm/mixin/Shadow;")) + { + isShadow = true; + break; + } + if(isShadow) + { + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(classMappings.containsKey(name)) + name = classMappings.get(name); + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(field.name) && entry.getKey().desc.equals(field.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(classNode.name, new HashMap<>()); + fieldMappings.get(classNode.name).put(new NodeWrapper(field.name, field.desc), mapped); + } + } + } + } + //Remap subclass calls + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof FieldInsnNode) + { + boolean hasReobfOwner = false; + FieldInsnNode fieldInsn = (FieldInsnNode)ain; + String reobfOwner = fieldInsn.owner; + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(fieldInsn.owner)) + { + hasReobfOwner = true; + reobfOwner = mapping.getKey(); + break; + } + boolean hasMapping = false; + if(fieldMappings.containsKey(fieldInsn.owner)) + for(Entry entry : fieldMappings.get(fieldInsn.owner).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + owner = getDeobfuscator().assureLoaded(reobfOwner); + }catch(NoClassInPathException e) + { + if(hasReobfOwner) + System.out.println("[SrgToMCPTransformer] Class not found: " + reobfOwner); + continue; + } + List superclasses = getSuperClasses(owner, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(classMappings.containsKey(name)) + name = classMappings.get(name); + if(fieldMappings.containsKey(name)) + for(Entry entry : fieldMappings.get(name).entrySet()) + if(entry.getKey().name.equals(fieldInsn.name) && entry.getKey().desc.equals(fieldInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + fieldMappings.putIfAbsent(fieldInsn.owner, new HashMap<>()); + fieldMappings.get(fieldInsn.owner).put(new NodeWrapper(fieldInsn.name, fieldInsn.desc), mapped); + } + } + }else if(ain instanceof MethodInsnNode && !((MethodInsnNode)ain).name.startsWith("<")) + { + boolean hasReobfOwner = false; + MethodInsnNode methodInsn = (MethodInsnNode)ain; + String reobfOwner = methodInsn.owner; + for(Entry mapping : classMappings.entrySet()) + if(mapping.getValue().equals(methodInsn.owner)) + { + hasReobfOwner = true; + reobfOwner = mapping.getKey(); + break; + } + boolean hasMapping = false; + if(methodMappings.containsKey(methodInsn.owner)) + for(Entry entry : methodMappings.get(methodInsn.owner).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + hasMapping = true; + break; + } + if(!hasMapping) + { + ClassNode owner; + try + { + owner = getDeobfuscator().assureLoaded(reobfOwner); + }catch(NoClassInPathException e) + { + if(hasReobfOwner) + System.out.println("[SrgToMCPTransformer] Class not found: " + reobfOwner); + continue; + } + List superclasses = getSuperClasses(owner, classMappings); + String mapped = null; + outer: + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(classMappings.containsKey(name)) + name = classMappings.get(name); + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + if(entry.getKey().name.equals(methodInsn.name) && entry.getKey().desc.equals(methodInsn.desc)) + { + mapped = entry.getValue(); + break outer; + } + } + if(mapped != null) + { + methodMappings.putIfAbsent(methodInsn.owner, new HashMap<>()); + methodMappings.get(methodInsn.owner).put(new NodeWrapper(methodInsn.name, methodInsn.desc), mapped); + } + } + } + for(Entry> entry : methodMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapMethodName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + for(Entry> entry : fieldMappings.entrySet()) + for(Entry entry2 : entry.getValue().entrySet()) + remapper.mapFieldName(entry.getKey(), entry2.getKey().name, entry2.getKey().desc, entry2.getValue(), true); + //Remap overrides + for(ClassNode classNode : classNodes()) + { + List superclasses = getSuperClasses(classNode, classMappings); + for(ClassNode superclass : superclasses) + { + String name = superclass.name; + if(classMappings.containsKey(name)) + name = classMappings.get(name); + if(methodMappings.containsKey(name)) + for(Entry entry : methodMappings.get(name).entrySet()) + for(MethodNode method : classNode.methods) + if(method.name.equals(entry.getKey().name) && method.desc.equals(entry.getKey().desc)) + remapper.mapMethodName(classNode.name, method.name, method.desc, entry.getValue(), true); + } + } + } + + /** + * Returns the accessible superclasses. + */ + private List getSuperClasses(ClassNode classNode, Map classMappings) + { + List list = new ArrayList<>(); + if(classNode == null) + return list; + if(classNode.superName != null) + { + try + { + String superName = classNode.superName; + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(superName)) + { + superName = entry.getKey(); + break; + } + ClassNode superClass = getDeobfuscator().assureLoaded(superName); + if(superClass != null) + { + for(ClassNode cn : getSuperClasses(superClass, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superClass)) + list.add(superClass); + } + }catch(NoClassInPathException e) + { + return list; + } + } + for(String inf : classNode.interfaces) + { + for(Entry entry : classMappings.entrySet()) + if(entry.getValue().equals(inf)) + { + inf = entry.getKey(); + break; + } + try + { + ClassNode superInterface = getDeobfuscator().assureLoaded(inf); + if(superInterface != null) + { + for(ClassNode cn : getSuperClasses(superInterface, classMappings)) + if(!list.contains(cn)) + list.add(cn); + if(!list.contains(superInterface)) + list.add(superInterface); + } + }catch(NoClassInPathException e) + { + return list; + } + } + return list; + } + + private Type getMappedType(Type t, Map classMappings) + { + if(t.getSort() == Type.OBJECT && classMappings.containsKey(t.getInternalName())) + return Type.getObjectType(classMappings.get(t.getInternalName())); + if(t.getSort() == Type.ARRAY) + { + int layers = 1; + Type element = t.getElementType(); + while(element.getSort() == Type.ARRAY) + { + element = element.getElementType(); + layers++; + } + if(element.getSort() == Type.OBJECT && classMappings.containsKey(element.getInternalName())) + { + String beginning = ""; + for(int i = 0; i < layers; i++) + beginning += "["; + beginning += "L"; + return Type.getType(beginning + classMappings.get(element.getInternalName()) + ";"); + } + } + return t; + } + + private class NodeWrapper + { + public String name; + public String desc; + + public NodeWrapper(String name, String desc) + { + this.name = name; + this.desc = desc; + } + } + + public static class Config extends AbstractNormalizer.Config + { + /** + * If false, we map SRG to deobbed names. + * If true, we map deobbed names to SRG. + */ + private boolean reverse = false; + + public Config() + { + super(SrgToMCPTransformer.class); + } + + public boolean isReverse() + { + return reverse; + } + + public void setReverse(boolean reverse) + { + this.reverse = reverse; + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/CustomRemapper.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/CustomRemapper.java index 389add5d..520c20b4 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/CustomRemapper.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/CustomRemapper.java @@ -22,6 +22,11 @@ import java.util.Map; public class CustomRemapper extends Remapper { + /** + * If this option is disabled, mapping "package/class" to "newclass" will result in "package/newclass". + */ + private boolean ignorePackages = false; + /** * Map method name to the new name. Subclasses can override. * @@ -131,7 +136,7 @@ public boolean fieldMappingExists(String owner, String oldName, String oldDesc) public String map(String in) { int lin = in.lastIndexOf('/'); String className = lin == -1 ? in : in.substring(lin + 1); - if (lin == -1) { + if (lin == -1 || ignorePackages) { return map.getOrDefault(in, in); } else { String newClassName = map.getOrDefault(in, className); @@ -187,4 +192,9 @@ public boolean map(String old, String newName) { public String unmap(String ref) { return mapReversed.get(ref) == null ? ref : mapReversed.get(ref); } + + public void setIgnorePackages(boolean ignorePackages) + { + this.ignorePackages = ignorePackages; + } } diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/EnumNormalizer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/EnumNormalizer.java new file mode 100644 index 00000000..b3ca2007 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/normalizer/EnumNormalizer.java @@ -0,0 +1,120 @@ +package com.javadeobfuscator.deobfuscator.transformers.normalizer; + +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer; +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +import org.assertj.core.internal.asm.Opcodes; +import org.assertj.core.internal.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; + +@TransformerConfig.ConfigOptions(configClass = EnumNormalizer.Config.class) +public class EnumNormalizer extends AbstractNormalizer +{ + @Override + public void remap(CustomRemapper remapper) + { + for(ClassNode classNode : classNodes()) + { + if(!classNode.superName.equals("java/lang/Enum")) + continue; + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null && clinit.instructions != null && clinit.instructions.getFirst() != null) + { + //Fix order + LinkedHashMap> result = new FlowAnalyzer(clinit).analyze(clinit.instructions.getFirst(), + new ArrayList<>(), new HashMap<>(), false, true); + List order = new ArrayList<>(); + FieldNode valuesArr = null; + boolean hasDuplicate = false; + for(List insns : result.values()) + for(AbstractInsnNode ain : insns) + if(ain.getOpcode() == Opcodes.PUTSTATIC + && Type.getType(((FieldInsnNode)ain).desc).getSort() == Type.OBJECT + && Type.getType(((FieldInsnNode)ain).desc).getInternalName().equals(classNode.name)) + { + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && Type.getType(f.desc).getInternalName().equals(classNode.name)).findFirst().orElse(null); + if(field != null && Modifier.isStatic(field.access)) + { + order.add(field); + classNode.fields.remove(field); + } + }else if(!hasDuplicate && ain.getOpcode() == Opcodes.PUTSTATIC + && Type.getType(((FieldInsnNode)ain).desc).getSort() == Type.ARRAY + && Type.getType(((FieldInsnNode)ain).desc).getElementType().getSort() == Type.OBJECT + && Type.getType(((FieldInsnNode)ain).desc).getElementType().getInternalName().equals(classNode.name)) + { + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && Type.getType(f.desc).getElementType().getInternalName().equals(classNode.name)).findFirst().orElse(null); + if(field != null && Modifier.isStatic(field.access)) + { + if(valuesArr != null) + hasDuplicate = true; + else + valuesArr = field; + } + } + if(valuesArr != null) + { + valuesArr.access |= Opcodes.ACC_SYNTHETIC; + classNode.fields.remove(valuesArr); + order.add(valuesArr); + } + Collections.reverse(order); + for(FieldNode field : order) + classNode.fields.add(0, field); + //Fix names + Frame[] frames; + try + { + frames = new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, clinit); + }catch(AnalyzerException e) + { + oops("unexpected analyzer exception", e); + continue; + } + for(List insns : result.values()) + for(AbstractInsnNode ain : insns) + if(ain.getOpcode() == Opcodes.PUTSTATIC + && Type.getType(((FieldInsnNode)ain).desc).getSort() == Type.OBJECT + && Type.getType(((FieldInsnNode)ain).desc).getInternalName().equals(classNode.name) + && Utils.getPrevious(ain) != null && Utils.getPrevious(ain).getOpcode() == Opcodes.INVOKESPECIAL) + { + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && Type.getType(f.desc).getInternalName().equals(classNode.name)).findFirst().orElse(null); + if(field != null && Modifier.isStatic(field.access)) + { + int argLen = Type.getArgumentTypes(((MethodInsnNode)Utils.getPrevious(ain)).desc).length; + Frame frame = frames[clinit.instructions.indexOf(Utils.getPrevious(ain))]; + if(frame.getStack(frame.getStackSize() - argLen).insns.size() == 1 + && frame.getStack(frame.getStackSize() - argLen).insns.iterator().next().getOpcode() == Opcodes.LDC) + { + String value = (String)((LdcInsnNode)frame.getStack(frame.getStackSize() - argLen).insns.iterator().next()).cst; + if(!field.name.equals(value)) + remapper.mapFieldName(classNode.name, field.name, field.desc, value, false); + } + } + } + } + } + } + + public static class Config extends AbstractNormalizer.Config { + public Config() { + super(EnumNormalizer.class); + } + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvaildClassRemover.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvalidClassRemover.java similarity index 91% rename from src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvaildClassRemover.java rename to src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvalidClassRemover.java index cc182708..da4bb5a4 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvaildClassRemover.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/InvalidClassRemover.java @@ -24,12 +24,12 @@ import com.javadeobfuscator.deobfuscator.config.*; import com.javadeobfuscator.deobfuscator.transformers.*; -public class InvaildClassRemover extends Transformer +public class InvalidClassRemover extends Transformer { @Override public boolean transform() { - System.out.println("[Special] [InvaildClassRemover] Starting"); + System.out.println("[Special] [InvalidClassRemover] Starting"); List patterns = new ArrayList<>(); if(getDeobfuscator().getConfig().getIgnoredClasses() != null) for (String ignored : getDeobfuscator().getConfig().getIgnoredClasses()) { @@ -46,7 +46,7 @@ public boolean transform() getDeobfuscator().getInputPassthrough().entrySet().removeIf(e -> e.getKey().endsWith(".class") && !hasClass(e.getKey(), patterns)); int after = getDeobfuscator().getInputPassthrough().size(); - System.out.println("[Special] [InvaildClassRemover] Removed " + (before - after) + " classes"); + System.out.println("[Special] [InvalidClassRemover] Removed " + (before - after) + " classes"); return before - after > 0; } diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/ParamorphismTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/ParamorphismTransformer.java new file mode 100644 index 00000000..a8629e0f --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/ParamorphismTransformer.java @@ -0,0 +1,374 @@ +/* + * Copyright 2016 Sam Sun + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.javadeobfuscator.deobfuscator.transformers.special; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.zip.GZIPInputStream; + +import org.assertj.core.internal.asm.Opcodes; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Frame; + +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.executor.Context; +import com.javadeobfuscator.deobfuscator.executor.MethodExecutor; +import com.javadeobfuscator.deobfuscator.executor.defined.JVMMethodProvider; +import com.javadeobfuscator.deobfuscator.executor.defined.MappedFieldProvider; +import com.javadeobfuscator.deobfuscator.executor.defined.MappedMethodProvider; +import com.javadeobfuscator.deobfuscator.executor.defined.PrimitiveFieldProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.ComparisonProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.DelegatingProvider; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.utils.InstructionModifier; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +public class ParamorphismTransformer extends Transformer +{ + public static boolean OVERRIDE = true; + + @Override + public boolean transform() + { + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new PrimitiveFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new MappedFieldProvider()); + provider.register(new ComparisonProvider() { + @Override + public boolean instanceOf(JavaValue target, Type type, Context context) { + if(target.type().equals("java/lang/Class") + && type.getDescriptor().equals("Ljava/lang/Class;")) + return true; + return false; + } + + @Override + public boolean checkcast(JavaValue target, Type type, Context context) { + if (type.getDescriptor().equals("[C")) { + if (!(target.value() instanceof char[])) { + return false; + } + } + return true; + } + + @Override + public boolean checkEquality(JavaValue first, JavaValue second, Context context) { + return false; + } + + @Override + public boolean canCheckInstanceOf(JavaValue target, Type type, Context context) { + if(type.getDescriptor().equals("java/lang/Class")) + return true; + return false; + } + + @Override + public boolean canCheckcast(JavaValue target, Type type, Context context) { + return true; + } + + @Override + public boolean canCheckEquality(JavaValue first, JavaValue second, Context context) { + return false; + } + }); + System.out.println("[Special] [ParamorphismTransformer] Starting"); + if(getDeobfuscator().invaildClasses.isEmpty() && !OVERRIDE) + { + System.out.println("[Special] [ParamorphismTransformer] Paramorphism not found or option not enabled. Exiting"); + return false; + } + AtomicInteger annotRemoved = new AtomicInteger(); + AtomicInteger flow = new AtomicInteger(); + AtomicInteger methodCalls = new AtomicInteger(); + AtomicInteger fieldCalls = new AtomicInteger(); + AtomicInteger stringCalls = new AtomicInteger(); + //Remove annotations + for(ClassNode classNode : classNodes()) + { + if(classNode.invisibleAnnotations != null) + { + Iterator itr = classNode.invisibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode next = itr.next(); + if(next.desc != null && next.desc.length() > 50) + { + itr.remove(); + annotRemoved.incrementAndGet(); + } + } + } + for(FieldNode field : classNode.fields) + if(field.invisibleAnnotations != null) + { + Iterator itr = field.invisibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode next = itr.next(); + if(next.desc != null && next.desc.length() > 50) + { + itr.remove(); + annotRemoved.incrementAndGet(); + } + } + } + for(MethodNode method : classNode.methods) + if(method.invisibleAnnotations != null) + { + Iterator itr = method.invisibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode next = itr.next(); + if(next.desc != null && next.desc.length() > 50) + { + itr.remove(); + annotRemoved.incrementAndGet(); + } + } + } + } + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if((Modifier.isStatic(method.access) || method.name.equals("")) && ain.getOpcode() == Opcodes.NEW + && ((TypeInsnNode)ain).desc.equals("java/lang/Object") + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.DUP + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.INVOKESPECIAL + && ((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Object") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("") + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.DUP + && ain.getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IFNONNULL) + { + AbstractInsnNode next = ain.getNext().getNext().getNext().getNext().getNext(); + while(next.getNext() != null && next.getNext().getOpcode() == Opcodes.INVOKEINTERFACE) + next = next.getNext(); + if(next.getNext() == null || !((next.getNext().getOpcode() >= Opcodes.IRETURN && + next.getNext().getOpcode() <= Opcodes.RETURN) || next.getNext().getOpcode() == Opcodes.ATHROW)) + continue; + AbstractInsnNode end = next.getNext(); + LabelNode lbl = ((JumpInsnNode)ain.getNext().getNext().getNext().getNext()).label; + method.instructions.remove(lbl.getNext()); + while(ain.getNext() != end) + method.instructions.remove(ain.getNext()); + method.instructions.remove(end); + method.instructions.remove(ain); + if(method.instructions.contains(lbl)) + method.instructions.remove(lbl); + flow.incrementAndGet(); + }else if(!Modifier.isStatic(method.access) && !method.name.equals("") && ain.getOpcode() == Opcodes.ALOAD + && ((VarInsnNode)ain).var == 0 && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.DUP + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.IFNONNULL) + { + AbstractInsnNode next = ain.getNext().getNext().getNext(); + while(next.getNext() != null && next.getNext().getOpcode() == Opcodes.INVOKEINTERFACE) + next = next.getNext(); + if(next.getNext() == null || !((next.getNext().getOpcode() >= Opcodes.IRETURN && + next.getNext().getOpcode() <= Opcodes.RETURN) || next.getNext().getOpcode() == Opcodes.ATHROW)) + continue; + AbstractInsnNode end = next.getNext(); + LabelNode lbl = ((JumpInsnNode)ain.getNext().getNext()).label; + method.instructions.remove(lbl.getNext()); + while(ain.getNext() != end) + method.instructions.remove(ain.getNext()); + method.instructions.remove(end); + method.instructions.remove(ain); + if(method.instructions.contains(lbl)) + method.instructions.remove(lbl); + flow.incrementAndGet(); + } + //Remove goto switchup + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + boolean modified = false; + for(AbstractInsnNode ain : method.instructions.toArray()) + { + boolean passed = false; + List labels = new ArrayList<>(); + while((passed ? ain.getOpcode() == Opcodes.GOTO : ain instanceof JumpInsnNode) + && ((JumpInsnNode)ain).label.getNext() != null && ((JumpInsnNode)ain).label.getNext().getOpcode() == Opcodes.GOTO) + { + if(labels.contains(((JumpInsnNode)ain).label)) + break; + labels.add(((JumpInsnNode)ain).label); + ((JumpInsnNode)ain).label = ((JumpInsnNode)((JumpInsnNode)ain).label.getNext()).label; + flow.incrementAndGet(); + passed = true; + modified = true; + } + } + if(modified) + { + InstructionModifier modifier = new InstructionModifier(); + + Frame[] frames; + try + { + frames = new Analyzer<>(new BasicInterpreter()).analyze(classNode.name, method); + }catch(AnalyzerException e) + { + throw new RuntimeException(e); + } + for(int i = 0; i < method.instructions.size(); i++) + { + if(!Utils.isInstruction(method.instructions.get(i))) continue; + if(frames[i] != null) continue; + + modifier.remove(method.instructions.get(i)); + } + + modifier.apply(method); + + // empty try catch nodes are illegal + if(method.tryCatchBlocks != null) + method.tryCatchBlocks.removeIf(tryCatchBlockNode -> Utils.getNext(tryCatchBlockNode.start) == + Utils.getNext(tryCatchBlockNode.end)); + for(AbstractInsnNode node : method.instructions.toArray()) + if(node.getOpcode() == Opcodes.GOTO) + { + AbstractInsnNode a = Utils.getNext(node); + AbstractInsnNode b = Utils.getNext(((JumpInsnNode)node).label); + if(a == b) + method.instructions.remove(node); + } + } + } + //Decrypt classes + Set toRemove = new HashSet<>(); + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + AbstractInsnNode first = clinit == null ? null : clinit.instructions.getFirst(); + if(first != null && first.getOpcode() == Opcodes.NEW + && first.getNext() != null && first.getNext().getOpcode() == Opcodes.DUP + && first.getNext().getNext() != null && first.getNext().getNext().getOpcode() == Opcodes.LDC + && ((LdcInsnNode)first.getNext().getNext()).cst instanceof Type + && first.getNext().getNext().getNext() != null + && first.getNext().getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)first.getNext().getNext().getNext()).name.equals("getClassLoader") + && ((MethodInsnNode)first.getNext().getNext().getNext()).owner.equals("java/lang/Class") + && first.getNext().getNext().getNext().getNext() != null + && first.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.LDC + && first.getNext().getNext().getNext().getNext().getNext() != null + && Utils.isInteger(first.getNext().getNext().getNext().getNext().getNext())) + { + int len = Utils.getIntValue(first.getNext().getNext().getNext().getNext().getNext()); + byte[] encrypted = getDeobfuscator().getInputPassthrough().get(((LdcInsnNode)first.getNext().getNext().getNext().getNext()).cst); + if(encrypted == null) + { + System.out.println("Encrypted file not found for " + classNode.name); + continue; + } + byte[] decrypted = new byte[len]; + try + { + DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(encrypted)); + dataStream.readFully(decrypted, 0, dataStream.available()); + for(int i = 7; i >= 0; i--) + decrypted[7 - i] = (byte)((int)(2272919233031569408L >> 8 * i & 255L)); + GZIPInputStream gzipStream = new GZIPInputStream(new ByteArrayInputStream(decrypted.clone())); + dataStream = new DataInputStream(gzipStream); + dataStream.readFully(decrypted); + gzipStream.close(); + getDeobfuscator().getInputPassthrough().remove(((LdcInsnNode)first.getNext().getNext().getNext().getNext()).cst); + toRemove.add(((TypeInsnNode)first).desc);//Loader + toRemove.add(classNode.name);//Interface impl + ClassReader reader = new ClassReader(decrypted); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_FRAMES); + toRemove.add(node.interfaces.get(0));//Interface + //Decrypt all + Map replacements = new HashMap<>(); + JavaValue instance = JavaValue.valueOf(new Object()); + for(MethodNode method : node.methods) + { + AbstractInsnNode mode = null; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof FieldInsnNode || ain instanceof MethodInsnNode) + { + mode = ain; + break; + }else if(ain instanceof LdcInsnNode && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.ARETURN) + { + mode = ain; + break; + } + if(mode == null) + throw new IllegalStateException("Could not find decryptor for method"); + Context context = new Context(provider); + if(mode.getOpcode() == Opcodes.GETFIELD && ((FieldInsnNode)mode).owner.equals(node.name)) + { + String res = MethodExecutor.execute(node, method, Arrays.asList(), instance, context); + replacements.put(method, new LdcInsnNode(res)); + }else if(mode instanceof FieldInsnNode || mode instanceof MethodInsnNode) + replacements.put(method, mode.clone(null)); + else if(mode instanceof LdcInsnNode) + replacements.put(method, mode.clone(null)); + } + for(ClassNode cn : classNodes()) + for(MethodNode mn : cn.methods) + for(AbstractInsnNode ain : mn.instructions.toArray()) + if(ain instanceof MethodInsnNode && ((MethodInsnNode)ain).owner.equals(classNode.name)) + { + MethodNode caller = node.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)ain).name) + && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + mn.instructions.set(ain, replacements.get(caller).clone(null)); + if(replacements.get(caller) instanceof LdcInsnNode) + stringCalls.incrementAndGet(); + else if(replacements.get(caller) instanceof MethodInsnNode) + methodCalls.incrementAndGet(); + else if(replacements.get(caller) instanceof FieldInsnNode) + fieldCalls.incrementAndGet(); + } + }catch(Exception e) + { + e.printStackTrace(); + System.out.println("Decryption failed for " + classNode.name); + continue; + } + } + } + for(String s : toRemove) + { + classes.remove(s); + classpath.remove(s); + } + System.out.println("[Special] [ParamorphismTransformer] Removed " + annotRemoved + " annotations"); + System.out.println("[Special] [ParamorphismTransformer] Removed " + flow + " flow obfuscations"); + System.out.println("[Special] [ParamorphismTransformer] Decrypted " + methodCalls + " method calls"); + System.out.println("[Special] [ParamorphismTransformer] Decrypted " + fieldCalls + " field calls"); + System.out.println("[Special] [ParamorphismTransformer] Decrypted " + stringCalls + " strings"); + return methodCalls.get() > 0 || fieldCalls.get() > 0 || stringCalls.get() > 0; + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/HideAccessObfuscationTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/HideAccessObfuscationTransformer.java index a3cb58de..cf3c2af5 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/HideAccessObfuscationTransformer.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/HideAccessObfuscationTransformer.java @@ -460,7 +460,7 @@ private void castFix(Context context) { for(int i = 0; i < mn.instructions.size(); i++) { AbstractInsnNode ain = mn.instructions.get(i); - if(ain.getOpcode() == Opcodes.CHECKCAST) + if(ain.getOpcode() == Opcodes.CHECKCAST && frames[i] != null) { Type typeBefore = frames[i].getStack(frames[i].getStackSize() - 1).getType(); Type typeNow = Type.getObjectType(((TypeInsnNode)ain).desc); @@ -473,9 +473,12 @@ else if(typeBefore.getSort() == Type.OBJECT && typeBefore.getInternalName().equa else if(typeNow.getSort() == Type.OBJECT && typeNow.getInternalName().equals("java/lang/Object") && typeBefore.getSort() == Type.ARRAY) set.add(ain); - else if(typeNow.getSort() == Type.OBJECT && typeBefore.getSort() == Type.OBJECT - && getDeobfuscator().isSubclass(typeNow.getInternalName(), typeBefore.getInternalName())) - set.add(ain); + else if(typeNow.getSort() == Type.OBJECT && typeBefore.getSort() == Type.OBJECT) + try + { + if(getDeobfuscator().isSubclass(typeNow.getInternalName(), typeBefore.getInternalName())) + set.add(ain); + }catch(Exception e) {} else if(typeNow.getSort() == Type.ARRAY && typeBefore.getSort() == Type.ARRAY) { typeBefore = typeBefore.getElementType(); diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/StringEncryptionTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/StringEncryptionTransformer.java index 69c0188f..024b230e 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/StringEncryptionTransformer.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/stringer/StringEncryptionTransformer.java @@ -95,6 +95,9 @@ public class StringEncryptionTransformer extends Transformer 0) + System.out.println("[Stringer] [StringEncryptionTransformer] Concatted " + concat + " strings"); int count = count(); System.out.println("[Stringer] [StringEncryptionTransformer] Found " + count + " encrypted strings"); if (count > 0) { @@ -106,6 +109,31 @@ public boolean transform() { System.out.println("[Stringer] [StringEncryptionTransformer] Done"); return true; } + + private int concatStrings() + { + int count = 0; + for(ClassNode classNode : classNodes()) + { + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.LDC && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.LDC + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("concat")) + { + method.instructions.remove(ain.getNext().getNext()); + String first = (String)((LdcInsnNode)ain).cst; + String sec = (String)((LdcInsnNode)ain.getNext()).cst; + String res = first.concat(sec); + method.instructions.remove(ain.getNext()); + ((LdcInsnNode)ain).cst = res; + count++; + } + } + } + return count; + } private int cleanup() { AtomicInteger total = new AtomicInteger(); diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/utils/Utils.java b/src/main/java/com/javadeobfuscator/deobfuscator/utils/Utils.java index 21206bbf..38920bac 100644 --- a/src/main/java/com/javadeobfuscator/deobfuscator/utils/Utils.java +++ b/src/main/java/com/javadeobfuscator/deobfuscator/utils/Utils.java @@ -328,31 +328,38 @@ public static InsnList cloneInsnList(InsnList original) { return newInsnList; } - public static AbstractInsnNode getNumberInsn(int num) { - switch (num) { - case -1: - return new InsnNode(Opcodes.ICONST_M1); - case 0: - return new InsnNode(Opcodes.ICONST_0); - case 1: - return new InsnNode(Opcodes.ICONST_1); - case 2: - return new InsnNode(Opcodes.ICONST_2); - case 3: - return new InsnNode(Opcodes.ICONST_3); - case 4: - return new InsnNode(Opcodes.ICONST_4); - case 5: - return new InsnNode(Opcodes.ICONST_5); - default: - if(num >= -128 && num <= 127) - return new IntInsnNode(Opcodes.BIPUSH, num); - else if(num >= -32768 && num <= 32767) - return new IntInsnNode(Opcodes.SIPUSH, num); - else - return new LdcInsnNode(num); + public static AbstractInsnNode getNumberInsn(int number) { + if (number >= -1 && number <= 5) + return new InsnNode(number + 3); + else if (number >= -128 && number <= 127) + return new IntInsnNode(Opcodes.BIPUSH, number); + else if (number >= -32768 && number <= 32767) + return new IntInsnNode(Opcodes.SIPUSH, number); + else + return new LdcInsnNode(number); + } + + public static AbstractInsnNode getLongInsn(long number) { + if (number >= 0 && number <= 1) + return new InsnNode((int) (number + 9)); + else + return new LdcInsnNode(number); + } + + public static AbstractInsnNode getFloatInsn(float number) { + if (number >= 0 && number <= 2) { + return new InsnNode((int) (number + 11)); + } else { + return new LdcInsnNode(number); } } + + public static AbstractInsnNode getDoubleInsn(double number) { + if (number >= 0 && number <= 1) + return new InsnNode((int) (number + 14)); + else + return new LdcInsnNode(number); + } public static void printClass(ClassNode classNode) { System.out.println(classNode.name + '\n');