Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockCompabilityStringTransformer;
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.JsrInlinerTransformer;
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.UselessPopCleanTransformer;
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
Expand Down Expand Up @@ -210,13 +211,28 @@ protected void registerAll() {
.register();

test("Branchlock String")
.transformers(UniversalNumberTransformer::new, () -> new BranchlockCompabilityStringTransformer(true))
.transformers(UniversalNumberTransformer::new, BranchlockCompabilityStringTransformer::new)
.inputJar("branchlock/branchlock-string.jar")
.register();

test("Branchlock String + Salting + Number")
.transformers(ComposedBranchlockTransformer::new)
.inputJar("branchlock/branchlock-string-salting-number.jar")
.register();

test("Branchlock String + Flow + Number")
.transformers(ComposedBranchlockTransformer::new)
.inputJar("branchlock/branchlock-string-flow-number.jar")
.register();

// test("Branchlock String + Salting + Flow + Number")
// .transformers(ComposedBranchlockTransformer::new)
// .inputJar("branchlock/branchlock-string-salting-flow-number.jar")
// .register();

test("Branchlock Flow 9")
.transformers(BranchlockFlowTransformer::new)
.inputJar("branchlock/flow/flow 9.jar")
.register();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralRepairTransformer;
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockCompabilityStringTransformer;
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockFlowTransformer;
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockSaltingTransformer;
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;

Expand All @@ -12,10 +13,11 @@ public ComposedBranchlockTransformer() {
super(
() -> new ComposedTransformer(true,
UniversalNumberTransformer::new,
() -> new BranchlockCompabilityStringTransformer(false),
BranchlockCompabilityStringTransformer::new,
BranchlockSaltingTransformer::new),
ComposedGeneralRepairTransformer::new, // Deletes "Logic Scrambler"
ComposedGeneralFlowTransformer::new // Deobfuscates part of flow obfuscation
BranchlockFlowTransformer::new,
ComposedGeneralFlowTransformer::new
);
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,55 @@
package uwu.narumi.deobfuscator.core.other.impl.branchlock;

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.matcher.Match;
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

public class BranchlockCompabilityStringTransformer extends Transformer {

private final boolean deleteClinit;
private String[] decryptedStrings;
private FieldInsnNode stringArray;

public BranchlockCompabilityStringTransformer(boolean deleteClinit) {
this.deleteClinit = deleteClinit;
}
private record DecryptedStringData(FieldInsnNode fieldInsnNode, String[] decryptedStrings) {}

private static final Map<ClassWrapper, DecryptedStringData> decryptedDataMap = new HashMap<>();

@Override
protected void transform() throws Exception {
scopedClasses().forEach(classWrapper -> {
decryptedStrings = null;
stringArray = null;
classWrapper.findClInit().ifPresent(clinit -> {
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
String className = classWrapper.name().replace("/", ".");
String methodName = clinit.name;
Arrays.stream(clinit.instructions.toArray())
.filter(ain -> ain instanceof LdcInsnNode)
.map(LdcInsnNode.class::cast)
.filter(ldc -> ldc.cst instanceof String)
.findFirst().ifPresent(ldc -> {
if (decryptedDataMap.containsKey(classWrapper)) {
decryptedStrings = decryptedDataMap.get(classWrapper).decryptedStrings();
stringArray = decryptedDataMap.get(classWrapper).fieldInsnNode();
} else {
classWrapper.findClInit().ifPresent(clinit -> {
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
String className = classWrapper.name().replace("/", ".");
String methodName = clinit.name;
Match stringEncryptionMatch = SequenceMatch.of(
StringMatch.of().capture("encrypted-string"),
MethodMatch.invokeVirtual().name("toCharArray").owner("java/lang/String"),
OpcodeMatch.of(ASTORE)
);
MatchContext stringEncryption = stringEncryptionMatch.findFirstMatch(methodContext);
if (stringEncryption != null) {
Match stringArr = OpcodeMatch.of(PUTSTATIC).capture("string-arr");
stringArray = stringArr.findFirstMatch(methodContext).insn().asFieldInsn();

String encryptedString = (String) ldc.cst;
LdcInsnNode encryptedStringInsn = (LdcInsnNode) stringEncryption.captures().get("encrypted-string").insn();
String encryptedString = encryptedStringInsn.asString();

char[] encryptedStringArray = encryptedString.toCharArray();
Match match = SequenceMatch.of(OpcodeMatch.of(DUP), NumberMatch.numInteger().capture("array-to"), OpcodeMatch.of(SWAP), NumberMatch.numInteger().capture("array-from"), OpcodeMatch.of(CALOAD), OpcodeMatch.of(CASTORE), OpcodeMatch.of(CASTORE));

Expand Down Expand Up @@ -175,13 +186,30 @@ protected void transform() throws Exception {
}
decryptedStrings[decStrIndex++] = new String(toDecrypt).intern();
}
});
});

decryptedDataMap.put(classWrapper, new DecryptedStringData(stringArray, decryptedStrings));

Set<LabelNode> labelsInStringDecryption = new HashSet<>();
Set<AbstractInsnNode> toRemove = new HashSet<>();
AbstractInsnNode firstNode = encryptedStringInsn;
while (firstNode != null) {
if (firstNode instanceof LabelNode label) {
labelsInStringDecryption.add(label);
} else {
toRemove.add(firstNode);
}
if (firstNode instanceof TableSwitchInsnNode) {
break;
}
firstNode = firstNode.getNext();
}
toRemove.forEach(clinit.instructions::remove);
clinit.tryCatchBlocks.removeIf(tryCatchBlockNode -> labelsInStringDecryption.contains(tryCatchBlockNode.start) || labelsInStringDecryption.contains(tryCatchBlockNode.handler) || labelsInStringDecryption.contains(tryCatchBlockNode.end));
}
});
}

if (stringArray != null) {
if (deleteClinit) {
classWrapper.methods().remove(classWrapper.findClInit().get());
}
classWrapper.fields().removeIf(fieldNode -> fieldNode.name.equals(stringArray.name) && fieldNode.desc.equals(stringArray.desc));

classWrapper.methods().forEach(methodNode -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package uwu.narumi.deobfuscator.core.other.impl.branchlock;

import org.objectweb.asm.tree.*;
import uwu.narumi.deobfuscator.api.asm.MethodContext;
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.JumpMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
import uwu.narumi.deobfuscator.api.transformer.Transformer;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class BranchlockFlowTransformer extends Transformer {

Match[] flowDupEquationMatches = new Match[] {
SequenceMatch.of(
OpcodeMatch.of(DUP),
JumpMatch.of().capture("fake-jump"),
JumpMatch.of().capture("correct-jump"),
NumberMatch.of()
),
SequenceMatch.of(
OpcodeMatch.of(DUP),
OpcodeMatch.of(SWAP),
JumpMatch.of().capture("fake-jump"),
JumpMatch.of().capture("correct-jump"),
NumberMatch.of()
)
};

Match flowDupJumpMatch = SequenceMatch.of(
OpcodeMatch.of(DUP),
JumpMatch.of().capture("fake-jump"),
OpcodeMatch.of(ASTORE).capture("correct-store"),
OpcodeMatch.of(ALOAD),
JumpMatch.of()
);

Match flowDupJumpMatch2 = SequenceMatch.of(
OpcodeMatch.of(DUP),
JumpMatch.of().capture("fake-jump"),
OpcodeMatch.of(ASTORE).capture("correct-store")
);

Match flowFakeLoop = SequenceMatch.of(
OpcodeMatch.of(ASTORE).capture("correct-store"),
OpcodeMatch.of(ALOAD).capture("fake-load"),
JumpMatch.of()
);

Match errorJump = SequenceMatch.of(
OpcodeMatch.of(ALOAD).capture("loaded-var"),
JumpMatch.of()
);

Match trashHandlers = SequenceMatch.of(
Match.of(ctx -> (ctx.insn().getOpcode() >= IRETURN && ctx.insn().getOpcode() <= RETURN) || ctx.insn().getOpcode() == ATHROW),
Match.of(ctx -> (ctx.insn().getOpcode() >= ACONST_NULL && ctx.insn().getOpcode() <= DCONST_1) || ctx.insn().getOpcode() == ALOAD || ctx.insn() instanceof LabelNode)
).doNotSkipLabels();

@Override
protected void transform() throws Exception {
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
List<TryCatchBlockNode> tryCatchBlocks = methodNode.tryCatchBlocks;
if (tryCatchBlocks != null && !tryCatchBlocks.isEmpty()) {
Set<AbstractInsnNode> toRemove = new HashSet<>();
Set<LabelNode> trashLabels = new HashSet<>();
for (Match flowDupEquationMatch : flowDupEquationMatches) {
flowDupEquationMatch.findAllMatches(methodContext).forEach(matchContext -> {
if (matchContext.captures().containsKey("swap")) System.out.println(matchContext.captures().containsKey("swap"));
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext() instanceof FrameNode && labelNode.getNext(2) != null && labelNode.getNext(2).getOpcode() == POP) {
toRemove.add(labelNode);
trashLabels.add(labelNode);
toRemove.add(labelNode.getNext());
toRemove.add(labelNode.getNext(2));
markChange();
} else if (labelNode != null && labelNode.getNext() != null && labelNode.getNext().getOpcode() == POP) {
toRemove.add(labelNode);
trashLabels.add(labelNode);
toRemove.add(labelNode.getNext());
markChange();
}
toRemove.addAll(matchContext.collectedInsns());
toRemove.remove(matchContext.captures().get("correct-jump").insn());
markChange();
});
}
flowFakeLoop.findAllMatches(methodContext).forEach(matchContext -> {
VarInsnNode varInsnNode = (VarInsnNode) matchContext.captures().get("correct-store").insn();
VarInsnNode varInsnNode1 = (VarInsnNode) matchContext.captures().get("fake-load").insn();
if (varInsnNode.var != varInsnNode1.var) {
toRemove.addAll(matchContext.collectedInsns());
toRemove.remove(matchContext.captures().get("correct-store").insn());
markChange();
}
});
JumpInsnNode jumpInsnNode = null;
try {
if ((methodNode.instructions.get(0) instanceof LabelNode && methodNode.instructions.get(1) instanceof JumpInsnNode)) {
jumpInsnNode = methodNode.instructions.get(1).asJump();
trashLabels.add((LabelNode) methodNode.instructions.get(0));
} else if (methodNode.instructions.get(0) instanceof JumpInsnNode) {
jumpInsnNode = methodNode.instructions.get(0).asJump();
}
if (jumpInsnNode != null) {
int i = 0;
if ((jumpInsnNode.label.getNext().isVarLoad() && ((VarInsnNode)jumpInsnNode.label.getNext()).var == 0 || (jumpInsnNode.label.getNext().getOpcode() >= ACONST_NULL && jumpInsnNode.label.getNext().getOpcode() <= DCONST_1))) {
while (jumpInsnNode.label.getNext(i) != null && !jumpInsnNode.label.getNext(i).isVarStore()) {
i++;
}
VarInsnNode flowVarIndex = (VarInsnNode) jumpInsnNode.label.getNext(i);
if (flowVarIndex != null) {
errorJump.findAllMatches(methodContext).forEach(matchContext -> {
if (((VarInsnNode) matchContext.captures().get("loaded-var").insn()).var == flowVarIndex.var) {
toRemove.addAll(matchContext.collectedInsns());
}
markChange();
});
}
toRemove.add(jumpInsnNode);
jumpInsnNode = null;
}
}
} catch (Exception exception) {
exception.printStackTrace();
}
flowDupJumpMatch.findAllMatches(methodContext).forEach(matchContext -> {
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext() instanceof FrameNode && labelNode.getNext(2) != null && labelNode.getNext(2).getOpcode() == GOTO) {
toRemove.add(labelNode);
trashLabels.add(labelNode);
toRemove.add(labelNode.getNext());
toRemove.add(labelNode.getNext(2));
markChange();
}
toRemove.addAll(matchContext.collectedInsns());
toRemove.remove(matchContext.captures().get("correct-store").insn());
markChange();
});
flowDupJumpMatch2.findAllMatches(methodContext).forEach(matchContext -> {
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext().getOpcode() == GOTO) {
toRemove.add(labelNode);
trashLabels.add(labelNode);
toRemove.add(labelNode.getNext());
markChange();
}
toRemove.addAll(matchContext.collectedInsns());
toRemove.remove(matchContext.captures().get("correct-store").insn());
markChange();
});

try {
AbstractInsnNode returnNode = trashHandlers.findFirstMatch(methodContext).insn();

AbstractInsnNode next = returnNode.getNext();
while (next != null) {
if (next instanceof LabelNode label) {
trashLabels.add(label);
}
next = next.getNext();
}
} catch (Exception ignored) {}

toRemove.forEach(
methodNode.instructions::remove
);

boolean changed = true;

if (jumpInsnNode != null) {
AbstractInsnNode next = jumpInsnNode.label;
while (next != null) {
if (next instanceof LabelNode label) {
trashLabels.add(label);
}
next = next.getNext();
}
while (jumpInsnNode.label.getNext() != null && !(jumpInsnNode.label.getNext() instanceof JumpInsnNode)) {
AbstractInsnNode abstractInsnNode = jumpInsnNode.label.getNext();
if (abstractInsnNode instanceof LabelNode) {
methodNode.instructions.remove(abstractInsnNode);
continue;
}
methodNode.instructions.remove(abstractInsnNode);
methodNode.instructions.insertBefore(jumpInsnNode, abstractInsnNode);
markChange();
}

methodNode.instructions.remove(jumpInsnNode);
markChange();
} else {
changed = !toRemove.isEmpty();
}

if (!changed) return;

Set<TryCatchBlockNode> tryCatchBlockNodes = new HashSet<>();

for (TryCatchBlockNode tryCatchBlock : tryCatchBlocks) {
if (trashLabels.contains(tryCatchBlock.start) || trashLabels.contains(tryCatchBlock.handler) || trashLabels.contains(tryCatchBlock.end)) {
tryCatchBlockNodes.add(tryCatchBlock);
markChange();
}
}

tryCatchBlockNodes.forEach(
methodNode.tryCatchBlocks::remove
);
}
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public class BranchlockSaltingTransformer extends Transformer {

private final Map<MethodInsnNode, Integer> salts = new WeakHashMap<>();

//TODO: Fix superClass`s / interface`s overridden methods not being deobfuscated

@Override
protected void transform() throws Exception {
scopedClasses().forEach(classWrapper -> {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading