44import dev .xdark .ssvm .mirror .type .InstanceClass ;
55import dev .xdark .ssvm .value .ObjectValue ;
66import dev .xdark .ssvm .value .SimpleArrayValue ;
7- import org .objectweb .asm .ClassReader ;
87import org .objectweb .asm .ClassWriter ;
98import org .objectweb .asm .Opcodes ;
109import org .objectweb .asm .tree .*;
1514import uwu .narumi .deobfuscator .api .asm .matcher .group .SequenceMatch ;
1615import uwu .narumi .deobfuscator .api .asm .matcher .impl .JumpMatch ;
1716import uwu .narumi .deobfuscator .api .asm .matcher .impl .MethodMatch ;
18- import uwu .narumi .deobfuscator .api .asm .matcher .impl .NumberMatch ;
1917import uwu .narumi .deobfuscator .api .asm .matcher .impl .OpcodeMatch ;
2018import uwu .narumi .deobfuscator .api .execution .SandBox ;
2119import uwu .narumi .deobfuscator .api .helper .AsmHelper ;
2220import 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 ;
2527import 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}
0 commit comments