Skip to content

Commit 006eae6

Browse files
committed
improve
1 parent 27089ad commit 006eae6

File tree

3 files changed

+220
-130
lines changed

3 files changed

+220
-130
lines changed

deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java

Lines changed: 83 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import dev.xdark.ssvm.mirror.type.InstanceClass;
55
import dev.xdark.ssvm.value.ObjectValue;
66
import dev.xdark.ssvm.value.SimpleArrayValue;
7-
import org.objectweb.asm.ClassReader;
87
import org.objectweb.asm.ClassWriter;
98
import org.objectweb.asm.Opcodes;
109
import org.objectweb.asm.tree.*;
@@ -15,13 +14,16 @@
1514
import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch;
1615
import uwu.narumi.deobfuscator.api.asm.matcher.impl.JumpMatch;
1716
import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch;
18-
import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch;
1917
import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch;
2018
import uwu.narumi.deobfuscator.api.execution.SandBox;
2119
import uwu.narumi.deobfuscator.api.helper.AsmHelper;
2220
import uwu.narumi.deobfuscator.api.transformer.Transformer;
21+
import uwu.narumi.deobfuscator.core.other.impl.zkm.helper.ZelixAsmHelper;
22+
import uwu.narumi.deobfuscator.core.other.impl.zkm.helper.ZelixStringDecryptionMatch;
2323

24-
import java.util.*;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
26+
import java.util.List;
2527
import java.util.concurrent.atomic.AtomicBoolean;
2628

