Skip to content

Commit 02efef5

Browse files
committed
Branchlock compability transformer
1 parent 1fa52fb commit 02efef5

File tree

1 file changed

+242
-0
lines changed

1 file changed

+242
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
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.MatchContext;
7+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
8+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.*;
9+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
10+
11+
import java.nio.charset.StandardCharsets;
12+
import java.util.Arrays;
13+
import java.util.concurrent.atomic.AtomicInteger;
14+
import java.util.concurrent.atomic.AtomicReference;
15+
16+
public class BranchlockCompabilityStringTransformer extends Transformer {
17+
18+
private final boolean deleteClinit;
19+
String[] decryptedStrings;
20+
FieldInsnNode stringArray;
21+
22+
23+
24+
public BranchlockCompabilityStringTransformer(boolean deleteClinit) {
25+
this.deleteClinit = deleteClinit;
26+
}
27+
28+
@Override
29+
protected void transform() throws Exception {
30+
scopedClasses().forEach(classWrapper -> {
31+
decryptedStrings = null;
32+
stringArray = null;
33+
classWrapper.findClInit().ifPresent(clinit -> {
34+
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
35+
String className = classWrapper.name().replace("/", ".");
36+
String methodName = clinit.name;
37+
Arrays.stream(clinit.instructions.toArray())
38+
.filter(ain -> ain instanceof LdcInsnNode)
39+
.map(LdcInsnNode.class::cast)
40+
.filter(ldc -> ldc.cst instanceof String)
41+
.findFirst().ifPresent(ldc -> {
42+
Match stringArr = OpcodeMatch.of(PUTSTATIC).capture("string-arr");
43+
stringArray = stringArr.findFirstMatch(methodContext).insn().asFieldInsn();
44+
45+
String encryptedString = new String(((String) ldc.cst).getBytes(), StandardCharsets.UTF_8);
46+
char[] encryptedStringArray = encryptedString.toCharArray();
47+
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));
48+
49+
/* First "char swapper" salting */
50+
for (MatchContext allMatch : match.findAllMatches(methodContext)) {
51+
int arrayFrom = allMatch.captures().get("array-from").insn().asInteger();
52+
int arrayTo = allMatch.captures().get("array-to").insn().asInteger();
53+
try {
54+
char store = encryptedStringArray[arrayFrom];
55+
encryptedStringArray[arrayFrom] = encryptedStringArray[arrayTo];
56+
encryptedStringArray[arrayTo] = store;
57+
} catch (Exception e) {
58+
break;
59+
}
60+
}
61+
62+
int encCharIndex = 0; // Under LDC
63+
64+
int decStrIndex = 0; // Under new StringArr
65+
66+
/* Finding new String Array creator for decrypted Strings */
67+
Match newArray = SequenceMatch.of(NumberMatch.numInteger().capture("salt"), OpcodeMatch.of(IXOR), OpcodeMatch.of(ILOAD), OpcodeMatch.of(IXOR), OpcodeMatch.of(ANEWARRAY));
68+
int salt = newArray.findFirstMatch(methodContext).captures().get("salt").insn().asInteger();
69+
int methodSalt = (methodName.hashCode() & 0xFFFF);
70+
71+
char[] classNameArray = className.toCharArray();
72+
73+
decryptedStrings = new String[encryptedStringArray[encCharIndex++] ^ salt ^ methodSalt];
74+
75+
Match saltOfStrLen = SequenceMatch.of(OpcodeMatch.of(IINC), OpcodeMatch.of(CALOAD), NumberMatch.numInteger().capture("salt"), OpcodeMatch.of(IXOR), OpcodeMatch.of(ILOAD), OpcodeMatch.of(IXOR), OpcodeMatch.of(ISTORE));
76+
int salt2 = saltOfStrLen.findFirstMatch(methodContext).captures().get("salt").insn().asInteger();
77+
78+
Match switchMatch = SequenceMatch.of(NumberMatch.numInteger().capture("switch-salt"), OpcodeMatch.of(IXOR), OpcodeMatch.of(LOOKUPSWITCH).capture("switch-table"));
79+
MatchContext switchContext = switchMatch.findFirstMatch(methodContext);
80+
int switchSalt = switchContext.captures().get("switch-salt").insn().asInteger();
81+
LookupSwitchInsnNode switchInsnNode = (LookupSwitchInsnNode) switchContext.captures().get("switch-table").insn();
82+
83+
Match switch2Match = SequenceMatch.of(OpcodeMatch.of(ILOAD), OpcodeMatch.of(TABLESWITCH).capture("switch-table"));
84+
MatchContext switch2Context = switch2Match.findFirstMatch(methodContext);
85+
TableSwitchInsnNode tableSwitchInsnNode = (TableSwitchInsnNode) switch2Context.captures().get("switch-table").insn();
86+
87+
88+
/* Creating same simulation */
89+
while (encCharIndex < encryptedStringArray.length) {
90+
int strLength = encryptedStringArray[encCharIndex++] ^ salt2 ^ methodSalt;
91+
char[] toDecrypt = new char[strLength];
92+
int decCharIndex = 0; // Under var9_8 = new char[var2_2];
93+
94+
while (strLength > 0) {
95+
char nowDecrypted = encryptedStringArray[encCharIndex];
96+
int switch2Value = 0;
97+
98+
Match swapKey = SequenceMatch.of(NumberMatch.numInteger().capture("swap-key"), OpcodeMatch.of(ISTORE), OpcodeMatch.of(GOTO));
99+
Match xorKey = SequenceMatch.of(OpcodeMatch.of(ILOAD), NumberMatch.numInteger().capture("xor-key"), OpcodeMatch.of(IXOR));
100+
101+
int switchValue = classNameArray[encCharIndex % classNameArray.length] ^ switchSalt;
102+
LabelNode switchCase = getLabelByKey(switchInsnNode, switchValue);
103+
AtomicInteger s2v = new AtomicInteger(-1337);
104+
swapKey.findAllMatches(methodContext).forEach(matchContext -> {
105+
MatchContext capturedSwapKey = matchContext.captures().get("swap-key");
106+
if (isInsnInLabelRange(clinit, switchCase, capturedSwapKey.insn())) {
107+
s2v.set(capturedSwapKey.insn().asInteger());
108+
}
109+
});
110+
111+
if (s2v.get() != -1337) {
112+
switch2Value = s2v.get();
113+
}
114+
115+
AtomicInteger xor = new AtomicInteger(-1337);
116+
xorKey.findAllMatches(methodContext).forEach(matchContext -> {
117+
MatchContext capturedXorKey = matchContext.captures().get("xor-key");
118+
if (isInsnInLabelRange(clinit, switchCase, capturedXorKey.insn())) {
119+
xor.set(capturedXorKey.insn().asInteger());
120+
}
121+
});
122+
123+
if (xor.get() != -1337) {
124+
nowDecrypted ^= xor.get();
125+
}
126+
127+
if (switch2Value == 0 && xor.get() == -1337) {
128+
toDecrypt[decCharIndex] = nowDecrypted;
129+
++decCharIndex;
130+
++encCharIndex;
131+
--strLength;
132+
switch2Value = 0;
133+
continue;
134+
}
135+
136+
if (switch2Value == 1) {
137+
toDecrypt[decCharIndex] = nowDecrypted;
138+
++decCharIndex;
139+
++encCharIndex;
140+
--strLength;
141+
switch2Value = 0;
142+
continue;
143+
}
144+
145+
while (true) {
146+
LabelNode tableCase = getLabelByKey(tableSwitchInsnNode, switch2Value);
147+
148+
AtomicInteger s2v2 = new AtomicInteger(-1337);
149+
swapKey.findAllMatches(methodContext).forEach(matchContext -> {
150+
MatchContext capturedSwapKey = matchContext.captures().get("swap-key");
151+
if (isInsnInLabelRange(clinit, tableCase, capturedSwapKey.insn())) {
152+
s2v2.set(capturedSwapKey.insn().asInteger());
153+
}
154+
});
155+
156+
switch2Value = s2v2.get();
157+
158+
AtomicInteger xor2 = new AtomicInteger(-1337);
159+
xorKey.findAllMatches(methodContext).forEach(matchContext -> {
160+
MatchContext capturedXorKey = matchContext.captures().get("xor-key");
161+
if (isInsnInLabelRange(clinit, tableCase, capturedXorKey.insn())) {
162+
xor2.set(capturedXorKey.insn().asInteger());
163+
}
164+
});
165+
166+
if (xor2.get() != -1337) {
167+
nowDecrypted ^= xor2.get();
168+
}
169+
170+
if (switch2Value == 1) {
171+
toDecrypt[decCharIndex] = nowDecrypted;
172+
++decCharIndex;
173+
++encCharIndex;
174+
--strLength;
175+
switch2Value = 0;
176+
break;
177+
}
178+
}
179+
}
180+
decryptedStrings[decStrIndex++] = new String(toDecrypt).intern();
181+
}
182+
});
183+
});
184+
185+
if (stringArray != null) {
186+
if (deleteClinit) {
187+
classWrapper.methods().remove(classWrapper.findClInit().get());
188+
}
189+
classWrapper.fields().removeIf(fieldNode -> fieldNode.name.equals(stringArray.name) && fieldNode.desc.equals(stringArray.desc));
190+
191+
classWrapper.methods().forEach(methodNode -> {
192+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
193+
methodNode.instructions.forEach(abstractInsnNode -> {
194+
Match getString = SequenceMatch.of(FieldMatch.getStatic().owner(stringArray.owner).name(stringArray.name).desc(stringArray.desc), NumberMatch.numInteger().capture("array-index"), OpcodeMatch.of(AALOAD));
195+
getString.findAllMatches(methodContext).forEach(matchContext -> {
196+
int arrayIndex = matchContext.captures().get("array-index").insn().asInteger();
197+
methodNode.instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedStrings[arrayIndex]));
198+
matchContext.removeAll();
199+
markChange();
200+
});
201+
});
202+
});
203+
}
204+
});
205+
}
206+
207+
public LabelNode getLabelByKey(LookupSwitchInsnNode lsi, int key) {
208+
int index = lsi.keys.indexOf(key);
209+
if (index == -1) {
210+
return lsi.dflt;
211+
}
212+
return lsi.labels.get(index);
213+
}
214+
215+
public LabelNode getLabelByKey(TableSwitchInsnNode tsi, int key) {
216+
if (key < tsi.min || key > tsi.max) {
217+
return tsi.dflt;
218+
}
219+
int index = key - tsi.min;
220+
return tsi.labels.get(index);
221+
}
222+
223+
public boolean isInsnInLabelRange(MethodNode method, LabelNode startLabel, AbstractInsnNode insn) {
224+
InsnList instructions = method.instructions;
225+
226+
int startIndex = instructions.indexOf(startLabel);
227+
if (startIndex == -1) return false;
228+
229+
int insnIndex = instructions.indexOf(insn);
230+
if (insnIndex == -1) return false;
231+
232+
int endIndex = instructions.size();
233+
for (int i = startIndex + 1; i < instructions.size(); i++) {
234+
if (instructions.get(i) instanceof LabelNode) {
235+
endIndex = i;
236+
break;
237+
}
238+
}
239+
240+
return insnIndex > startIndex && insnIndex < endIndex;
241+
}
242+
}

0 commit comments

Comments
 (0)