Skip to content

Commit 8f2635e

Browse files
authored
GuardProtector 1.0 (#115)
* Strings * Flow * Number * Composed * Number * Small bug fixes * Number bring back * non-null check for decrypt alike methods * tests
1 parent cf3d9e5 commit 8f2635e

File tree

7 files changed

+339
-0
lines changed

7 files changed

+339
-0
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,5 +234,10 @@ protected void registerAll() {
234234
.transformers(BranchlockFlowTransformer::new)
235235
.inputJar("branchlock/flow/flow 9.jar")
236236
.register();
237+
238+
test("GuardProtector 1.0")
239+
.transformers(ComposedGuardProtectorTransformer::new)
240+
.inputClass(InputType.CUSTOM_CLASS, "guardprotector/Class951.class")
241+
.register();
237242
}
238243
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package uwu.narumi.deobfuscator.core.other.composed;
2+
3+
import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer;
4+
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer;
5+
import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer;
6+
import uwu.narumi.deobfuscator.core.other.impl.guardprotector.GuardProtectorFlowTransformer;
7+
import uwu.narumi.deobfuscator.core.other.impl.guardprotector.GuardProtectorNumberTransformer;
8+
import uwu.narumi.deobfuscator.core.other.impl.guardprotector.GuardProtectorStringTransformer;
9+
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
10+
11+
public class ComposedGuardProtectorTransformer extends ComposedTransformer {
12+
public ComposedGuardProtectorTransformer() {
13+
super(GuardProtectorStringTransformer::new,
14+
() -> new ComposedTransformer(true,
15+
ComposedPeepholeCleanTransformer::new,
16+
GuardProtectorFlowTransformer::new,
17+
ComposedGeneralFlowTransformer::new
18+
),
19+
GuardProtectorNumberTransformer::new,
20+
UniversalNumberTransformer::new);
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.guardprotector;
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.OpcodeMatch;
8+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
9+
10+
import java.util.HashSet;
11+
import java.util.Set;
12+
13+
public class GuardProtectorFlowTransformer extends Transformer {
14+
15+
Match useless0Switch = SequenceMatch.of(OpcodeMatch.of(ICONST_0), OpcodeMatch.of(LOOKUPSWITCH)).doNotSkip();
16+
Match flowVarStore = SequenceMatch.of(Match.of(ctx -> {
17+
AbstractInsnNode abstractInsnNode = ctx.insn();
18+
return abstractInsnNode.getOpcode() == GETSTATIC && abstractInsnNode.isFieldInsn() && (abstractInsnNode.asFieldInsn().desc.equals("I") || abstractInsnNode.asFieldInsn().desc.equals("Z"));
19+
}).capture("stored-type"), OpcodeMatch.of(ISTORE).capture("stored-var"));
20+
Match flowVarEquations = SequenceMatch.of(OpcodeMatch.of(ILOAD).capture("loaded-var"), OpcodeMatch.of(IFEQ).or(OpcodeMatch.of(IFNE)).capture("stored-jump"));
21+
Match flowTableSwitch = SequenceMatch.of(OpcodeMatch.of(ILOAD).capture("loaded-var"), OpcodeMatch.of(TABLESWITCH).capture("switch"));
22+
23+
@Override
24+
protected void transform() throws Exception {
25+
scopedClasses().forEach(classWrapper -> classWrapper.methods().forEach(methodNode -> {
26+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
27+
useless0Switch.findAllMatches(methodContext).forEach(matchContext -> {
28+
matchContext.removeAll();
29+
markChange();
30+
});
31+
Set<AbstractInsnNode> toRemove = new HashSet<>();
32+
flowVarStore.findAllMatches(methodContext).forEach(varStore -> {
33+
FieldInsnNode fieldInsn = varStore.captures().get("stored-type").insn().asFieldInsn();
34+
if (!hasPutStatic(classWrapper.classNode(), fieldInsn)) {
35+
// FieldRef fieldRef = FieldRef.of(fieldInsn); //This doesn`t work. Fields are still present in classes (maybe because they are private static final)
36+
// context().removeField(fieldRef);
37+
boolean ifNeJump = fieldInsn.desc.equals("Z");
38+
int varIndex = ((VarInsnNode) varStore.captures().get("stored-var").insn()).var;
39+
toRemove.addAll(varStore.collectedInsns());
40+
flowVarEquations.findAllMatches(methodContext).forEach(jumpEquation -> {
41+
int loadedVarIndex = ((VarInsnNode) jumpEquation.captures().get("loaded-var").insn()).var;
42+
JumpInsnNode jump = jumpEquation.captures().get("stored-jump").insn().asJump();
43+
if ((ifNeJump && jump.getOpcode() == IFNE) || (!ifNeJump && jump.getOpcode() == IFEQ) && varIndex == loadedVarIndex) {
44+
// if (jump.label != null) {
45+
// toRemove.add(jump.label);
46+
// markChange();
47+
// methodNode.instructions.forEach(insn -> {
48+
// if (isInsnInLabelRange(methodNode, jump.label, insn)) {
49+
// toRemove.add(insn);
50+
// }
51+
// });
52+
// }
53+
methodNode.instructions.insert(jump, new JumpInsnNode(GOTO, jump.label));
54+
toRemove.addAll(jumpEquation.collectedInsns());
55+
markChange();
56+
}
57+
});
58+
if (!ifNeJump) {
59+
flowTableSwitch.findAllMatches(methodContext).forEach(flowSwitch -> {
60+
int loadedVarIndex = ((VarInsnNode) flowSwitch.captures().get("loaded-var").insn()).var;
61+
if (varIndex == loadedVarIndex) {
62+
toRemove.addAll(flowSwitch.collectedInsns());
63+
TableSwitchInsnNode tableSwitchInsnNode = (TableSwitchInsnNode)flowSwitch.captures().get("switch").insn();
64+
tableSwitchInsnNode.labels.forEach(labelNode -> {
65+
toRemove.add(labelNode);
66+
markChange();
67+
methodNode.instructions.forEach(insn -> {
68+
if (isInsnInLabelRange(methodNode, labelNode, insn)) {
69+
toRemove.add(insn);
70+
}
71+
});
72+
});
73+
}
74+
});
75+
}
76+
}
77+
});
78+
toRemove.forEach(methodNode.instructions::remove);
79+
}));
80+
}
81+
82+
public boolean isInsnInLabelRange(MethodNode method, LabelNode startLabel, AbstractInsnNode insn) {
83+
InsnList instructions = method.instructions;
84+
85+
int startIndex = instructions.indexOf(startLabel);
86+
if (startIndex == -1) return false;
87+
88+
int insnIndex = instructions.indexOf(insn);
89+
if (insnIndex == -1) return false;
90+
91+
int endIndex = instructions.size();
92+
for (int i = startIndex + 1; i < instructions.size(); i++) {
93+
if (instructions.get(i) instanceof LabelNode) {
94+
endIndex = i;
95+
break;
96+
}
97+
}
98+
99+
return insnIndex > startIndex && insnIndex < endIndex;
100+
}
101+
102+
boolean hasPutStatic(ClassNode classNode, FieldInsnNode target) {
103+
for (MethodNode method : classNode.methods) {
104+
for (AbstractInsnNode insn : method.instructions) {
105+
if (insn instanceof FieldInsnNode) {
106+
FieldInsnNode fin = (FieldInsnNode) insn;
107+
if (fin.getOpcode() == PUTSTATIC
108+
&& fin.owner.equals(target.owner)
109+
&& fin.name.equals(target.name)
110+
&& fin.desc.equals(target.desc)) {
111+
return true;
112+
}
113+
}
114+
}
115+
}
116+
return false;
117+
}
118+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.guardprotector;
2+
3+
import org.objectweb.asm.tree.IntInsnNode;
4+
import org.objectweb.asm.tree.LdcInsnNode;
5+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
6+
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
7+
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
8+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
9+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
10+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
11+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
12+
import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer;
13+
14+
import java.util.HashMap;
15+
import java.util.concurrent.atomic.AtomicReference;
16+
17+
public class GuardProtectorNumberTransformer extends Transformer {
18+
19+
Match longArrayInit = SequenceMatch.of(NumberMatch.of().capture("array-size"), Match.of(ctx -> {
20+
if (ctx.insn() instanceof IntInsnNode intInsn) {
21+
return intInsn.operand == T_LONG;
22+
}
23+
return false;
24+
}), OpcodeMatch.of(PUTSTATIC).capture("array"));
25+
26+
Match putStatic = SequenceMatch.of(OpcodeMatch.of(GETSTATIC), NumberMatch.of().capture("array-index"), NumberMatch.of().capture("array-value"), OpcodeMatch.of(LASTORE));
27+
Match getStatic = SequenceMatch.of(OpcodeMatch.of(GETSTATIC).capture("array"), NumberMatch.of().capture("array-index"), OpcodeMatch.of(LALOAD));
28+
29+
@Override
30+
protected void transform() throws Exception {
31+
scopedClasses().forEach(classWrapper -> {
32+
HashMap<Integer, Long> longValues = new HashMap<>();
33+
AtomicReference<String> fieldArrayName = new AtomicReference<>();
34+
classWrapper.findClInit().ifPresent(clinit -> {
35+
MethodContext methodContext = MethodContext.of(classWrapper, clinit);
36+
MatchContext clinitMatch = longArrayInit.findFirstMatch(methodContext);
37+
if (clinitMatch != null) {
38+
fieldArrayName.set(clinitMatch.captures().get("array").insn().asFieldInsn().name);
39+
putStatic.findAllMatches(methodContext).forEach(putStatic -> {
40+
int arrayIndex = putStatic.captures().get("array-index").insn().asNumber().intValue();
41+
long arrayValue = putStatic.captures().get("array-value").insn().asNumber().longValue();
42+
longValues.put(arrayIndex, arrayValue);
43+
putStatic.removeAll();
44+
});
45+
clinitMatch.removeAll();
46+
}
47+
});
48+
classWrapper.methods().forEach(methodNode -> {
49+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
50+
getStatic.findAllMatches(methodContext).forEach(matchContext -> {
51+
if (matchContext.captures().get("array").insn().asFieldInsn().name.equals(fieldArrayName.get())) {
52+
methodNode.instructions.insert(matchContext.insn(), new LdcInsnNode(longValues.get(matchContext.captures().get("array-index").insn().asInteger())));
53+
matchContext.removeAll();
54+
}
55+
});
56+
});
57+
Transformer.transform(UniversalNumberTransformer::new, classWrapper, context());
58+
});
59+
}
60+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.guardprotector;
2+
3+
import org.objectweb.asm.tree.AbstractInsnNode;
4+
import org.objectweb.asm.tree.LdcInsnNode;
5+
import org.objectweb.asm.tree.MethodInsnNode;
6+
import org.objectweb.asm.tree.MethodNode;
7+
import uwu.narumi.deobfuscator.api.asm.MethodContext;
8+
import uwu.narumi.deobfuscator.api.asm.matcher.Match;
9+
import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext;
10+
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
11+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch;
12+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
13+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
14+
import uwu.narumi.deobfuscator.api.asm.matcher.impl.StringMatch;
15+
import uwu.narumi.deobfuscator.api.transformer.Transformer;
16+
17+
import java.util.ArrayList;
18+
import java.util.List;
19+
20+
public class GuardProtectorStringTransformer extends Transformer {
21+
22+
private static final Match ENCRYPTED_STRING = SequenceMatch.of(
23+
StringMatch.of().capture("key"),
24+
MethodMatch.invokeStatic().desc("(Ljava/lang/String;)Ljava/lang/String;").capture("decrypt-method")
25+
);
26+
27+
private static final Match ENCRYPTED_STRING_HASH = SequenceMatch.of(
28+
OpcodeMatch.of(IXOR),
29+
NumberMatch.of().capture("hash"),
30+
OpcodeMatch.of(IXOR)
31+
);
32+
33+
@Override
34+
protected void transform() throws Exception {
35+
scopedClasses().forEach(classWrapper -> {
36+
List<MethodNode> toRemove = new ArrayList<>();
37+
38+
// Find all encrypted strings
39+
classWrapper.methods().forEach(methodNode -> {
40+
MethodContext methodContext = MethodContext.of(classWrapper, methodNode);
41+
42+
// Find encrypted strings
43+
ENCRYPTED_STRING.findAllMatches(methodContext).forEach(matchContext -> {
44+
AbstractInsnNode keyInsn = matchContext.captures().get("key").insn();
45+
MethodInsnNode decryptMethodInsn = matchContext.captures().get("decrypt-method").insn().asMethodInsn();
46+
47+
// Get decrypt method
48+
findMethod(classWrapper.classNode(), method -> method.name.equals(decryptMethodInsn.name) && method.desc.equals(decryptMethodInsn.desc)).ifPresent(decryptMethod -> {
49+
String key = keyInsn.asString();
50+
51+
MethodContext decryptMethodContext = MethodContext.of(classWrapper, decryptMethod);
52+
MatchContext matchContext1 = ENCRYPTED_STRING_HASH.findFirstMatch(decryptMethodContext);
53+
if (matchContext1 != null) {
54+
int hash = matchContext1.captures().get("hash").insn().asNumber().intValue();
55+
String methodName = decryptMethod.name;
56+
57+
String decryptedString = decrypt(key, methodName, hash);
58+
59+
methodNode.instructions.remove(keyInsn);
60+
methodNode.instructions.set(decryptMethodInsn, new LdcInsnNode(decryptedString));
61+
markChange();
62+
63+
toRemove.add(decryptMethod);
64+
}
65+
});
66+
});
67+
});
68+
classWrapper.methods().removeAll(toRemove);
69+
});
70+
}
71+
72+
private String decrypt(String string, String methodName, int key) {
73+
char[] chars = string.toCharArray();
74+
75+
for (int i = 0; i < chars.length; ++i) {
76+
chars[i] = (char)(chars [i] ^ methodName.hashCode() ^ key);
77+
}
78+
79+
return String.valueOf(chars);
80+
}
81+
}
Binary file not shown.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import com.google.common.cache.Cache;
2+
import com.google.common.cache.CacheBuilder;
3+
import java.util.concurrent.Executors;
4+
import java.util.concurrent.ScheduledExecutorService;
5+
import java.util.concurrent.TimeUnit;
6+
import mapped.Class290;
7+
import mapped.Class295;
8+
import mapped.Class320;
9+
import mapped.Class616;
10+
import mapped.Class633;
11+
12+
@Class290
13+
public class Class951 {
14+
private static int field2969;
15+
private static Cache<Integer, Class633> field2970 = CacheBuilder.newBuilder().expireAfterWrite(3L, TimeUnit.MINUTES).build();
16+
private static final ScheduledExecutorService field2971 = Executors.newScheduledThreadPool(1);
17+
private static int field2972 = 325170080;
18+
private static long[] field2973;
19+
private static final int field2974;
20+
private static final int field2975;
21+
22+
@Class295
23+
public static synchronized void method3419(Class320 var0) {
24+
Runnable var1 = (Runnable)field2970.getIfPresent(var0.field1546);
25+
if (var1 != null) {
26+
field2970.invalidate(var0.field1546);
27+
Class633 var2 = (Class633)var1;
28+
var2.field2499 = var0;
29+
var2.run();
30+
}
31+
}
32+
33+
@Class295
34+
public static synchronized void method3420(Class320 var0, Class633 var1) {
35+
method3421(var0, var1, null, 3);
36+
}
37+
38+
public static synchronized void method3421(Class320 var0, Class633 var1, Runnable var2, int var3) {
39+
field2969++;
40+
var0.field1546 = field2969;
41+
field2970.put(var0.field1546, var1);
42+
Class616.method1994(var0);
43+
field2971.schedule(() -> method3422(var0.field1546, var2), (long)var3, TimeUnit.SECONDS);
44+
}
45+
46+
private static synchronized void method3422(Integer var0, Runnable var1) {
47+
Class633 var2 = (Class633)field2970.getIfPresent(var0);
48+
if (var2 != null) {
49+
field2970.invalidate(var0);
50+
var1.run();
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)