Skip to content

Commit cf3d9e5

Browse files
authored
Branchlock 4.1.7 (#116)
* Add todo to salting * Flow transformer * Quicker static cache for Strings * Composed workaround * Samples for tests * Samples for tests * Tests init * Tests results * Fix flow * Fix flow tests * Remove clinit string decryptor * Remove clinit string decryptor tests * I forgot to put in cache
1 parent 51e4d2b commit cf3d9e5

File tree

103 files changed

+1874
-4578
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+1874
-4578
lines changed

deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
55
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
66
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockCompabilityStringTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockFlowTransformer;
78
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.JsrInlinerTransformer;
89
import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.UselessPopCleanTransformer;
910
import uwu.narumi.deobfuscator.core.other.impl.pool.InlineLocalVariablesTransformer;
@@ -210,13 +211,28 @@ protected void registerAll() {
210211
.register();
211212

212213
test("Branchlock String")
213-
.transformers(UniversalNumberTransformer::new, () -> new BranchlockCompabilityStringTransformer(true))
214+
.transformers(UniversalNumberTransformer::new, BranchlockCompabilityStringTransformer::new)
214215
.inputJar("branchlock/branchlock-string.jar")
215216
.register();
216217

217218
test("Branchlock String + Salting + Number")
218219
.transformers(ComposedBranchlockTransformer::new)
219220
.inputJar("branchlock/branchlock-string-salting-number.jar")
220221
.register();
222+
223+
test("Branchlock String + Flow + Number")
224+
.transformers(ComposedBranchlockTransformer::new)
225+
.inputJar("branchlock/branchlock-string-flow-number.jar")
226+
.register();
227+
228+
// test("Branchlock String + Salting + Flow + Number")
229+
// .transformers(ComposedBranchlockTransformer::new)
230+
// .inputJar("branchlock/branchlock-string-salting-flow-number.jar")
231+
// .register();
232+
233+
test("Branchlock Flow 9")
234+
.transformers(BranchlockFlowTransformer::new)
235+
.inputJar("branchlock/flow/flow 9.jar")
236+
.register();
221237
}
222238
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedBranchlockTransformer.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
55
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralRepairTransformer;
66
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockCompabilityStringTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockFlowTransformer;
78
import uwu.narumi.deobfuscator.core.other.impl.branchlock.BranchlockSaltingTransformer;
89
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
910

@@ -12,10 +13,11 @@ public ComposedBranchlockTransformer() {
1213
super(
1314
() -> new ComposedTransformer(true,
1415
UniversalNumberTransformer::new,
15-
() -> new BranchlockCompabilityStringTransformer(false),
16+
BranchlockCompabilityStringTransformer::new,
1617
BranchlockSaltingTransformer::new),
1718
ComposedGeneralRepairTransformer::new, // Deletes "Logic Scrambler"
18-
ComposedGeneralFlowTransformer::new // Deobfuscates part of flow obfuscation
19+
BranchlockFlowTransformer::new,
20+
ComposedGeneralFlowTransformer::new
1921
);
2022
}
2123
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/branchlock/BranchlockCompabilityStringTransformer.java

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
11
package uwu.narumi.deobfuscator.core.other.impl.branchlock;
22

33
import org.objectweb.asm.tree.*;
4+
import uwu.narumi.deobfuscator.api.asm.ClassWrapper;
45
import uwu.narumi.deobfuscator.api.asm.MethodContext;
56
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
67
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
78
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
89
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
910
import uwu.narumi.deobfuscator.api.transformer.Transformer;
1011

11-
import java.util.Arrays;
12+
import java.util.HashMap;
13+
import java.util.HashSet;
14+
import java.util.Map;
15+
import java.util.Set;
1216
import java.util.concurrent.atomic.AtomicInteger;
1317

1418
public class BranchlockCompabilityStringTransformer extends Transformer {
1519

16-
private final boolean deleteClinit;
1720
private String[] decryptedStrings;
1821
private FieldInsnNode stringArray;
1922

20-
public BranchlockCompabilityStringTransformer(boolean deleteClinit) {
21-
this.deleteClinit = deleteClinit;
22-
}
23+
private record DecryptedStringData(FieldInsnNode fieldInsnNode, String[] decryptedStrings) {}
24+
25+
private static final Map<ClassWrapper, DecryptedStringData> decryptedDataMap = new HashMap<>();
2326

2427
@Override
2528
protected void transform() throws Exception {
2629
scopedClasses().forEach(classWrapper -> {
2730
decryptedStrings = null;
2831
stringArray = null;
29-
classWrapper.findClInit().ifPresent(clinit -> {
30-
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
31-
String className = classWrapper.name().replace("/", ".");
32-
String methodName = clinit.name;
33-
Arrays.stream(clinit.instructions.toArray())
34-
.filter(ain -> ain instanceof LdcInsnNode)
35-
.map(LdcInsnNode.class::cast)
36-
.filter(ldc -> ldc.cst instanceof String)
37-
.findFirst().ifPresent(ldc -> {
32+
if (decryptedDataMap.containsKey(classWrapper)) {
33+
decryptedStrings = decryptedDataMap.get(classWrapper).decryptedStrings();
34+
stringArray = decryptedDataMap.get(classWrapper).fieldInsnNode();
35+
} else {
36+
classWrapper.findClInit().ifPresent(clinit -> {
37+
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
38+
String className = classWrapper.name().replace("/", ".");
39+
String methodName = clinit.name;
40+
Match stringEncryptionMatch = SequenceMatch.of(
41+
StringMatch.of().capture("encrypted-string"),
42+
MethodMatch.invokeVirtual().name("toCharArray").owner("java/lang/String"),
43+
OpcodeMatch.of(ASTORE)
44+
);
45+
MatchContext stringEncryption = stringEncryptionMatch.findFirstMatch(methodContext);
46+
if (stringEncryption != null) {
3847
Match stringArr = OpcodeMatch.of(PUTSTATIC).capture("string-arr");
3948
stringArray = stringArr.findFirstMatch(methodContext).insn().asFieldInsn();
4049

41-
String encryptedString = (String) ldc.cst;
50+
LdcInsnNode encryptedStringInsn = (LdcInsnNode) stringEncryption.captures().get("encrypted-string").insn();
51+
String encryptedString = encryptedStringInsn.asString();
52+
4253
char[] encryptedStringArray = encryptedString.toCharArray();
4354
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));
4455

@@ -175,13 +186,30 @@ protected void transform() throws Exception {
175186
}
176187
decryptedStrings[decStrIndex++] = new String(toDecrypt).intern();
177188
}
178-
});
179-
});
189+
190+
decryptedDataMap.put(classWrapper, new DecryptedStringData(stringArray, decryptedStrings));
191+
192+
Set<LabelNode> labelsInStringDecryption = new HashSet<>();
193+
Set<AbstractInsnNode> toRemove = new HashSet<>();
194+
AbstractInsnNode firstNode = encryptedStringInsn;
195+
while (firstNode != null) {
196+
if (firstNode instanceof LabelNode label) {
197+
labelsInStringDecryption.add(label);
198+
} else {
199+
toRemove.add(firstNode);
200+
}
201+
if (firstNode instanceof TableSwitchInsnNode) {
202+
break;
203+
}
204+
firstNode = firstNode.getNext();
205+
}
206+
toRemove.forEach(clinit.instructions::remove);
207+
clinit.tryCatchBlocks.removeIf(tryCatchBlockNode -> labelsInStringDecryption.contains(tryCatchBlockNode.start) || labelsInStringDecryption.contains(tryCatchBlockNode.handler) || labelsInStringDecryption.contains(tryCatchBlockNode.end));
208+
}
209+
});
210+
}
180211

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

187215
classWrapper.methods().forEach(methodNode -> {
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.branchlock;
2+
3+
import org.objectweb.asm.tree.*;
4+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
5+
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
6+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
7+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.JumpMatch;
8+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
9+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
10+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
11+
12+
import java.util.HashSet;
13+
import java.util.List;
14+
import java.util.Set;
15+
16+
public class BranchlockFlowTransformer extends Transformer {
17+
18+
Match[] flowDupEquationMatches = new Match[] {
19+
SequenceMatch.of(
20+
OpcodeMatch.of(DUP),
21+
JumpMatch.of().capture("fake-jump"),
22+
JumpMatch.of().capture("correct-jump"),
23+
NumberMatch.of()
24+
),
25+
SequenceMatch.of(
26+
OpcodeMatch.of(DUP),
27+
OpcodeMatch.of(SWAP),
28+
JumpMatch.of().capture("fake-jump"),
29+
JumpMatch.of().capture("correct-jump"),
30+
NumberMatch.of()
31+
)
32+
};
33+
34+
Match flowDupJumpMatch = SequenceMatch.of(
35+
OpcodeMatch.of(DUP),
36+
JumpMatch.of().capture("fake-jump"),
37+
OpcodeMatch.of(ASTORE).capture("correct-store"),
38+
OpcodeMatch.of(ALOAD),
39+
JumpMatch.of()
40+
);
41+
42+
Match flowDupJumpMatch2 = SequenceMatch.of(
43+
OpcodeMatch.of(DUP),
44+
JumpMatch.of().capture("fake-jump"),
45+
OpcodeMatch.of(ASTORE).capture("correct-store")
46+
);
47+
48+
Match flowFakeLoop = SequenceMatch.of(
49+
OpcodeMatch.of(ASTORE).capture("correct-store"),
50+
OpcodeMatch.of(ALOAD).capture("fake-load"),
51+
JumpMatch.of()
52+
);
53+
54+
Match errorJump = SequenceMatch.of(
55+
OpcodeMatch.of(ALOAD).capture("loaded-var"),
56+
JumpMatch.of()
57+
);
58+
59+
Match trashHandlers = SequenceMatch.of(
60+
Match.of(ctx -> (ctx.insn().getOpcode() >= IRETURN && ctx.insn().getOpcode() <= RETURN) || ctx.insn().getOpcode() == ATHROW),
61+
Match.of(ctx -> (ctx.insn().getOpcode() >= ACONST_NULL && ctx.insn().getOpcode() <= DCONST_1) || ctx.insn().getOpcode() == ALOAD || ctx.insn() instanceof LabelNode)
62+
).doNotSkipLabels();
63+
64+
@Override
65+
protected void transform() throws Exception {
66+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
67+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
68+
List<TryCatchBlockNode> tryCatchBlocks = methodNode.tryCatchBlocks;
69+
if (tryCatchBlocks != null && !tryCatchBlocks.isEmpty()) {
70+
Set<AbstractInsnNode> toRemove = new HashSet<>();
71+
Set<LabelNode> trashLabels = new HashSet<>();
72+
for (Match flowDupEquationMatch : flowDupEquationMatches) {
73+
flowDupEquationMatch.findAllMatches(methodContext).forEach(matchContext -> {
74+
if (matchContext.captures().containsKey("swap")) System.out.println(matchContext.captures().containsKey("swap"));
75+
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
76+
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext() instanceof FrameNode && labelNode.getNext(2) != null && labelNode.getNext(2).getOpcode() == POP) {
77+
toRemove.add(labelNode);
78+
trashLabels.add(labelNode);
79+
toRemove.add(labelNode.getNext());
80+
toRemove.add(labelNode.getNext(2));
81+
markChange();
82+
} else if (labelNode != null && labelNode.getNext() != null && labelNode.getNext().getOpcode() == POP) {
83+
toRemove.add(labelNode);
84+
trashLabels.add(labelNode);
85+
toRemove.add(labelNode.getNext());
86+
markChange();
87+
}
88+
toRemove.addAll(matchContext.collectedInsns());
89+
toRemove.remove(matchContext.captures().get("correct-jump").insn());
90+
markChange();
91+
});
92+
}
93+
flowFakeLoop.findAllMatches(methodContext).forEach(matchContext -> {
94+
VarInsnNode varInsnNode = (VarInsnNode) matchContext.captures().get("correct-store").insn();
95+
VarInsnNode varInsnNode1 = (VarInsnNode) matchContext.captures().get("fake-load").insn();
96+
if (varInsnNode.var != varInsnNode1.var) {
97+
toRemove.addAll(matchContext.collectedInsns());
98+
toRemove.remove(matchContext.captures().get("correct-store").insn());
99+
markChange();
100+
}
101+
});
102+
JumpInsnNode jumpInsnNode = null;
103+
try {
104+
if ((methodNode.instructions.get(0) instanceof LabelNode && methodNode.instructions.get(1) instanceof JumpInsnNode)) {
105+
jumpInsnNode = methodNode.instructions.get(1).asJump();
106+
trashLabels.add((LabelNode) methodNode.instructions.get(0));
107+
} else if (methodNode.instructions.get(0) instanceof JumpInsnNode) {
108+
jumpInsnNode = methodNode.instructions.get(0).asJump();
109+
}
110+
if (jumpInsnNode != null) {
111+
int i = 0;
112+
if ((jumpInsnNode.label.getNext().isVarLoad() && ((VarInsnNode)jumpInsnNode.label.getNext()).var == 0 || (jumpInsnNode.label.getNext().getOpcode() >= ACONST_NULL && jumpInsnNode.label.getNext().getOpcode() <= DCONST_1))) {
113+
while (jumpInsnNode.label.getNext(i) != null && !jumpInsnNode.label.getNext(i).isVarStore()) {
114+
i++;
115+
}
116+
VarInsnNode flowVarIndex = (VarInsnNode) jumpInsnNode.label.getNext(i);
117+
if (flowVarIndex != null) {
118+
errorJump.findAllMatches(methodContext).forEach(matchContext -> {
119+
if (((VarInsnNode) matchContext.captures().get("loaded-var").insn()).var == flowVarIndex.var) {
120+
toRemove.addAll(matchContext.collectedInsns());
121+
}
122+
markChange();
123+
});
124+
}
125+
toRemove.add(jumpInsnNode);
126+
jumpInsnNode = null;
127+
}
128+
}
129+
} catch (Exception exception) {
130+
exception.printStackTrace();
131+
}
132+
flowDupJumpMatch.findAllMatches(methodContext).forEach(matchContext -> {
133+
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
134+
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext() instanceof FrameNode && labelNode.getNext(2) != null && labelNode.getNext(2).getOpcode() == GOTO) {
135+
toRemove.add(labelNode);
136+
trashLabels.add(labelNode);
137+
toRemove.add(labelNode.getNext());
138+
toRemove.add(labelNode.getNext(2));
139+
markChange();
140+
}
141+
toRemove.addAll(matchContext.collectedInsns());
142+
toRemove.remove(matchContext.captures().get("correct-store").insn());
143+
markChange();
144+
});
145+
flowDupJumpMatch2.findAllMatches(methodContext).forEach(matchContext -> {
146+
LabelNode labelNode = matchContext.captures().get("fake-jump").insn().asJump().label;
147+
if (labelNode != null && labelNode.getNext() != null && labelNode.getNext().getOpcode() == GOTO) {
148+
toRemove.add(labelNode);
149+
trashLabels.add(labelNode);
150+
toRemove.add(labelNode.getNext());
151+
markChange();
152+
}
153+
toRemove.addAll(matchContext.collectedInsns());
154+
toRemove.remove(matchContext.captures().get("correct-store").insn());
155+
markChange();
156+
});
157+
158+
try {
159+
AbstractInsnNode returnNode = trashHandlers.findFirstMatch(methodContext).insn();
160+
161+
AbstractInsnNode next = returnNode.getNext();
162+
while (next != null) {
163+
if (next instanceof LabelNode label) {
164+
trashLabels.add(label);
165+
}
166+
next = next.getNext();
167+
}
168+
} catch (Exception ignored) {}
169+
170+
toRemove.forEach(
171+
methodNode.instructions::remove
172+
);
173+
174+
boolean changed = true;
175+
176+
if (jumpInsnNode != null) {
177+
AbstractInsnNode next = jumpInsnNode.label;
178+
while (next != null) {
179+
if (next instanceof LabelNode label) {
180+
trashLabels.add(label);
181+
}
182+
next = next.getNext();
183+
}
184+
while (jumpInsnNode.label.getNext() != null && !(jumpInsnNode.label.getNext() instanceof JumpInsnNode)) {
185+
AbstractInsnNode abstractInsnNode = jumpInsnNode.label.getNext();
186+
if (abstractInsnNode instanceof LabelNode) {
187+
methodNode.instructions.remove(abstractInsnNode);
188+
continue;
189+
}
190+
methodNode.instructions.remove(abstractInsnNode);
191+
methodNode.instructions.insertBefore(jumpInsnNode, abstractInsnNode);
192+
markChange();
193+
}
194+
195+
methodNode.instructions.remove(jumpInsnNode);
196+
markChange();
197+
} else {
198+
changed = !toRemove.isEmpty();
199+
}
200+
201+
if (!changed) return;
202+
203+
Set<TryCatchBlockNode> tryCatchBlockNodes = new HashSet<>();
204+
205+
for (TryCatchBlockNode tryCatchBlock : tryCatchBlocks) {
206+
if (trashLabels.contains(tryCatchBlock.start) || trashLabels.contains(tryCatchBlock.handler) || trashLabels.contains(tryCatchBlock.end)) {
207+
tryCatchBlockNodes.add(tryCatchBlock);
208+
markChange();
209+
}
210+
}
211+
212+
tryCatchBlockNodes.forEach(
213+
methodNode.tryCatchBlocks::remove
214+
);
215+
}
216+
}));
217+
}
218+
}

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/branchlock/BranchlockSaltingTransformer.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ public class BranchlockSaltingTransformer extends Transformer {
2222

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

25+
//TODO: Fix superClass`s / interface`s overridden methods not being deobfuscated
26+
2527
@Override
2628
protected void transform() throws Exception {
2729
scopedClasses().forEach(classWrapper -> {
Binary file not shown.
Binary file not shown.
34.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)