Skip to content

Commit

Permalink
parallel unit tests, use match api in hp888 transformers
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicPlayerA10 committed Sep 25, 2024
1 parent 55d9311 commit 9859d2b
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 125 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).**<br>
>
> - 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)
> <br>
>
> 1. *You can also provide samples of obfuscation to help with development of the transformers.*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand Down Expand Up @@ -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<MatchContext> findAllMatches(MethodContext methodContext) {
List<MatchContext> 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
*/
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -108,7 +132,7 @@ public Transformation transformation() {
* @param predicate Your lambda predicate
* @return A new {@link Match}
*/
public static Match predicate(Predicate<MatchContext> predicate) {
public static Match of(Predicate<MatchContext> predicate) {
return new Match() {
@Override
protected boolean test(MatchContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Match> skipMatches = new ArrayList<>(List.of(FRAME_MATCH, LABEL_MATCH, LINE_MATCH));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -219,13 +219,13 @@ public static void removeField(FieldInsnNode fieldInsnNode, Context context) {
&& fieldNode.desc.equals(fieldInsnNode.desc));
}

public static Optional<MethodNode> 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<MethodNode> 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();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent
Original file line number Diff line number Diff line change
Expand Up @@ -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<MethodNode> 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<Type> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}));
Expand All @@ -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)
Expand Down

0 comments on commit 9859d2b

Please sign in to comment.