Skip to content

Commit

Permalink
Add transformers for unknown obf
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicPlayerA10 committed Jan 7, 2025
1 parent 7eea5b9 commit 9fea3f9
Show file tree
Hide file tree
Showing 15 changed files with 367 additions and 105 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package uwu.narumi.deobfuscator.api.asm.matcher;

import org.jetbrains.annotations.ApiStatus;
import org.objectweb.asm.tree.AbstractInsnNode;
import uwu.narumi.deobfuscator.api.asm.InsnContext;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
Expand Down Expand Up @@ -34,12 +35,13 @@ public boolean matches(MethodContext methodContext) {
}

/**
* Matches the instrustion and merges if successful
* Matches the instruction and merges if successful
*
* @param insnContext Instruction context
* @param currentMatchContext Match context to merge into
* @return If matches
*/
@ApiStatus.Internal
public boolean matchAndMerge(InsnContext insnContext, MatchContext currentMatchContext) {
MatchContext result = this.matchResult(insnContext);
if (result != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,32 @@
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
import uwu.narumi.deobfuscator.api.asm.InsnContext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Immutable match context. After matching process, the context is frozen by {@link MatchContext#freeze()}
*/
public class MatchContext {
private final InsnContext insnContext;
private final Map<String, MatchContext> captures;
private final List<AbstractInsnNode> collectedInsns;
private final Set<AbstractInsnNode> collectedInsns;

private MatchContext(InsnContext insnContext, Map<String, MatchContext> captures, List<AbstractInsnNode> collectedInsns) {
private MatchContext(InsnContext insnContext, Map<String, MatchContext> captures, Set<AbstractInsnNode> collectedInsns) {
this.insnContext = insnContext;
this.captures = captures;
this.collectedInsns = collectedInsns;
}

public static MatchContext of(InsnContext insnContext) {
return new MatchContext(insnContext, new HashMap<>(), new ArrayList<>());
return new MatchContext(insnContext, new HashMap<>(), new HashSet<>());
}

public MatchContext freeze() {
return new MatchContext(this.insnContext, Collections.unmodifiableMap(this.captures), Collections.unmodifiableList(this.collectedInsns));
return new MatchContext(this.insnContext, Collections.unmodifiableMap(this.captures), Collections.unmodifiableSet(this.collectedInsns));
}

/**
Expand All @@ -40,12 +40,7 @@ public MatchContext freeze() {
*/
void merge(MatchContext other) {
this.captures.putAll(other.captures);
for (AbstractInsnNode insn : other.collectedInsns) {
// Don't allow duplicates
if (this.collectedInsns.contains(insn)) continue;

this.collectedInsns.add(insn);
}
this.collectedInsns.addAll(other.collectedInsns);
}

/**
Expand Down Expand Up @@ -81,7 +76,7 @@ public Map<String, MatchContext> captures() {
/**
* Collected instructions that matches this match and children matches
*/
public List<AbstractInsnNode> collectedInsns() {
public Set<AbstractInsnNode> collectedInsns() {
return collectedInsns;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package uwu.narumi.deobfuscator.api.asm.matcher.impl;

import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.OriginalSourceValue;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;

/**
* Match instruction from stack
*/
public class FrameMatch extends Match {
private final Match match;
private final FrameMatchOptions options;

/**
* @param match A {@link Match} to match against that stack value
* @param options Options for the frame match
*/
private FrameMatch(Match match, FrameMatchOptions options) {
this.match = match;
this.options = options;
}

public static FrameMatch stack(int stackValueIdx, Match match) {
return new FrameMatch(match, new StackFrameOptions(stackValueIdx, false));
}

public static FrameMatch stackOriginal(int stackValueIdx, Match match) {
return new FrameMatch(match, new StackFrameOptions(stackValueIdx, true));
}

public static FrameMatch localVariable(int localVariableIdx, Match match) {
return new FrameMatch(match, new LocalVariableFrameOptions(localVariableIdx));
}

@Override
protected boolean test(MatchContext context) {
if (context.insnContext().methodContext().frames() == null) {
throw new IllegalStateException("Got frameless method context");
}

if (context.frame() == null) {
// If we expect stack values, then frame can't be null
return false;
}

// Get the source value
OriginalSourceValue sourceValue;
if (this.options instanceof StackFrameOptions stackFrameOptions) {
// Pop values from stack and match them
int index = context.frame().getStackSize() - (stackFrameOptions.stackValueIdx + 1);
if (index < 0) {
// If the stack value should exist but does not, then it does not match
return false;
}

if (this.match instanceof SkipMatch) {
// Skip match earlier
return true;
}

Frame<OriginalSourceValue> frame = context.frame();
sourceValue = frame.getStack(index);
if (stackFrameOptions.originalValue) {
sourceValue = sourceValue.originalSource;
}
} else if (this.options instanceof LocalVariableFrameOptions lvFrameOptions) {
if (this.match instanceof SkipMatch) {
// Skip match earlier
return true;
}

Frame<OriginalSourceValue> frame = context.frame();
sourceValue = frame.getLocal(lvFrameOptions.localVariableIdx);
} else {
throw new IllegalStateException("Unknown frame match options");
}

if (!sourceValue.isOneWayProduced()) {
// We only want stack values that are one way produced
return false;
}

AbstractInsnNode stackValueInsn = sourceValue.getProducer();
return this.match.matchAndMerge(context.insnContext().of(stackValueInsn), context);
}

sealed interface FrameMatchOptions permits StackFrameOptions, LocalVariableFrameOptions {
}

/**
* @param stackValueIdx Index of the value in the stack, starting from the top of the stack, so '0' is the top value.
* @param originalValue
*/
record StackFrameOptions(int stackValueIdx, boolean originalValue) implements FrameMatchOptions {
}

record LocalVariableFrameOptions(int localVariableIdx) implements FrameMatchOptions {
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package uwu.narumi.deobfuscator.api.asm.matcher.impl;

import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.tree.VarInsnNode;
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;

public class VarLoadMatch extends Match {
private Match localStoreMatch = null;

private VarLoadMatch() {
}

public static VarLoadMatch of() {
return new VarLoadMatch();
}

/**
* Match local variable store instruction of this variable
*/
public VarLoadMatch localStoreMatch(@Nullable Match match) {
this.localStoreMatch = match;
return this;
}

@Override
protected boolean test(MatchContext context) {
boolean matches = context.insnContext().insn().isVarLoad();

// Match local variable store instruction
if (matches && localStoreMatch != null) {
VarInsnNode varInsn = (VarInsnNode) context.insnContext().insn();

matches = FrameMatch.localVariable(varInsn.var, localStoreMatch).matchAndMerge(context.insnContext(), context);
}

return matches;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package uwu.narumi.deobfuscator;

import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer;
import uwu.narumi.deobfuscator.core.other.composed.ComposedUnknownObf1Transformer;
import uwu.narumi.deobfuscator.core.other.composed.ComposedZelixTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
Expand Down Expand Up @@ -53,10 +54,11 @@ protected void registerAll() {
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "JSR.class")
.register();

// Samples
test("Some flow obf sample")
.transformers(ComposedGeneralFlowTransformer::new)
.input(OutputType.SINGLE_CLASS, InputType.CUSTOM_CLASS, "FlowObfSample.class")
// Unknown obf 1
// TODO: If you know the obfuscator used to obfuscate this class, you can make a PR which renames this test
test("Unknown obf 1")
.transformers(ComposedUnknownObf1Transformer::new)
.input(OutputType.MULTIPLE_CLASSES, InputType.CUSTOM_CLASS, "unknown/obf1")
.register();

// Zelix
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package uwu.narumi.deobfuscator.core.other.composed;

import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.clean.LocalVariableNamesCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.unknown.obf1.UnknownObf1_StringByteArrayTransformer;

public class ComposedUnknownObf1Transformer extends ComposedTransformer {
public ComposedUnknownObf1Transformer() {
super(
// Remove local variables names
LocalVariableNamesCleanTransformer::new,

// Fix flow
ComposedGeneralFlowTransformer::new,

// Decrypt strings
UnknownObf1_StringByteArrayTransformer::new
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public ComposedPeepholeCleanTransformer() {
UnUsedLabelCleanTransformer::new,
UselessGotosCleanTransformer::new,

// Early pop clean (for correct unused var stores removal)
UselessPopCleanTransformer::new,
// Pop unused local variables stores
PopUnUsedLocalVariablesTransformer::new,
// Remove useless POP instructions. This also cleans up garbage var stores from the PopUnUsedLocalVariablesTransformer
Expand Down
Loading

0 comments on commit 9fea3f9

Please sign in to comment.