2729
/**
@@ -39,15 +41,11 @@ public class ZelixStringTransformer extends Transformer {
3941
JumpMatch.of(GOTO)
4042
);
4143

42-
private static final Match DECRYPTION_MATCH = SequenceMatch.of(
43-
NumberMatch.numInteger().capture("key1"),
44-
NumberMatch.numInteger().capture("key2"),
45-
MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node")
46-
);
44+
private static final Match DECRYPTION_MATCH = new ZelixStringDecryptionMatch();
4745

4846
@Override
4947
protected void transform() throws Exception {
50-
Map<ClassWrapper, ClassWrapper> encryptedClassWrapper = new HashMap<>();
48+
List<ClassData> encryptedClassWrapper = new ArrayList<>();
5149

5250
for (ClassWrapper classWrapper : scopedClasses()) {
5351
if (classWrapper.findClInit().isEmpty())
@@ -63,45 +61,51 @@ protected void transform() throws Exception {
6361
MatchContext match = matches.get(matches.size() - 1);
6462

6563
// copy clinitMethod
66-
byte[] clonedClass = cloneClassWithClinit(classWrapper, clinitMethod, match);
67-
byte[] modifiedClass = modifyByteCode(classWrapper, clonedClass);
64+
byte[] clonedClass = copyToTempClass(classWrapper, clinitMethod, match);
6865

69-
ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modifiedClass);
66+
ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", clonedClass);
7067
if (tmpClassWrapper == null)
7168
continue;
7269

73-
encryptedClassWrapper.put(classWrapper, tmpClassWrapper);
70+
encryptedClassWrapper.add(new ClassData(classWrapper, clinitMethod, tmpClassWrapper, match));
7471
}
7572

7673
SandBox sandBox = new SandBox(context());
77-
encryptedClassWrapper.forEach((classWrapper, tmpClassWrapper) -> {
78-
AtomicBoolean shouldCleanArray = new AtomicBoolean(true);
74+
75+
for (ClassData classData : encryptedClassWrapper) {
76+
AtomicBoolean shouldCleanArray = new AtomicBoolean(false);
7977

8078
try {
81-
InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName());
82-
83-
for (MethodNode method : classWrapper.methods()) {
84-
MethodContext methodContext = MethodContext.of(classWrapper, method);
85-
86-
DECRYPTION_MATCH.findAllMatches(methodContext).forEach(matchContext -> {
87-
int key1 = matchContext.captures().get("key1").insn().asInteger();
88-
int key2 = matchContext.captures().get("key2").insn().asInteger();
89-
MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn();
90-
91-
try {
92-
String decryptedString = sandBox.getInvocationUtil().invokeStringReference(
93-
clazz.getMethod(decryptedMethod.name, decryptedMethod.desc),
94-
Argument.int32(key1),
95-
Argument.int32(key2)
96-
);
97-
98-
matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString));
99-
matchContext.removeAll();
100-
markChange();
101-
} catch (Exception e) {
102-
shouldCleanArray.set(false);
103-
}
104-
});
79+
InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classData.classWrapper.canonicalName());
80+
81+
for (MethodNode method : classData.classWrapper.methods()) {
82+
MethodContext methodContext = MethodContext.of(classData.classWrapper, method);
83+
84+
List<MatchContext> matches = DECRYPTION_MATCH.findAllMatches(methodContext);
85+
86+
if (!matches.isEmpty()) {
87+
shouldCleanArray.set(true);
88+
matches.forEach(matchContext -> {
89+
int key1 = matchContext.captures().get("key1").insn().asInteger();
90+
int key2 = matchContext.captures().get("key2").insn().asInteger();
91+
MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn();
92+
93+
try {
94+
String decryptedString = sandBox.getInvocationUtil().invokeStringReference(
95+
clazz.getMethod(decryptedMethod.name, decryptedMethod.desc),
96+
Argument.int32(key1),
97+
Argument.int32(key2)
98+
);
99+
100+
matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString));
101+
matchContext.removeAll();
102+
markChange();
103+
} catch (Exception e) {
104+
shouldCleanArray.set(false);
105+
}
106+
});
107+
}
108+
105109
}
106110

107111
// TODO: Access directly array without getter
@@ -110,16 +114,15 @@ protected void transform() throws Exception {
110114
);
111115

112116
int length = arrayValue.getLength();
113-
MethodNode clinitMethod = classWrapper.findClInit().get();
114117

115118
VarInsnNode startArrayInsn = null;
116119

117-
for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) {
120+
for (AbstractInsnNode insn : classData.clinitMethod.instructions.toArray()) {
118121
if (insn instanceof LabelNode)
119122
break;
120123

121124
if (insn.getOpcode() == Opcodes.INVOKESTATIC)
122-
clinitMethod.instructions.remove(insn);
125+
classData.clinitMethod.instructions.remove(insn);
123126
else if (insn instanceof VarInsnNode varInsnNode &&
124127
insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") &&
125128
insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger()
@@ -143,30 +146,25 @@ else if (insn instanceof VarInsnNode varInsnNode &&
143146
insnList.add(new InsnNode(Opcodes.AASTORE));
144147
}
145148

146-
clinitMethod.instructions.insert(startArrayInsn, insnList);
149+
classData.clinitMethod.instructions.insert(startArrayInsn, insnList);
147150
markChange();
148151
}
149152

150153
} catch (Exception e) {
151154
e.printStackTrace();
152155
}
153156

154-
context().removeCompiledClass(tmpClassWrapper);
157+
context().removeCompiledClass(classData.tempClassWrapper);
155158

156159
if (shouldCleanArray.get())
157-
cleanUpFunction(classWrapper);
158-
});
160+
cleanUpFunction(classData);
161+
}
159162
}
160163

161-
private void cleanUpFunction(ClassWrapper classWrapper) {
162-
MethodNode clinitMethod = classWrapper.findClInit().get();
163-
MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod);
164-
List<MatchContext> matches = CLINIT_DETECTION.findAllMatches(methodContext);
165-
MatchContext match = matches.get(matches.size() - 1);
164+
private void cleanUpFunction(ClassData classData) {
165+
JumpInsnNode jumpInsnNode = classData.match.captures().get("label").insn().asJump();
166166

167-
JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump();
168-
169-
Arrays.stream(clinitMethod.instructions.toArray()).filter(insn -> insn instanceof LabelNode).map(insn -> (LabelNode) insn).findFirst().ifPresent(firstLabel -> {
167+
Arrays.stream(classData.clinitMethod.instructions.toArray()).filter(insn -> insn instanceof LabelNode).map(insn -> (LabelNode) insn).findFirst().ifPresent(firstLabel -> {
170168
AbstractInsnNode currentInsn = jumpInsnNode.label;
171169

172170
List<AbstractInsnNode> removedInsns = new ArrayList<>();
@@ -175,88 +173,49 @@ private void cleanUpFunction(ClassWrapper classWrapper) {
175173
removedInsns.add(currentInsn);
176174
}
177175

178-
removedInsns.forEach(insn -> clinitMethod.instructions.remove(insn));
176+
removedInsns.forEach(insn -> classData.clinitMethod.instructions.remove(insn));
179177
});
180178

181-
classWrapper.methods().removeIf(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;"));
179+
classData.classWrapper.methods().removeIf(ZelixAsmHelper::isStringDecryptMethod);
182180
}
183181

184-
private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) {
185-
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
186-
ClassNode classNode = new ClassNode();
187-
182+
private byte[] copyToTempClass(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) {
188183
String tmpClassName = "tmp/" + classWrapper.name();
189184

190-
classNode.access = ACC_PUBLIC | ACC_STATIC;
191-
classNode.name = tmpClassName;
192-
classNode.version = classWrapper.classNode().version;
193-
classNode.superName = "java/lang/Object";
194-
195-
classNode.methods.add(clinit);
196-
197-
// create a tmp array for getting data
198-
// TODO: Can we find a way to access direcly variable without getter?
199-
classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null));
200-
201-
MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]);
202-
getMethodNode.visitCode();
203-
getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, "arr", "[Ljava/lang/String;");
204-
getMethodNode.visitInsn(Opcodes.ARETURN);
205-
getMethodNode.visitMaxs(1, 0);
206-
getMethodNode.visitEnd();
207-
208-
classNode.methods.add(getMethodNode);
185+
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
186+
ClassNode classNode = new ClassNode();
187+
classNode.visit(
188+
classWrapper.classNode().version,
189+
ACC_PUBLIC | ACC_SUPER,
190+
"tmp/" + classWrapper.name(),
191+
"",
192+
"java/lang/Object",
193+
new String[0]
194+
);
195+
196+
MethodNode copiedClinit = AsmHelper.copyMethod(clinit);
197+
classNode.methods.add(copiedClinit);
198+
ZelixAsmHelper.createTempFieldForSSVM(classNode, tmpClassName, "arr", "getArr", "[Ljava/lang/String;");
209199

210200
// copy array
211201
JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump();
212202
AbstractInsnNode node = jumpInsnNode.label;
213203

214-
while (node.getOpcode() != Opcodes.GOTO) {
204+
while (node.getOpcode() != Opcodes.GOTO) { // is it safe?
215205
node = node.getNext();
216206

217-
if (node instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) {
218-
FieldNode fieldNode = classWrapper.findField(fieldInsnNode.name, fieldInsnNode.desc).get();
219-
classNode.fields.add(fieldNode);
220-
}
207+
if (node instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name()))
208+
classWrapper.findField(fieldInsnNode.name, fieldInsnNode.desc).ifPresent(fieldNode -> classNode.fields.add(fieldNode));
221209
}
222210

223-
classWrapper.findMethod(this::isDecryptMethod).ifPresent(method -> {
224-
if (!classNode.methods.contains(method))
225-
classNode.methods.add(method);
226-
});
227-
228-
classNode.accept(cw);
229-
return cw.toByteArray();
230-
}
211+
classWrapper.findMethod(ZelixAsmHelper::isStringDecryptMethod).ifPresent(method -> classNode.methods.add(AsmHelper.copyMethod(method)));
231212

232-
private boolean isDecryptMethod(MethodNode methodNode) {
233-
if (!methodNode.desc.equals("(II)Ljava/lang/String;"))
234-
return false;
235-
236-
if ((methodNode.access & (ACC_PRIVATE | ACC_STATIC)) == 0)
237-
return false;
238-
239-
return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH);
240-
}
241-
242-
private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) {
243-
ClassReader cr = new ClassReader(classByte);
244-
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
245-
ClassNode classNode = new ClassNode();
246-
247-
cr.accept(classNode, 0);
248-
249-
MethodNode clinitMethod = classNode.methods.stream()
250-
.filter(methodNode -> methodNode.name.equals("<clinit>"))
251-
.findFirst()
252-
.get();
253-
254-
MethodContext methodContext = MethodContext.of(classNode, clinitMethod);
213+
MethodContext methodContext = MethodContext.of(classNode, copiedClinit);
255214
List<MatchContext> matches = CLINIT_DETECTION.findAllMatches(methodContext);
256-
MatchContext match = matches.get(matches.size() - 1);
215+
MatchContext match2 = matches.get(matches.size() - 1);
257216

258-
JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump();
259-
AbstractInsnNode currentInsn = jumpInsnNode.label;
217+
JumpInsnNode jumpInsnNode2 = match2.captures().get("label").insn().asJump();
218+
AbstractInsnNode currentInsn = jumpInsnNode2.label;
260219

261220
// mark a return opcode
262221
while (currentInsn != null) {
@@ -269,12 +228,12 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) {
269228
currentInsn = currentInsn.getNext();
270229
}
271230

272-
for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) {
231+
for (AbstractInsnNode insn : copiedClinit.instructions.toArray()) {
273232
if (insn instanceof LabelNode)
274233
break;
275234

276235
if (insn.getOpcode() == Opcodes.INVOKESTATIC) {
277-
clinitMethod.instructions.remove(insn);
236+
copiedClinit.instructions.remove(insn);
278237
} else if (insn instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && !typeInsnNode.desc.equals("java/lang/String")) {
279238
typeInsnNode.desc = "java/lang/String";
280239
} else if (insn instanceof VarInsnNode varInsnNode &&
@@ -284,21 +243,15 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) {
284243
InsnList list = new InsnList();
285244
list.add(new VarInsnNode(Opcodes.ALOAD, varInsnNode.var));
286245
list.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, "arr", "[Ljava/lang/String;"));
287-
clinitMethod.instructions.insert(insn, list);
246+
copiedClinit.instructions.insert(insn, list);
288247
}
289248
}
290249

291-
for (MethodNode methodNode : classNode.methods) {
292-
for (AbstractInsnNode insn : methodNode.instructions) {
293-
if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) {
294-
methodInsnNode.owner = "tmp/" + methodInsnNode.owner;
295-
} else if (insn instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) {
296-
fieldInsnNode.owner = "tmp/" + fieldInsnNode.owner;
297-
}
298-
}
299-
}
250+
ZelixAsmHelper.renameOwner(classNode, classWrapper);
300251

301252
classNode.accept(cw);
302253
return cw.toByteArray();
303254
}
255+
256+
record ClassData(ClassWrapper classWrapper, MethodNode clinitMethod, ClassWrapper tempClassWrapper, MatchContext match) { }
304257
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package uwu.narumi.deobfuscator.core.other.impl.zkm.helper;
2+
3+
import org.objectweb.asm.Opcodes;
4+
import org.objectweb.asm.tree.*;
5+
import uwu.narumi.deobfuscator.api.asm.ClassWrapper;
6+
7+
import java.util.Arrays;
8+
9+
import static org.objectweb.asm.Opcodes.*;
10+
11+
public class ZelixAsmHelper {
12+
13+
public static void createTempFieldForSSVM(ClassNode classNode, String tmpClassName, String name, String getterName, String desc) {
14+
classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, name, desc, null, null));
15+
16+
MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, getterName, "()" + desc, null, new String[0]);
17+
getMethodNode.visitCode();
18+
getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, name, desc);
19+
getMethodNode.visitInsn(Opcodes.ARETURN);
20+
getMethodNode.visitMaxs(1, 0);
21+
getMethodNode.visitEnd();
22+
23+
classNode.methods.add(getMethodNode);
24+
}
25+
26+
public static boolean isStringDecryptMethod(MethodNode methodNode) {
27+
if (!methodNode.desc.equals("(II)Ljava/lang/String;"))
28+
return false;
29+
30+
if ((methodNode.access & (ACC_PRIVATE | ACC_STATIC)) == 0)
31+
return false;
32+
33+
return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH);
34+
}
35+
36+
// TODO: Can we find a way to access direcly variable without getter?
37+
38+
public static void renameOwner(ClassNode classNode, ClassWrapper classWrapper) {
39+
for (MethodNode methodNode : classNode.methods) {
40+
for (AbstractInsnNode insn : methodNode.instructions) {
41+
if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) {
42+
methodInsnNode.owner = "tmp/" + methodInsnNode.owner;
43+
} else if (insn instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) {
44+
fieldInsnNode.owner = "tmp/" + fieldInsnNode.owner;
45+
}
46+
}
47+
}
48+
}
49+
50+
}

0 commit comments

Comments
 (0)