diff --git a/README.md b/README.md index e2977f65..18c955b0 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ A deobfuscator for java > **This is dev(v2) version of diobfuscator version and it's not completed yet, if you are willing to help there is a list of things that needs to be completed. The old diobfuscator was moved to [v1 branch](https://github.com/narumii/Deobfuscator/tree/v1).**
> > - Porting old transformers to new code base -> - Testing InstructionMatcher +> - Testing `Match` API > - Implementing/Improving transformers > - Writing tests -> - Feedback on how the new api presents itself (mainly InstructionMatcher) +> - Feedback on how the new api presents itself (mainly `Match` API) >
> > 1. *You can also provide samples of obfuscation to help with development of the transformers.* diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/Match.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/Match.java index 9114195c..51529b8c 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/Match.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/Match.java @@ -1,7 +1,11 @@ package uwu.narumi.deobfuscator.api.asm.matcher; +import org.objectweb.asm.tree.AbstractInsnNode; import uwu.narumi.deobfuscator.api.asm.InstructionContext; +import uwu.narumi.deobfuscator.api.asm.MethodContext; +import java.util.ArrayList; +import java.util.List; import java.util.function.Predicate; /** @@ -40,6 +44,26 @@ public boolean matchAndMerge(InstructionContext insnContext, MatchContext curren return result != null; } + /** + * Finds all matches in the method + * + * @param methodContext Method context + * @return List of all matches + */ + public List findAllMatches(MethodContext methodContext) { + List allMatches = new ArrayList<>(); + + for (AbstractInsnNode insn : methodContext.methodNode().instructions) { + InstructionContext insnContext = methodContext.newInsnContext(insn); + MatchContext match = this.matchResult(insnContext); + if (match != null) { + allMatches.add(match); + } + } + + return allMatches; + } + /** * @return {@link MatchContext} if matches or {@code null} if it does not match */ @@ -72,15 +96,15 @@ public MatchContext matchResult(InstructionContext insnContext) { protected abstract boolean test(MatchContext context); public Match and(Match match) { - return Match.predicate(context -> this.matchAndMerge(context.insnContext(), context) && match.matchAndMerge(context.insnContext(), context)); + return Match.of(context -> this.matchAndMerge(context.insnContext(), context) && match.matchAndMerge(context.insnContext(), context)); } public Match or(Match match) { - return Match.predicate(context -> this.matchAndMerge(context.insnContext(), context) || match.matchAndMerge(context.insnContext(), context)); + return Match.of(context -> this.matchAndMerge(context.insnContext(), context) || match.matchAndMerge(context.insnContext(), context)); } public Match not() { - return Match.predicate(context -> !matchAndMerge(context.insnContext(), context)); + return Match.of(context -> !matchAndMerge(context.insnContext(), context)); } public Match defineTransformation(Transformation transformation) { @@ -108,7 +132,7 @@ public Transformation transformation() { * @param predicate Your lambda predicate * @return A new {@link Match} */ - public static Match predicate(Predicate predicate) { + public static Match of(Predicate predicate) { return new Match() { @Override protected boolean test(MatchContext context) { diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/group/SequenceMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/group/SequenceMatch.java index fc43f706..9e02b994 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/group/SequenceMatch.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/group/SequenceMatch.java @@ -17,9 +17,9 @@ // TODO: backwards match? public class SequenceMatch extends Match { - private static final Match FRAME_MATCH = Match.predicate(context -> context.insn() instanceof FrameNode); - private static final Match LABEL_MATCH = Match.predicate(context -> context.insn() instanceof LabelNode); - private static final Match LINE_MATCH = Match.predicate(context -> context.insn() instanceof LineNumberNode); + private static final Match FRAME_MATCH = Match.of(context -> context.insn() instanceof FrameNode); + private static final Match LABEL_MATCH = Match.of(context -> context.insn() instanceof LabelNode); + private static final Match LINE_MATCH = Match.of(context -> context.insn() instanceof LineNumberNode); private final Match[] matches; private final List skipMatches = new ArrayList<>(List.of(FRAME_MATCH, LABEL_MATCH, LINE_MATCH)); diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/InsnMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/InsnMatch.java index 0bdbe35e..3104a379 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/InsnMatch.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/InsnMatch.java @@ -4,6 +4,9 @@ import uwu.narumi.deobfuscator.api.asm.matcher.Match; import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; +/** + * Matches an instruction by its instance. + */ public class InsnMatch extends Match { private final AbstractInsnNode node; diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/IntInsnMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/IntInsnMatch.java deleted file mode 100644 index b5a0c7ce..00000000 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/IntInsnMatch.java +++ /dev/null @@ -1,27 +0,0 @@ -package uwu.narumi.deobfuscator.api.asm.matcher.impl; - -import org.objectweb.asm.tree.IntInsnNode; -import uwu.narumi.deobfuscator.api.asm.matcher.Match; -import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; - -public class IntInsnMatch extends Match { - - private final int opcode; - private final int operand; - - private IntInsnMatch(int opcode, int operand) { - this.opcode = opcode; - this.operand = operand; - } - - public static IntInsnMatch of(int opcode, int operand) { - return new IntInsnMatch(opcode, operand); - } - - @Override - protected boolean test(MatchContext context) { - return context.insn() instanceof IntInsnNode intInsn - && intInsn.getOpcode() == opcode - && intInsn.operand == operand; - } -} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/NumberMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/NumberMatch.java index 0fdf406b..5d2501e2 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/NumberMatch.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/NumberMatch.java @@ -15,23 +15,23 @@ public static NumberMatch of(Number number) { } public static Match of() { - return predicate(ctx -> ctx.insn().isNumber()); + return Match.of(ctx -> ctx.insn().isNumber()); } public static Match numDouble() { - return predicate(ctx -> ctx.insn().isDouble()); + return Match.of(ctx -> ctx.insn().isDouble()); } public static Match numFloat() { - return predicate(ctx -> ctx.insn().isFloat()); + return Match.of(ctx -> ctx.insn().isFloat()); } public static Match numInteger() { - return predicate(ctx -> ctx.insn().isInteger()); + return Match.of(ctx -> ctx.insn().isInteger()); } public static Match numLong() { - return predicate(ctx -> ctx.insn().isLong()); + return Match.of(ctx -> ctx.insn().isLong()); } @Override diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/StringMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/StringMatch.java index af8821a5..827317bc 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/StringMatch.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/StringMatch.java @@ -16,7 +16,7 @@ public static StringMatch of(String string) { } public static Match of() { - return Match.predicate(context -> context.insn().isString()); + return Match.of(context -> context.insn().isString()); } @Override diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/TypeMatch.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/TypeMatch.java deleted file mode 100644 index 906ea3a3..00000000 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/matcher/impl/TypeMatch.java +++ /dev/null @@ -1,39 +0,0 @@ -package uwu.narumi.deobfuscator.api.asm.matcher.impl; - -import org.objectweb.asm.tree.TypeInsnNode; -import uwu.narumi.deobfuscator.api.asm.matcher.Match; -import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; - -public class TypeMatch extends Match { - - private final int opcode; - private final String desc; - - private TypeMatch(int opcode, String desc) { - this.opcode = opcode; - this.desc = desc; - } - - public static TypeMatch of(int opcode, String desc) { - return new TypeMatch(opcode, desc); - } - - public static TypeMatch of(String desc) { - return of(-1, desc); - } - - public static TypeMatch of(int opcode) { - return of(opcode, null); - } - - public static TypeMatch of() { - return of(-1, null); - } - - @Override - protected boolean test(MatchContext context) { - return context.insn() instanceof TypeInsnNode typeInsn - && (opcode == -1 || typeInsn.getOpcode() == opcode) - && (desc == null || typeInsn.desc.equals(desc)); - } -} diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java index 66c88a27..65e3b00d 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/helper/AsmHelper.java @@ -219,13 +219,13 @@ public static void removeField(FieldInsnNode fieldInsnNode, Context context) { && fieldNode.desc.equals(fieldInsnNode.desc)); } - public static Optional findMethod( - ClassNode classNode, MethodInsnNode methodInsnNode) { - return classNode == null || classNode.methods == null - ? Optional.empty() - : classNode.methods.stream() - .filter(methodNode -> methodNode.name.equals(methodInsnNode.name)) - .filter(methodNode -> methodNode.desc.equals(methodInsnNode.desc)) + public static Optional findMethod(ClassNode classNode, MethodRef methodRef) { + if (classNode == null || classNode.methods == null) { + return Optional.empty(); + } + return classNode.methods.stream() + .filter(methodNode -> methodNode.name.equals(methodRef.name())) + .filter(methodNode -> methodNode.desc.equals(methodRef.desc())) .findFirst(); } diff --git a/deobfuscator-impl/src/test/resources/junit-platform.properties b/deobfuscator-impl/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..317c292e --- /dev/null +++ b/deobfuscator-impl/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.parallel.enabled = true +junit.jupiter.execution.parallel.mode.default = concurrent diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java index b2626cc0..b7e98d25 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java @@ -3,67 +3,82 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.asm.MethodContext; +import uwu.narumi.deobfuscator.api.asm.MethodRef; +import uwu.narumi.deobfuscator.api.asm.matcher.Match; +import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.StringMatch; import uwu.narumi.deobfuscator.api.context.Context; import uwu.narumi.deobfuscator.api.transformer.Transformer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Optional; /** * Strings are encrypted using a constant pool size of a provided class. */ public class HP888StringTransformer extends Transformer { + private static final Match ENCRYPTED_STRING = SequenceMatch.of( + StringMatch.of().capture("key"), + MethodMatch.invokeStatic().desc("(Ljava/lang/String;)Ljava/lang/String;").capture("decrypt-method") + ); + + private static final Match CLASS_FOR_CONSTANT_POOL = SequenceMatch.of( + MethodMatch.invokeStatic(), + OpcodeMatch.of(LDC).and(Match.of(ctx -> ((LdcInsnNode) ctx.insn()).cst instanceof Type)).capture("class"), + MethodMatch.invokeInterface() + ); + @Override protected void transform(ClassWrapper scope, Context context) throws Exception { - context.classes().stream().map(ClassWrapper::classNode).forEach(classNode -> { + context.classes().forEach(classWrapper -> { List toRemove = new ArrayList<>(); // Find all encrypted strings - classNode.methods.forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) - .filter(node -> node.getOpcode() == INVOKESTATIC) - .filter(node -> node.getPrevious() != null && node.getPrevious().isString()) - .map(MethodInsnNode.class::cast) - .filter(node -> node.owner.equals(classNode.name)) - .filter(node -> node.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")) - .forEach(node -> findMethod(classNode, method -> method.name.equals(node.name) && method.desc.equals(node.desc)).ifPresent(method -> { - String string = node.getPrevious().asString(); + classWrapper.methods().forEach(methodNode -> { + MethodContext methodContext = MethodContext.framed(classWrapper, methodNode); + + // Find encrypted strings + ENCRYPTED_STRING.findAllMatches(methodContext).forEach(matchContext -> { + AbstractInsnNode keyInsn = matchContext.captures().get("key").insn(); + MethodInsnNode decryptMethodInsn = (MethodInsnNode) matchContext.captures().get("decrypt-method").insn(); + MethodRef methodRef = MethodRef.of(decryptMethodInsn); + + // Get decrypt method + findMethod(classWrapper.classNode(), methodRef).ifPresent(decryptMethod -> { + String key = keyInsn.asString(); + + MethodContext decryptMethodContext = MethodContext.framed(classWrapper, decryptMethod); + + // Find class for constant pool + LdcInsnNode constantPoolClassLdc = (LdcInsnNode) CLASS_FOR_CONSTANT_POOL.findAllMatches(decryptMethodContext) + .get(0).captures().get("class").insn(); + Type classForConstantPoolType = (Type) constantPoolClassLdc.cst; // Prepare data for decryption - ClassWrapper classForConstantPool = context.getClasses().get(getClassForConstantPool(method).orElseThrow().getInternalName()); + ClassWrapper classForConstantPool = context.getClasses().get(classForConstantPoolType.getInternalName()); int constantPoolSize = classForConstantPool.getConstantPool().getSize(); - String class0 = classNode.name; - String class1 = classNode.name; + String class0 = classWrapper.name(); + String class1 = classWrapper.name(); // Decrypt! - String decryptedString = decrypt(string, constantPoolSize, class0.hashCode(), class1.hashCode()); + String decryptedString = decrypt(key, constantPoolSize, class0.hashCode(), class1.hashCode()); - methodNode.instructions.remove(node.getPrevious()); - methodNode.instructions.set(node, new LdcInsnNode(decryptedString)); + methodNode.instructions.remove(keyInsn); + methodNode.instructions.set(decryptMethodInsn, new LdcInsnNode(decryptedString)); markChange(); - toRemove.add(method); - }))); - classNode.methods.removeAll(toRemove); - toRemove.clear(); + toRemove.add(decryptMethod); + }); + }); + }); + classWrapper.methods().removeAll(toRemove); }); } - private Optional getClassForConstantPool(MethodNode methodNode) { - return Arrays.stream(methodNode.instructions.toArray()) - .filter(node -> node.getOpcode() == INVOKESTATIC) - .map(AbstractInsnNode::next) - .filter(next -> next.getOpcode() == LDC) - .filter(next -> next.next().getOpcode() == INVOKEINTERFACE) - .filter(abstractInsnNode -> abstractInsnNode instanceof LdcInsnNode) - .map(LdcInsnNode.class::cast) - .map(ldcInsnNode -> ldcInsnNode.cst) - .map(Type.class::cast) - .findFirst(); - } - private String decrypt(String string, int constantPoolSize, int className0HashCode, int className1HashCode) { char[] charArray = string.toCharArray(); int i = 0; diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java index c2d71ea5..d4995089 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixLongEncryptionMPCTransformer.java @@ -64,7 +64,7 @@ public class ZelixLongEncryptionMPCTransformer extends Transformer { .and(StackMatch.of(0, MethodMatch.invokeInterface().desc("(J)J") .and(StackMatch.of(0, NumberMatch.numLong().capture("decrypt-key"))) // Decrypt key // Create decrypter - .and(StackMatch.of(1, MethodMatch.invokeStatic().and(Match.predicate(context -> + .and(StackMatch.of(1, MethodMatch.invokeStatic().and(Match.of(context -> ((MethodInsnNode) context.insn()).desc.startsWith("(JJLjava/lang/Object;)"))).capture("create-decrypter-method") .and(StackMatch.of(0, MethodMatch.invokeVirtual().and(StackMatch.of(0, MethodMatch.invokeStatic())))) // Class lookup diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixParametersTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixParametersTransformer.java index c1bd5699..2de625a2 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixParametersTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixParametersTransformer.java @@ -56,7 +56,7 @@ public class ZelixParametersTransformer extends Transformer { private static final Match OBJECT_ARRAY_ALOAD = OpcodeMatch.of(ALOAD).and( - Match.predicate(context -> { + Match.of(context -> { // The object array is always the first argument to method return ((VarInsnNode) context.insn()).var == MethodHelper.getFirstParameterIdx(context.insnContext().methodNode()); })); @@ -71,7 +71,7 @@ public class ZelixParametersTransformer extends Transformer { )) ); - private static final Match OBJECT_ARRAY_VAR_USAGE = Match.predicate(ctx -> ctx.insn().isVarStore()).capture("var-store") + private static final Match OBJECT_ARRAY_VAR_USAGE = Match.of(ctx -> ctx.insn().isVarStore()).capture("var-store") .and( StackMatch.of(0, MethodMatch.invokeVirtual().capture("to-primitive") // Converting to a primitive type .and(OBJECT_ARRAY_ACCESS)