From d31a1358e18ca6015f0b379b305f815071d26d97 Mon Sep 17 00:00:00 2001 From: ThisTestUser Date: Sat, 8 Aug 2020 11:09:55 -0400 Subject: [PATCH] August Update --- README.md | 6 +- commonerrors/Allatori.md | 2 + commonerrors/AntiReleak.md | 2 + commonerrors/DashO.md | 2 + commonerrors/General.md | 1 + commonerrors/JObf.md | 2 + commonerrors/Radon.md | 2 + commonerrors/Stringer.md | 2 + docs/index.md | 8 +- .../antireleak/InvokedynamicTransformer.java | 788 +++++++++ .../StringEncryptionTransformer.java | 299 ++++ .../transformers/special/JObfTransformer.java | 814 +++++++++ .../special/RadonTransformer.java | 940 +++++++++++ .../special/RadonTransformerV2.java | 1463 +++++++++++++++++ 14 files changed, 4326 insertions(+), 5 deletions(-) create mode 100644 commonerrors/Allatori.md create mode 100644 commonerrors/AntiReleak.md create mode 100644 commonerrors/DashO.md create mode 100644 commonerrors/General.md create mode 100644 commonerrors/JObf.md create mode 100644 commonerrors/Radon.md create mode 100644 commonerrors/Stringer.md create mode 100644 src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/InvokedynamicTransformer.java create mode 100644 src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/StringEncryptionTransformer.java create mode 100644 src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/JObfTransformer.java create mode 100644 src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformer.java create mode 100644 src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformerV2.java diff --git a/README.md b/README.md index fdfd713c..8c56aee3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Deobfuscator [![Build Status](https://ci.samczsun.com/buildStatus/icon?job=java-deobfuscator/Deobfuscator)](https://ci.samczsun.com/job/java-deobfuscator/job/Deobfuscator) +# Deobfuscator This project aims to deobfuscate most commercially-available obfuscators for Java. @@ -36,6 +36,8 @@ Take a look at [USAGE.md](USAGE.md) for more information. If you're trying to recover the names of classes or methods, tough luck. That information is typically stripped out and there's no way to recover it. +If you are using one of our transformers, check out the commonerrors folder to check for tips. + Otherwise, check out [this guide](CUSTOMTRANSFORMER.md) on how to implement your own transformer (also, open a issue/PR so I can add support for it) ## Supported Obfuscators @@ -70,4 +72,4 @@ They were written specifically for Android apps. ## Licensing -Java Deobfuscator is licensed under the Apache 2.0 license. +Java Deobfuscator is licensed under the Apache 2.0 license. \ No newline at end of file diff --git a/commonerrors/Allatori.md b/commonerrors/Allatori.md new file mode 100644 index 00000000..57391241 --- /dev/null +++ b/commonerrors/Allatori.md @@ -0,0 +1,2 @@ +## Flow Obfuscation +The latest versions of Allatori contain some flow obfuscation which moves around some instructions, so it is necessary to run allatori.FlowObfuscationTransformer to fix this. This transformer should always work, but if it doesn't, open an issue! Note: It is not necessary to run this before allatori.StringEncryptionTransformer. \ No newline at end of file diff --git a/commonerrors/AntiReleak.md b/commonerrors/AntiReleak.md new file mode 100644 index 00000000..a5c49672 --- /dev/null +++ b/commonerrors/AntiReleak.md @@ -0,0 +1,2 @@ +## InvokeDynamic Transformer +This transformer was made to be a universal decryptor for all antireleak invokedynamics. This will only reverse the invokedynamics so you can reverse engineer the plugin, not remove all traces of your user id! \ No newline at end of file diff --git a/commonerrors/DashO.md b/commonerrors/DashO.md new file mode 100644 index 00000000..ba017e4e --- /dev/null +++ b/commonerrors/DashO.md @@ -0,0 +1,2 @@ +## String Encryption Transformer +If you get an error relating to SourceResult, it is because DashO's flow obfuscation was applied, which messes with the string decryptor. Run dasho.FlowObfuscationTransformer first, and you will able to decrypt all the strings. \ No newline at end of file diff --git a/commonerrors/General.md b/commonerrors/General.md new file mode 100644 index 00000000..23528c28 --- /dev/null +++ b/commonerrors/General.md @@ -0,0 +1 @@ +In general, files must be deobfuscated in the reverse order in which obfuscators were applied. For example, if your file is obfuscated with Stringer and then Allatori, you would run the Allatori transformers, and then the Stringer ones. Also, if an obfuscator was applied multiple times (e.g. several layers of Allatori), you must run the transformers multiple times. The only exception with this rule is with flow obfuscation transformers, which typically do not need to be run multiple times. \ No newline at end of file diff --git a/commonerrors/JObf.md b/commonerrors/JObf.md new file mode 100644 index 00000000..4e07be47 --- /dev/null +++ b/commonerrors/JObf.md @@ -0,0 +1,2 @@ +## JObfTransformer (superblaubeere27's obfuscator) +One issue with this obfuscator is that it may inject local variables into the code, which prevents the file from being completely deobfuscated. In that case, use general.LocalVariableRemover, write to file, and then run the deobfuscator on the output. If you are still having issues with some features not being deobfuscated, use DeadCodeRemover and then RedundantGotoRemover to get rid of the junk code that may be in the way. Note that there are some settings which you cannot configure in this transformer without forking this project. \ No newline at end of file diff --git a/commonerrors/Radon.md b/commonerrors/Radon.md new file mode 100644 index 00000000..9c5c123f --- /dev/null +++ b/commonerrors/Radon.md @@ -0,0 +1,2 @@ +## RadonTransformer(V2) +RadonTransformer was meant to work on the earlier versions of Radon, while RadonTransformerV2 works on the later versions. Use RadonTransformer first, and if it doesn't work, use RadonTransformerV2. Note that there are some settings in the transformer that you cannot configure without forking the project. \ No newline at end of file diff --git a/commonerrors/Stringer.md b/commonerrors/Stringer.md new file mode 100644 index 00000000..4aa7262a --- /dev/null +++ b/commonerrors/Stringer.md @@ -0,0 +1,2 @@ +## HideAccessObfuscation Transformer +If you see an error while running the transformer, it means that you did not remove the string encryption. Run stringer.StringEncryptionTransformer first and you will be able decrypt Stringer's hide access. Note that you need to add all libraries that are used by the JAR, otherwise you will not be able to decrypt the file! diff --git a/docs/index.md b/docs/index.md index 833f91ef..8c56aee3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,9 +1,9 @@ -# Deobfuscator [![Build Status](https://ci.samczsun.com/buildStatus/icon?job=java-deobfuscator/Deobfuscator)](https://ci.samczsun.com/job/java-deobfuscator/job/Deobfuscator) +# Deobfuscator This project aims to deobfuscate most commercially-available obfuscators for Java. ## Updates -To download an updated version of Java Deobfuscator, go to the [releases tab on the GitHub repo](https://github.com/java-deobfuscator/deobfuscator/releases). +To download an updated version of Java Deobfuscator, go to the releases tab. If you would like to run this program with a GUI, go to https://github.com/java-deobfuscator/deobfuscator-gui and grab a download. Put the deobfuscator-gui.jar in the same folder as deobfuscator.jar. @@ -36,6 +36,8 @@ Take a look at [USAGE.md](USAGE.md) for more information. If you're trying to recover the names of classes or methods, tough luck. That information is typically stripped out and there's no way to recover it. +If you are using one of our transformers, check out the commonerrors folder to check for tips. + Otherwise, check out [this guide](CUSTOMTRANSFORMER.md) on how to implement your own transformer (also, open a issue/PR so I can add support for it) ## Supported Obfuscators @@ -70,4 +72,4 @@ They were written specifically for Android apps. ## Licensing -Java Deobfuscator is licensed under the Apache 2.0 license. +Java Deobfuscator is licensed under the Apache 2.0 license. \ No newline at end of file diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/InvokedynamicTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/InvokedynamicTransformer.java new file mode 100644 index 00000000..ab4c04a3 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/InvokedynamicTransformer.java @@ -0,0 +1,788 @@ +/* + * 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.antireleak; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +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.defined.types.JavaClass; +import com.javadeobfuscator.deobfuscator.executor.defined.types.JavaMethodHandle; +import com.javadeobfuscator.deobfuscator.executor.exceptions.ExecutionException; +import com.javadeobfuscator.deobfuscator.executor.providers.ComparisonProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.DelegatingProvider; +import com.javadeobfuscator.deobfuscator.executor.values.JavaInteger; +import com.javadeobfuscator.deobfuscator.executor.values.JavaObject; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +public class InvokedynamicTransformer extends Transformer +{ + private Map bootstrapMap = new HashMap<>(); + + @Override + public boolean transform() + { + System.out.println("[AntiReleak] [InvokedynamicTransformer] Starting"); + System.out.println( + "[AntiReleak] [InvokedynamicTransformer] Finding invokedynamic instructions"); + int amount = findInvokeDynamic(); + System.out.println("[AntiReleak] [InvokedynamicTransformer] Found " + + amount + " invokedynamic instructions"); + if(amount > 0) + { + System.out.println( + "[AntiReleak] [InvokedynamicTransformer] Inlining invokedynamic"); + long start = System.currentTimeMillis(); + int inlined = inlineInvokeDynamic(amount); + long end = System.currentTimeMillis(); + System.out.println("[AntiReleak] [InvokedynamicTransformer] Removed " + + inlined + " invokedynamic instructions, took " + + TimeUnit.MILLISECONDS.toSeconds(end - start) + "s"); + System.out.println( + "[AntiReleak] [InvokedynamicTransformer] Cleaning up bootstrap methods"); + cleanup(); + } + System.out.println("[AntiReleak] [InvokedynamicTransformer] Done"); + return amount > 0; + } + + private int findInvokeDynamic() + { + AtomicInteger total = new AtomicInteger(); + classNodes().forEach(classNode -> { + classNode.methods.forEach(methodNode -> { + for(int i = 0; i < methodNode.instructions.size(); i++) + { + AbstractInsnNode abstractInsnNode = + methodNode.instructions.get(i); + if(abstractInsnNode instanceof InvokeDynamicInsnNode) + { + InvokeDynamicInsnNode dyn = + (InvokeDynamicInsnNode)abstractInsnNode; + if(dyn.bsmArgs.length == 3 && + (dyn.bsmArgs[2].equals(182) || dyn.bsmArgs[2].equals(184))) + total.incrementAndGet(); + else if(dyn.bsmArgs.length == 8 && dyn.bsmArgs[0] instanceof Integer) + total.incrementAndGet(); + else if(dyn.bsmArgs.length == 9 && dyn.bsmArgs[0] instanceof String + && isInteger((String)dyn.bsmArgs[0])) + total.incrementAndGet(); + else if(dyn.bsmArgs.length == 7 && dyn.bsmArgs[0].equals("1") + && dyn.bsmArgs[1].equals("8")) + total.incrementAndGet(); + } + } + }); + }); + return total.get(); + } + + private int inlineInvokeDynamic(int expected) + { + AtomicInteger total = new AtomicInteger(); + final boolean[] alerted = new boolean[100]; + + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new PrimitiveFieldProvider()); + provider.register(new MappedFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new ComparisonProvider() + { + @Override + public boolean instanceOf(JavaValue target, Type type, + Context context) + { + if(type.getDescriptor().equals("Ljava/lang/String;")) + if(!(target.value() instanceof String)) + return false; + if(type.getDescriptor().equals("Ljava/lang/Integer;")) + if(!(target.value() instanceof Integer)) + return false; + return true; + } + + @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 true; + } + + @Override + public boolean canCheckInstanceOf(JavaValue target, Type type, + Context context) + { + return true; + } + + @Override + public boolean canCheckcast(JavaValue target, Type type, + Context context) + { + return true; + } + + @Override + public boolean canCheckEquality(JavaValue first, JavaValue second, + Context context) + { + return false; + } + }); + classNodes().forEach(classNode -> { + patchIndyTransformer(classNode); + classNode.methods.forEach(methodNode -> { + for(int i = 0; i < methodNode.instructions.size(); i++) + { + AbstractInsnNode abstractInsnNode = + methodNode.instructions.get(i); + if(abstractInsnNode instanceof InvokeDynamicInsnNode) + { + InvokeDynamicInsnNode dyn = + (InvokeDynamicInsnNode)abstractInsnNode; + if((dyn.bsmArgs.length == 3 && + (dyn.bsmArgs[2].equals(182) || dyn.bsmArgs[2].equals(184))) + || (dyn.bsmArgs.length == 9 && dyn.bsmArgs[0] instanceof String && isInteger((String)dyn.bsmArgs[0])) + || (dyn.bsmArgs.length == 8 && dyn.bsmArgs[0] instanceof Integer) + || (dyn.bsmArgs.length == 7 && dyn.bsmArgs[0].equals("1") && dyn.bsmArgs[1].equals("8"))) + { + Handle bootstrap = dyn.bsm; + ClassNode bootstrapClassNode = + classes.get(bootstrap.getOwner()); + MethodNode bootstrapMethodNode = + bootstrapClassNode.methods.stream() + .filter(mn -> mn.name + .equals(bootstrap.getName()) + && mn.desc + .equals(bootstrap.getDesc())) + .findFirst().orElse(null); + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); // Lookup + args.add(JavaValue.valueOf(dyn.name)); // dyn + // method + // name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); // dyn method + // type + Context context = new Context(provider); + context.dictionary = classpath; + context.constantPools = getDeobfuscator().getConstantPools(); + context.file = getDeobfuscator().getConfig().getInput(); + for(int i1 = 0; i1 < dyn.bsmArgs.length; i1++) + { + Object o = dyn.bsmArgs[i1]; + if(o.getClass() == Type.class) + { + Type type = (Type)o; + args.add(JavaValue.valueOf(new JavaClass( + type.getInternalName().replace('/', '.'), context))); + }else if(o.getClass() == Integer.class && Type.getArgumentTypes(bootstrapMethodNode.desc)[i1 + 3] + .getSort() == Type.INT) + args.add(new JavaInteger((int)o)); + else + args.add(JavaValue.valueOf(o)); + } + try + { + JavaMethodHandle result = + MethodExecutor.execute(bootstrapClassNode, + bootstrapMethodNode, args, null, + context); + String clazz = + result.clazz.replace('.', '/'); + MethodInsnNode replacement = null; + switch(result.type) + { + case "virtual": + replacement = new MethodInsnNode( + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? Opcodes.INVOKEINTERFACE + : Opcodes.INVOKEVIRTUAL, clazz, + result.name, result.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode( + Opcodes.INVOKESTATIC, clazz, + result.name, result.desc, + false); + break; + } + methodNode.instructions.set(abstractInsnNode, replacement); + //Removes extra casts (should be ok?) + if(replacement.getNext() != null + && replacement.getNext().getOpcode() == Opcodes.CHECKCAST + && Type.getReturnType(replacement.desc).getDescriptor().equals(((TypeInsnNode)replacement.getNext()).desc)) + methodNode.instructions.remove(replacement.getNext()); + total.incrementAndGet(); + int x = (int)(total.get() * 1.0d / expected + * 100); + if(x != 0 && x % 10 == 0 && !alerted[x - 1]) + { + System.out.println( + "[AntiReleak] [InvokedynamicTransformer] Done " + + x + "%"); + alerted[x - 1] = true; + } + }catch(ExecutionException ex) + { + if(ex.getCause() != null) + ex.getCause() + .printStackTrace(System.out); + throw ex; + }catch(Throwable t) + { + System.out.println(classNode.name); + throw t; + } + } + } + } + }); + }); + return total.get(); + } + + /** + * Patch the invokedynamic bootstrap method, jumping to the ConstantCallSite section. + * @param classNode The class + */ + private void patchIndyTransformer(ClassNode classNode) + { + Iterator it = classNode.methods.iterator(); + while(it.hasNext()) + { + MethodNode node = it.next(); + if(node.desc.equals( + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;")) + { + InsnList list = Utils.cloneInsnList(node.instructions); + boolean patched = false; + for(int i = 0; i < node.instructions.size(); i++) + { + AbstractInsnNode abstractInsnNode = + node.instructions.get(i); + if(abstractInsnNode.getType() == AbstractInsnNode.LABEL) + { + LabelNode labelNode = (LabelNode)abstractInsnNode; + AbstractInsnNode after3 = labelNode.getNext().getNext().getNext(); + if(labelNode.getNext() != null && labelNode.getNext().getOpcode() == Opcodes.ASTORE + && labelNode.getNext().getNext() != null && labelNode.getNext().getNext().getOpcode() == + Opcodes.ALOAD && after3 != null && after3.getOpcode() == Opcodes.INVOKESTATIC) + { + //Start at label 80 by removing everything before it + while(labelNode.getPrevious() != null) + node.instructions.remove(labelNode.getPrevious()); + //Delete "astore10" from Label 80 + node.instructions.remove(labelNode.getNext()); + //5 nexts from after3 if the beginning of label 101 + AbstractInsnNode after8 = after3.getNext().getNext().getNext().getNext().getNext(); + if(after8 != null + && after8.getType() == AbstractInsnNode.LABEL) + if(after8.getNext() != null + && after8.getNext().getOpcode() == Opcodes.ILOAD + && after8.getNext().getNext() != null + && after8.getNext().getNext().getOpcode() == Opcodes.IFNE) + { + //Label 101 (after 80) is a trap, delete it (Removing getNext twice) + node.instructions.remove(after8.getNext()); + node.instructions.remove(after8.getNext()); + patched = true; + } + } + } + } + if(patched) + bootstrapMap.put(classNode, list); + }else if(node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + InsnList list = Utils.cloneInsnList(node.instructions); + boolean patched = false; + for(int i = 0; i < node.instructions.size(); i++) + { + AbstractInsnNode ain = + node.instructions.get(i); + if(ain.getOpcode() == Opcodes.INVOKESTATIC) + { + MethodInsnNode methodInsn = (MethodInsnNode)ain; + if(methodInsn.desc.equals("(Ljava/lang/String;)Ljava/lang/String;") + && methodInsn.name.equals("getProperty") + && methodInsn.owner.equals("java/lang/System")) + { + node.instructions.remove(methodInsn.getPrevious()); + node.instructions.set(methodInsn, new LdcInsnNode("1.8.0_144")); + patched = true; + } + } + } + if(patched) + { + for(int i = 0; i < node.instructions.size(); i++) + { + AbstractInsnNode ain = + node.instructions.get(i); + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).owner.equals("java/lang/Object") + && ((MethodInsnNode)ain).name.equals("equals")) + node.instructions.remove(ain.getNext()); + else if(ain.getOpcode() == Opcodes.LDC + && ((LdcInsnNode)ain).cst instanceof Type + && ((Type)((LdcInsnNode)ain).cst).getInternalName().equals("java/net/URL") + && ain.getPrevious() != null && ain.getPrevious().getOpcode() == Opcodes.IFNONNULL + && ain.getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getOpcode() == Opcodes.ALOAD) + { + LabelNode toJump = null; + boolean first = false; + AbstractInsnNode next = ain.getNext(); + while(next != null) + { + if(next.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)next).owner.equals("java/lang/String") + && ((MethodInsnNode)next).name.equals("equals")) + { + if(!first) + { + first = true; + next = next.getNext(); + continue; + } + toJump = (LabelNode)next.getNext(); + break; + } + next = next.getNext(); + } + node.instructions.set(ain.getPrevious(), new JumpInsnNode(Opcodes.GOTO, toJump)); + while(next != null) + { + if(next.getOpcode() == Opcodes.IFEQ) + { + node.instructions.remove(next); + break; + } + next = next.getNext(); + } + }else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).owner.equals("java/lang/String") + && ((MethodInsnNode)ain).name.equals("length") + && ain.getNext().getOpcode() == Opcodes.ICONST_5 + && ain.getNext().getNext().getOpcode() == Opcodes.IF_ICMPLE) + { + AbstractInsnNode next = ain.getNext(); + boolean foundEquals = false; + while(next != null) + { + if(next.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)next).owner.equals("java/lang/Object") + && ((MethodInsnNode)next).name.equals("equals")) + foundEquals = true; + if(foundEquals && next.getOpcode() == -1) + { + node.instructions.insert(ain.getNext().getNext(), + new JumpInsnNode(Opcodes.GOTO, (LabelNode)next)); + break; + } + next = next.getNext(); + } + }else if(ain.getOpcode() == Opcodes.AASTORE + && ain.getPrevious().getOpcode() == Opcodes.INVOKESTATIC + && ain.getPrevious().getPrevious().getOpcode() == Opcodes.LDC + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.AASTORE + && ain.getNext().getNext() != null + && ain.getNext().getNext().getOpcode() == Opcodes.GETSTATIC) + { + AbstractInsnNode next = ain.getNext().getNext().getNext().getNext().getNext().getNext(); + if(next.getOpcode() != Opcodes.ALOAD) + while(next != null) + { + if(next.getOpcode() == -1 + && next.getNext() != null + && next.getNext().getOpcode() == Opcodes.ALOAD + && next.getNext().getNext() != null + && next.getNext().getNext().getOpcode() == Opcodes.INSTANCEOF) + { + node.instructions.insert( + ain.getNext().getNext().getNext().getNext().getNext(), + new JumpInsnNode(Opcodes.GOTO, (LabelNode)next)); + node.instructions.remove(next.getNext().getNext()); + node.instructions.set(next.getNext(), new InsnNode(Opcodes.ICONST_1)); + break; + } + next = next.getNext(); + } + }else if(ain.getOpcode() == Opcodes.POP + && ((ain.getPrevious() != null + && ain.getPrevious() instanceof LabelNode + && ain.getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getPrevious().getPrevious()).name.equals("invoke") + && ((MethodInsnNode)ain.getPrevious().getPrevious()).owner.equals("java/lang/reflect/Method") + && ain.getPrevious().getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getPrevious().getOpcode() == Opcodes.AASTORE + && ain.getPrevious().getPrevious().getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getPrevious().getPrevious().getOpcode() == Opcodes.ALOAD + && ain.getPrevious().getPrevious().getPrevious().getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getPrevious().getPrevious().getPrevious().getOpcode() == Opcodes.ICONST_0) + || (ain.getPrevious() != null + && ain.getPrevious().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getPrevious()).name.equals("invoke") + && ((MethodInsnNode)ain.getPrevious()).owner.equals("java/lang/reflect/Method") + && ain.getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getOpcode() == Opcodes.AASTORE + && ain.getPrevious().getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getPrevious().getOpcode() == Opcodes.ALOAD + && ain.getPrevious().getPrevious().getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getPrevious().getPrevious().getOpcode() == Opcodes.ICONST_0))) + { + //ProxySelector.setDefault() + while(ain.getPrevious().getOpcode() != Opcodes.CHECKCAST) + { + node.instructions.remove(ain.getPrevious()); + i--; + } + node.instructions.insert(ain.getPrevious(), new InsnNode(Opcodes.POP)); + }else if(ain.getOpcode() == Opcodes.BIPUSH + && ((IntInsnNode)ain).operand == 20 + && ain.getNext() != null + && ain.getNext().getOpcode() == Opcodes.AALOAD + && ain.getNext().getNext() != null + && ain.getNext().getNext().getOpcode() == Opcodes.AASTORE) + { + //ProxySelector.getDefault() + AbstractInsnNode next = ain; + boolean notFound = false; + while(next != null) + { + if(next.getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)next).name.equals("forName") + && ((MethodInsnNode)next).owner.equals("java/lang/Class")) + break; + if(next.getOpcode() == Opcodes.LDC + && ((LdcInsnNode)next).cst instanceof Type) + { + notFound = true; + break; + } + next = next.getNext(); + } + InsnNode popNode = null; + if(!notFound) + { + while(next.getNext() != null) + { + if(next.getNext().getOpcode() == Opcodes.LDC + && ((LdcInsnNode)next.getNext()).cst instanceof Type) + { + node.instructions.set(next, popNode = new InsnNode(Opcodes.POP)); + break; + } + node.instructions.remove(next.getNext()); + } + LabelNode toJump = null; + boolean first = false; + //Fix because this code screws up an older patch + AbstractInsnNode next1 = popNode.getNext().getNext(); + while(next1 != null) + { + if(next1.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)next1).owner.equals("java/lang/String") + && ((MethodInsnNode)next1).name.equals("equals")) + { + if(!first) + { + first = true; + next1 = next1.getNext(); + continue; + } + toJump = (LabelNode)next1.getNext(); + break; + } + next1 = next1.getNext(); + } + //Lookup ifnonnull + AbstractInsnNode ifnonnull = ain; + while(ifnonnull != null) + { + if(ifnonnull.getOpcode() == Opcodes.IFNONNULL) + break; + ifnonnull = ifnonnull.getPrevious(); + } + node.instructions.set(ifnonnull, new JumpInsnNode(Opcodes.GOTO, toJump)); + while(next1 != null) + { + if(next1.getOpcode() == Opcodes.IFEQ) + { + node.instructions.remove(next1); + break; + } + next1 = next1.getNext(); + } + } + } + } + bootstrapMap.put(classNode, list); + } + }else if(node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;)Ljava/lang/Object;")) + { + InsnList list = Utils.cloneInsnList(node.instructions); + boolean patched = false; + for(int i = 0; i < node.instructions.size(); i++) + { + AbstractInsnNode ain = + node.instructions.get(i); + if(ain.getOpcode() == Opcodes.CHECKCAST + && ((TypeInsnNode)ain).desc.equals("java/lang/invoke/MethodHandles$Lookup") + && Utils.getPrevious(ain) != null + && Utils.getPrevious(ain).getOpcode() == Opcodes.ALOAD) + { + AbstractInsnNode prev = Utils.getPrevious(ain).getPrevious(); + while(!Utils.isInstruction(prev) && !(prev instanceof LabelNode)) + prev = prev.getPrevious(); + if(prev instanceof LabelNode) + { + node.instructions.insert(new JumpInsnNode(Opcodes.GOTO, (LabelNode)prev)); + patched = true; + break; + } + } + } + if(patched) + bootstrapMap.put(classNode, list); + }else if(node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + InsnList list = Utils.cloneInsnList(node.instructions); + boolean patched = false; + for(int i = 0; i < node.instructions.size(); i++) + { + AbstractInsnNode ain = + node.instructions.get(i); + if(ain.getOpcode() == Opcodes.NEW + && ((TypeInsnNode)ain).desc.equals("java/util/zip/ZipFile")) + { + AbstractInsnNode next = ain; + LabelNode lbl = null; + while(true) + { + if(next instanceof LabelNode) + lbl = (LabelNode)next; + if(next.getOpcode() == Opcodes.ALOAD && ((VarInsnNode)next).var == 8) + break; + next = next.getNext(); + } + node.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, (LabelNode)lbl)); + patched = true; + }else if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.IFNE) + { + LabelNode jump = ((JumpInsnNode)ain.getNext()).label; + node.instructions.remove(ain.getNext()); + node.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, jump)); + } + } + if(patched) + bootstrapMap.put(classNode, list); + } + } + } + + /** + * Removes the invokedynamic decryption method, string decryption method, + * and removes the extra fields. + * Also removes signatures and attributes. + */ + private void cleanup() + { + classNodes().forEach(classNode -> { + if(bootstrapMap.containsKey(classNode)) + { + boolean hasIndyNode = false; + //Special string decrypt method remover + for(int i = 0; i < classNode.methods.size(); i++) + { + MethodNode method = classNode.methods.get(i); + if((method.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") + || method.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;)Ljava/lang/Object;")) + && i > 0 && classNode.methods.get(i - 1).desc.equals("(Ljava/lang/String;)Ljava/lang/String;")) + { + classNode.methods.remove(i - 1); + hasIndyNode = true; + break; + } + } + Iterator it = classNode.methods.iterator(); + List bootstrapReferences = new ArrayList<>(); + List bootstrapFieldReferences = new ArrayList<>(); + while(it.hasNext()) + { + MethodNode node = it.next(); + if(node.desc.equals( + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;" + + "Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;")) + { + //Check invokes + for(AbstractInsnNode ain : bootstrapMap.get(classNode).toArray()) + if(ain.getOpcode() == Opcodes.INVOKESTATIC) + { + MethodInsnNode methodNode = (MethodInsnNode)ain; + if(methodNode.desc.equals("(Ljava/lang/String;)Ljava/lang/String;") + && methodNode.owner.equals(classNode.name)) + bootstrapReferences.add(methodNode); + }else if(ain.getOpcode() == Opcodes.PUTSTATIC || ain.getOpcode() == Opcodes.GETSTATIC) + { + FieldInsnNode fieldNode = (FieldInsnNode)ain; + if(fieldNode.owner.equals(classNode.name)) + bootstrapFieldReferences.add(fieldNode); + } + it.remove(); + hasIndyNode = true; + }else if(node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;)Ljava/lang/Object;") + || node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") + || node.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + //Check putstatic + for(AbstractInsnNode ain : bootstrapMap.get(classNode).toArray()) + if(ain.getOpcode() == Opcodes.PUTSTATIC || ain.getOpcode() == Opcodes.GETSTATIC) + { + FieldInsnNode fieldNode = (FieldInsnNode)ain; + if(fieldNode.owner.equals(classNode.name)) + bootstrapFieldReferences.add(fieldNode); + } + it.remove(); + hasIndyNode = true; + } + } + for(MethodInsnNode insnNode : bootstrapReferences) + { + MethodNode method = classNode.methods.stream().filter( + m -> m.name.equals(insnNode.name) && m.desc.equals(insnNode.desc)).findFirst().orElse(null); + if(method != null && method.desc + .equals("(Ljava/lang/String;)Ljava/lang/String;")) + { + classNode.methods.remove(method); + hasIndyNode = true; + } + } + if(hasIndyNode) + { + //Signature and attributes + classNode.signature = null; + if(classNode.attrs != null) + { + Iterator attributeIterator = classNode.attrs.iterator(); + while(attributeIterator.hasNext()) + { + Attribute attribute = attributeIterator.next(); + if(attribute.type.equalsIgnoreCase("PluginVersion")) + attributeIterator.remove(); + if(attribute.type.equalsIgnoreCase("CompileVersion")) + attributeIterator.remove(); + } + } + for(FieldInsnNode fieldInsn : bootstrapFieldReferences) + { + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(fieldInsn.name) + && f.desc.equals(fieldInsn.desc)).findFirst().orElse(null); + if(field != null) + { + if(classNode.fields.indexOf(field) < classNode.fields.size() - 1) + { + FieldNode next = classNode.fields.get(classNode.fields.indexOf(field) + 1); + try + { + Integer.parseInt(next.name); + classNode.fields.remove(next); + }catch(NumberFormatException e) + { + + } + } + if(classNode.fields.indexOf(field) > 0) + { + FieldNode next = classNode.fields.get(classNode.fields.indexOf(field) - 1); + try + { + Integer.parseInt(next.name); + classNode.fields.remove(next); + }catch(NumberFormatException e) + { + + } + } + classNode.fields.remove(field); + } + } + } + } + }); + } + + private boolean isInteger(String str) + { + try + { + Integer.parseInt(str); + }catch(NumberFormatException e) + { + return false; + } + return true; + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/StringEncryptionTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/StringEncryptionTransformer.java new file mode 100644 index 00000000..28a7aed7 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/antireleak/StringEncryptionTransformer.java @@ -0,0 +1,299 @@ +/* + * 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.antireleak; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +import com.javadeobfuscator.deobfuscator.analyzer.AnalyzerResult; +import com.javadeobfuscator.deobfuscator.analyzer.MethodAnalyzer; +import com.javadeobfuscator.deobfuscator.analyzer.frame.Frame; +import com.javadeobfuscator.deobfuscator.analyzer.frame.LdcFrame; +import com.javadeobfuscator.deobfuscator.analyzer.frame.MethodFrame; +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.JavaObject; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; + +public class StringEncryptionTransformer extends Transformer +{ + /** + * Contains a map of patched objects and their objects to null + */ + private Map patched = new HashMap<>(); + + @Override + public boolean transform() + { + System.out + .println("[AntiReleak] [StringEncryptionTransformer] Starting"); + + int total = decrypt(); + System.out + .println("[AntiReleak] [StringEncryptionTransformer] Decrypted " + + total + " strings"); + + System.out.println("[AntiReleak] [StringEncryptionTransformer] Done"); + return total > 0; + } + + private int decrypt() + { + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new PrimitiveFieldProvider()); + provider.register(new MappedFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new ComparisonProvider() + { + @Override + public boolean instanceOf(JavaValue target, Type type, + Context context) + { + return true; + } + + @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 true; + } + + @Override + public boolean canCheckInstanceOf(JavaValue target, Type type, + Context context) + { + return true; + } + + @Override + public boolean canCheckcast(JavaValue target, Type type, + Context context) + { + return true; + } + + @Override + public boolean canCheckEquality(JavaValue first, JavaValue second, + Context context) + { + return false; + } + }); + + AtomicInteger total = new AtomicInteger(0); + //Note: It isn't possible to remove the string encryption method because some aload methods use it + classNodes().forEach(classNode -> { + classNode.methods.forEach(methodNode -> { + AnalyzerResult analyzerResult = null; + InsnList methodInsns = methodNode.instructions; + for(int insnIndex = 0; insnIndex < methodInsns + .size(); insnIndex++) + { + AbstractInsnNode ain = methodInsns.get(insnIndex); + if(ain.getOpcode() == Opcodes.INVOKESTATIC) + { + MethodInsnNode min = (MethodInsnNode)ain; + AbstractInsnNode previous = ain.getPrevious(); + if(min.owner.equals(classNode.name) + && min.desc + .equals("(Ljava/lang/String;)Ljava/lang/String;") + && methodNode.desc.equals("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;" + + "Ljava/lang/invoke/MethodType;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/invoke/CallSite;") + && previous != null + && previous.getOpcode() == Opcodes.LDC) + { + LdcInsnNode ldc = (LdcInsnNode)previous; + if(ldc.cst instanceof String) + { + Context context = new Context(provider); + MethodNode decryptMethod = + classNode.methods.stream() + .filter(mn -> mn.name.equals(min.name) + && mn.desc.equals(min.desc)) + .findFirst().orElse(null); + if(decryptMethod == null) + throw new RuntimeException("Decrypt method cannot be found"); + String result = + MethodExecutor.execute( + classes.get(min.owner), decryptMethod, + Arrays.asList(new JavaObject(ldc.cst, + "java/lang/String")), + null, context); + methodNode.instructions + .remove(ain.getPrevious()); + methodNode.instructions.set(ain, + new LdcInsnNode(result)); + total.getAndIncrement(); + } + }else if(min.desc.equals(methodNode.desc) + && min.name.equals(methodNode.name) + && min.owner.equals(classNode.name) + && methodNode.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + if(!patched.containsKey(classNode)) + { + FieldNode toNull = null; + for(int i = 0; i < methodNode.instructions.size(); i++) + { + AbstractInsnNode ain1 = + methodNode.instructions.get(i); + if(ain1.getOpcode() == Opcodes.INVOKESTATIC) + { + MethodInsnNode methodInsn = (MethodInsnNode)ain1; + if(methodInsn.desc.equals("(Ljava/lang/String;)Ljava/lang/String;") + && methodInsn.name.equals("getProperty") + && methodInsn.owner.equals("java/lang/System")) + { + methodNode.instructions.remove(methodInsn.getPrevious()); + methodNode.instructions.set(methodInsn, new LdcInsnNode("1.8.0_144")); + } + }else if(ain1.getOpcode() == Opcodes.AASTORE + && ain1.getPrevious().getOpcode() == Opcodes.INVOKESTATIC + && ain1.getPrevious().getPrevious().getOpcode() == Opcodes.LDC + && ain1.getNext() != null && ain1.getNext().getOpcode() == Opcodes.AASTORE + && ain1.getNext().getNext() != null + && ain1.getNext().getNext().getOpcode() == Opcodes.GETSTATIC) + { + AbstractInsnNode next = ain1.getNext().getNext().getNext().getNext().getNext().getNext(); + while(next != null) + { + if(next.getOpcode() == -1 + && next.getNext() != null + && next.getNext().getOpcode() == Opcodes.ALOAD + && next.getNext().getNext() != null + && next.getNext().getNext().getOpcode() == Opcodes.INSTANCEOF) + { + methodNode.instructions.insert( + ain1.getNext().getNext().getNext().getNext().getNext(), + new JumpInsnNode(Opcodes.GOTO, (LabelNode)next)); + methodNode.instructions.remove(next.getNext().getNext()); + methodNode.instructions.set(next.getNext(), new InsnNode(Opcodes.ICONST_1)); + break; + } + next = next.getNext(); + } + } + if(toNull == null && ain1.getOpcode() == Opcodes.GETSTATIC) + { + FieldInsnNode fieldInsn = (FieldInsnNode)ain1; + if(fieldInsn.owner.equals(classNode.name)) + toNull = classNode.fields.stream().filter(f -> f.desc.equals(fieldInsn.desc) + && f.name.equals(fieldInsn.name)).findFirst().orElse(null); + } + } + patched.put(classNode, toNull); + } + Context context = new Context(provider); + context.dictionary = classpath; + context.file = getDeobfuscator().getConfig().getInput(); + List args = new ArrayList<>(); + provider.setField(classNode.name, patched.get(classNode).name, + patched.get(classNode).desc, null, null, context); + if(analyzerResult == null) + analyzerResult = MethodAnalyzer.analyze(classNode, methodNode); + List toRemove = ((MethodFrame)analyzerResult.getFrames().get(min).get(0)).getArgs(); + for(Frame f : toRemove) + if(f.getOpcode() == Opcodes.ACONST_NULL) + args.add(JavaValue.valueOf(null)); + else if(f.getOpcode() == Opcodes.LDC) + args.add(JavaValue.valueOf(((LdcFrame)f).getConstant())); + else + { + toRemove = new ArrayList<>(); + args.clear(); + break; + } + for(Frame f : toRemove) + methodNode.instructions.remove(analyzerResult.getInsnNode(f)); + if(args.size() != 0) + { + String result = MethodExecutor.execute(classNode, methodNode, args, null, context); + methodNode.instructions.set(min, new LdcInsnNode(result)); + total.getAndIncrement(); + } + }else if(min.desc.equals(methodNode.desc) + && min.name.equals(methodNode.name) + && min.owner.equals(classNode.name) + && methodNode.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;" + + "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + Context context = new Context(provider); + context.dictionary = classpath; + context.file = getDeobfuscator().getConfig().getInput(); + context.constantPools = getDeobfuscator().getConstantPools(); + List args = new ArrayList<>(); + if(analyzerResult == null) + analyzerResult = MethodAnalyzer.analyze(classNode, methodNode); + List toRemove = ((MethodFrame)analyzerResult.getFrames().get(min).get(0)).getArgs(); + for(Frame f : toRemove) + if(f.getOpcode() == Opcodes.ACONST_NULL) + args.add(JavaValue.valueOf(null)); + else if(f.getOpcode() == Opcodes.LDC) + args.add(JavaValue.valueOf(((LdcFrame)f).getConstant())); + else + { + toRemove = new ArrayList<>(); + args.clear(); + break; + } + for(Frame f : toRemove) + methodNode.instructions.remove(analyzerResult.getInsnNode(f)); + if(args.size() != 0) + { + String result = MethodExecutor.execute(classNode, methodNode, args, null, context); + methodNode.instructions.set(min, new LdcInsnNode(result)); + total.getAndIncrement(); + } + } + } + } + }); + }); + return total.get(); + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/JObfTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/JObfTransformer.java new file mode 100644 index 00000000..d6a2099d --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/JObfTransformer.java @@ -0,0 +1,814 @@ +package com.javadeobfuscator.deobfuscator.transformers.special; + +import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; + +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.defined.types.JavaFieldHandle; +import com.javadeobfuscator.deobfuscator.executor.defined.types.JavaHandle; +import com.javadeobfuscator.deobfuscator.executor.defined.types.JavaMethodHandle; +import com.javadeobfuscator.deobfuscator.executor.providers.ComparisonProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.DelegatingProvider; +import com.javadeobfuscator.deobfuscator.executor.values.JavaArray; +import com.javadeobfuscator.deobfuscator.executor.values.JavaObject; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +public class JObfTransformer extends Transformer +{ + public static boolean FAST_INDY = true; + public static boolean CLASS_ENCRYPTION = false; + + @Override + public boolean transform() throws Throwable + { + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new MappedFieldProvider()); + provider.register(new PrimitiveFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new ComparisonProvider() + { + @Override + public boolean instanceOf(JavaValue target, Type type, + Context context) + { + if(target.value() instanceof JavaObject + && type.getInternalName().equals(((JavaObject)target.value()).type())) + return true; + return false; + } + + @Override + public boolean checkcast(JavaValue target, Type type, + Context context) + { + return true; + } + + @Override + public boolean checkEquality(JavaValue first, JavaValue second, + Context context) + { + return false; + } + + @Override + public boolean canCheckInstanceOf(JavaValue target, Type type, + Context context) + { + return true; + } + + @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] [JObfTransformer] Starting"); + AtomicInteger num = new AtomicInteger(); + AtomicInteger unpoolNum = new AtomicInteger(); + AtomicInteger unpoolString = new AtomicInteger(); + AtomicInteger inlinedIfs = new AtomicInteger(); + AtomicInteger indy = new AtomicInteger(); + //Fold numbers + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + boolean modified; + do + { + modified = false; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(Utils.isInteger(ain) && ain.getNext() != null && Utils.isInteger(ain.getNext()) && ain.getNext().getNext() != null + && isArth(ain.getNext().getNext())) + { + int res = doArth(Utils.getIntValue(ain), Utils.getIntValue(ain.getNext()), ain.getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(res)); + num.incrementAndGet(); + modified = true; + }else if(Utils.isInteger(ain) && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.INEG) + { + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(-Utils.getIntValue(ain))); + num.incrementAndGet(); + modified = true; + }else if(ain.getOpcode() == Opcodes.LDC && ((LdcInsnNode)ain).cst instanceof String + && "".equals(((String)((LdcInsnNode)ain).cst).replace(" ", "")) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getNext()).name.equals("length") + && ((MethodInsnNode)ain.getNext()).owner.equals("java/lang/String")) + { + if(ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.POP) + { + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain); + }else if(ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.POP2) + { + method.instructions.set(ain.getNext().getNext(), new InsnNode(Opcodes.POP)); + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain); + }else + { + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(((String)((LdcInsnNode)ain).cst).length())); + num.incrementAndGet(); + } + modified = true; + } + }while(modified); + } + //Reduntant ifs + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if((Utils.isInteger(ain) || ain.getOpcode() == Opcodes.ACONST_NULL) + && ain.getNext() != null && isSingleIf(ain.getNext())) + { + AbstractInsnNode next = ain.getNext(); + if(runSingleIf(ain, next)) + { + while(ain.getNext() != null && !(ain.getNext() instanceof LabelNode)) + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, ((JumpInsnNode)next).label)); + }else + { + method.instructions.remove(next); + method.instructions.remove(ain); + } + }else if(Utils.isInteger(ain) && ain.getNext() != null + && Utils.isInteger(ain.getNext()) && ain.getNext().getNext() != null + && isDoubleIf(ain.getNext().getNext())) + { + AbstractInsnNode next = ain.getNext().getNext(); + if(runDoubleIf(Utils.getIntValue(ain), Utils.getIntValue(ain.getNext()), next)) + { + while(ain.getNext() != null && !(ain.getNext() instanceof LabelNode)) + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, ((JumpInsnNode)next).label)); + }else + { + method.instructions.remove(next); + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain); + } + } + } + //Reduntant ifs + for(ClassNode classNode : classNodes()) + { + Set toRemove = new HashSet<>(); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)ain).owner.equals(classNode.name)) + { + MethodNode refer = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)ain).name) + && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + if(refer != null && !Modifier.isNative(refer.access) && !Modifier.isAbstract(refer.access) + && Modifier.isPrivate(refer.access) && Modifier.isStatic(refer.access)) + { + int mode = -1; + AbstractInsnNode first = refer.instructions.getFirst(); + if(first.getOpcode() >= Opcodes.LLOAD && first.getOpcode() <= Opcodes.DLOAD + && first.getNext() != null && first.getNext().getOpcode() >= Opcodes.LLOAD + && first.getNext().getOpcode() <= Opcodes.DLOAD + && first.getNext().getNext() != null + && first.getNext().getNext().getOpcode() >= Opcodes.LCMP + && first.getNext().getNext().getOpcode() <= Opcodes.DCMPG + && first.getNext().getNext().getNext() != null + && first.getNext().getNext().getNext().getOpcode() == Opcodes.IRETURN) + mode = 0; + else if((first.getOpcode() == Opcodes.ILOAD || first.getOpcode() == Opcodes.ALOAD) + && ((first.getNext().getOpcode() >= Opcodes.IFEQ + && first.getNext().getOpcode() <= Opcodes.IFLE) + || first.getNext().getOpcode() == Opcodes.IFNULL || first.getNext().getOpcode() == Opcodes.IFNONNULL) + && first.getNext().getNext() != null + && Utils.getIntValue(first.getNext().getNext()) == 1 + && first.getNext().getNext().getNext() != null + && first.getNext().getNext().getNext().getOpcode() == Opcodes.GOTO + && Utils.isInteger(((JumpInsnNode)first.getNext().getNext().getNext()).label.getPrevious()) + && Utils.getIntValue(((JumpInsnNode)first.getNext().getNext().getNext()).label.getPrevious()) == 0 + && refer.instructions.getLast().getOpcode() == Opcodes.IRETURN + && refer.instructions.getLast().getPrevious() + == ((JumpInsnNode)first.getNext().getNext().getNext()).label) + mode = 1; + else if((first.getOpcode() == Opcodes.ILOAD || first.getOpcode() == Opcodes.ALOAD) + && first.getNext() != null && (first.getNext().getOpcode() == Opcodes.ILOAD + || first.getNext().getOpcode() == Opcodes.ALOAD) + && first.getNext().getNext() != null + && ((first.getNext().getNext().getOpcode() >= Opcodes.IF_ICMPEQ + && first.getNext().getNext().getOpcode() <= Opcodes.IF_ICMPLE) + || first.getNext().getNext().getOpcode() == Opcodes.IF_ACMPEQ + || first.getNext().getNext().getOpcode() == Opcodes.IF_ACMPNE) + && first.getNext().getNext().getNext() != null + && Utils.getIntValue(first.getNext().getNext().getNext()) == 1 + && first.getNext().getNext().getNext().getNext() != null + && first.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.GOTO + && Utils.isInteger(((JumpInsnNode)first.getNext().getNext().getNext().getNext()).label.getPrevious()) + && Utils.getIntValue(((JumpInsnNode)first.getNext().getNext().getNext().getNext()).label.getPrevious()) == 0 + && refer.instructions.getLast().getOpcode() == Opcodes.IRETURN + && refer.instructions.getLast().getPrevious() + == ((JumpInsnNode)first.getNext().getNext().getNext().getNext()).label) + mode = 2; + if(mode == 0) + { + toRemove.add(refer); + method.instructions.set(ain, first.getNext().getNext().clone(null)); + inlinedIfs.incrementAndGet(); + }else if(mode == 1) + { + toRemove.add(refer); + LabelNode jump = ((JumpInsnNode)ain.getNext()).label; + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, + new JumpInsnNode(first.getNext().getOpcode(), jump)); + inlinedIfs.incrementAndGet(); + }else if(mode == 2) + { + toRemove.add(refer); + LabelNode jump = ((JumpInsnNode)ain.getNext()).label; + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, + new JumpInsnNode(first.getNext().getNext().getOpcode(), jump)); + inlinedIfs.incrementAndGet(); + } + } + } + toRemove.forEach(m -> classNode.methods.remove(m)); + } + //Unpool numbers + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode first = clinit.instructions.getFirst(); + if(first != null && first.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)first).desc.equals("()V") + && ((MethodInsnNode)first).owner.equals(classNode.name)) + { + MethodNode refMethod = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)first).name) + && m.desc.equals("()V")).findFirst().orElse(null); + if(refMethod.instructions.getFirst().getNext().getNext().getOpcode() == Opcodes.PUTSTATIC + && ((FieldInsnNode)refMethod.instructions.getFirst().getNext().getNext()).desc.equals("[I")) + { + FieldInsnNode insnNode = (FieldInsnNode)refMethod.instructions.getFirst().getNext().getNext(); + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(insnNode.name) + && f.desc.equals(insnNode.desc)).findFirst().orElse(null); + Context context = new Context(provider); + MethodExecutor.execute(classNode, refMethod, Arrays.asList(), null, context); + int[] result = (int[])context.provider.getField(classNode.name, insnNode.name, insnNode.desc, + null, context); + classNode.methods.remove(refMethod); + clinit.instructions.remove(clinit.instructions.getFirst()); + classNode.fields.remove(field); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).name.equals(field.name) + && ((FieldInsnNode)ain).desc.equals(field.desc) + && ((FieldInsnNode)ain).owner.equals(classNode.name)) + { + if(!Utils.isInteger(ain.getNext())) + throw new IllegalStateException(); + method.instructions.remove(ain.getNext().getNext()); + int value = Utils.getIntValue(ain.getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(result[value])); + unpoolNum.incrementAndGet(); + } + } + } + } + } + } + //Decrypt encrypted strings + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode first = clinit.instructions.getFirst(); + if(first != null && first.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)first).desc.equals("()V") + && ((MethodInsnNode)first).owner.equals(classNode.name)) + { + MethodNode refMethod = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)first).name) + && m.desc.equals("()V")).findFirst().orElse(null); + AbstractInsnNode methodFirst = refMethod.instructions.getFirst(); + if(methodFirst.getOpcode() == Opcodes.NEW && ((TypeInsnNode)methodFirst).desc.equals("java/lang/Exception") + && methodFirst.getNext() != null && methodFirst.getNext().getOpcode() == Opcodes.DUP + && methodFirst.getNext().getNext() != null && methodFirst.getNext().getNext().getOpcode() == Opcodes.INVOKESPECIAL + && ((MethodInsnNode)methodFirst.getNext().getNext()).name.equals("") + && ((MethodInsnNode)methodFirst.getNext().getNext()).owner.equals("java/lang/Exception") + && methodFirst.getNext().getNext().getNext() != null + && methodFirst.getNext().getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)methodFirst.getNext().getNext().getNext()).name.equals("getStackTrace") + && (((MethodInsnNode)methodFirst.getNext().getNext().getNext()).owner.equals("java/lang/Exception") + || ((MethodInsnNode)methodFirst.getNext().getNext().getNext()).owner.equals("java/lang/Throwable")) + && refMethod.instructions.getLast().getPrevious().getOpcode() == Opcodes.PUTSTATIC) + { + FieldInsnNode insnNode = (FieldInsnNode)refMethod.instructions.getLast().getPrevious(); + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(insnNode.name) + && f.desc.equals(insnNode.desc)).findFirst().orElse(null); + Context context = new Context(provider); + context.dictionary = classpath; + MethodExecutor.execute(classNode, refMethod, Arrays.asList(), null, context); + Object[] result = (Object[])context.provider.getField(classNode.name, insnNode.name, insnNode.desc, + null, context); + classNode.methods.remove(refMethod); + clinit.instructions.remove(clinit.instructions.getFirst()); + classNode.fields.remove(field); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).name.equals(field.name) + && ((FieldInsnNode)ain).desc.equals(field.desc) + && ((FieldInsnNode)ain).owner.equals(classNode.name)) + { + if(!Utils.isInteger(ain.getNext())) + throw new IllegalStateException(); + method.instructions.remove(ain.getNext().getNext()); + int value = Utils.getIntValue(ain.getNext()); + method.instructions.remove(ain.getNext()); + if(result[value] == null) + System.out.println("Array contains null string?"); + method.instructions.set(ain, new LdcInsnNode(result[value])); + } + } + classNode.sourceFile = null; + } + } + } + } + //Unpool strings + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode first = clinit.instructions.getFirst(); + if(first != null && first.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)first).desc.equals("()V") + && ((MethodInsnNode)first).owner.equals(classNode.name)) + { + MethodNode refMethod = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)first).name) + && m.desc.equals("()V")).findFirst().orElse(null); + if(refMethod.instructions.getFirst().getNext().getNext().getOpcode() == Opcodes.PUTSTATIC + && ((FieldInsnNode)refMethod.instructions.getFirst().getNext().getNext()).desc.equals("[Ljava/lang/String;")) + { + FieldInsnNode insnNode = (FieldInsnNode)refMethod.instructions.getFirst().getNext().getNext(); + FieldNode field = classNode.fields.stream().filter(f -> f.name.equals(insnNode.name) + && f.desc.equals(insnNode.desc)).findFirst().orElse(null); + Context context = new Context(provider); + context.dictionary = classpath; + Set toRemove = new HashSet<>(); + for(AbstractInsnNode ain : refMethod.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)ain).owner.equals(classNode.name)) + toRemove.add(classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)ain).name) + && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null)); + for(MethodNode m : toRemove) + for(AbstractInsnNode ain : m.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain).owner.equals("java/lang/String") + && ((MethodInsnNode)ain).name.equals("getBytes") + && ((MethodInsnNode)ain).desc.equals("(Ljava/nio/charset/Charset;)[B")) + MethodExecutor.customMethodFunc.put(ain, (list, ctx) -> + new JavaArray((list.get(1).as(String.class)).getBytes(StandardCharsets.UTF_8))); + else if(ain.getOpcode() == Opcodes.INVOKESPECIAL && ((MethodInsnNode)ain).owner.equals("java/lang/String") + && ((MethodInsnNode)ain).name.equals("") + && ((MethodInsnNode)ain).desc.equals("([BLjava/nio/charset/Charset;)V")) + MethodExecutor.customMethodFunc.put(ain, (list, ctx) -> { + list.get(2).initialize(new String(list.get(0).as(byte[].class), StandardCharsets.UTF_8)); + return null; + }); + MethodExecutor.execute(classNode, refMethod, Arrays.asList(), null, context); + Object[] result = (Object[])context.provider.getField(classNode.name, insnNode.name, insnNode.desc, + null, context); + for(MethodNode m : toRemove) + classNode.methods.remove(m); + classNode.methods.remove(refMethod); + clinit.instructions.remove(clinit.instructions.getFirst()); + classNode.fields.remove(field); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).name.equals(field.name) + && ((FieldInsnNode)ain).desc.equals(field.desc) + && ((FieldInsnNode)ain).owner.equals(classNode.name)) + { + if(!Utils.isInteger(ain.getNext())) + throw new IllegalStateException(); + method.instructions.remove(ain.getNext().getNext()); + int value = Utils.getIntValue(ain.getNext()); + method.instructions.remove(ain.getNext()); + if(result[value] == null) + System.out.println("Array contains null string?"); + method.instructions.set(ain, new LdcInsnNode(result[value])); + unpoolString.incrementAndGet(); + } + } + } + } + } + } + //Remove InvokeDynamics + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode first = clinit.instructions.getFirst(); + if(first != null && first.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)first).desc.equals("()V") + && ((MethodInsnNode)first).owner.equals(classNode.name)) + { + MethodNode refMethod = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)first).name) + && m.desc.equals("()V")).findFirst().orElse(null); + FieldNode[] fields = isIndyMethod(classNode, refMethod); + if(fields != null) + { + MethodNode bootstrap = classNode.methods.stream().filter(m -> isBootstrap(classNode, fields, m)).findFirst().orElse(null); + Context refCtx = new Context(provider); + refCtx.dictionary = classpath; + if(FAST_INDY) + { + Map indys = new HashMap<>(); + Map indyClasses = new HashMap<>(); + for(AbstractInsnNode ain : refMethod.instructions.toArray()) + if(ain instanceof LdcInsnNode && ((LdcInsnNode)ain).cst instanceof String) + indys.put(Utils.getIntValue(ain.getPrevious()), (String)((LdcInsnNode)ain).cst); + else if(ain instanceof LdcInsnNode && ((LdcInsnNode)ain).cst instanceof Type) + indyClasses.put(Utils.getIntValue(ain.getPrevious()), (Type)((LdcInsnNode)ain).cst); + else if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).name.equals("TYPE") + && ((FieldInsnNode)ain).desc.equals("Ljava/lang/Class;")) + indyClasses.put(Utils.getIntValue(ain.getPrevious()), getTypeForClass(((FieldInsnNode)ain).owner)); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC + && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 0 + && ((InvokeDynamicInsnNode)ain).bsm.getName().equals(bootstrap.name) + && ((InvokeDynamicInsnNode)ain).bsm.getDesc().equals(bootstrap.desc) + && ((InvokeDynamicInsnNode)ain).bsm.getOwner().equals(classNode.name)) + { + int value = Integer.parseInt(((InvokeDynamicInsnNode)ain).name); + String[] decrypted = indys.get(value).split(":"); + if(decrypted[3].length() <= 2) + { + method.instructions.set(ain, new MethodInsnNode(decrypted[3].length() == 2 ? Opcodes.INVOKEVIRTUAL + : Opcodes.INVOKESTATIC, decrypted[0].replace('.', '/'), + decrypted[1], decrypted[2], false)); + }else + method.instructions.set(ain, new FieldInsnNode(decrypted[3].length() == 3 ? Opcodes.GETFIELD + : decrypted[3].length() == 4 ? Opcodes.GETSTATIC : decrypted[3].length() == 5 ? + Opcodes.PUTFIELD : Opcodes.PUTSTATIC, decrypted[0].replace('.', '/'), + decrypted[1], indyClasses.get(Integer.parseInt(decrypted[2])).getDescriptor())); + indy.incrementAndGet(); + } + }else + { + MethodExecutor.execute(classNode, refMethod, Arrays.asList(), null, refCtx); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC + && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 0 + && ((InvokeDynamicInsnNode)ain).bsm.getName().equals(bootstrap.name) + && ((InvokeDynamicInsnNode)ain).bsm.getDesc().equals(bootstrap.desc) + && ((InvokeDynamicInsnNode)ain).bsm.getOwner().equals(classNode.name)) + { + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); //Lookup + args.add(JavaValue.valueOf(((InvokeDynamicInsnNode)ain).name)); //dyn method name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); //dyn method type + try + { + Context context = new Context(provider); + context.dictionary = classpath; + + JavaHandle result = MethodExecutor.execute(classNode, bootstrap, args, null, context); + AbstractInsnNode replacement = null; + if(result instanceof JavaMethodHandle) + { + JavaMethodHandle jmh = (JavaMethodHandle)result; + String clazz = jmh.clazz.replace('.', '/'); + switch (jmh.type) { + case "virtual": + replacement = new MethodInsnNode((classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? + Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, clazz, jmh.name, jmh.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, clazz, jmh.name, jmh.desc, false); + break; + } + }else + { + JavaFieldHandle jfh = (JavaFieldHandle)result; + String clazz = jfh.clazz.replace('.', '/'); + switch (jfh.type) { + case "virtual": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTFIELD : Opcodes.GETFIELD, clazz, jfh.name, jfh.desc); + break; + case "static": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTSTATIC : Opcodes.GETSTATIC, clazz, jfh.name, jfh.desc); + break; + } + } + method.instructions.insert(ain, replacement); + method.instructions.remove(ain); + indy.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + classNode.fields.remove(fields[0]); + classNode.fields.remove(fields[1]); + classNode.methods.remove(bootstrap); + classNode.methods.remove(refMethod); + clinit.instructions.remove(first); + } + } + } + } + AtomicInteger decrypted = new AtomicInteger(); + //Warning: No checks will be done to verify if classloader is from JObf + if(CLASS_ENCRYPTION) + { + String[] lines = null; + int index = -1; + if(getDeobfuscator().getInputPassthrough().containsKey("META-INF/MANIFEST.MF")) + { + lines = new String(getDeobfuscator().getInputPassthrough().get("META-INF/MANIFEST.MF")).split("\n"); + for(int i = 0; i < lines.length; i++) + if(lines[i].startsWith("Main-Class: ")) + { + index = i; + break; + } + } + String className = index == -1 ? null : lines[index].substring("Main-Class: ".length(), lines[index].length() - 1).replace('.', '/'); + ClassNode loader = classNodes().stream().filter(c -> className == null ? + c.superName.equals("java/lang/ClassLoader") : c.name.equals(className)).findFirst().orElse(null); + if(loader != null) + { + Context context = new Context(provider); + context.dictionary = classpath; + MethodNode clinit = loader.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + MethodExecutor.execute(loader, clinit, Arrays.asList(), null, context); + String realMainClass = null; + MethodNode main = loader.methods.stream().filter(m -> m.name.equals("main") + && m.desc.equals("([Ljava/lang/String;)V")).findFirst().orElse(null); + for(AbstractInsnNode ain : main.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain).owner.equals("java/lang/ClassLoader") + && ((MethodInsnNode)ain).name.equals("loadClass")) + { + realMainClass = (String)((LdcInsnNode)ain.getPrevious()).cst; + break; + } + MethodNode decMethod = loader.methods.stream().filter(m -> m.desc.equals("([B[B)[B")).findFirst().orElse(null); + //Decryption array + byte[] b = (byte[])context.provider.getField(className, loader.fields.get(0).name, loader.fields.get(0).desc, + null, context); + //Decrypt all classes + List remove = new ArrayList<>(); + for(Entry entry : getDeobfuscator().getInputPassthrough().entrySet()) + { + byte[] decBytes = MethodExecutor.execute(loader, decMethod, Arrays.asList( + new JavaArray(entry.getValue()), new JavaArray(b)), + null, context); + try + { + ClassReader reader = new ClassReader(decBytes); + ClassNode node = new ClassNode(); + reader.accept(node, ClassReader.SKIP_FRAMES); + getDeobfuscator().loadInput(node.name + ".class", decBytes); + remove.add(entry.getKey()); + decrypted.incrementAndGet(); + }catch(Exception e) + { + //Not an encrypted resource + continue; + } + } + remove.forEach(n -> getDeobfuscator().getInputPassthrough().remove(n)); + classes.remove(className); + classpath.remove(className); + if(index != -1) + { + lines[index] = "Main-Class: " + realMainClass; + String res = ""; + for(String line : lines) + res += line + "\n"; + res = res.substring(0, res.length() - 1); + getDeobfuscator().getInputPassthrough().put("META-INF/MANIFEST.MF", res.getBytes()); + } + } + } + System.out.println("[Special] [JObfTransformer] Removed " + num + " number obfuscation instructions"); + System.out.println("[Special] [JObfTransformer] Inlined " + unpoolNum + " numbers"); + System.out.println("[Special] [JObfTransformer] Unpooled " + unpoolString + " strings"); + System.out.println("[Special] [JObfTransformer] Inlined " + inlinedIfs + " if statements"); + System.out.println("[Special] [JObfTransformer] Removed " + indy + " invokedynamics"); + if(CLASS_ENCRYPTION) + System.out.println("[Special] [JObfTransformer] Decrypted " + decrypted + " classes"); + System.out.println("[Special] [JObfTransformer] Done"); + return num.get() > 0 || unpoolNum.get() > 0 || unpoolString.get() > 0 || inlinedIfs.get() > 0 || indy.get() > 0; + } + + private Type getTypeForClass(String clazz) + { + switch(clazz) + { + case "java/lang/Integer": + return Type.INT_TYPE; + case "java/lang/Boolean": + return Type.BOOLEAN_TYPE; + case "java/lang/Character": + return Type.CHAR_TYPE; + case "java/lang/Byte": + return Type.BYTE_TYPE; + case "java/lang/Short": + return Type.SHORT_TYPE; + case "java/lang/Float": + return Type.FLOAT_TYPE; + case "java/lang/Long": + return Type.LONG_TYPE; + case "java/lang/Double": + return Type.DOUBLE_TYPE; + default: + return null; + } + } + + private boolean isBootstrap(ClassNode classNode, FieldNode[] fields, MethodNode method) + { + if(!method.desc.equals("(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;")) + return false; + boolean[] verify = new boolean[2]; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).desc.equals(fields[0].desc) + && ((FieldInsnNode)ain).name.equals(fields[0].name) + && ((FieldInsnNode)ain).owner.equals(classNode.name)) + verify[0] = true; + else if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).desc.equals(fields[1].desc) + && ((FieldInsnNode)ain).name.equals(fields[1].name) + && ((FieldInsnNode)ain).owner.equals(classNode.name)) + verify[1] = true; + if(verify[0] && verify[1]) + return true; + return false; + } + + private FieldNode[] isIndyMethod(ClassNode classNode, MethodNode method) + { + FieldNode[] arrs = new FieldNode[2]; + if(method.instructions.getFirst() != null && Utils.isInteger(method.instructions.getFirst()) + && method.instructions.getFirst().getNext() != null && method.instructions.getFirst().getNext().getOpcode() == Opcodes.ANEWARRAY + && ((TypeInsnNode)method.instructions.getFirst().getNext()).desc.equals("java/lang/String") + && method.instructions.getFirst().getNext().getNext() != null + && method.instructions.getFirst().getNext().getNext().getOpcode() == Opcodes.PUTSTATIC + && classNode.name.equals(((FieldInsnNode)method.instructions.getFirst().getNext().getNext()).owner)) + arrs[0] = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)method.instructions.getFirst().getNext().getNext()).name) + && f.desc.equals(((FieldInsnNode)method.instructions.getFirst().getNext().getNext()).desc)).findFirst().orElse(null); + if(arrs[0] == null) + return null; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain != null && Utils.isInteger(ain) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.ANEWARRAY + && ((TypeInsnNode)ain.getNext()).desc.equals("java/lang/Class") + && ain.getNext().getNext() != null + && ain.getNext().getNext().getOpcode() == Opcodes.PUTSTATIC + && classNode.name.equals(((FieldInsnNode)ain.getNext().getNext()).owner)) + { + arrs[1] = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain.getNext().getNext()).name) + && f.desc.equals(((FieldInsnNode)ain.getNext().getNext()).desc)).findFirst().orElse(null); + return arrs; + } + return null; + } + + private boolean isArth(AbstractInsnNode ain) + { + if(ain.getOpcode() == Opcodes.IADD || ain.getOpcode() == Opcodes.ISUB || ain.getOpcode() == Opcodes.IMUL + || ain.getOpcode() == Opcodes.IDIV || ain.getOpcode() == Opcodes.IXOR || ain.getOpcode() == Opcodes.IAND + || ain.getOpcode() == Opcodes.ISHL) + return true; + return false; + } + + private int doArth(int num1, int num2, AbstractInsnNode ain) + { + switch(ain.getOpcode()) + { + case IADD: + return num1 + num2; + case ISUB: + return num1 - num2; + case IMUL: + return num1 * num2; + case IDIV: + return num1 / num2; + case IXOR: + return num1 ^ num2; + case IAND: + return num1 & num2; + case ISHL: + return num1 << num2; + } + throw new RuntimeException("Unexpected opcode"); + } + + private boolean isSingleIf(AbstractInsnNode ain) + { + if(ain.getOpcode() == Opcodes.IFNULL || ain.getOpcode() == Opcodes.IFNONNULL + || (ain.getOpcode() >= Opcodes.IFEQ && ain.getOpcode() <= Opcodes.IFLE)) + return true; + return false; + } + + private boolean runSingleIf(AbstractInsnNode v, AbstractInsnNode ain) + { + int value = Utils.getIntValue(v); + switch(ain.getOpcode()) + { + case IFNULL: + return true; + case IFNONNULL: + return false; + case IFEQ: + return value == 0; + case IFNE: + return value != 0; + case IFLT: + return value < 0; + case IFGE: + return value >= 0; + case IFGT: + return value > 0; + case IFLE: + return value <= 0; + } + throw new RuntimeException("Unexpected opcode"); + } + + private boolean isDoubleIf(AbstractInsnNode ain) + { + if(ain.getOpcode() >= Opcodes.IF_ICMPEQ && ain.getOpcode() <= Opcodes.IF_ICMPLE) + return true; + return false; + } + + private boolean runDoubleIf(int num1, int num2, AbstractInsnNode ain) + { + switch(ain.getOpcode()) + { + case IF_ICMPEQ: + return num1 == num2; + case IF_ICMPNE: + return num1 != num2; + case IF_ICMPLT: + return num1 < num2; + case IF_ICMPGE: + return num1 >= num2; + case IF_ICMPGT: + return num1 > num2; + case IF_ICMPLE: + return num1 <= num2; + } + throw new RuntimeException("Unexpected opcode"); + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformer.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformer.java new file mode 100644 index 00000000..c6104231 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformer.java @@ -0,0 +1,940 @@ +package com.javadeobfuscator.deobfuscator.transformers.special; + +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +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.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; + +import com.javadeobfuscator.deobfuscator.asm.source.ConstantPropagatingSourceFinder; +import com.javadeobfuscator.deobfuscator.asm.source.SourceFinder; +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.defined.types.JavaFieldHandle; +import com.javadeobfuscator.deobfuscator.executor.defined.types.JavaHandle; +import com.javadeobfuscator.deobfuscator.executor.defined.types.JavaMethodHandle; +import com.javadeobfuscator.deobfuscator.executor.providers.ComparisonProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.DelegatingProvider; +import com.javadeobfuscator.deobfuscator.executor.values.JavaInteger; +import com.javadeobfuscator.deobfuscator.executor.values.JavaObject; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.utils.InstructionModifier; +import com.javadeobfuscator.deobfuscator.utils.TransformerHelper; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +public class RadonTransformer extends Transformer +{ + /** + * 0 = None + * 1 = Light + * 2 = Normal/Heavy + * 3 = Combined + */ + public static int FLOW_MODE = 3; + /** + * 0 = None + * 1 = Legacy + * 2 = New + */ + public static int NUMBER_MODE = 2; + /** + * 0 = None + * 1 = Legacy + * 2 = New + */ + public static int STRING_POOL_MODE = 2; + public static boolean INDY = true; + public static boolean STRING = true; + public static boolean TRASH_CLASSES = false; + + public static boolean FAST_INDY = true; + + @Override + public boolean transform() throws Throwable + { + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new PrimitiveFieldProvider()); + provider.register(new MappedFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new ComparisonProvider() { + @Override + public boolean instanceOf(JavaValue target, Type type, Context context) { + 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) { + 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] [RadonTransformer] Starting"); + if(TRASH_CLASSES) + { + AtomicInteger trash = new AtomicInteger(); + getDeobfuscator().getInputPassthrough().keySet().removeIf(s -> { + if(s.endsWith(".class")) + { + trash.incrementAndGet(); + return true; + } + return false; + }); + System.out.println("[Special] [RadonTransformer] Removed " + trash + " classes"); + } + AtomicInteger flow = new AtomicInteger(); + AtomicInteger number = new AtomicInteger(); + AtomicInteger indy = new AtomicInteger(); + AtomicInteger str = new AtomicInteger(); + AtomicInteger strPool = new AtomicInteger(); + Map> fakeFields = new HashMap<>(); + //Bad Annotations + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + if(method.visibleAnnotations != null) + { + Iterator itr = method.visibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode node = itr.next(); + if(node.desc.equals("@") || node.desc.equals("")) + itr.remove(); + } + } + if(method.invisibleAnnotations != null) + { + Iterator itr = method.invisibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode node = itr.next(); + if(node.desc.equals("@") || node.desc.equals("")) + itr.remove(); + } + } + } + if(FLOW_MODE == 1 || FLOW_MODE == 3) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.GETSTATIC + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.IFEQ + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.ACONST_NULL + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.ATHROW) + { + LabelNode jump = ((JumpInsnNode)ain.getNext()).label; + ClassNode owner = classNodes().stream().filter(c -> c.name.equals(((FieldInsnNode)ain).owner)).findFirst().orElse(null); + if(owner == null) + continue; + FieldNode field = owner.fields.stream().filter(f -> + f.name.equals(((FieldInsnNode)ain).name) && f.desc.equals(((FieldInsnNode)ain).desc)).findFirst().orElse(null); + if(field != null && Modifier.isFinal(field.access)) + { + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, jump)); + fakeFields.putIfAbsent(owner, new HashSet<>()); + fakeFields.get(owner).add(field); + flow.incrementAndGet(); + } + } + } + if(FLOW_MODE == 2 || FLOW_MODE == 3) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + ClassNode owner = null; + AbstractInsnNode getstatic = null; + FieldNode field = null; + int var = -1; + boolean unresolved = false; + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(var == -1 && ain.getOpcode() == Opcodes.GETSTATIC) + { + owner = classNodes().stream().filter(c -> c.name.equals(((FieldInsnNode)ain).owner)).findFirst().orElse(null); + if(owner == null) + continue; + field = owner.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && f.desc.equals(((FieldInsnNode)ain).desc)).findFirst().orElse(null); + if(field == null) + continue; + if(Modifier.isStatic(field.access) && Modifier.isFinal(field.access) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.ISTORE) + { + field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && f.desc.equals(((FieldInsnNode)ain).desc)).findFirst().orElse(null); + var = ((VarInsnNode)ain.getNext()).var; + getstatic = ain; + } + } + if(var != -1 && ain.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)ain).var == var + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.IFNE) + { + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain); + }else if(var != -1 && ain.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)ain).var == var + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.IFEQ + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.ACONST_NULL + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.ATHROW) + { + JumpInsnNode jump = (JumpInsnNode)ain.getNext(); + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, jump.label)); + }else if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.IFEQ + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.ACONST_NULL + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.ATHROW) + { + LabelNode jump = ((JumpInsnNode)ain.getNext()).label; + FieldNode field1 = classNode.fields.stream().filter(f -> + f.name.equals(((FieldInsnNode)ain).name) && f.desc.equals(((FieldInsnNode)ain).desc)).findFirst().orElse(null); + if(field1 != null && Modifier.isFinal(field1.access)) + { + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new JumpInsnNode(Opcodes.GOTO, jump)); + flow.incrementAndGet(); + } + }else if(ain.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)ain).var == var) + unresolved = true; + } + if(!unresolved && getstatic != null) + { + method.instructions.remove(getstatic.getNext()); + method.instructions.remove(getstatic); + fakeFields.putIfAbsent(owner, new HashSet<>()); + fakeFields.get(owner).add(field); + flow.incrementAndGet(); + } + } + } + fakeFields.entrySet().forEach(e -> e.getValue().forEach(f -> e.getKey().fields.remove(f))); + if(NUMBER_MODE == 1) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(Utils.isInteger(ain) && ain.getNext() != null && Utils.isInteger(ain.getNext()) + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.IXOR) + { + int result = Utils.getIntValue(ain) ^ Utils.getIntValue(ain.getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(result)); + number.incrementAndGet(); + }else if(Utils.isLong(ain) && ain.getNext() != null && Utils.isLong(ain.getNext()) + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.LXOR) + { + long result = Utils.getLongValue(ain) ^ Utils.getLongValue(ain.getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new LdcInsnNode(result)); + number.incrementAndGet(); + } + }else if(NUMBER_MODE == 2) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(Utils.isInteger(ain) && ain.getNext() != null && Utils.isInteger(ain.getNext()) + && ain.getNext().getNext() != null + && ain.getNext().getNext().getOpcode() == Opcodes.SWAP + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.DUP_X1 + && ain.getNext().getNext().getNext().getNext()!= null + && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.POP2 + && ain.getNext().getNext().getNext().getNext().getNext() != null + && Utils.isInteger(ain.getNext().getNext().getNext().getNext().getNext()) + && ain.getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IXOR) + { + int result = Utils.getIntValue(ain) ^ Utils.getIntValue( + ain.getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(result)); + number.incrementAndGet(); + }else if(Utils.isLong(ain) && ain.getNext() != null && Utils.isLong(ain.getNext()) + && ain.getNext().getNext() != null + && ain.getNext().getNext().getOpcode() == Opcodes.DUP2_X2 + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.POP2 + && ain.getNext().getNext().getNext().getNext()!= null + && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.POP2 + && ain.getNext().getNext().getNext().getNext().getNext() != null + && Utils.isLong(ain.getNext().getNext().getNext().getNext().getNext()) + && ain.getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.LXOR) + { + long result = Utils.getLongValue(ain.getNext()) ^ Utils.getLongValue( + ain.getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, new LdcInsnNode(result)); + number.incrementAndGet(); + }else if(Utils.isInteger(ain) && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.I2L + && ain.getNext().getNext() != null + && Utils.isInteger(ain.getNext().getNext()) + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.I2L + && ain.getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.LXOR + && ain.getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.L2I) + { + int result = (int)((long)Utils.getIntValue(ain) ^ (long)Utils.getIntValue( + ain.getNext().getNext())); + method.instructions.remove(ain.getNext().getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext().getNext()); + method.instructions.remove(ain.getNext().getNext()); + method.instructions.remove(ain.getNext()); + method.instructions.set(ain, Utils.getIntInsn(result)); + number.incrementAndGet(); + } + } + Map> stringDecrypt = new HashMap<>(); + Set stringDecryptClass = new HashSet<>(); + Map> stringDecryptField = new HashMap<>(); + if(STRING_POOL_MODE == 1) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ain.getPrevious() != null + && Utils.isInteger(ain.getPrevious())) + { + ClassNode decryptorNode = classNodes().stream().filter(c -> c.name.equals(((MethodInsnNode)ain).owner)).findFirst().orElse(null); + MethodNode decryptorMethod = decryptorNode == null ? null : decryptorNode.methods.stream().filter(m -> + m.name.equals(((MethodInsnNode)ain).name) && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + if(isCorrectStringPool(decryptorMethod, 0)) + { + int prev = Utils.getIntValue(ain.getPrevious()); + try + { + Context context = new Context(provider); + context.push(classNode.name, method.name, getDeobfuscator().getConstantPools().get(classNode).getSize()); + context.dictionary = classpath; + String res = MethodExecutor.execute(decryptorNode, decryptorMethod, Arrays.asList(new JavaInteger(prev)), null, context); + modifier.remove(ain.getPrevious()); + modifier.replace(ain, new LdcInsnNode(res)); + stringDecrypt.putIfAbsent(decryptorNode, new HashSet<>()); + stringDecrypt.get(decryptorNode).add(decryptorMethod); + strPool.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + modifier.apply(method); + } + }else if(STRING_POOL_MODE == 2) + { + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode firstInstr = clinit.instructions.getFirst(); + if(firstInstr != null && firstInstr.getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)firstInstr).owner.equals(classNode.name)) + { + MethodNode pool = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)firstInstr).name) + && m.desc.equals(((MethodInsnNode)firstInstr).desc)).findFirst().orElse(null); + if(pool != null && isCorrectStringPool(pool, 1)) + { + FieldInsnNode putstatic = null; + for(AbstractInsnNode ain : pool.instructions.toArray()) + if(ain.getOpcode() == Opcodes.PUTSTATIC) + { + putstatic = (FieldInsnNode)ain; + break; + } + if(putstatic != null && putstatic.owner.equals(classNode.name)) + { + FieldInsnNode putstaticF = putstatic; + FieldNode poolField = classNode.fields.stream().filter(f -> f.name.equals(putstaticF.name) + && f.desc.equals(putstaticF.desc)).findFirst().orElse(null); + if(poolField != null) + { + Context context = new Context(provider); + context.dictionary = classpath; + MethodExecutor.execute(classNode, pool, Arrays.asList(), null, context); + Object[] value = (Object[])context.provider.getField(classNode.name, poolField.name, poolField.desc, null, context); + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + { + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ((FieldInsnNode)ain).name.equals(poolField.name) + && ((FieldInsnNode)ain).desc.equals(poolField.desc) + && ain.getNext() != null && Utils.isInteger(ain.getNext()) + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.AALOAD) + { + modifier.remove(ain.getNext().getNext()); + modifier.remove(ain.getNext()); + modifier.replace(ain, new LdcInsnNode(value[Utils.getIntValue(ain.getNext())])); + strPool.incrementAndGet(); + } + } + modifier.apply(method); + } + stringDecrypt.putIfAbsent(classNode, new HashSet<>()); + stringDecrypt.get(classNode).add(pool); + stringDecryptField.putIfAbsent(classNode, new HashSet<>()); + stringDecryptField.get(classNode).add(poolField); + clinit.instructions.remove(clinit.instructions.getFirst()); + } + } + } + } + } + } + } + if(STRING) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + Frame[] frames; + try + { + frames = new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, method); + }catch(AnalyzerException e) + { + oops("unexpected analyzer exception", e); + continue; + } + insns: + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + if(ain.getOpcode() == Opcodes.INVOKESTATIC) + { + ClassNode decryptorNode = classNodes().stream().filter(c -> c.name.equals(((MethodInsnNode)ain).owner)).findFirst().orElse(null); + MethodNode decryptorMethod = decryptorNode == null ? null : decryptorNode.methods.stream().filter(m -> + m.name.equals(((MethodInsnNode)ain).name) && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + int res = isCorrectStringDecrypt(decryptorNode, decryptorMethod); + if(res != -1) + { + Frame f1 = frames[method.instructions.indexOf(ain)]; + if(f1 == null) + continue; + Type[] argTypes = Type.getArgumentTypes(((MethodInsnNode)ain).desc); + Frame currentFrame = frames[method.instructions.indexOf(ain)]; + List args = new ArrayList<>(); + List instructions = new ArrayList<>(); + + for(int i = 0, stackOffset = currentFrame.getStackSize() - argTypes.length; i < argTypes.length; i++) + { + Optional consensus = SourceFinder.findSource(method, frames, instructions, new ConstantPropagatingSourceFinder(), + ain, currentFrame.getStack(stackOffset)).consensus(); + if(!consensus.isPresent()) + continue insns; + + Object o = consensus.get(); + if(o instanceof Integer) + args.add(new JavaInteger((int)o)); + else + args.add(new JavaObject(o, "java/lang/String")); + stackOffset++; + } + instructions = new ArrayList<>(new HashSet<>(instructions)); + Context context = new Context(provider); + context.dictionary = classpath; + context.push(classNode.name, method.name, getDeobfuscator().getConstantPool(classNode).getSize()); + try + { + if(!stringDecryptClass.contains(decryptorNode) && res == 1) + { + MethodExecutor.execute(decryptorNode, decryptorNode.methods.stream().filter(m -> m.name.equals("")). + findFirst().orElse(null), Arrays.asList(), null, context); + patchMethodString(decryptorNode); + } + List pops = new ArrayList<>(); + for(AbstractInsnNode a : method.instructions.toArray()) + if(a.getOpcode() == Opcodes.POP && frames[method.instructions.indexOf(a)] != null) + { + SourceValue value = frames[method.instructions.indexOf(a)].getStack( + frames[method.instructions.indexOf(a)].getStackSize() - 1); + if(!value.insns.isEmpty() + && instructions.contains(value.insns.iterator().next())) + pops.add(a); + } + modifier.replace(ain, new LdcInsnNode(MethodExecutor.execute(decryptorNode, decryptorMethod, + args, null, context))); + modifier.removeAll(instructions); + modifier.removeAll(pops); + if(res == 0) + { + stringDecrypt.putIfAbsent(decryptorNode, new HashSet<>()); + stringDecrypt.get(decryptorNode).add(decryptorMethod); + }else + stringDecryptClass.add(decryptorNode); + str.getAndIncrement(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + modifier.apply(method); + } + } + Map> indyBootstrap = new HashMap<>(); + Map> indyBootstrap1 = new HashMap<>(); + if(INDY) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 4) + { + InvokeDynamicInsnNode dyn = (InvokeDynamicInsnNode)ain; + boolean verify = true; + if(!(dyn.bsmArgs[0] instanceof Integer)) + verify = false; + for(int i = 1; i < 4; i++) + { + Object o = dyn.bsmArgs[i]; + if(!(o instanceof String)) + { + verify = false; + break; + } + } + if(verify) + { + Handle bootstrap = dyn.bsm; + ClassNode bootstrapClassNode = classes.get(bootstrap.getOwner()); + MethodNode bootstrapMethodNode = bootstrapClassNode.methods.stream().filter(mn -> mn.name.equals(bootstrap.getName()) + && mn.desc.equals(bootstrap.getDesc())).findFirst().orElse(null); + if(!indyBootstrap.containsKey(bootstrapClassNode) || !indyBootstrap.get(bootstrapClassNode).contains(bootstrapMethodNode)) + patchMethodIndy(bootstrapMethodNode); + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); //Lookup + args.add(JavaValue.valueOf(dyn.name)); //dyn method name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); //dyn method type + for(Object o : dyn.bsmArgs) + args.add(JavaValue.valueOf(o)); + try + { + Context context = new Context(provider); + context.dictionary = this.classpath; + MethodInsnNode replacement = null; + + if(FAST_INDY) + { + String[] result = MethodExecutor.execute(bootstrapClassNode, bootstrapMethodNode, args, null, context); + switch (result[3]) { + case "findVirtual": + replacement = new MethodInsnNode(Opcodes.INVOKEVIRTUAL, result[0].replace('.', '/'), result[1], result[2], + false); + break; + case "findStatic": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, result[0].replace('.', '/'), result[1], result[2], false); + break; + } + }else + { + JavaMethodHandle result = MethodExecutor.execute(bootstrapClassNode, bootstrapMethodNode, args, null, context); + String clazz = result.clazz.replace('.', '/'); + switch (result.type) { + case "virtual": + replacement = new MethodInsnNode((classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? + Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, clazz, result.name, result.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, clazz, result.name, result.desc, false); + break; + } + } + method.instructions.set(ain, replacement); + indyBootstrap.putIfAbsent(bootstrapClassNode, new HashSet<>()); + indyBootstrap.get(bootstrapClassNode).add(bootstrapMethodNode); + indy.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + }else if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 5) + { + InvokeDynamicInsnNode dyn = (InvokeDynamicInsnNode)ain; + boolean string = true; + if(!(dyn.bsmArgs[0] instanceof Integer) || !(dyn.bsmArgs[1] instanceof Integer)) + string = false; + for(int i = 2; i < 5; i++) + { + Object o = dyn.bsmArgs[i]; + if(!(o instanceof String)) + { + string = false; + break; + } + } + if(string) + { + Handle bootstrap = dyn.bsm; + ClassNode bootstrapClassNode = classes.get(bootstrap.getOwner()); + MethodNode bootstrapMethodNode = bootstrapClassNode.methods.stream().filter(mn -> mn.name.equals(bootstrap.getName()) + && mn.desc.equals(bootstrap.getDesc())).findFirst().orElse(null); + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); //Lookup + args.add(JavaValue.valueOf(dyn.name)); //dyn method name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); //dyn method type + for(Object o : dyn.bsmArgs) + args.add(JavaValue.valueOf(o)); + try + { + Context context = new Context(provider); + context.dictionary = this.classpath; + + JavaHandle result = MethodExecutor.execute(bootstrapClassNode, bootstrapMethodNode, args, null, context); + AbstractInsnNode replacement = null; + if(result instanceof JavaMethodHandle) + { + JavaMethodHandle jmh = (JavaMethodHandle)result; + String clazz = jmh.clazz.replace('.', '/'); + switch (jmh.type) { + case "virtual": + replacement = new MethodInsnNode((classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? + Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, clazz, jmh.name, jmh.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, clazz, jmh.name, jmh.desc, false); + break; + } + }else + { + JavaFieldHandle jfh = (JavaFieldHandle)result; + String clazz = jfh.clazz.replace('.', '/'); + switch (jfh.type) { + case "virtual": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTFIELD : Opcodes.GETFIELD, clazz, jfh.name, jfh.desc); + break; + case "static": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTSTATIC : Opcodes.GETSTATIC, clazz, jfh.name, jfh.desc); + break; + } + } + method.instructions.set(ain, replacement); + indyBootstrap.putIfAbsent(bootstrapClassNode, new HashSet<>()); + indyBootstrap.get(bootstrapClassNode).add(bootstrapMethodNode); + indy.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + }else if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 0) + { + InvokeDynamicInsnNode dyn = (InvokeDynamicInsnNode)ain; + Handle bootstrap = dyn.bsm; + ClassNode bootstrapClassNode = classes.get(bootstrap.getOwner()); + MethodNode bootstrapMethodNode = bootstrapClassNode.methods.stream().filter(mn -> mn.name.equals(bootstrap.getName()) + && mn.desc.equals(bootstrap.getDesc())).findFirst().orElse(null); + if((!indyBootstrap1.containsKey(bootstrapClassNode) || !indyBootstrap1.get(bootstrapClassNode).contains(bootstrapMethodNode)) + && !isCorrectIndy(bootstrapMethodNode)) + continue; + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); //Lookup + args.add(JavaValue.valueOf(dyn.name)); //dyn method name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); //dyn method type + try + { + Context context = new Context(provider); + context.dictionary = this.classpath; + + JavaHandle result = MethodExecutor.execute(bootstrapClassNode, bootstrapMethodNode, args, null, context); + AbstractInsnNode replacement = null; + if(result instanceof JavaMethodHandle) + { + JavaMethodHandle jmh = (JavaMethodHandle)result; + String clazz = jmh.clazz.replace('.', '/'); + switch (jmh.type) { + case "virtual": + replacement = new MethodInsnNode((classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? + Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, clazz, jmh.name, jmh.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, clazz, jmh.name, jmh.desc, false); + break; + case "special": + replacement = new MethodInsnNode(Opcodes.INVOKESPECIAL, clazz, jmh.name, jmh.desc, false); + break; + } + }else + { + JavaFieldHandle jfh = (JavaFieldHandle)result; + String clazz = jfh.clazz.replace('.', '/'); + switch (jfh.type) { + case "virtual": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTFIELD : Opcodes.GETFIELD, clazz, jfh.name, jfh.desc); + break; + case "static": + replacement = new FieldInsnNode(jfh.setter ? + Opcodes.PUTSTATIC : Opcodes.GETSTATIC, clazz, jfh.name, jfh.desc); + break; + } + } + method.instructions.set(ain, replacement); + indyBootstrap1.putIfAbsent(bootstrapClassNode, new HashSet<>()); + indyBootstrap1.get(bootstrapClassNode).add(bootstrapMethodNode); + indy.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + indyBootstrap.entrySet().forEach(e -> e.getValue().forEach(m -> e.getKey().methods.remove(m))); + indyBootstrap1.entrySet().forEach(e -> { + classes.remove(e.getKey().name); + classpath.remove(e.getKey().name); + }); + stringDecrypt.entrySet().forEach(e -> e.getValue().forEach(m -> e.getKey().methods.remove(m))); + stringDecryptField.entrySet().forEach(e -> e.getValue().forEach(m -> e.getKey().fields.remove(m))); + stringDecryptClass.forEach(e -> { + classes.remove(e.name); + classpath.remove(e.name); + }); + System.out.println("[Special] [RadonTransformer] Removed " + flow + " fake jump instructions"); + System.out.println("[Special] [RadonTransformer] Fixed " + number + " number instructions"); + System.out.println("[Special] [RadonTransformer] Unpooled " + strPool + " strings"); + System.out.println("[Special] [RadonTransformer] Decrypted " + str + " strings"); + System.out.println("[Special] [RadonTransformer] Removed " + indy + " invokedynamic instructions"); + System.out.println("[Special] [RadonTransformer] Done"); + return flow.get() > 0 || number.get() > 0 || strPool.get() > 0 || str.get() > 0 || indy.get() > 0; + } + + private boolean isCorrectStringPool(MethodNode method, int mode) + { + if(method == null) + return false; + if(mode == 0 ? !method.desc.equals("(I)Ljava/lang/String;") : !method.desc.equals("()V")) + return false; + int numberCount = 0; + int storeCount = 0; + int dupCount = 0; + int ldcCount = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(Utils.isInteger(ain)) + numberCount++; + else if(ain.getOpcode() == Opcodes.LDC && ((LdcInsnNode)ain).cst instanceof String) + ldcCount++; + else if(ain.getOpcode() == Opcodes.DUP) + dupCount++; + else if(ain.getOpcode() == Opcodes.AASTORE) + storeCount++; + if(numberCount == storeCount + 1 && dupCount == storeCount && ldcCount == storeCount) + return true; + return false; + } + + private int isCorrectStringDecrypt(ClassNode classNode, MethodNode method) + { + if(method == null) + return -1; + if(method.desc.equals("(Ljava/lang/String;I)Ljava/lang/String;")) + { + int charArray = 0; + int ixor = 0; + int special = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.IXOR) + ixor++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("toCharArray") + && ((MethodInsnNode)ain).owner.equals("java/lang/String")) + charArray++; + else if(ain.getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)ain).desc.equals("(Ljava/lang/String;Ljava/lang/String;)V") + && ((MethodInsnNode)ain).owner.equals(classNode.name)) + special++; + if(charArray == 1 && ixor == 1) + { + if(special == 1) + return 1; + return 0; + } + }else if(method.desc.equals("(Ljava/lang/Object;I)Ljava/lang/String;")) + { + int stackTrace = 0; + int stackTrace2 = 0; + int charArray = 0; + int ixor = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.IXOR) + ixor++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("toCharArray") + && ((MethodInsnNode)ain).owner.equals("java/lang/String")) + charArray++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("getStackTrace") + && ((MethodInsnNode)ain).owner.equals("java/lang/Throwable")) + stackTrace++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("getStackTrace") + && ((MethodInsnNode)ain).owner.equals("java/lang/Thread")) + stackTrace2++; + if(charArray == 1 && stackTrace == 1 && ixor == 3) + return 0; + else if(charArray == 1 && stackTrace == 0 && stackTrace2 == 1 + && ixor == 8 && classNode.superName.equals("java/lang/Thread")) + return 1; + }else if(method.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;I)Ljava/lang/String;") + || method.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/String;")) + { + int stackTrace = 0; + int charArray = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("toCharArray") + && ((MethodInsnNode)ain).owner.equals("java/lang/String")) + charArray++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("getStackTrace") + && (((MethodInsnNode)ain).owner.equals("java/lang/Thread") + || ((MethodInsnNode)ain).owner.equals("java/lang/Throwable"))) + stackTrace++; + if(charArray == 6 && stackTrace == 2) + return 0; + } + return -1; + } + + private boolean isCorrectIndy(MethodNode method) + { + if(method == null) + return false; + if(method.desc.equals("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")) + { + int string = 0; + int lookup = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.LDC && ((LdcInsnNode)ain).cst instanceof String) + string++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.startsWith("find") + && ((MethodInsnNode)ain).owner.equals("java/lang/invoke/MethodHandles$Lookup")) + lookup++; + if(string == 1 && lookup == 7) + return true; + } + return true; + } + + private void patchMethodIndy(MethodNode method) + { + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof MethodInsnNode && (((MethodInsnNode)ain).owner.equals("java/lang/Runtime") + || (((MethodInsnNode)ain).owner.equals("java/util/concurrent/ThreadLocalRandom")))) + { + if(((MethodInsnNode)ain).name.equals("nextInt")) + method.instructions.remove(Utils.getNext(ain)); + method.instructions.remove(ain); + } + + if(FAST_INDY) + { + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)ain).owner.equals("java/lang/Class") + && ((MethodInsnNode)ain).name.equals("forName")) + method.instructions.remove(ain); + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain).owner.equals("java/lang/invoke/MethodHandle") + && ((MethodInsnNode)ain).name.equals("asType")) + method.instructions.set(ain, new InsnNode(Opcodes.POP)); + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain).owner.equals("java/lang/invoke/MethodHandles$Lookup")) + MethodExecutor.customMethodFunc.put(ain, (args, ctx) -> { + String owner = args.remove(0).as(String.class); + String name = args.remove(0).as(String.class); + String desc = args.remove(0).as(String.class); + args.remove(0); + return JavaValue.valueOf(new String[]{owner, name, desc, ((MethodInsnNode)ain).name}); + }); + } + } + } + + private void patchMethodString(ClassNode classNode) + { + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof MethodInsnNode && ((MethodInsnNode)ain).name.equals("availableProcessors") + && ((MethodInsnNode)ain).owner.equals("java/lang/Runtime")) + method.instructions.set(ain, new LdcInsnNode(4)); + else if(ain instanceof MethodInsnNode && ((MethodInsnNode)ain).name.equals("getRuntime") + && ((MethodInsnNode)ain).owner.equals("java/lang/Runtime")) + method.instructions.set(ain, new InsnNode(Opcodes.ACONST_NULL)); + } +} diff --git a/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformerV2.java b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformerV2.java new file mode 100644 index 00000000..b24c90b4 --- /dev/null +++ b/src/main/java/com/javadeobfuscator/deobfuscator/transformers/special/RadonTransformerV2.java @@ -0,0 +1,1463 @@ +package com.javadeobfuscator.deobfuscator.transformers.special; + +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicInteger; + +import org.assertj.core.internal.asm.Opcodes; +import org.objectweb.asm.Handle; +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 org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; + +import com.javadeobfuscator.deobfuscator.analyzer.FlowAnalyzer; +import com.javadeobfuscator.deobfuscator.asm.source.ConstantPropagatingSourceFinder; +import com.javadeobfuscator.deobfuscator.asm.source.SourceFinder; +import com.javadeobfuscator.deobfuscator.config.TransformerConfig; +import com.javadeobfuscator.deobfuscator.exceptions.NoClassInPathException; +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.defined.types.JavaMethodHandle; +import com.javadeobfuscator.deobfuscator.executor.providers.ComparisonProvider; +import com.javadeobfuscator.deobfuscator.executor.providers.DelegatingProvider; +import com.javadeobfuscator.deobfuscator.executor.values.JavaInteger; +import com.javadeobfuscator.deobfuscator.executor.values.JavaObject; +import com.javadeobfuscator.deobfuscator.executor.values.JavaValue; +import com.javadeobfuscator.deobfuscator.transformers.Transformer; +import com.javadeobfuscator.deobfuscator.utils.InstructionModifier; +import com.javadeobfuscator.deobfuscator.utils.TransformerHelper; +import com.javadeobfuscator.deobfuscator.utils.Utils; + +public class RadonTransformerV2 extends Transformer +{ + public static boolean EJECTOR = true; + public static boolean ANTI_DEBUG = true; + public static boolean FLOW_OBF = true; + public static boolean STRING_POOL = true; + public static boolean NUMBER = true; + public static boolean NUMBER_CONTEXT_OBF = true; + public static boolean INDY = true; + public static boolean STRING = true; + + @Override + public boolean transform() throws Throwable + { + DelegatingProvider provider = new DelegatingProvider(); + provider.register(new PrimitiveFieldProvider()); + provider.register(new MappedFieldProvider()); + provider.register(new JVMMethodProvider()); + provider.register(new MappedMethodProvider(classes)); + provider.register(new ComparisonProvider() { + @Override + public boolean instanceOf(JavaValue target, Type type, Context context) { + if(type.getDescriptor().equals("Ljava/lang/Long;")) + if(!(target.value() instanceof Long)) + return false; + if(type.getDescriptor().equals("Ljava/lang/Integer;")) + if(!(target.value() instanceof Integer)) + return false; + return true; + } + + @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) { + return true; + } + + @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] [RadonTransformerV2] Starting"); + AtomicInteger eject = new AtomicInteger(); + AtomicInteger antiDebug = new AtomicInteger(); + AtomicInteger tryCatch = new AtomicInteger(); + AtomicInteger flowObf = new AtomicInteger(); + AtomicInteger strPool = new AtomicInteger(); + AtomicInteger number = new AtomicInteger(); + AtomicInteger indy = new AtomicInteger(); + AtomicInteger str = new AtomicInteger(); + //Bad Annotations + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + if(method.visibleAnnotations != null) + { + Iterator itr = method.visibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode node = itr.next(); + if(node.desc.equals("@") || node.desc.equals("")) + itr.remove(); + } + } + if(method.invisibleAnnotations != null) + { + Iterator itr = method.invisibleAnnotations.iterator(); + while(itr.hasNext()) + { + AnnotationNode node = itr.next(); + if(node.desc.equals("@") || node.desc.equals("")) + itr.remove(); + } + } + } + if(EJECTOR) + for(ClassNode classNode : classNodes()) + { + Set ejectMethods = new HashSet<>(); + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)ain).owner.equals(classNode.name)) + { + MethodNode ejectMethod = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)ain).name) + && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + String start = method.name.replace('<', '_').replace('>', '_') + "$"; + if(ejectMethod != null && ejectMethod.name.startsWith(start)) + { + ejectMethods.add(ejectMethod); + for(AbstractInsnNode a1 : ejectMethod.instructions.toArray()) + if(a1.getOpcode() == Opcodes.ILOAD && a1.getNext() != null + && Utils.isInteger(a1.getNext()) && a1.getNext().getNext() != null + && a1.getNext().getNext().getOpcode() == Opcodes.IXOR + && a1.getNext().getNext().getNext() != null + && Utils.isInteger(a1.getNext().getNext().getNext()) + && a1.getNext().getNext().getNext().getNext() != null + && a1.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IF_ICMPNE) + { + LdcInsnNode prevInt = (LdcInsnNode)ain.getPrevious(); + int res = (int)prevInt.cst ^ Utils.getIntValue(a1.getNext()); + if(res != Utils.getIntValue(a1.getNext().getNext().getNext())) + continue; + //Get nodes passed + LinkedHashMap> passed = new FlowAnalyzer(method).analyze( + a1.getNext().getNext().getNext().getNext(), new ArrayList<>(), new HashMap<>(), + false, false); + List list = new ArrayList<>(); + passed.values().forEach(li -> list.addAll(li)); + list.remove(0);//ICMPNE + list.remove(list.size() - 1);//return + list.removeIf(a -> a.getOpcode() == Opcodes.CHECKCAST);//Won't be dealing with that + AbstractInsnNode last = list.get(list.size() - 1); + if(last instanceof FieldInsnNode) + { + if(last.getOpcode() == Opcodes.PUTSTATIC) + { + if(list.size() != 2) + throw new RuntimeException("Unexpected Ejector pattern (PS)"); + AbstractInsnNode prev = list.get(0); + method.instructions.remove(ain.getPrevious().getPrevious()); + method.instructions.remove(ain.getPrevious()); + method.instructions.insertBefore(ain, prev.clone(null)); + method.instructions.set(ain, last.clone(null)); + eject.incrementAndGet(); + }else if(last.getOpcode() == Opcodes.PUTFIELD) + { + if(list.size() != 3) + throw new RuntimeException("Unexpected Ejector pattern (PS)"); + AbstractInsnNode prev = list.get(1); + method.instructions.remove(ain.getPrevious());//number + method.instructions.insertBefore(ain, prev.clone(null)); + method.instructions.set(ain, last.clone(null)); + eject.incrementAndGet(); + } + }else if(last instanceof MethodInsnNode) + { + if(last.getOpcode() == Opcodes.INVOKESTATIC) + { + List constants = new ArrayList<>(); + for(int i = 0; i < list.size(); i++) + { + AbstractInsnNode a = list.get(i); + if(a.getOpcode() >= Opcodes.ISTORE + && a.getOpcode() <= Opcodes.ASTORE) + constants.add(list.get(i - 1)); + } + Collections.reverse(constants); + method.instructions.remove(ain.getPrevious().getPrevious().getPrevious()); + method.instructions.remove(ain.getPrevious().getPrevious()); + method.instructions.remove(ain.getPrevious()); + for(AbstractInsnNode a : constants) + method.instructions.insertBefore(ain, a.clone(null)); + method.instructions.set(ain, last.clone(null)); + eject.incrementAndGet(); + }else if(last.getOpcode() == Opcodes.INVOKEVIRTUAL) + { + List constants = new ArrayList<>(); + for(int i = 0; i < list.size(); i++) + { + AbstractInsnNode a = list.get(i); + if(a.getOpcode() >= Opcodes.ISTORE + && a.getOpcode() <= Opcodes.ASTORE) + constants.add(list.get(i - 1)); + } + Collections.reverse(constants); + method.instructions.remove(ain.getPrevious().getPrevious()); + method.instructions.remove(ain.getPrevious()); + for(AbstractInsnNode a : constants) + method.instructions.insertBefore(ain, a.clone(null)); + method.instructions.set(ain, last.clone(null)); + eject.incrementAndGet(); + } + }else + throw new RuntimeException("Unexpected Ejector pattern"); + } + } + } + ejectMethods.forEach(m -> classNode.methods.remove(m)); + } + if(ANTI_DEBUG) + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit == null) + continue; + for(AbstractInsnNode ain : clinit.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.INVOKESTATIC && ((MethodInsnNode)ain).owner.equals("java/lang/management/ManagementFactory") + && ((MethodInsnNode)ain).name.equals("getRuntimeMXBean") && ain.getNext() != null + && ain.getNext().getOpcode() == Opcodes.INVOKEINTERFACE + && ((MethodInsnNode)ain.getNext()).owner.equals("java/lang/management/RuntimeMXBean") + && ((MethodInsnNode)ain.getNext()).name.equals("getInputArguments") + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Object") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("toString") + && ain.getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getNext().getNext().getNext()).owner.equals("java/lang/String") + && (((MethodInsnNode)ain.getNext().getNext().getNext()).name.equals("toLowerCase") + || ((MethodInsnNode)ain.getNext().getNext().getNext()).name.equals("toUpperCase")) + && ain.getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.LDC + && ain.getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain.getNext().getNext().getNext().getNext().getNext()).owner.equals("java/lang/String") + && ((MethodInsnNode)ain.getNext().getNext().getNext().getNext().getNext()).name.equals("contains") + && ain.getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IFEQ) + { + AbstractInsnNode jumpSite = ((JumpInsnNode)ain.getNext().getNext().getNext().getNext().getNext().getNext()).label; + while(ain.getNext() != jumpSite) + clinit.instructions.remove(ain.getNext()); + clinit.instructions.remove(ain); + antiDebug.incrementAndGet(); + } + } + } + //Reverse nullcheckmutilator + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL && ((MethodInsnNode)ain).owner.equals("java/lang/Object") + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.POP + && ain.getNext().getNext() != null && ain.getNext().getNext() instanceof LabelNode + && ain.getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getOpcode() == Opcodes.GOTO + && ain.getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext() instanceof LabelNode) + { + String desc = ((MethodInsnNode)ain).desc; + if(Type.getArgumentTypes(desc).length == 0 + && ain.getPrevious() != null && ain.getPrevious() instanceof LabelNode) + { + TryCatchBlockNode nullCatch = null; + for(TryCatchBlockNode tcbn : method.tryCatchBlocks) + if(tcbn.type.equals("java/lang/NullPointerException") + && tcbn.start == ain.getPrevious() && tcbn.end == ain.getNext().getNext() + && tcbn.handler == ain.getNext().getNext().getNext().getNext()) + { + nullCatch = tcbn; + break; + } + if(nullCatch != null) + { + method.tryCatchBlocks.remove(nullCatch); + method.instructions.remove(nullCatch.handler.getNext()); + ((JumpInsnNode)ain.getNext().getNext().getNext()).setOpcode(Opcodes.IFNONNULL); + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain); + } + }else if(Type.getArgumentTypes(desc).length == 1 + && ain.getPrevious() != null && ain.getPrevious().getOpcode() == Opcodes.ACONST_NULL + && ain.getPrevious().getPrevious() != null && ain.getPrevious().getPrevious() instanceof LabelNode) + { + TryCatchBlockNode nullCatch = null; + for(TryCatchBlockNode tcbn : method.tryCatchBlocks) + if(tcbn.type.equals("java/lang/NullPointerException") + && tcbn.start == ain.getPrevious().getPrevious() && tcbn.end == ain.getNext().getNext() + && tcbn.handler == ain.getNext().getNext().getNext().getNext()) + { + nullCatch = tcbn; + break; + } + if(nullCatch != null) + { + method.tryCatchBlocks.remove(nullCatch); + method.instructions.remove(nullCatch.handler.getNext()); + ((JumpInsnNode)ain.getNext().getNext().getNext()).setOpcode(Opcodes.IFNONNULL); + method.instructions.remove(ain.getNext()); + method.instructions.remove(ain.getPrevious()); + method.instructions.remove(ain); + } + } + } + } + //Reverse instructionsetreducer + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + { + AbstractInsnNode replace = null; + List remove = new ArrayList<>(); + List labels = new ArrayList<>(); + if(ain.getOpcode() == Opcodes.DUP && ain.getNext() != null && Utils.isInteger(ain.getNext()) + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.IADD + && ain.getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getOpcode() == Opcodes.IFLT + && ain.getNext().getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.DUP + && ain.getNext().getNext().getNext().getNext().getNext() != null + && Utils.isInteger(ain.getNext().getNext().getNext().getNext().getNext()) + && ain.getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IADD + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IFGT + && ((JumpInsnNode)ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext()).label == + ((JumpInsnNode)ain.getNext().getNext().getNext()).label + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext().getNext() != null + && ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.DUP) + { + AbstractInsnNode now = ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext().getNext(); + remove.add(ain.getNext().getNext().getNext().getNext().getNext().getNext().getNext()); + remove.add(ain.getNext().getNext().getNext().getNext().getNext().getNext()); + remove.add(ain.getNext().getNext().getNext().getNext().getNext()); + remove.add(ain.getNext().getNext().getNext().getNext()); + remove.add(ain.getNext().getNext().getNext()); + remove.add(ain.getNext().getNext()); + remove.add(ain.getNext()); + boolean firstPass = true; + while(true) + { + if(now.getNext() != null && ((!firstPass && Utils.getIntValue(now.getNext()) == -1) || + (firstPass && Utils.getIntValue(now.getNext()) == Utils.getIntValue(ain.getNext()))) + && now.getNext().getNext() != null && now.getNext().getNext().getOpcode() == Opcodes.IADD + && now.getNext().getNext().getNext() != null && now.getNext().getNext().getNext().getOpcode() == Opcodes.DUP + && now.getNext().getNext().getNext().getNext() != null + && now.getNext().getNext().getNext().getNext().getOpcode() == Opcodes.IFNE + && now.getNext().getNext().getNext().getNext().getNext() != null + && now.getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.POP + && now.getNext().getNext().getNext().getNext().getNext().getNext() != null + && now.getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.POP + && now.getNext().getNext().getNext().getNext().getNext().getNext().getNext() != null + && now.getNext().getNext().getNext().getNext().getNext().getNext().getNext().getOpcode() == Opcodes.GOTO) + { + labels.add(((JumpInsnNode)now.getNext().getNext().getNext().getNext().getNext().getNext().getNext()).label); + remove.add(now.getNext().getNext().getNext().getNext().getNext().getNext().getNext()); + remove.add(now.getNext().getNext().getNext().getNext().getNext().getNext()); + remove.add(now.getNext().getNext().getNext().getNext().getNext()); + remove.add(now.getNext().getNext().getNext().getNext()); + remove.add(now.getNext().getNext().getNext()); + remove.add(now.getNext().getNext()); + remove.add(now.getNext()); + remove.add(now); + now = ((JumpInsnNode)now.getNext().getNext().getNext().getNext()).label; + firstPass = false; + }else + break; + } + if(now.getNext() != null + && now.getNext().getOpcode() == Opcodes.POP && now.getNext().getNext() != null + && now.getNext().getNext().getOpcode() == Opcodes.GOTO + && ((JumpInsnNode)now.getNext().getNext()).label == + ((JumpInsnNode)ain.getNext().getNext().getNext()).label) + { + remove.add(now.getNext().getNext()); + remove.add(now.getNext()); + remove.add(now); + now = ((JumpInsnNode)now.getNext().getNext()).label; + if(now.getNext() != null && now.getNext().getOpcode() == Opcodes.POP + && now.getNext().getNext() != null && now.getNext().getNext().getOpcode() == Opcodes.GOTO) + { + remove.add(now.getNext().getNext()); + remove.add(now.getNext()); + remove.add(now); + labels.add(((JumpInsnNode)now.getNext().getNext()).label); + if(ain.getPrevious() instanceof LabelNode) + { + Frame[] frames; + try + { + frames = new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, method); + }catch(AnalyzerException e) + { + throw new RuntimeException(e); + } + Frame value = frames[method.instructions.indexOf(ain.getPrevious())]; + Set insns = value.getStack(value.getStackSize() - 1).insns; + if(insns.size() == 1 + && insns.iterator().next().getNext() != null + && insns.iterator().next().getNext().getOpcode() == Opcodes.GOTO) + { + replace = insns.iterator().next().getNext(); + remove.add(ain); + remove.add(ain.getPrevious()); + } + } + if(replace == null) + replace = ain; + LabelNode dflt = labels.remove(labels.size() - 1); + method.instructions.set(replace, new TableSwitchInsnNode(-Utils.getIntValue(ain.getNext()), + -Utils.getIntValue(ain.getNext().getNext().getNext().getNext().getNext()), + dflt, labels.toArray(new LabelNode[0]))); + for(AbstractInsnNode a : remove) + method.instructions.remove(a); + } + } + } + } + if(FLOW_OBF) + { + List fakeExceptionClasses = new ArrayList<>(); + for(ClassNode classNode : classNodes()) + { + try + { + if(classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null) == null + && getDeobfuscator().isSubclass("java/lang/Throwable", classNode.name)) + fakeExceptionClasses.add(classNode.name); + }catch(NoClassInPathException e) + { + //Ignore errors + } + } + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + Iterator itr = method.tryCatchBlocks.iterator(); + while(itr.hasNext()) + { + TryCatchBlockNode tcbn = itr.next(); + if(fakeExceptionClasses.contains(tcbn.type)) + { + itr.remove(); + tryCatch.incrementAndGet(); + } + } + //Dead code + InstructionModifier modifier = new InstructionModifier(); + + Frame[] frames = new Analyzer<>(new BasicInterpreter()).analyze(classNode.name, method); + 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); + } + fakeExceptionClasses.forEach(s -> { + classes.remove(s); + classpath.remove(s); + }); + //Jumps + for(ClassNode classNode : classNodes()) + { + List remove = new ArrayList<>(); + for(MethodNode method : classNode.methods) + { + LinkedHashMap> res = new FlowAnalyzer(method).analyze(method.instructions.getFirst(), Arrays.asList(), + new HashMap<>(), true, true); + InstructionModifier modifier = new InstructionModifier(); + boolean fail = false; + int store = -1; + FieldNode field = null; + for(Entry> entry : res.entrySet()) + for(AbstractInsnNode ain : entry.getValue()) + if(store == -1 && ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).desc.equals("Z") + && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.ISTORE) + { + field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && f.desc.equals("Z")).findFirst().orElse(null); + if(field != null && Modifier.isFinal(field.access) && Modifier.isPublic(field.access) + && field.value == null) + { + modifier.remove(ain.getNext()); + modifier.remove(ain); + store = ((VarInsnNode)ain.getNext()).var; + } + }else if(store != -1 && ain.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)ain).var == store) + if(getNextFollowGoto(ain, 1) != null && getNextFollowGoto(ain, 1).getOpcode() == Opcodes.IFEQ + && getNextFollowGoto(ain, 2) != null && getNextFollowGoto(ain, 2).getOpcode() == Opcodes.ACONST_NULL + && getNextFollowGoto(ain, 3) != null + && getNextFollowGoto(ain, 3).getOpcode() == Opcodes.ATHROW) + { + modifier.remove(getNextFollowGoto(ain, 3)); + modifier.remove(getNextFollowGoto(ain, 2)); + modifier.remove(getNextFollowGoto(ain, 1)); + modifier.replace(ain, new JumpInsnNode(Opcodes.GOTO, ((JumpInsnNode)getNextFollowGoto(ain, 1)).label)); + flowObf.incrementAndGet(); + }else + { + fail = true; + break; + } + if(!fail) + { + modifier.apply(method); + if(!remove.contains(field)) + remove.add(field); + } + } + remove.forEach(f -> classNode.fields.remove(f)); + } + //2nd jump + for(ClassNode classNode : classNodes()) + { + List remove = new ArrayList<>(); + for(MethodNode method : classNode.methods) + { + LinkedHashMap> res = new FlowAnalyzer(method).analyze(method.instructions.getFirst(), Arrays.asList(), + new HashMap<>(), true, true); + InstructionModifier modifier = new InstructionModifier(); + List breaks = new ArrayList<>(); + List checked = new ArrayList<>(); + int store = -1; + FieldNode field = null; + for(Entry> entry : res.entrySet()) + for(AbstractInsnNode ain : entry.getValue()) + { + if(store == -1 && ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).desc.equals("I") + && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.ISTORE) + { + field = classNode.fields.stream().filter(f -> f.name.equals(((FieldInsnNode)ain).name) + && f.desc.equals("I")).findFirst().orElse(null); + if(field != null && Modifier.isFinal(field.access) && Modifier.isPublic(field.access) + && field.value == null) + { + checked.add(ain.getNext()); + modifier.remove(ain.getNext()); + modifier.remove(ain); + store = ((VarInsnNode)ain.getNext()).var; + } + }else if(store != -1 && ain.getOpcode() == Opcodes.ILOAD && ((VarInsnNode)ain).var == store) + if(getNextFollowGoto(ain, 1) != null && getNextFollowGoto(ain, 1).getOpcode() == Opcodes.IFEQ) + { + breaks.add(getNextFollowGoto(ain, 2)); + checked.add(ain); + modifier.remove(getNextFollowGoto(ain, 1)); + modifier.replace(ain, new JumpInsnNode(Opcodes.GOTO, ((JumpInsnNode)getNextFollowGoto(ain, 1)).label)); + flowObf.incrementAndGet(); + } + } + if(store != -1) + { + LinkedHashMap> res2 = new FlowAnalyzer(method).analyze(method.instructions.getFirst(), breaks, + new HashMap<>(), false, true); + boolean reached = false; + boolean pass = true; + lp: + for(Entry> entry : res2.entrySet()) + for(AbstractInsnNode ain : entry.getValue()) + { + if(reached && (ain.getOpcode() == Opcodes.ISTORE || ain.getOpcode() == Opcodes.ILOAD) + && ((VarInsnNode)ain).var == store && !checked.contains(ain)) + { + pass = false; + break lp; + } + if(ain == checked.get(0)) + reached = true; + } + if(pass) + { + modifier.apply(method); + if(!remove.contains(field)) + remove.add(field); + } + //Dead code + InstructionModifier modifier2 = new InstructionModifier(); + + Frame[] frames = new Analyzer<>(new BasicInterpreter()).analyze(classNode.name, method); + for(int i = 0; i < method.instructions.size(); i++) + { + if(!Utils.isInstruction(method.instructions.get(i))) + continue; + if(frames[i] != null) + continue; + + modifier2.remove(method.instructions.get(i)); + } + modifier2.apply(method); + for(int i = 0; i < method.instructions.size(); i++) + { + AbstractInsnNode node = method.instructions.get(i); + if(node.getOpcode() == Opcodes.GOTO) + { + AbstractInsnNode a = Utils.getNext(node); + AbstractInsnNode b = Utils.getNext(((JumpInsnNode)node).label); + if(a == b) + method.instructions.remove(node); + } + } + } + } + remove.forEach(f -> classNode.fields.remove(f)); + } + //BlockSplitter + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + if(method.localVariables == null || method.localVariables.isEmpty()) + { + if(skipLabel(method.instructions.getFirst()) != null + && skipLabel(method.instructions.getFirst()).getOpcode() == Opcodes.GOTO + && skipLabel(method.instructions.getFirst()).getNext() != null + && skipLabel(method.instructions.getFirst()).getNext() instanceof LabelNode + && ((JumpInsnNode)skipLabel(method.instructions.getFirst())).label != + skipLabel(method.instructions.getFirst()).getNext()) + { + List p2Block = new ArrayList<>(); + LabelNode jumpPoint1 = ((JumpInsnNode)skipLabel(method.instructions.getFirst())).label; + AbstractInsnNode now = skipLabel(method.instructions.getFirst()); + if(method.instructions.getLast().getOpcode() == Opcodes.GOTO + && ((JumpInsnNode)method.instructions.getLast()).label == skipLabel(method.instructions.getFirst()).getNext()) + method.instructions.remove(method.instructions.getLast()); + while(now.getNext() != jumpPoint1) + { + p2Block.add(now.getNext()); + method.instructions.remove(now.getNext()); + } + method.instructions.remove(now); + for(AbstractInsnNode ain : p2Block) + method.instructions.add(ain); + } + } + } + if(NUMBER) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + Map> frames = new HashMap<>(); + Map replace = new LinkedHashMap<>(); + try + { + Frame[] fr = new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, method); + for(int i = 0; i < fr.length; i++) + { + Frame f = fr[i]; + frames.put(method.instructions.get(i), f); + } + }catch(AnalyzerException e) + { + oops("unexpected analyzer exception", e); + continue; + } + for(AbstractInsnNode ain : method.instructions.toArray()) + { + int mode = -1; + if(ain.getOpcode() == Opcodes.IADD || ain.getOpcode() == Opcodes.ISUB || ain.getOpcode() == Opcodes.IMUL + || ain.getOpcode() == Opcodes.IDIV || ain.getOpcode() == Opcodes.IREM || ain.getOpcode() == Opcodes.ISHL + || ain.getOpcode() == Opcodes.ISHR || ain.getOpcode() == Opcodes.IUSHR || ain.getOpcode() == Opcodes.IAND + || ain.getOpcode() == Opcodes.IOR || ain.getOpcode() == Opcodes.IXOR) + mode = 0;//Int + else if(ain.getOpcode() == Opcodes.LADD || ain.getOpcode() == Opcodes.LSUB || ain.getOpcode() == Opcodes.LMUL + || ain.getOpcode() == Opcodes.LDIV || ain.getOpcode() == Opcodes.LREM || ain.getOpcode() == Opcodes.LAND + || ain.getOpcode() == Opcodes.LOR || ain.getOpcode() == Opcodes.LXOR) + mode = 1;//Long + else if(ain.getOpcode() == Opcodes.LSHL || ain.getOpcode() == Opcodes.LSHR || ain.getOpcode() == Opcodes.LUSHR) + mode = 2;//Long shift + else if(ain.getOpcode() == Opcodes.DADD || ain.getOpcode() == Opcodes.DSUB || ain.getOpcode() == Opcodes.DMUL + || ain.getOpcode() == Opcodes.DDIV || ain.getOpcode() == Opcodes.DREM) + mode = 3;//Double + else if(ain.getOpcode() == Opcodes.FADD || ain.getOpcode() == Opcodes.FSUB || ain.getOpcode() == Opcodes.FMUL + || ain.getOpcode() == Opcodes.FDIV || ain.getOpcode() == Opcodes.FREM) + mode = 4;//Float + if(mode == -1) + continue; + Frame f = frames.get(ain); + SourceValue arg1 = f.getStack(f.getStackSize() - 2); + SourceValue arg2 = f.getStack(f.getStackSize() - 1); + if(arg1.insns.size() != 1 || arg2.insns.size() != 1) + continue; + AbstractInsnNode a1 = arg1.insns.iterator().next(); + AbstractInsnNode a2 = arg2.insns.iterator().next(); + for(Entry entry : replace.entrySet()) + if(entry.getKey() == a1) + a1 = entry.getValue(); + else if(entry.getKey() == a2) + a2 = entry.getValue(); + boolean verify = false; + if(mode == 0 && Utils.isInteger(a1) && Utils.isInteger(a2)) + verify = true; + else if(mode == 1 && Utils.isLong(a1) && Utils.isLong(a2)) + verify = true; + else if(mode == 2 && Utils.isLong(a1) && Utils.isInteger(a2)) + verify = true; + else if(mode == 3 && isDouble(a1) && isDouble(a2)) + verify = true; + else if(mode == 4 && isFloat(a1) && isFloat(a2)) + verify = true; + if(verify) + { + AbstractInsnNode newValue; + if((newValue = doMath(a1, a2, ain.getOpcode(), mode)) != null) + { + replace.put(ain, newValue); + method.instructions.set(ain, newValue); + method.instructions.remove(a1); + method.instructions.remove(a2); + number.getAndAdd(2); + } + } + } + } + } + Set numberDecryptClass = new HashSet<>(); + if(NUMBER_CONTEXT_OBF) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + if(ain.getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)ain).desc.equals("(Ljava/lang/Object;I)Ljava/lang/Object;") + && ain.getPrevious() != null && Utils.isInteger(ain.getPrevious()) + && ain.getPrevious().getPrevious() != null + && ain.getPrevious().getPrevious().getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)ain.getPrevious().getPrevious()).name.equals("valueOf") + && (((MethodInsnNode)ain.getPrevious().getPrevious()).owner.equals("java/lang/Integer") + || ((MethodInsnNode)ain.getPrevious().getPrevious()).owner.equals("java/lang/Long")) + && ain.getPrevious().getPrevious().getPrevious() != null + && (Utils.isInteger(ain.getPrevious().getPrevious().getPrevious()) + || Utils.isLong(ain.getPrevious().getPrevious().getPrevious())) + && ain.getNext() != null && ain.getNext().getOpcode() == Opcodes.CHECKCAST + && ain.getNext().getNext() != null && ain.getNext().getNext().getOpcode() == Opcodes.INVOKEVIRTUAL + && ((((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Integer") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("intValue")) + || (((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Long") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("longValue")) + || (((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Double") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("doubleValue")) + || (((MethodInsnNode)ain.getNext().getNext()).owner.equals("java/lang/Float") + && ((MethodInsnNode)ain.getNext().getNext()).name.equals("floatValue")))) + { + boolean isLong = Utils.isLong(ain.getPrevious().getPrevious().getPrevious()); + ClassNode decryptorNode = classNodes().stream().filter(c -> c.name.equals(((MethodInsnNode)ain).owner)).findFirst().orElse(null); + MethodNode decryptorMethod = decryptorNode == null ? null : decryptorNode.methods.stream().filter(m -> + m.name.equals(((MethodInsnNode)ain).name) && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + MethodNode clinit = decryptorNode == null ? null : decryptorNode.methods.stream().filter(m -> + m.name.equals("")).findFirst().orElse(null); + if(numberDecryptClass.contains(decryptorNode) || isCorrectNumberDecrypt(clinit)) + { + Context context = new Context(provider); + context.dictionary = classpath; + context.push(classNode.name, method.name, getDeobfuscator().getConstantPool(classNode).getSize()); + try + { + if(!numberDecryptClass.contains(decryptorNode)) + { + patchMethodNumber(clinit); + MethodExecutor.execute(decryptorNode, clinit, Arrays.asList(), null, context); + numberDecryptClass.add(decryptorNode); + } + JavaValue first = isLong ? new JavaObject(Utils.getLongValue(ain.getPrevious().getPrevious().getPrevious()), "java/lang/Long") + : new JavaObject(Utils.getIntValue(ain.getPrevious().getPrevious().getPrevious()), "java/lang/Integer"); + Object res = MethodExecutor.execute(decryptorNode, decryptorMethod, + Arrays.asList(first, new JavaInteger(Utils.getIntValue(ain.getPrevious()))), null, context); + switch(((MethodInsnNode)ain.getNext().getNext()).owner) + { + case "java/lang/Integer": + modifier.replace(ain, Utils.getIntInsn((int)res)); + break; + case "java/lang/Long": + modifier.replace(ain, Utils.getLongInsn((long)res)); + break; + case "java/lang/Float": + modifier.replace(ain, Utils.getFloatInsn((float)res)); + break; + case "java/lang/Double": + modifier.replace(ain, Utils.getDoubleInsn((double)res)); + break; + default: + throw new RuntimeException("Unexpected type: " + ((MethodInsnNode)ain.getNext().getNext()).owner); + } + modifier.removeAll(Arrays.asList(ain.getPrevious().getPrevious().getPrevious(), + ain.getPrevious().getPrevious(), ain.getPrevious(), ain.getNext().getNext(), ain.getNext())); + numberDecryptClass.add(decryptorNode); + number.getAndIncrement(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + modifier.apply(method); + } + } + if(STRING_POOL) + for(ClassNode classNode : classNodes()) + { + MethodNode clinit = classNode.methods.stream().filter(m -> m.name.equals("")).findFirst().orElse(null); + if(clinit != null) + { + AbstractInsnNode firstInstr = skipGoto(clinit.instructions.getFirst()); + if(firstInstr != null && firstInstr.getOpcode() == Opcodes.INVOKESTATIC + && ((MethodInsnNode)firstInstr).owner.equals(classNode.name)) + { + MethodNode pool = classNode.methods.stream().filter(m -> m.name.equals(((MethodInsnNode)firstInstr).name) + && m.desc.equals(((MethodInsnNode)firstInstr).desc)).findFirst().orElse(null); + if(pool != null && isCorrectStringPool(pool)) + { + FieldInsnNode putstatic = null; + for(AbstractInsnNode ain : pool.instructions.toArray()) + if(ain.getOpcode() == Opcodes.PUTSTATIC) + { + putstatic = (FieldInsnNode)ain; + break; + } + if(putstatic != null && putstatic.owner.equals(classNode.name)) + { + FieldInsnNode putstaticF = putstatic; + FieldNode poolField = classNode.fields.stream().filter(f -> f.name.equals(putstaticF.name) + && f.desc.equals(putstaticF.desc)).findFirst().orElse(null); + if(poolField != null) + { + Context context = new Context(provider); + context.dictionary = classpath; + MethodExecutor.execute(classNode, pool, Arrays.asList(), null, context); + Object[] value = (Object[])context.provider.getField(classNode.name, poolField.name, poolField.desc, null, context); + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + { + if(ain.getOpcode() == Opcodes.GETSTATIC && ((FieldInsnNode)ain).owner.equals(classNode.name) + && ((FieldInsnNode)ain).name.equals(poolField.name) + && ((FieldInsnNode)ain).desc.equals(poolField.desc) + && getNextFollowGoto(ain, 1) != null && Utils.isInteger(getNextFollowGoto(ain, 1)) + && getNextFollowGoto(ain, 2) != null && getNextFollowGoto(ain, 2).getOpcode() == Opcodes.AALOAD) + { + modifier.remove(getNextFollowGoto(ain, 2)); + modifier.remove(getNextFollowGoto(ain, 1)); + modifier.replace(ain, new LdcInsnNode(value[Utils.getIntValue(getNextFollowGoto(ain, 1))])); + strPool.incrementAndGet(); + } + } + modifier.apply(method); + } + classNode.methods.remove(pool); + classNode.fields.remove(poolField); + clinit.instructions.remove(firstInstr); + } + } + } + } + } + } + Set indyBootstrap = new HashSet<>(); + if(INDY) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEDYNAMIC && ((InvokeDynamicInsnNode)ain).bsmArgs.length == 4) + { + InvokeDynamicInsnNode dyn = (InvokeDynamicInsnNode)ain; + boolean verify = true; + if(!(dyn.bsmArgs[0] instanceof Integer)) + verify = false; + for(int i = 1; i < 4; i++) + { + Object o = dyn.bsmArgs[i]; + if(!(o instanceof String)) + { + verify = false; + break; + } + } + if(verify) + { + Handle bootstrap = dyn.bsm; + ClassNode bootstrapClassNode = classes.get(bootstrap.getOwner()); + MethodNode bootstrapMethodNode = bootstrapClassNode.methods.stream().filter(mn -> mn.name.equals(bootstrap.getName()) + && mn.desc.equals(bootstrap.getDesc())).findFirst().orElse(null); + if(!indyBootstrap.contains(bootstrapClassNode)) + patchMethod(bootstrapMethodNode); + List args = new ArrayList<>(); + args.add(new JavaObject(null, "java/lang/invoke/MethodHandles$Lookup")); //Lookup + args.add(JavaValue.valueOf(dyn.name)); //dyn method name + args.add(new JavaObject(null, "java/lang/invoke/MethodType")); //dyn method type + for(Object o : dyn.bsmArgs) + args.add(JavaValue.valueOf(o)); + try + { + Context context = new Context(provider); + context.dictionary = this.classpath; + + JavaMethodHandle result = MethodExecutor.execute(bootstrapClassNode, bootstrapMethodNode, args, null, context); + String clazz = result.clazz.replace('.', '/'); + MethodInsnNode replacement = null; + switch (result.type) { + case "virtual": + replacement = new MethodInsnNode((classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0 ? + Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, clazz, result.name, result.desc, + (classpath.get(clazz).access & Opcodes.ACC_INTERFACE) != 0); + break; + case "static": + replacement = new MethodInsnNode(Opcodes.INVOKESTATIC, clazz, result.name, result.desc, false); + break; + } + method.instructions.insert(ain, replacement); + method.instructions.remove(ain); + indyBootstrap.add(bootstrapClassNode); + indy.incrementAndGet(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + } + Set stringDecryptClass = new HashSet<>(); + if(STRING) + { + for(ClassNode classNode : classNodes()) + for(MethodNode method : classNode.methods) + { + InstructionModifier modifier = new InstructionModifier(); + Frame[] frames; + try + { + frames = new Analyzer<>(new SourceInterpreter()).analyze(classNode.name, method); + }catch(AnalyzerException e) + { + oops("unexpected analyzer exception", e); + continue; + } + insns: + for(AbstractInsnNode ain : TransformerHelper.instructionIterator(method)) + if(ain.getOpcode() == Opcodes.INVOKESTATIC) + { + ClassNode decryptorNode = classNodes().stream().filter(c -> c.name.equals(((MethodInsnNode)ain).owner)).findFirst().orElse(null); + MethodNode decryptorMethod = decryptorNode == null ? null : decryptorNode.methods.stream().filter(m -> + m.name.equals(((MethodInsnNode)ain).name) && m.desc.equals(((MethodInsnNode)ain).desc)).findFirst().orElse(null); + if(isCorrectStringDecrypt(decryptorNode, decryptorMethod)) + { + Frame f1 = frames[method.instructions.indexOf(ain)]; + if(f1 == null) + continue; + Type[] argTypes = Type.getArgumentTypes(((MethodInsnNode)ain).desc); + Frame currentFrame = frames[method.instructions.indexOf(ain)]; + List args = new ArrayList<>(); + List instructions = new ArrayList<>(); + + for(int i = 0, stackOffset = currentFrame.getStackSize() - argTypes.length; i < argTypes.length; i++) + { + Optional consensus = SourceFinder.findSource(method, frames, instructions, new ConstantPropagatingSourceFinder(), + ain, currentFrame.getStack(stackOffset)).consensus(); + if(!consensus.isPresent()) + continue insns; + + Object o = consensus.get(); + if(o instanceof Integer) + args.add(new JavaInteger((int)o)); + else + args.add(new JavaObject(o, "java/lang/String")); + stackOffset++; + } + instructions = new ArrayList<>(new HashSet<>(instructions)); + Context context = new Context(provider); + context.dictionary = classpath; + context.push(classNode.name, method.name, getDeobfuscator().getConstantPool(classNode).getSize()); + try + { + if(!stringDecryptClass.contains(decryptorNode)) + { + MethodExecutor.execute(decryptorNode, decryptorNode.methods.stream().filter(m -> m.name.equals("")). + findFirst().orElse(null), Arrays.asList(), null, context); + patchMethodString(decryptorNode); + } + List pops = new ArrayList<>(); + for(AbstractInsnNode a : method.instructions.toArray()) + if(a.getOpcode() == Opcodes.POP && frames[method.instructions.indexOf(a)] != null) + { + SourceValue value = frames[method.instructions.indexOf(a)].getStack( + frames[method.instructions.indexOf(a)].getStackSize() - 1); + if(instructions.contains(value.insns.iterator().next())) + pops.add(a); + } + modifier.replace(ain, new LdcInsnNode(MethodExecutor.execute(decryptorNode, decryptorMethod, + args, null, context))); + modifier.removeAll(instructions); + modifier.removeAll(pops); + stringDecryptClass.add(decryptorNode); + str.getAndIncrement(); + }catch(Exception e) + { + e.printStackTrace(); + } + } + } + modifier.apply(method); + } + } + numberDecryptClass.forEach(e -> { + classes.remove(e.name); + classpath.remove(e.name); + }); + indyBootstrap.forEach(c -> { + classes.remove(c.name); + classpath.remove(c.name); + }); + stringDecryptClass.forEach(e -> { + classes.remove(e.name); + classpath.remove(e.name); + }); + System.out.println("[Special] [RadonTransformerV2] Unejected " + eject + " methods"); + System.out.println("[Special] [RadonTransformerV2] Removed " + antiDebug + " anti-debug injections"); + System.out.println("[Special] [RadonTransformerV2] Removed " + tryCatch + " try-catch blocks"); + System.out.println("[Special] [RadonTransformerV2] Removed " + flowObf + " fake jumps"); + System.out.println("[Special] [RadonTransformerV2] Folded " + number + " numbers"); + System.out.println("[Special] [RadonTransformerV2] Unpooled " + strPool + " strings"); + System.out.println("[Special] [RadonTransformerV2] Removed " + indy + " invokedynamics"); + System.out.println("[Special] [RadonTransformerV2] Decrypted " + str + " strings"); + return antiDebug.get() > 0 || tryCatch.get() > 0 || flowObf.get() > 0 || strPool.get() > 0 || indy.get() > 0 || str.get() > 0; + } + + private void patchMethodNumber(MethodNode clinit) + { + for(AbstractInsnNode ain : clinit.instructions.toArray()) + if(ain instanceof MethodInsnNode + && ((MethodInsnNode)ain).owner.equals("java/util/concurrent/ThreadLocalRandom")) + { + if(((MethodInsnNode)ain).name.equals("nextInt")) + clinit.instructions.set(ain, new InsnNode(Opcodes.ICONST_0)); + else + clinit.instructions.remove(ain); + } + } + + private boolean isCorrectNumberDecrypt(MethodNode clinit) + { + if(clinit == null) + return false; + int threadLoc = 0; + for(AbstractInsnNode ain : clinit.instructions.toArray()) + if(ain instanceof MethodInsnNode + && ((MethodInsnNode)ain).owner.equals("java/util/concurrent/ThreadLocalRandom")) + threadLoc++; + if(threadLoc == 2) + return true; + return false; + } + + private void patchMethodString(ClassNode classNode) + { + int var = -1; + for(MethodNode method : classNode.methods) + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof MethodInsnNode && ((MethodInsnNode)ain).name.equals("availableProcessors") + && ((MethodInsnNode)ain).owner.equals("java/lang/Runtime")) + method.instructions.set(ain, new LdcInsnNode(4)); + else if(ain instanceof MethodInsnNode && ((MethodInsnNode)ain).name.equals("getRuntime") + && ((MethodInsnNode)ain).owner.equals("java/lang/Runtime")) + method.instructions.set(ain, new InsnNode(Opcodes.ACONST_NULL)); + else if(ain.getOpcode() == Opcodes.NEW + && ((TypeInsnNode)ain).desc.equals("java/util/concurrent/atomic/AtomicInteger")) + { + method.instructions.remove(Utils.getNext(ain)); + method.instructions.set(Utils.getNext(ain), new IincInsnNode(var = ((VarInsnNode)Utils.getNext(ain)).var, 1)); + method.instructions.remove(ain); + }else if(ain.getOpcode() == Opcodes.INVOKESPECIAL + && ((MethodInsnNode)ain).owner.equals("java/util/concurrent/atomic/AtomicInteger") + && ((MethodInsnNode)ain).name.equals("")) + method.instructions.set(ain, new InsnNode(Opcodes.ACONST_NULL)); + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).owner.equals("java/util/concurrent/atomic/AtomicInteger") + && ((MethodInsnNode)ain).name.equals("incrementAndGet")) + { + method.instructions.remove(Utils.getNext(ain)); + method.instructions.remove(ain); + }else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).owner.equals("java/util/concurrent/atomic/AtomicInteger") + && ((MethodInsnNode)ain).name.equals("get")) + { + method.instructions.remove(Utils.getPrevious(ain)); + method.instructions.set(ain, new VarInsnNode(Opcodes.ILOAD, var)); + } + } + + private boolean isCorrectStringDecrypt(ClassNode classNode, MethodNode method) + { + if(method == null) + return false; + if(method.desc.equals("(Ljava/lang/Object;I)Ljava/lang/String;")) + { + int stackTrace = 0; + int charArray = 0; + int hashCode = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("hashCode") + && ((MethodInsnNode)ain).owner.equals("java/lang/String")) + hashCode++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("toCharArray") + && ((MethodInsnNode)ain).owner.equals("java/lang/String")) + charArray++; + else if(ain.getOpcode() == Opcodes.INVOKEVIRTUAL + && ((MethodInsnNode)ain).name.equals("getStackTrace") + && ((MethodInsnNode)ain).owner.equals("java/lang/Thread")) + stackTrace++; + if(charArray == 1 && stackTrace == 1 && hashCode == 2) + return true; + if(charArray == 2 && stackTrace == 1 && hashCode == 9) + return true; + if(charArray == 2 && stackTrace == 2 && hashCode == 21) + return true; + } + return false; + } + + private void patchMethod(MethodNode method) + { + for(AbstractInsnNode ain : method.instructions.toArray()) + if(ain instanceof MethodInsnNode && (((MethodInsnNode)ain).owner.equals("java/lang/Runtime") + || (((MethodInsnNode)ain).owner.equals("java/util/concurrent/ThreadLocalRandom")))) + { + if(((MethodInsnNode)ain).name.equals("nextInt")) + method.instructions.remove(Utils.getNext(ain)); + method.instructions.remove(ain); + } + } + + private boolean isCorrectStringPool(MethodNode method) + { + if(method == null) + return false; + if(!method.desc.equals("()V")) + return false; + int numberCount = 0; + int storeCount = 0; + int dupCount = 0; + int ldcCount = 0; + for(AbstractInsnNode ain : method.instructions.toArray()) + if(Utils.isInteger(ain)) + numberCount++; + else if(ain.getOpcode() == Opcodes.LDC && ((LdcInsnNode)ain).cst instanceof String) + ldcCount++; + else if(ain.getOpcode() == Opcodes.DUP) + dupCount++; + else if(ain.getOpcode() == Opcodes.AASTORE) + storeCount++; + if(numberCount == storeCount + 1 && dupCount == storeCount && ldcCount == storeCount) + return true; + return false; + } + + private AbstractInsnNode skipGoto(AbstractInsnNode node) + { + while(node instanceof LabelNode || node instanceof LineNumberNode || node instanceof FrameNode) + node = node.getNext(); + if(node.getOpcode() == Opcodes.GOTO) + { + JumpInsnNode cast = (JumpInsnNode)node; + node = cast.label; + while(!Utils.isInstruction(node)) + node = node.getNext(); + } + return node; + } + + private AbstractInsnNode skipLabel(AbstractInsnNode node) + { + while(node instanceof LabelNode || node instanceof LineNumberNode || node instanceof FrameNode) + node = node.getNext(); + return node; + } + + private AbstractInsnNode getNextFollowGoto(AbstractInsnNode node, int amount) + { + for(int i = 0; i < amount; i++) + node = Utils.getNextFollowGoto(node); + return node; + } + + private boolean isDouble(AbstractInsnNode node) + { + if(node == null) return false; + if(node.getOpcode() == Opcodes.DCONST_0 + || node.getOpcode() == Opcodes.DCONST_1) + return true; + if(node instanceof LdcInsnNode) + { + LdcInsnNode ldc = (LdcInsnNode)node; + if(ldc.cst instanceof Double) + return true; + } + return false; + } + + private double getDoubleValue(AbstractInsnNode node) + { + if(node.getOpcode() >= Opcodes.DCONST_0 + && node.getOpcode() <= Opcodes.DCONST_1) + return node.getOpcode() - 14; + if(node instanceof LdcInsnNode) + { + LdcInsnNode ldc = (LdcInsnNode)node; + if(ldc.cst instanceof Double) + return (double)ldc.cst; + } + return 0; + } + + private AbstractInsnNode getDoubleInsn(double d) + { + if(d == 0) + return new InsnNode(Opcodes.DCONST_0); + if(d == 1) + return new InsnNode(Opcodes.DCONST_1); + return new LdcInsnNode(d); + } + + private boolean isFloat(AbstractInsnNode node) + { + if(node == null) return false; + if(node.getOpcode() >= Opcodes.FCONST_0 + && node.getOpcode() <= Opcodes.FCONST_2) + return true; + if(node instanceof LdcInsnNode) + { + LdcInsnNode ldc = (LdcInsnNode)node; + if(ldc.cst instanceof Float) + return true; + } + return false; + } + + private float getFloatValue(AbstractInsnNode node) + { + if(node.getOpcode() >= Opcodes.FCONST_0 + && node.getOpcode() <= Opcodes.FCONST_2) + return node.getOpcode() - 11; + if(node instanceof LdcInsnNode) + { + LdcInsnNode ldc = (LdcInsnNode)node; + if(ldc.cst instanceof Float) + return (float)ldc.cst; + } + return 0; + } + + private AbstractInsnNode getFloatInsn(float f) + { + if(f == 0) + return new InsnNode(Opcodes.FCONST_0); + if(f == 1) + return new InsnNode(Opcodes.FCONST_1); + if(f == 2) + return new InsnNode(Opcodes.FCONST_2); + return new LdcInsnNode(f); + } + + private AbstractInsnNode getLongInsn(long l) + { + if(l == 0) + return new InsnNode(Opcodes.LCONST_0); + if(l == 1) + return new InsnNode(Opcodes.LCONST_1); + return new LdcInsnNode(l); + } + + private AbstractInsnNode doMath(AbstractInsnNode value1, AbstractInsnNode value2, int opcode, int mode) + { + switch(mode) + { + case 0: + int i = Utils.getIntValue(value1); + int i2 = Utils.getIntValue(value2); + int iRes = -1; + switch(opcode) + { + case Opcodes.IADD: + iRes = i + i2; + break; + case Opcodes.ISUB: + iRes = i - i2; + break; + case Opcodes.IMUL: + iRes = i * i2; + break; + case Opcodes.IDIV: + iRes = i / i2; + break; + case Opcodes.IREM: + iRes = i % i2; + break; + case Opcodes.ISHL: + iRes = i << i2; + break; + case Opcodes.ISHR: + iRes = i >> i2; + break; + case Opcodes.IUSHR: + iRes = i >>> i2; + break; + case Opcodes.IAND: + iRes = i & i2; + break; + case Opcodes.IOR: + iRes = i | i2; + break; + case Opcodes.IXOR: + iRes = i ^ i2; + break; + default: + throw new RuntimeException(); + } + return Utils.getIntInsn(iRes); + case 1: + long l = Utils.getLongValue(value1); + long l2 = Utils.getLongValue(value2); + long lRes = -1; + switch(opcode) + { + case Opcodes.LADD: + lRes = l + l2; + break; + case Opcodes.LSUB: + lRes = l - l2; + break; + case Opcodes.LMUL: + lRes = l * l2; + break; + case Opcodes.LDIV: + lRes = l / l2; + break; + case Opcodes.LREM: + lRes = l % l2; + break; + case Opcodes.LAND: + lRes = l & l2; + break; + case Opcodes.LOR: + lRes = l | l2; + break; + case Opcodes.LXOR: + lRes = l ^ l2; + break; + default: + throw new RuntimeException(); + } + return getLongInsn(lRes); + case 2: + long li = Utils.getLongValue(value1); + int li2 = Utils.getIntValue(value2); + long liRes = -1; + switch(opcode) + { + case Opcodes.LSHL: + liRes = li << li2; + break; + case Opcodes.LSHR: + liRes = li >> li2; + break; + case Opcodes.LUSHR: + liRes = li >>> li2; + break; + default: + throw new RuntimeException(); + } + return getLongInsn(liRes); + case 3: + double d = getDoubleValue(value1); + double d2 = getDoubleValue(value2); + double dRes = -1; + switch(opcode) + { + case Opcodes.DADD: + dRes = d + d2; + break; + case Opcodes.DSUB: + dRes = d - d2; + break; + case Opcodes.DMUL: + dRes = d * d2; + break; + case Opcodes.DDIV: + dRes = d / d2; + break; + case Opcodes.DREM: + dRes = d % d2; + break; + default: + throw new RuntimeException(); + } + return getDoubleInsn(dRes); + case 4: + float f = getFloatValue(value1); + float f2 = getFloatValue(value2); + float fRes = -1; + switch(opcode) + { + case Opcodes.FADD: + fRes = f + f2; + break; + case Opcodes.FSUB: + fRes = f - f2; + break; + case Opcodes.FMUL: + fRes = f * f2; + break; + case Opcodes.FDIV: + fRes = f / f2; + break; + case Opcodes.FREM: + fRes = f % f2; + break; + default: + throw new RuntimeException(); + } + return getFloatInsn(fRes); + } + throw new RuntimeException(); + } +}