From a99014d15f6ba49a8bf85c64636ab5162dba4312 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Wed, 6 Aug 2025 14:29:19 +0700 Subject: [PATCH 01/15] Zelix string transformer --- .../impl/zkm/ZelixStringTransformer.java | 405 ++++++++---------- .../zkm/ZelixStringTwoCipherTransformer.java | 271 ++++++++++++ 2 files changed, 439 insertions(+), 237 deletions(-) create mode 100644 deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 896e1d0d..e594a787 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -1,271 +1,202 @@ package uwu.narumi.deobfuscator.core.other.impl.zkm; +import dev.xdark.ssvm.invoke.Argument; +import dev.xdark.ssvm.mirror.type.InstanceClass; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; +import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.asm.MethodContext; +import uwu.narumi.deobfuscator.api.asm.matcher.Match; +import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; +import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; +import uwu.narumi.deobfuscator.api.execution.SandBox; import uwu.narumi.deobfuscator.api.transformer.Transformer; +import java.io.File; +import java.nio.file.Files; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; +import java.util.Iterator; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; public class ZelixStringTransformer extends Transformer { + private static final Match ZELIX_STRING_CLINIT_MATCH = SequenceMatch.of( + OpcodeMatch.of(ALOAD), + FieldMatch.create().desc("[Ljava/lang/String;").capture("field-1"), + NumberMatch.of(), + OpcodeMatch.of(ANEWARRAY), + FieldMatch.create().desc("[Ljava/lang/String;").capture("field-2"), + OpcodeMatch.of(GOTO) + ); + + private static final Match INVOKE_NUMBER_MATCH = SequenceMatch.of( + NumberMatch.numInteger().capture("key1"), + NumberMatch.numInteger().capture("key2"), + MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node") + ); - HashMap keyType1 = new HashMap<>(); - HashMap> keyType2 = new HashMap<>(); - HashMap staticArraySize = new HashMap<>(); - HashMap> offsets = new HashMap<>(); - HashMap> encryptedStrings = new HashMap<>(); - - /* Written by https://github.com/Lampadina17 | OG 19/07/2024, Rewritten 09/08/2024 */ @Override protected void transform() throws Exception { - scopedClasses().forEach(classWrapper -> { - /* Extract key type 1 from hardcoded xor encryption */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.desc.equals("(Ljava/lang/String;)[C")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof IntInsnNode) - .filter(ain -> ain.getNext() instanceof InsnNode) - .filter(ain -> ain.getNext().getOpcode() == IXOR) - .map(IntInsnNode.class::cast) - .forEach(iin -> keyType1.put(classWrapper.name(), iin.operand))); - - /* Temporary variable */ - List key2 = new ArrayList<>(); - - /* Extract key type 2 from hardcoded switch case xor encryption */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.desc.equals("([C)Ljava/lang/String;")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof IntInsnNode) - .map(IntInsnNode.class::cast) - .forEach(iin -> key2.add((byte) iin.operand))); - - /* Store the key data by class */ - if (!key2.isEmpty()) keyType2.put(classWrapper.name(), key2); - - /* Retrieve array length (static block) */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> { - if (methodNode.instructions.getFirst() instanceof IntInsnNode && methodNode.instructions.getFirst().getNext() instanceof TypeInsnNode) { - staticArraySize.put(classWrapper.name(), ((IntInsnNode) methodNode.instructions.getFirst()).operand); - } - }); + List encryptedClassWrapper = new ArrayList<>(); - /* Temporary variable */ - List strings = new ArrayList<>(); - - /* Retrieve ciphered strings (static block) */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof LdcInsnNode) - .map(LdcInsnNode.class::cast) - .filter(ldc -> ldc.cst instanceof String) - .forEach(ldc -> strings.add((String) ldc.cst))); - - if (!strings.isEmpty()) encryptedStrings.put(classWrapper.name(), strings); - - /* Temporary Variable */ - List offsets = new ArrayList<>(); - - /* Retrieve weird zkm "offsets" */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .forEach(ain -> { - AbstractInsnNode prev = ain.getPrevious(); - if (prev != null && prev.getPrevious() != null && prev.previous() instanceof MethodInsnNode min && min.name.equals("length")) { - if (ain instanceof IntInsnNode iin) { - offsets.add(iin.operand); - } else if (ain instanceof InsnNode in && in.getOpcode() >= ICONST_M1 && in.getOpcode() <= ICONST_5) { - offsets.add(getValue(in)); - } - } - })); - - if (!offsets.isEmpty()) this.offsets.put(classWrapper.name(), offsets); - }); + for (ClassWrapper classWrapper: scopedClasses()) { + if (classWrapper.findClInit().isEmpty()) + continue; + + MethodNode clinitMethod = classWrapper.findClInit().get(); + MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); + MatchContext match = ZELIX_STRING_CLINIT_MATCH.findFirstMatch(methodContext); + + if (match == null) + continue; + + // copy clinitMethod + byte[] clonedClass = cloneClassWithClinit(classWrapper, clinitMethod, match); + byte[] removedInsnByte = removeDependantInsn(classWrapper, clonedClass); + byte[] modified = renameInvoke(classWrapper, removedInsnByte); + + context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modified); + encryptedClassWrapper.add(classWrapper); + } + + SandBox sandBox = new SandBox(context()); + + for (ClassWrapper classWrapper : encryptedClassWrapper) { + try { + InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); - /* Decrypt and cleanup */ - scopedClasses().forEach(classWrapper -> { - classWrapper.methods().stream() - .forEach(methodNode -> { - List encrypted = encryptedStrings.get(classWrapper.name()); - List offsets = this.offsets.get(classWrapper.name()); - - if (encrypted != null && encrypted.size() == 2 && offsets != null && offsets.size() == 2) { - /* for classes that has big static block */ - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain.getOpcode() == AALOAD) - .filter(ain -> ain.getPrevious().getPrevious().getOpcode() == ALOAD) - .forEach(ain -> { - int index = 0; - if (ain.getPrevious() instanceof IntInsnNode iin) index = getValue(iin); - else if (ain.getPrevious() instanceof InsnNode in) index = getValue(in); - - List key2 = keyType2.get(classWrapper.name()); - - if (key2 != null) - try { - String[] decryptedStrings = ZKMCipher.StaticInit( - encrypted.get(0), - encrypted.get(1), - keyType1.get(classWrapper.name()), - shiftBytes(key2), - offsets.get(0), - offsets.get(1), - staticArraySize.get(classWrapper.name()), - key2.get(0)); - - /* Re-insert original string back to its place */ - methodNode.instructions.insert(ain, new LdcInsnNode(decryptedStrings[index])); - /* Cleanup */ - methodNode.instructions.remove(ain.getPrevious().getPrevious()); - methodNode.instructions.remove(ain.getPrevious()); - methodNode.instructions.remove(ain); - this.markChange(); - } catch (Exception e) { - } - }); - } else { - AtomicBoolean cleanup = new AtomicBoolean(false); - - List key2 = keyType2.get(classWrapper.name()); - - if (key2 != null) { - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof LdcInsnNode) - .map(LdcInsnNode.class::cast) - .filter(ldc -> ldc.cst instanceof String) - .forEach(ldc -> { - try { - ldc.cst = ZKMCipher.cipher2(ZKMCipher.cipher1((String) ldc.cst, keyType1.get(classWrapper.name())), shiftBytes(key2), key2.get(0)); - cleanup.set(true); - } catch (Exception e) { - } - }); - - if (cleanup.get()) - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain.getOpcode() == SWAP) - .filter(ain -> ain.getNext() != null && ain.getNext() instanceof MethodInsnNode) - .filter(ain -> ain.getNext().getNext() != null && ain.getNext().getNext() instanceof MethodInsnNode) - .filter(ain -> ain.getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getOpcode() == SWAP) - .forEach(ain -> { - /* Do cleanup */ - methodNode.instructions.remove(ain.getNext().getNext().getNext()); - methodNode.instructions.remove(ain.getNext().getNext()); - methodNode.instructions.remove(ain.getNext()); - methodNode.instructions.remove(ain); - this.markChange(); - }); - } - } + for (MethodNode method : classWrapper.methods()) { + MethodContext methodContext = MethodContext.of(classWrapper, method); + INVOKE_NUMBER_MATCH.findAllMatches(methodContext).forEach(matchContext -> { + int key1 = matchContext.captures().get("key1").insn().asInteger(); + int key2 = matchContext.captures().get("key2").insn().asInteger(); + MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); + + String value = sandBox.getInvocationUtil().invokeStringReference( + clazz.getMethod(decryptedMethod.name, "(II)Ljava/lang/String;"), + Argument.int32(key1), + Argument.int32(key2) + ); + + System.out.println(value); }); - }); - LOGGER.info("Decrypted {} strings in {} classes", this.getChangesCount(), scopedClasses().size()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } } - /* Convert arraylist to array and shift values, when a bug transform into a feature (Key type 2) */ - private byte[] shiftBytes(List input) { - byte[] keyBytes = new byte[input.size() - 1]; + private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassNode classNode = new ClassNode(); - int j = 1; - for (int i = 0; i < keyBytes.length; i++) { - keyBytes[i] = input.get(j); - j++; - } - return keyBytes; + classNode.access = ACC_PUBLIC | ACC_STATIC; + classNode.name = "tmp/" + classWrapper.name(); + classNode.version = classWrapper.classNode().version; + classNode.superName = "java/lang/Object"; + + classNode.methods.add(clinit); + + addArrayField(classWrapper, classNode, match, "field-1"); + addArrayField(classWrapper, classNode, match, "field-2"); + + classNode.accept(cw); + return cw.toByteArray(); } - public int getValue(AbstractInsnNode in) { - int opcode = in.getOpcode(); - return switch (opcode) { - case ICONST_M1 -> -1; - case ICONST_0 -> 0; - case ICONST_1 -> 1; - case ICONST_2 -> 2; - case ICONST_3 -> 3; - case ICONST_4 -> 4; - case ICONST_5 -> 5; - case SIPUSH, BIPUSH -> ((IntInsnNode) in).operand; - default -> throw new RuntimeException("Unsupported opcode"); - }; + private void addArrayField(ClassWrapper classWrapper, ClassNode classNode, MatchContext matchContext, String key) { + FieldInsnNode field1 = matchContext.captures().get(key).insn().asFieldInsn(); + FieldNode fieldNode1 = classWrapper.findField(field1.name, field1.desc).get(); + classNode.fields.add(fieldNode1); } - public static class ZKMCipher { + private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) { + ClassReader cr = new ClassReader(classByte); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassNode classNode = new ClassNode(); - public static char[] cipher1(final String var0, final int key) { // All old versions - final char[] input = var0.toCharArray(); - if (input.length < 2) { - input[0] ^= key; + cr.accept(classNode, 0); + + MethodNode clinitMethod = classNode.methods.stream() + .filter(methodNode -> methodNode.name.equals("")) + .findFirst() + .get(); + + /** + * Remove all instructions after executing "putstatic" 2 arrays. They can cause compiling issue as it must load on other classes. + * TODO: Is it better if we can remove all instructions that embedded on label? + */ + + Iterator iterator = clinitMethod.instructions.iterator(); + + LabelNode returnLabelnode = null; + boolean markToRemove = false; + + List removedInsn = new ArrayList<>(); + + while (iterator.hasNext()) { + AbstractInsnNode insn = iterator.next(); + + if (insn.getOpcode() == Opcodes.ALOAD && + insn.getNext() instanceof FieldInsnNode fieldNode1 && fieldNode1.desc.equals("[Ljava/lang/String;") && + fieldNode1.getNext().isNumber() && + fieldNode1.getNext(2).getOpcode() == Opcodes.ANEWARRAY && + fieldNode1.getNext(3) instanceof FieldInsnNode fieldNode2 && fieldNode2.desc.equals("[Ljava/lang/String;") && + fieldNode2.getNext() instanceof JumpInsnNode jumpInsnNode) { + + returnLabelnode = jumpInsnNode.label; } - return input; + + if (returnLabelnode != null && insn instanceof LabelNode labelNode1 && labelNode1.getLabel() == returnLabelnode.getLabel()) { + markToRemove = true; + iterator.next(); + } else if (markToRemove && insn.getOpcode() != Opcodes.RETURN) + removedInsn.add(insn); } - public static String cipher2(final char[] input, final byte[] keys, final int length) throws Exception { - if (keys.length != length) throw new Exception("Key is invalid"); - for (int i = 0; input.length > i; ++i) { - input[i] ^= (char) keys[i % length]; - } - return (new String(input)).intern(); + removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); + removedInsn.clear(); + + for (AbstractInsnNode insn : clinitMethod.instructions) { + if (insn.getOpcode() == Opcodes.INVOKESTATIC) + removedInsn.add(insn); } - public static String[] StaticInit(String encrypted1, String encrypted2, int key1, byte[] key2, int offset, int offset2, int arraysize, int length) throws Exception { - final String[] h2 = new String[arraysize]; - int n = 0; - String s; - int n2 = (s = encrypted1).length(); - int n3 = offset; - int n4 = -1; - - Label_0023: - while (true) { - while (true) { - ++n4; - final String s2 = s; - final int n5 = n4; - String s3 = s2.substring(n5, n5 + n3); - int n6 = -1; - while (true) { - final String a = ZKMCipher.cipher2(ZKMCipher.cipher1(s3, key1), key2, length); - switch (n6) { - default: { - h2[n++] = a; - if ((n4 += n3) < n2) { - n3 = s.charAt(n4); - continue Label_0023; - } - n2 = (s = encrypted2).length(); - n3 = offset2; - n4 = -1; - break; - } - case 0: { - h2[n++] = a; - if ((n4 += n3) < n2) { - n3 = s.charAt(n4); - break; - } - break Label_0023; - } - } - ++n4; - final String s4 = s; - final int n7 = n4; - s3 = s4.substring(n7, n7 + n3); - n6 = 0; - } + removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); + + classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { + if (!classNode.methods.contains(method)) classNode.methods.add(method); + }); + + classNode.accept(cw); + return cw.toByteArray(); + } + + private byte[] renameInvoke(ClassWrapper classWrapper, byte[] classByte) { + ClassReader cr = new ClassReader(classByte); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassNode classNode = new ClassNode(); + + cr.accept(classNode, 0); + + for (MethodNode methodNode : classNode.methods) { + for (AbstractInsnNode insn : methodNode.instructions) { + if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) { + methodInsnNode.owner = "tmp/" + methodInsnNode.owner; + } else if (insn instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) { + fieldInsnNode.owner = "tmp/" + fieldInsnNode.owner; } } - return h2; } + + classNode.accept(cw); + return cw.toByteArray(); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java new file mode 100644 index 00000000..fd50675c --- /dev/null +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java @@ -0,0 +1,271 @@ +package uwu.narumi.deobfuscator.core.other.impl.zkm; + +import org.objectweb.asm.tree.*; +import uwu.narumi.deobfuscator.api.transformer.Transformer; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +public class ZelixStringTwoCipherTransformer extends Transformer { + + HashMap keyType1 = new HashMap<>(); + HashMap> keyType2 = new HashMap<>(); + HashMap staticArraySize = new HashMap<>(); + HashMap> offsets = new HashMap<>(); + + HashMap> encryptedStrings = new HashMap<>(); + + /* Written by https://github.com/Lampadina17 | OG 19/07/2024, Rewritten 09/08/2024 */ + @Override + protected void transform() throws Exception { + scopedClasses().forEach(classWrapper -> { + /* Extract key type 1 from hardcoded xor encryption */ + classWrapper.methods().stream() + .filter(methodNode -> methodNode.desc.equals("(Ljava/lang/String;)[C")) + .forEach(methodNode -> + Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain instanceof IntInsnNode) + .filter(ain -> ain.getNext() instanceof InsnNode) + .filter(ain -> ain.getNext().getOpcode() == IXOR) + .map(IntInsnNode.class::cast) + .forEach(iin -> keyType1.put(classWrapper.name(), iin.operand))); + + /* Temporary variable */ + List key2 = new ArrayList<>(); + + /* Extract key type 2 from hardcoded switch case xor encryption */ + classWrapper.methods().stream() + .filter(methodNode -> methodNode.desc.equals("([C)Ljava/lang/String;")) + .forEach(methodNode -> + Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain instanceof IntInsnNode) + .map(IntInsnNode.class::cast) + .forEach(iin -> key2.add((byte) iin.operand))); + + /* Store the key data by class */ + if (!key2.isEmpty()) keyType2.put(classWrapper.name(), key2); + + /* Retrieve array length (static block) */ + classWrapper.methods().stream() + .filter(methodNode -> methodNode.name.equals("")) + .forEach(methodNode -> { + if (methodNode.instructions.getFirst() instanceof IntInsnNode && methodNode.instructions.getFirst().getNext() instanceof TypeInsnNode) { + staticArraySize.put(classWrapper.name(), ((IntInsnNode) methodNode.instructions.getFirst()).operand); + } + }); + + /* Temporary variable */ + List strings = new ArrayList<>(); + + /* Retrieve ciphered strings (static block) */ + classWrapper.methods().stream() + .filter(methodNode -> methodNode.name.equals("")) + .forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain instanceof LdcInsnNode) + .map(LdcInsnNode.class::cast) + .filter(ldc -> ldc.cst instanceof String) + .forEach(ldc -> strings.add((String) ldc.cst))); + + if (!strings.isEmpty()) encryptedStrings.put(classWrapper.name(), strings); + + /* Temporary Variable */ + List offsets = new ArrayList<>(); + + /* Retrieve weird zkm "offsets" */ + classWrapper.methods().stream() + .filter(methodNode -> methodNode.name.equals("")) + .forEach(methodNode -> + Arrays.stream(methodNode.instructions.toArray()) + .forEach(ain -> { + AbstractInsnNode prev = ain.getPrevious(); + if (prev != null && prev.getPrevious() != null && prev.previous() instanceof MethodInsnNode min && min.name.equals("length")) { + if (ain instanceof IntInsnNode iin) { + offsets.add(iin.operand); + } else if (ain instanceof InsnNode in && in.getOpcode() >= ICONST_M1 && in.getOpcode() <= ICONST_5) { + offsets.add(getValue(in)); + } + } + })); + + if (!offsets.isEmpty()) this.offsets.put(classWrapper.name(), offsets); + }); + + /* Decrypt and cleanup */ + scopedClasses().forEach(classWrapper -> { + classWrapper.methods().stream() + .forEach(methodNode -> { + List encrypted = encryptedStrings.get(classWrapper.name()); + List offsets = this.offsets.get(classWrapper.name()); + + if (encrypted != null && encrypted.size() == 2 && offsets != null && offsets.size() == 2) { + /* for classes that has big static block */ + Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain.getOpcode() == AALOAD) + .filter(ain -> ain.getPrevious().getPrevious().getOpcode() == ALOAD) + .forEach(ain -> { + int index = 0; + if (ain.getPrevious() instanceof IntInsnNode iin) index = getValue(iin); + else if (ain.getPrevious() instanceof InsnNode in) index = getValue(in); + + List key2 = keyType2.get(classWrapper.name()); + + if (key2 != null) + try { + String[] decryptedStrings = ZKMCipher.StaticInit( + encrypted.get(0), + encrypted.get(1), + keyType1.get(classWrapper.name()), + shiftBytes(key2), + offsets.get(0), + offsets.get(1), + staticArraySize.get(classWrapper.name()), + key2.get(0)); + + /* Re-insert original string back to its place */ + methodNode.instructions.insert(ain, new LdcInsnNode(decryptedStrings[index])); + /* Cleanup */ + methodNode.instructions.remove(ain.getPrevious().getPrevious()); + methodNode.instructions.remove(ain.getPrevious()); + methodNode.instructions.remove(ain); + this.markChange(); + } catch (Exception e) { + } + }); + } else { + AtomicBoolean cleanup = new AtomicBoolean(false); + + List key2 = keyType2.get(classWrapper.name()); + + if (key2 != null) { + Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain instanceof LdcInsnNode) + .map(LdcInsnNode.class::cast) + .filter(ldc -> ldc.cst instanceof String) + .forEach(ldc -> { + try { + ldc.cst = ZKMCipher.cipher2(ZKMCipher.cipher1((String) ldc.cst, keyType1.get(classWrapper.name())), shiftBytes(key2), key2.get(0)); + cleanup.set(true); + } catch (Exception e) { + } + }); + + if (cleanup.get()) + Arrays.stream(methodNode.instructions.toArray()) + .filter(ain -> ain.getOpcode() == SWAP) + .filter(ain -> ain.getNext() != null && ain.getNext() instanceof MethodInsnNode) + .filter(ain -> ain.getNext().getNext() != null && ain.getNext().getNext() instanceof MethodInsnNode) + .filter(ain -> ain.getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getOpcode() == SWAP) + .forEach(ain -> { + /* Do cleanup */ + methodNode.instructions.remove(ain.getNext().getNext().getNext()); + methodNode.instructions.remove(ain.getNext().getNext()); + methodNode.instructions.remove(ain.getNext()); + methodNode.instructions.remove(ain); + this.markChange(); + }); + } + } + }); + }); + LOGGER.info("Decrypted {} strings in {} classes", this.getChangesCount(), scopedClasses().size()); + } + + /* Convert arraylist to array and shift values, when a bug transform into a feature (Key type 2) */ + private byte[] shiftBytes(List input) { + byte[] keyBytes = new byte[input.size() - 1]; + + int j = 1; + for (int i = 0; i < keyBytes.length; i++) { + keyBytes[i] = input.get(j); + j++; + } + return keyBytes; + } + + public int getValue(AbstractInsnNode in) { + int opcode = in.getOpcode(); + return switch (opcode) { + case ICONST_M1 -> -1; + case ICONST_0 -> 0; + case ICONST_1 -> 1; + case ICONST_2 -> 2; + case ICONST_3 -> 3; + case ICONST_4 -> 4; + case ICONST_5 -> 5; + case SIPUSH, BIPUSH -> ((IntInsnNode) in).operand; + default -> throw new RuntimeException("Unsupported opcode"); + }; + } + + public static class ZKMCipher { + + public static char[] cipher1(final String var0, final int key) { // All old versions + final char[] input = var0.toCharArray(); + if (input.length < 2) { + input[0] ^= key; + } + return input; + } + + public static String cipher2(final char[] input, final byte[] keys, final int length) throws Exception { + if (keys.length != length) throw new Exception("Key is invalid"); + for (int i = 0; input.length > i; ++i) { + input[i] ^= (char) keys[i % length]; + } + return (new String(input)).intern(); + } + + public static String[] StaticInit(String encrypted1, String encrypted2, int key1, byte[] key2, int offset, int offset2, int arraysize, int length) throws Exception { + final String[] h2 = new String[arraysize]; + int n = 0; + String s; + int n2 = (s = encrypted1).length(); + int n3 = offset; + int n4 = -1; + + Label_0023: + while (true) { + while (true) { + ++n4; + final String s2 = s; + final int n5 = n4; + String s3 = s2.substring(n5, n5 + n3); + int n6 = -1; + while (true) { + final String a = ZKMCipher.cipher2(ZKMCipher.cipher1(s3, key1), key2, length); + switch (n6) { + default: { + h2[n++] = a; + if ((n4 += n3) < n2) { + n3 = s.charAt(n4); + continue Label_0023; + } + n2 = (s = encrypted2).length(); + n3 = offset2; + n4 = -1; + break; + } + case 0: { + h2[n++] = a; + if ((n4 += n3) < n2) { + n3 = s.charAt(n4); + break; + } + break Label_0023; + } + } + ++n4; + final String s4 = s; + final int n7 = n4; + s3 = s4.substring(n7, n7 + n3); + n6 = 0; + } + } + } + return h2; + } + } +} From e270964edbd1e4b444315d509dcd2be70270b1b3 Mon Sep 17 00:00:00 2001 From: EpicPlayerA10 Date: Fri, 22 Aug 2025 22:25:05 +0200 Subject: [PATCH 02/15] make getFieldNumberPool return null instead of throwing --- .../impl/universal/pool/UniversalNumberPoolTransformer.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/pool/UniversalNumberPoolTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/pool/UniversalNumberPoolTransformer.java index 59d4c065..dca07ae7 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/pool/UniversalNumberPoolTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/pool/UniversalNumberPoolTransformer.java @@ -75,6 +75,10 @@ protected void transform() throws Exception { // Get whole number pool Number[] numberPool = getFieldNumberPool(numberPoolMatchCtx.insnContext().methodContext(), numberPoolSize, fieldRefPool); + if (numberPool == null) { + LOGGER.warn("Number pool is not fully initialized for {}#{}{}", numberPoolMatchCtx.insnContext().methodContext().classWrapper().name(), numberPoolMatchCtx.insnContext().methodContext().methodNode().name, numberPoolMatchCtx.insnContext().methodContext().methodNode().desc); + return; + } Match numberPoolReferenceMatch; if (isPrimitiveArray) { @@ -159,7 +163,7 @@ public static Number[] getFieldNumberPool(MethodContext methodContext, int poolS for (Number number : numberPool) { if (number == null) { // Number pool is not fully initialized - throw new IllegalStateException("Number pool is not fully initialized"); + return null; } } From 4160f61e0b92934588894c7ee471d0929adfde2b Mon Sep 17 00:00:00 2001 From: EpicPlayerA10 Date: Fri, 22 Aug 2025 23:49:52 +0200 Subject: [PATCH 03/15] workaround very yes yes :D --- .../number/InlineConstantValuesTransformer.java | 14 ++++++++++++++ .../qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/number/InlineConstantValuesTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/number/InlineConstantValuesTransformer.java index ba358c8b..b335efa3 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/number/InlineConstantValuesTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/number/InlineConstantValuesTransformer.java @@ -1,6 +1,7 @@ package uwu.narumi.deobfuscator.core.other.impl.universal.number; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.analysis.OriginalSourceValue; import uwu.narumi.deobfuscator.api.asm.InsnContext; import uwu.narumi.deobfuscator.api.asm.MethodContext; @@ -17,6 +18,16 @@ public class InlineConstantValuesTransformer extends Transformer { @Override protected void transform() throws Exception { scopedClasses().parallelStream().forEach(classWrapper -> classWrapper.methods().parallelStream().forEach(methodNode -> { + // Very good workaround for inlining constants, yes yes :D + // By placing labels before each instruction, we ensure that the analyzer + // does not merge frames and we get more accurate stack values + Set tempLabels = new HashSet<>(); + for (AbstractInsnNode insn : methodNode.instructions.toArray()) { + LabelNode labelNode = new LabelNode(); + tempLabels.add(labelNode); + methodNode.instructions.insert(insn, labelNode); + } + MethodContext methodContext = MethodContext.of(classWrapper, methodNode); Set poppedInsns = new HashSet<>(); @@ -54,6 +65,9 @@ protected void transform() throws Exception { } } } + + // Remove temp labels + tempLabels.forEach(labelNode -> methodNode.instructions.remove(labelNode)); })); } } diff --git a/testData/results/custom-classes/qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec b/testData/results/custom-classes/qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec index 681f9a51..f5b6116d 100644 --- a/testData/results/custom-classes/qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec +++ b/testData/results/custom-classes/qprotect/sample3/fastcode/IlllIIlIIlllIIIl.dec @@ -107,7 +107,7 @@ public class IlllIIlIIlllIIIl { char[] var3 = IlIIlIlIIIIIlll[var2].toCharArray(); int var14 = var3[0]; - int var4 = switch ((~var14 | 0xFF) - ~var14) { + short var4 = switch ((~var14 | 0xFF) - ~var14) { case 0 -> 55; case 1 -> 95; case 2 -> 0; @@ -363,7 +363,7 @@ public class IlllIIlIIlllIIIl { case 252 -> 232; case 253 -> 181; case 254 -> 134; - default -> 29907 - 29696; + default -> 211; }; int var15 = (short)var1; int var5 = (~var15 | 0xFF) - ~var15 + ~var4 + 1; From ca5dc4f72e5549dcf60f8c28447ad489653a82d4 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sat, 23 Aug 2025 08:54:15 +0700 Subject: [PATCH 04/15] clean temp classes --- .editorconfig | 2 +- .../api/classpath/ClassInfoStorage.java | 7 +++++ .../deobfuscator/api/context/Context.java | 10 ++++++- .../src/test/java/Bootstrap.java | 5 ++-- .../impl/zkm/ZelixStringTransformer.java | 28 ++++++++++--------- 5 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.editorconfig b/.editorconfig index 57821c84..6062365e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,4 +3,4 @@ charset = utf-8 end_of_line = lf indent_style = space ij_continuation_indent_size = 4 -indent_size = 2 +indent_size = 4 diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassInfoStorage.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassInfoStorage.java index d21a4493..12dc8189 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassInfoStorage.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/classpath/ClassInfoStorage.java @@ -54,6 +54,13 @@ public void addRawClass(byte[] bytes) { classesInfo.putIfAbsent(className, classNode); } + public void removeRawClass(ClassNode classNode) { + String className = classNode.name; + + compiledClasses.remove(className); + classesInfo.remove(className); + } + @Override public byte @Nullable [] getClass(String name) { return compiledClasses.get(name); diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java index 1c0d611c..db40c14a 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/context/Context.java @@ -108,7 +108,7 @@ public List scopedClasses(ClassWrapper scope) { .toList(); } - public void addCompiledClass(String pathInJar, byte[] bytes) { + public ClassWrapper addCompiledClass(String pathInJar, byte[] bytes) { try { // Fix class bytes bytes = ClassHelper.fixClass(bytes); @@ -120,9 +120,17 @@ public void addCompiledClass(String pathInJar, byte[] bytes) { this.classesMap.putIfAbsent(classWrapper.name(), classWrapper); this.compiledClasses.addRawClass(bytes); + return classWrapper; } catch (InvalidClassException e) { LOGGER.error("Failed to load class {}", pathInJar); } + + return null; + } + + public void removeCompiledClass(ClassWrapper classWrapper) { + this.classesMap.remove(classWrapper.name()); + this.compiledClasses.removeRawClass(classWrapper.classNode()); } public void addFile(String path, byte[] bytes) { diff --git a/deobfuscator-impl/src/test/java/Bootstrap.java b/deobfuscator-impl/src/test/java/Bootstrap.java index 0984b8f8..a95c350c 100644 --- a/deobfuscator-impl/src/test/java/Bootstrap.java +++ b/deobfuscator-impl/src/test/java/Bootstrap.java @@ -3,17 +3,18 @@ import uwu.narumi.deobfuscator.Deobfuscator; import uwu.narumi.deobfuscator.api.context.DeobfuscatorOptions; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer; +import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixStringTransformer; public class Bootstrap { public static void main(String[] args) { Deobfuscator.from( DeobfuscatorOptions.builder() - .inputJar(Path.of("work", "obf-test.jar")) // Specify your input jar here + .inputJar(Path.of("work", "grimac-3.0.94-obf-out.jar")) // Specify your input jar here //.libraries(Path.of("work", "libs")) // Specify your libraries here if needed .transformers( // Pick your transformers here - () -> new ComposedGeneralFlowTransformer() + ZelixStringTransformer::new ) .continueOnError() .classWriterFlags(ClassWriter.COMPUTE_FRAMES) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index e594a787..9a27f4d8 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -18,11 +18,7 @@ import uwu.narumi.deobfuscator.api.execution.SandBox; import uwu.narumi.deobfuscator.api.transformer.Transformer; -import java.io.File; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +import java.util.*; public class ZelixStringTransformer extends Transformer { private static final Match ZELIX_STRING_CLINIT_MATCH = SequenceMatch.of( @@ -43,9 +39,9 @@ public class ZelixStringTransformer extends Transformer { @Override protected void transform() throws Exception { - List encryptedClassWrapper = new ArrayList<>(); + Map encryptedClassWrapper = new HashMap<>(); - for (ClassWrapper classWrapper: scopedClasses()) { + for (ClassWrapper classWrapper : scopedClasses()) { if (classWrapper.findClInit().isEmpty()) continue; @@ -61,13 +57,15 @@ protected void transform() throws Exception { byte[] removedInsnByte = removeDependantInsn(classWrapper, clonedClass); byte[] modified = renameInvoke(classWrapper, removedInsnByte); - context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modified); - encryptedClassWrapper.add(classWrapper); + ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modified); + if (tmpClassWrapper == null) + continue; + + encryptedClassWrapper.put(classWrapper, tmpClassWrapper); } SandBox sandBox = new SandBox(context()); - - for (ClassWrapper classWrapper : encryptedClassWrapper) { + encryptedClassWrapper.forEach((classWrapper, tmpClassWrapper) -> { try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); @@ -84,13 +82,17 @@ protected void transform() throws Exception { Argument.int32(key2) ); - System.out.println(value); + matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); + matchContext.removeAll(); + markChange(); }); } } catch (Exception e) { e.printStackTrace(); } - } + + context().removeCompiledClass(tmpClassWrapper); + }); } private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { From d0e1091475db3cb48add01f692f8d887fb50fbcf Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sat, 23 Aug 2025 08:59:02 +0700 Subject: [PATCH 05/15] oops editor config --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index 6062365e..57821c84 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,4 +3,4 @@ charset = utf-8 end_of_line = lf indent_style = space ij_continuation_indent_size = 4 -indent_size = 4 +indent_size = 2 From dbae2ca95f29f1a3b70950c120a9a592b063c8c2 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sat, 23 Aug 2025 11:23:57 +0700 Subject: [PATCH 06/15] update --- .editorconfig | 2 +- .../impl/zkm/ZelixStringTransformer.java | 51 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/.editorconfig b/.editorconfig index 57821c84..6062365e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,4 +3,4 @@ charset = utf-8 end_of_line = lf indent_style = space ij_continuation_indent_size = 4 -indent_size = 2 +indent_size = 4 diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 9a27f4d8..ec4b75bf 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -69,6 +69,8 @@ protected void transform() throws Exception { try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); + List methods = new ArrayList<>(); + for (MethodNode method : classWrapper.methods()) { MethodContext methodContext = MethodContext.of(classWrapper, method); INVOKE_NUMBER_MATCH.findAllMatches(methodContext).forEach(matchContext -> { @@ -84,9 +86,13 @@ protected void transform() throws Exception { matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); matchContext.removeAll(); + methods.add(decryptedMethod); markChange(); }); } + methods.forEach(decryptedMethod -> { + classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc)); + }); } catch (Exception e) { e.printStackTrace(); } @@ -119,29 +125,13 @@ private void addArrayField(ClassWrapper classWrapper, ClassNode classNode, Match classNode.fields.add(fieldNode1); } - private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) { - ClassReader cr = new ClassReader(classByte); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - ClassNode classNode = new ClassNode(); - - cr.accept(classNode, 0); - - MethodNode clinitMethod = classNode.methods.stream() - .filter(methodNode -> methodNode.name.equals("")) - .findFirst() - .get(); - - /** - * Remove all instructions after executing "putstatic" 2 arrays. They can cause compiling issue as it must load on other classes. - * TODO: Is it better if we can remove all instructions that embedded on label? - */ - - Iterator iterator = clinitMethod.instructions.iterator(); + private List getLeftOfEncryptionInsns(Iterable instructions) { + Iterator iterator = instructions.iterator(); LabelNode returnLabelnode = null; boolean markToRemove = false; - List removedInsn = new ArrayList<>(); + List insns = new ArrayList<>(); while (iterator.hasNext()) { AbstractInsnNode insn = iterator.next(); @@ -160,9 +150,30 @@ private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) markToRemove = true; iterator.next(); } else if (markToRemove && insn.getOpcode() != Opcodes.RETURN) - removedInsn.add(insn); + insns.add(insn); } + return insns; + } + + private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) { + ClassReader cr = new ClassReader(classByte); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassNode classNode = new ClassNode(); + + cr.accept(classNode, 0); + + MethodNode clinitMethod = classNode.methods.stream() + .filter(methodNode -> methodNode.name.equals("")) + .findFirst() + .get(); + + /** + * Remove all instructions after executing "putstatic" 2 arrays. They can cause compiling issue as it must load on other classes. + * TODO: Is it better if we can remove all instructions that embedded on label? + */ + List removedInsn = getLeftOfEncryptionInsns(clinitMethod.instructions); + removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); removedInsn.clear(); From ac85c91000bd60519d07a1a1eeb1cc075dc7ed69 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sun, 24 Aug 2025 18:04:46 +0700 Subject: [PATCH 07/15] Clinit detection --- .editorconfig | 2 +- .../deobfuscator/api/asm/MethodContext.java | 27 ++++- .../src/test/java/Bootstrap.java | 9 +- .../impl/zkm/ZelixStringTransformer.java | 112 +++++++++--------- 4 files changed, 80 insertions(+), 70 deletions(-) diff --git a/.editorconfig b/.editorconfig index 6062365e..57821c84 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,4 +3,4 @@ charset = utf-8 end_of_line = lf indent_style = space ij_continuation_indent_size = 4 -indent_size = 4 +indent_size = 2 diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/MethodContext.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/MethodContext.java index a444d6f9..9982b323 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/MethodContext.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/MethodContext.java @@ -2,6 +2,7 @@ import org.jetbrains.annotations.Unmodifiable; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.analysis.Frame; import org.objectweb.asm.tree.analysis.OriginalSourceValue; @@ -15,6 +16,7 @@ */ public class MethodContext { private final ClassWrapper classWrapper; + private final ClassNode classNode; // for copied method without classWrapper private final MethodNode methodNode; private final FramesProvider framesProvider; // Lazily initialized @@ -22,12 +24,16 @@ public class MethodContext { // Lazily initialized private Map> consumersMap = null; - private MethodContext( - ClassWrapper classWrapper, - MethodNode methodNode, - FramesProvider framesProvider - ) { + private MethodContext(ClassWrapper classWrapper, MethodNode methodNode, FramesProvider framesProvider) { this.classWrapper = classWrapper; + this.classNode = classWrapper.classNode(); + this.methodNode = methodNode; + this.framesProvider = framesProvider; + } + + private MethodContext(ClassNode classNode, MethodNode methodNode, FramesProvider framesProvider) { + this.classWrapper = null; + this.classNode = classNode; this.methodNode = methodNode; this.framesProvider = framesProvider; } @@ -46,6 +52,13 @@ public MethodNode methodNode() { return methodNode; } + /** + * Class node + */ + public ClassNode classNode() { + return classNode; + } + /** * Frames of the method */ @@ -86,4 +99,8 @@ public static MethodContext of(ClassWrapper classWrapper, MethodNode methodNode) public static MethodContext of(ClassWrapper classWrapper, MethodNode methodNode, FramesProvider framesProvider) { return new MethodContext(classWrapper, methodNode, framesProvider); } + + public static MethodContext of(ClassNode classNode, MethodNode methodNode) { + return new MethodContext(classNode, methodNode, MethodHelper::analyzeSource); + } } diff --git a/deobfuscator-impl/src/test/java/Bootstrap.java b/deobfuscator-impl/src/test/java/Bootstrap.java index a95c350c..d73a0cad 100644 --- a/deobfuscator-impl/src/test/java/Bootstrap.java +++ b/deobfuscator-impl/src/test/java/Bootstrap.java @@ -3,22 +3,21 @@ import uwu.narumi.deobfuscator.Deobfuscator; import uwu.narumi.deobfuscator.api.context.DeobfuscatorOptions; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer; -import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixStringTransformer; public class Bootstrap { public static void main(String[] args) { Deobfuscator.from( DeobfuscatorOptions.builder() - .inputJar(Path.of("work", "grimac-3.0.94-obf-out.jar")) // Specify your input jar here + .inputJar(Path.of("work", "obf-test.jar")) // Specify your input jar here //.libraries(Path.of("work", "libs")) // Specify your libraries here if needed .transformers( // Pick your transformers here - ZelixStringTransformer::new + () -> new ComposedGeneralFlowTransformer() ) .continueOnError() .classWriterFlags(ClassWriter.COMPUTE_FRAMES) .build() - ).start(); + ).start(); } -} +} \ No newline at end of file diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index ec4b75bf..2f3b39f1 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -11,7 +11,7 @@ import uwu.narumi.deobfuscator.api.asm.matcher.Match; import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch; -import uwu.narumi.deobfuscator.api.asm.matcher.impl.FieldMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.JumpMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; @@ -19,24 +19,25 @@ import uwu.narumi.deobfuscator.api.transformer.Transformer; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; public class ZelixStringTransformer extends Transformer { - private static final Match ZELIX_STRING_CLINIT_MATCH = SequenceMatch.of( + private static final Match CLINIT_DETECTION = SequenceMatch.of( + OpcodeMatch.of(ILOAD), + JumpMatch.of(IF_ICMPGE).capture("label"), OpcodeMatch.of(ALOAD), - FieldMatch.create().desc("[Ljava/lang/String;").capture("field-1"), - NumberMatch.of(), - OpcodeMatch.of(ANEWARRAY), - FieldMatch.create().desc("[Ljava/lang/String;").capture("field-2"), - OpcodeMatch.of(GOTO) + OpcodeMatch.of(ILOAD), + MethodMatch.of(INVOKEVIRTUAL).owner("java/lang/String").name("charAt").desc("(I)C"), + OpcodeMatch.of(ISTORE), + JumpMatch.of(GOTO) ); - private static final Match INVOKE_NUMBER_MATCH = SequenceMatch.of( + private static final Match DECRYPTION_MATCH = SequenceMatch.of( NumberMatch.numInteger().capture("key1"), NumberMatch.numInteger().capture("key2"), MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node") ); - @Override protected void transform() throws Exception { Map encryptedClassWrapper = new HashMap<>(); @@ -47,11 +48,13 @@ protected void transform() throws Exception { MethodNode clinitMethod = classWrapper.findClInit().get(); MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); - MatchContext match = ZELIX_STRING_CLINIT_MATCH.findFirstMatch(methodContext); + List matches = CLINIT_DETECTION.findAllMatches(methodContext); - if (match == null) + if (matches.isEmpty()) continue; + MatchContext match = matches.get(matches.size() - 1); + // copy clinitMethod byte[] clonedClass = cloneClassWithClinit(classWrapper, clinitMethod, match); byte[] removedInsnByte = removeDependantInsn(classWrapper, clonedClass); @@ -73,26 +76,25 @@ protected void transform() throws Exception { for (MethodNode method : classWrapper.methods()) { MethodContext methodContext = MethodContext.of(classWrapper, method); - INVOKE_NUMBER_MATCH.findAllMatches(methodContext).forEach(matchContext -> { + DECRYPTION_MATCH.findAllMatches(methodContext).forEach(matchContext -> { int key1 = matchContext.captures().get("key1").insn().asInteger(); int key2 = matchContext.captures().get("key2").insn().asInteger(); MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); - String value = sandBox.getInvocationUtil().invokeStringReference( - clazz.getMethod(decryptedMethod.name, "(II)Ljava/lang/String;"), - Argument.int32(key1), - Argument.int32(key2) - ); + String value = sandBox.getInvocationUtil().invokeStringReference( + clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), + Argument.int32(key1), + Argument.int32(key2) + ); - matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); - matchContext.removeAll(); - methods.add(decryptedMethod); - markChange(); + matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); + matchContext.removeAll(); + methods.add(decryptedMethod); + markChange(); }); } - methods.forEach(decryptedMethod -> { - classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc)); - }); + + methods.forEach(decryptedMethod -> classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc))); } catch (Exception e) { e.printStackTrace(); } @@ -112,45 +114,37 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit classNode.methods.add(clinit); - addArrayField(classWrapper, classNode, match, "field-1"); - addArrayField(classWrapper, classNode, match, "field-2"); + // copy array + JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); + AbstractInsnNode node = jumpInsnNode.label; + + while (node.getOpcode() != Opcodes.GOTO) { + node = node.getNext(); + + if (node instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) { + FieldNode fieldNode = classWrapper.findField(fieldInsnNode.name, fieldInsnNode.desc).get(); + classNode.fields.add(fieldNode); + } + } classNode.accept(cw); return cw.toByteArray(); } - private void addArrayField(ClassWrapper classWrapper, ClassNode classNode, MatchContext matchContext, String key) { - FieldInsnNode field1 = matchContext.captures().get(key).insn().asFieldInsn(); - FieldNode fieldNode1 = classWrapper.findField(field1.name, field1.desc).get(); - classNode.fields.add(fieldNode1); - } - - private List getLeftOfEncryptionInsns(Iterable instructions) { - Iterator iterator = instructions.iterator(); - - LabelNode returnLabelnode = null; - boolean markToRemove = false; + private List getLeftOfEncryptionInsns(MatchContext match) { + JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); + AbstractInsnNode insn = jumpInsnNode.label; List insns = new ArrayList<>(); - while (iterator.hasNext()) { - AbstractInsnNode insn = iterator.next(); - - if (insn.getOpcode() == Opcodes.ALOAD && - insn.getNext() instanceof FieldInsnNode fieldNode1 && fieldNode1.desc.equals("[Ljava/lang/String;") && - fieldNode1.getNext().isNumber() && - fieldNode1.getNext(2).getOpcode() == Opcodes.ANEWARRAY && - fieldNode1.getNext(3) instanceof FieldInsnNode fieldNode2 && fieldNode2.desc.equals("[Ljava/lang/String;") && - fieldNode2.getNext() instanceof JumpInsnNode jumpInsnNode) { - - returnLabelnode = jumpInsnNode.label; + while (insn != null) { + if (insn.getOpcode() == Opcodes.GOTO) { + match.insnContext().methodNode().instructions.insert(insn, new InsnNode(Opcodes.RETURN)); + match.insnContext().methodNode().instructions.remove(insn); + break; } - if (returnLabelnode != null && insn instanceof LabelNode labelNode1 && labelNode1.getLabel() == returnLabelnode.getLabel()) { - markToRemove = true; - iterator.next(); - } else if (markToRemove && insn.getOpcode() != Opcodes.RETURN) - insns.add(insn); + insn = insn.getNext(); } return insns; @@ -168,11 +162,10 @@ private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) .findFirst() .get(); - /** - * Remove all instructions after executing "putstatic" 2 arrays. They can cause compiling issue as it must load on other classes. - * TODO: Is it better if we can remove all instructions that embedded on label? - */ - List removedInsn = getLeftOfEncryptionInsns(clinitMethod.instructions); + MethodContext methodContext = MethodContext.of(classNode, clinitMethod); + List matches = CLINIT_DETECTION.findAllMatches(methodContext); + + List removedInsn = getLeftOfEncryptionInsns(matches.get(matches.size() - 1)); removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); removedInsn.clear(); @@ -185,7 +178,8 @@ private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { - if (!classNode.methods.contains(method)) classNode.methods.add(method); + if (!classNode.methods.contains(method)) + classNode.methods.add(method); }); classNode.accept(cw); From 99f957a0833d4b5ca02db2f45b6e0c6d35c3099a Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:03:55 +0700 Subject: [PATCH 08/15] Clean up function and clinit --- .../impl/zkm/ZelixStringTransformer.java | 90 +++++++++++++------ 1 file changed, 64 insertions(+), 26 deletions(-) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 2f3b39f1..688d6a7a 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -69,6 +69,8 @@ protected void transform() throws Exception { SandBox sandBox = new SandBox(context()); encryptedClassWrapper.forEach((classWrapper, tmpClassWrapper) -> { + AtomicBoolean isDecryptedFully = new AtomicBoolean(true); + try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); @@ -81,28 +83,67 @@ protected void transform() throws Exception { int key2 = matchContext.captures().get("key2").insn().asInteger(); MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); - String value = sandBox.getInvocationUtil().invokeStringReference( - clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), - Argument.int32(key1), - Argument.int32(key2) - ); - - matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); - matchContext.removeAll(); - methods.add(decryptedMethod); - markChange(); + try { + String value = sandBox.getInvocationUtil().invokeStringReference( + clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), + Argument.int32(key1), + Argument.int32(key2) + ); + + matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); + matchContext.removeAll(); + methods.add(decryptedMethod); + markChange(); + } catch (Exception e) { + isDecryptedFully.set(false); + } }); } - methods.forEach(decryptedMethod -> classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc))); + if (isDecryptedFully.get()) + methods.forEach(decryptedMethod -> classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc))); } catch (Exception e) { e.printStackTrace(); } context().removeCompiledClass(tmpClassWrapper); + + if (isDecryptedFully.get()) + cleanUpFunction(classWrapper); }); } + private void cleanUpFunction(ClassWrapper classWrapper) { + MethodNode clinitMethod = classWrapper.findClInit().get(); + MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); + List matches = CLINIT_DETECTION.findAllMatches(methodContext); + MatchContext match = matches.get(matches.size() - 1); + + JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); + AbstractInsnNode currentInsn = jumpInsnNode.label; + + List removedInsns = new ArrayList<>(); + LabelNode firstLabel = null; + + for (AbstractInsnNode insn: clinitMethod.instructions.toArray()) { + if (insn instanceof LabelNode labelNode) { + firstLabel = labelNode; + break; + } + } + + if (firstLabel == null) + return; + + while (currentInsn != firstLabel) { + currentInsn = currentInsn.getPrevious(); + removedInsns.add(currentInsn); + } + + removedInsns.forEach(insn -> clinitMethod.instructions.remove(insn)); + classWrapper.methods().removeIf(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")); + } + private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ClassNode classNode = new ClassNode(); @@ -131,12 +172,10 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit return cw.toByteArray(); } - private List getLeftOfEncryptionInsns(MatchContext match) { + private void setReturnOpcode(MatchContext match) { JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); AbstractInsnNode insn = jumpInsnNode.label; - List insns = new ArrayList<>(); - while (insn != null) { if (insn.getOpcode() == Opcodes.GOTO) { match.insnContext().methodNode().instructions.insert(insn, new InsnNode(Opcodes.RETURN)); @@ -146,8 +185,16 @@ private List getLeftOfEncryptionInsns(MatchContext match) { insn = insn.getNext(); } + } + + private void removeInvokeOnFirstLabel(InsnList instructions) { + for (AbstractInsnNode insn: instructions.toArray()) { + if (insn instanceof LabelNode) + break; - return insns; + if (insn.getOpcode() == Opcodes.INVOKESTATIC) + instructions.remove(insn); + } } private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) { @@ -165,17 +212,8 @@ private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) MethodContext methodContext = MethodContext.of(classNode, clinitMethod); List matches = CLINIT_DETECTION.findAllMatches(methodContext); - List removedInsn = getLeftOfEncryptionInsns(matches.get(matches.size() - 1)); - - removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); - removedInsn.clear(); - - for (AbstractInsnNode insn : clinitMethod.instructions) { - if (insn.getOpcode() == Opcodes.INVOKESTATIC) - removedInsn.add(insn); - } - - removedInsn.forEach(insn -> clinitMethod.instructions.remove(insn)); + setReturnOpcode(matches.get(matches.size() - 1)); + removeInvokeOnFirstLabel(clinitMethod.instructions); classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { if (!classNode.methods.contains(method)) From ca76d2dbe4be8d2f64b86e72b474b0da1085533a Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Mon, 25 Aug 2025 11:03:07 +0700 Subject: [PATCH 09/15] update --- .../deobfuscator/api/asm/ClassWrapper.java | 6 + .../deobfuscator/TestDeobfuscation.java | 1 + .../composed/ComposedZelixTransformer.java | 3 + .../impl/zkm/ZelixStringTransformer.java | 151 +++-- .../zkm/EnhancedStringEncManyStrings.dec | 617 +++++------------- .../zkm/EnhancedStringEncSomeStrings.dec | 367 +---------- .../custom-jars/SnakeGame-obf-zkm/d.dec | 179 ++--- 7 files changed, 322 insertions(+), 1002 deletions(-) diff --git a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/ClassWrapper.java b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/ClassWrapper.java index 0617e442..8f3288f8 100644 --- a/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/ClassWrapper.java +++ b/deobfuscator-api/src/main/java/uwu/narumi/deobfuscator/api/asm/ClassWrapper.java @@ -14,6 +14,8 @@ import uwu.narumi.deobfuscator.api.classpath.InheritanceClassWriter; import uwu.narumi.deobfuscator.api.inheritance.InheritanceGraph; +import static org.objectweb.asm.Opcodes.ACC_ENUM; + public class ClassWrapper { protected static final Logger LOGGER = LogManager.getLogger(ClassWrapper.class); @@ -123,6 +125,10 @@ public String canonicalName() { return classNode.name.replace('/', '.'); } + public boolean isEnumClass() { + return (classNode.access & ACC_ENUM) != 0; + } + /** * Compiles class to bytes. */ diff --git a/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java b/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java index 7574c973..5d306fa1 100644 --- a/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java +++ b/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java @@ -16,6 +16,7 @@ import uwu.narumi.deobfuscator.core.other.impl.universal.StringBuilderTransformer; import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer; import uwu.narumi.deobfuscator.base.TestDeobfuscationBase; +import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixStringTransformer; import uwu.narumi.deobfuscator.transformer.TestSandboxSecurityTransformer; import java.util.Map; diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java index ddab934e..ae47974d 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java @@ -8,6 +8,7 @@ import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer; import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixLongEncryptionMPCTransformer; import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixParametersTransformer; +import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixStringTransformer; import uwu.narumi.deobfuscator.core.other.impl.zkm.ZelixUselessTryCatchRemoverTransformer; import java.util.HashMap; @@ -43,6 +44,8 @@ public ComposedZelixTransformer(boolean experimental, Map classI InlineStaticFieldTransformer::new, UniversalNumberTransformer::new, + ZelixStringTransformer::new, + // Cleanup ComposedPeepholeCleanTransformer::new ); diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 688d6a7a..1e41e59e 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -2,6 +2,8 @@ import dev.xdark.ssvm.invoke.Argument; import dev.xdark.ssvm.mirror.type.InstanceClass; +import dev.xdark.ssvm.value.ObjectValue; +import dev.xdark.ssvm.value.SimpleArrayValue; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; @@ -16,11 +18,19 @@ import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; import uwu.narumi.deobfuscator.api.execution.SandBox; +import uwu.narumi.deobfuscator.api.helper.AsmHelper; import uwu.narumi.deobfuscator.api.transformer.Transformer; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; +/** + * A comprehensive string transformer using temp class to deobfuscate + * @author toidicakhia + */ public class ZelixStringTransformer extends Transformer { private static final Match CLINIT_DETECTION = SequenceMatch.of( OpcodeMatch.of(ILOAD), @@ -38,6 +48,12 @@ public class ZelixStringTransformer extends Transformer { MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node") ); + private static final Match STRING_ARRAY_MATCH = SequenceMatch.of( + NumberMatch.numInteger(), + OpcodeMatch.of(ANEWARRAY), + OpcodeMatch.of(ASTORE).capture("inject-insn") + ); + @Override protected void transform() throws Exception { Map encryptedClassWrapper = new HashMap<>(); @@ -57,10 +73,9 @@ protected void transform() throws Exception { // copy clinitMethod byte[] clonedClass = cloneClassWithClinit(classWrapper, clinitMethod, match); - byte[] removedInsnByte = removeDependantInsn(classWrapper, clonedClass); - byte[] modified = renameInvoke(classWrapper, removedInsnByte); + byte[] modifiedClass = modifyByteCode(classWrapper, clonedClass); - ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modified); + ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modifiedClass); if (tmpClassWrapper == null) continue; @@ -74,8 +89,6 @@ protected void transform() throws Exception { try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); - List methods = new ArrayList<>(); - for (MethodNode method : classWrapper.methods()) { MethodContext methodContext = MethodContext.of(classWrapper, method); DECRYPTION_MATCH.findAllMatches(methodContext).forEach(matchContext -> { @@ -84,15 +97,14 @@ protected void transform() throws Exception { MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); try { - String value = sandBox.getInvocationUtil().invokeStringReference( + String decryptedString = sandBox.getInvocationUtil().invokeStringReference( clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), Argument.int32(key1), Argument.int32(key2) ); - matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(value)); + matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString)); matchContext.removeAll(); - methods.add(decryptedMethod); markChange(); } catch (Exception e) { isDecryptedFully.set(false); @@ -100,8 +112,34 @@ protected void transform() throws Exception { }); } - if (isDecryptedFully.get()) - methods.forEach(decryptedMethod -> classWrapper.methods().removeIf(methodNode -> methodNode.name.equals(decryptedMethod.name) && methodNode.desc.equals(decryptedMethod.desc))); + if (classWrapper.isEnumClass()) { + SimpleArrayValue arrayValue = (SimpleArrayValue) sandBox.getInvocationUtil().invokeReference( + clazz.getMethod("getArr", "()[Ljava/lang/String;") + ); + + int length = arrayValue.getLength(); + + MethodNode clinitMethod = classWrapper.findClInit().get(); + MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); + MatchContext match2 = STRING_ARRAY_MATCH.findFirstMatch(methodContext); + VarInsnNode fieldNode = (VarInsnNode) match2.captures().get("inject-insn").insn(); + InsnList insnList = new InsnList(); + + for (int i = 0; i < length; i++) { + ObjectValue value = arrayValue.getReference(i); + String decryptedString = sandBox.vm().getOperations().readUtf8(value); + + // insert data + insnList.add(new VarInsnNode(Opcodes.ALOAD, fieldNode.var)); + insnList.add(AsmHelper.numberInsn(i)); + insnList.add(new LdcInsnNode(decryptedString)); + insnList.add(new InsnNode(Opcodes.AASTORE)); + } + + match2.insnContext().methodNode().instructions.insert(fieldNode, insnList); + markChange(); + } + } catch (Exception e) { e.printStackTrace(); } @@ -125,7 +163,7 @@ private void cleanUpFunction(ClassWrapper classWrapper) { List removedInsns = new ArrayList<>(); LabelNode firstLabel = null; - for (AbstractInsnNode insn: clinitMethod.instructions.toArray()) { + for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { if (insn instanceof LabelNode labelNode) { firstLabel = labelNode; break; @@ -148,13 +186,29 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ClassNode classNode = new ClassNode(); + String tmpClassName = "tmp/" + classWrapper.name(); + classNode.access = ACC_PUBLIC | ACC_STATIC; - classNode.name = "tmp/" + classWrapper.name(); + classNode.name = tmpClassName; classNode.version = classWrapper.classNode().version; classNode.superName = "java/lang/Object"; classNode.methods.add(clinit); + if (classWrapper.isEnumClass()) { + classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null)); + + // add function + MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]); + getMethodNode.visitCode(); + getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, "arr", "[Ljava/lang/String;"); + getMethodNode.visitInsn(Opcodes.ARETURN); + getMethodNode.visitMaxs(1, 0); + getMethodNode.visitEnd(); + + classNode.methods.add(getMethodNode); + } + // copy array JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); AbstractInsnNode node = jumpInsnNode.label; @@ -172,38 +226,16 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit return cw.toByteArray(); } - private void setReturnOpcode(MatchContext match) { - JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); - AbstractInsnNode insn = jumpInsnNode.label; - - while (insn != null) { - if (insn.getOpcode() == Opcodes.GOTO) { - match.insnContext().methodNode().instructions.insert(insn, new InsnNode(Opcodes.RETURN)); - match.insnContext().methodNode().instructions.remove(insn); - break; - } - - insn = insn.getNext(); - } - } - - private void removeInvokeOnFirstLabel(InsnList instructions) { - for (AbstractInsnNode insn: instructions.toArray()) { - if (insn instanceof LabelNode) - break; - - if (insn.getOpcode() == Opcodes.INVOKESTATIC) - instructions.remove(insn); - } - } - private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) { + private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { ClassReader cr = new ClassReader(classByte); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ClassNode classNode = new ClassNode(); cr.accept(classNode, 0); + String tmpClassName = "tmp/" + classWrapper.name(); + MethodNode clinitMethod = classNode.methods.stream() .filter(methodNode -> methodNode.name.equals("")) .findFirst() @@ -211,26 +243,43 @@ private byte[] removeDependantInsn(ClassWrapper classWrapper, byte[] classByte) MethodContext methodContext = MethodContext.of(classNode, clinitMethod); List matches = CLINIT_DETECTION.findAllMatches(methodContext); + MatchContext match = matches.get(matches.size() - 1); + + JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); + AbstractInsnNode currentInsn = jumpInsnNode.label; + + while (currentInsn != null) { + if (currentInsn.getOpcode() == Opcodes.GOTO) { + if (classWrapper.isEnumClass()) { + MatchContext match2 = STRING_ARRAY_MATCH.findFirstMatch(methodContext); + VarInsnNode fieldNode = (VarInsnNode) match2.captures().get("inject-insn").insn(); + MethodNode methodNode = match2.insnContext().methodNode(); + methodNode.instructions.insertBefore(currentInsn, new VarInsnNode(Opcodes.ALOAD, fieldNode.var)); + methodNode.instructions.insertBefore(currentInsn, new FieldInsnNode(Opcodes.PUTSTATIC, tmpClassName, "arr", "[Ljava/lang/String;")); + } + + match.insnContext().methodNode().instructions.insert(currentInsn, new InsnNode(Opcodes.RETURN)); + match.insnContext().methodNode().instructions.remove(currentInsn); + break; + } + + currentInsn = currentInsn.getNext(); + } + + + for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { + if (insn instanceof LabelNode) + break; - setReturnOpcode(matches.get(matches.size() - 1)); - removeInvokeOnFirstLabel(clinitMethod.instructions); + if (insn.getOpcode() == Opcodes.INVOKESTATIC) + clinitMethod.instructions.remove(insn); + } classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { if (!classNode.methods.contains(method)) classNode.methods.add(method); }); - classNode.accept(cw); - return cw.toByteArray(); - } - - private byte[] renameInvoke(ClassWrapper classWrapper, byte[] classByte) { - ClassReader cr = new ClassReader(classByte); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - ClassNode classNode = new ClassNode(); - - cr.accept(classNode, 0); - for (MethodNode methodNode : classNode.methods) { for (AbstractInsnNode insn : methodNode.instructions) { if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) { diff --git a/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec b/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec index d3f365f0..20f4016e 100644 --- a/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec +++ b/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec @@ -1,482 +1,165 @@ public class a { private static final String[] a; - private static final String[] b; + private static final String[] b = new String[85]; public static void main(String[] var0) { - a(a(16601, -26271)); - System.out.println(a(16547, 28980)); - System.out.println(a(16573, -6570)); - System.out.println(a(16549, 15866)); - System.out.println(a(16569, 30320)); - System.out.println(a(16513, 5692)); - System.out.println(a(16592, -18585)); - System.out.println(a(16518, -12275)); - System.out.println(a(16558, -5500)); - System.out.println(a(16533, 28407)); - System.out.println(a(16554, -31785)); - System.out.println(a(16598, 14166)); - System.out.println(a(16519, 21847)); - System.out.println(a(16597, 6470)); - System.out.println(a(16570, -27431)); - System.out.println(a(16567, -23765)); - System.out.println(a(16514, 26042)); - System.out.println(a(16544, -32719)); - System.out.println(a(16531, 13283)); - System.out.println(a(16556, 14179)); - System.out.println(a(16524, -10611)); - System.out.println(a(16602, 11272)); - System.out.println(a(16545, -397)); - System.out.println(a(16563, 19319)); - System.out.println(a(16559, 10170)); - System.out.println(a(16552, -16576)); - System.out.println(a(16546, 17855)); - System.out.println(a(16528, 428)); - System.out.println(a(16574, 23266)); - System.out.println(a(16600, -26763)); - System.out.println(a(16550, -19257)); - System.out.println(a(16583, 19975)); - System.out.println(a(16568, -28711)); - System.out.println(a(16593, 8545)); - System.out.println(a(16571, 16428)); - System.out.println(a(16604, 8759)); - System.out.println(a(16543, -31121)); - System.out.println(a(16565, 26258)); - System.out.println(a(16576, 18214)); - System.out.println(a(16542, 5982)); - System.out.println(a(16582, -20163)); - System.out.println(a(16595, 14588)); - System.out.println(a(16605, -26780)); - System.out.println(a(16572, -3689)); - System.out.println(a(16534, -4917)); - System.out.println(a(16553, -25655)); - System.out.println(a(16599, -6645)); - System.out.println(a(16575, 9955)); - System.out.println(a(16555, -5264)); - System.out.println(a(16536, 23938)); - System.out.println(a(16541, 14767)); - System.out.println(a(16523, 26124)); - System.out.println(a(16594, 10432)); - System.out.println(a(16606, 23179)); - System.out.println(a(16512, 28813)); - System.out.println(a(16527, 17243)); - System.out.println(a(16537, -17368)); - System.out.println(a(16521, 6489)); - System.out.println(a(16538, 32319)); - System.out.println(a(16557, -8581)); - System.out.println(a(16520, -15798)); - System.out.println(a(16516, -21175)); - System.out.println(a(16607, 10624)); - System.out.println(a(16561, 13569)); - System.out.println(a(16562, 31959)); - System.out.println(a(16539, 4326)); - System.out.println(a(16529, -27529)); - System.out.println(a(16580, 16893)); - System.out.println(a(16581, 13937)); - System.out.println(a(16603, 20273)); - System.out.println(a(16522, -27961)); - System.out.println(a(16548, 5675)); - System.out.println(a(16564, -31220)); - System.out.println(a(16525, -11503)); - System.out.println(a(16560, -15803)); - System.out.println(a(16596, -30597)); - System.out.println(a(16526, -10863)); - System.out.println(a(16540, 20769)); - System.out.println(a(16532, 27173)); - System.out.println(a(16515, 28785)); - System.out.println(a(16551, 26445)); + a("placek"); + System.out.println("ihokhCHjv9SiaYuzejy2fJKixynVPRv3nRAfCO5EhnnE2QKDfuC9WbkanWNCI7NDAv0hL2acuz6F2pZ1hpBnjKxXcPMbtipEdUsDyRW7oZEMjAq6"); + System.out.println("9AtoYb4vKMG5aAxCL04GgiSWpLyG6XVMoMabqnByGRGKuJ0QBPBWvsbU7Sbj8JfEwd8DUo7cDqVhpumVz75mCn8mrKfJKcvC7ZDwzVKV4L35NBCoFCsYwliHR"); + System.out + .println("ktaw64UElpARXLQeB9X236Mp9aQfvWPatUiTPkoWsDBYHIYklvdV67rhcR3Jd7UsYh3sYPvCcGllczuDCwxah8tFaIJhM6vbNKodSLf1sbdvM60BdE31iQhvUCdZCDI6TlpweX"); + System.out.println("PBhv1xI0YfA4lVZddBfBeBwFW5vECaeAoPW5trBr2Cj9O8M1uxjrMKYYSXJuFYMS1bXElhC1XIITPMgT2tZZ4OEVRO7xesFcHHrzTL"); + System.out + .println("Gf43ZwRbaGwoW4KRIyndxhWS1JcOTECyI4wLkRaOUwPswO3titAUhIfAV8B1K9QxZ8TrGMWosTcSKLl19BbweirEHCRByXuhBgnTI8rVK6OLwwysnmBIIb7A0W4UeH7jwcYdv"); + System.out.println("w9lP6nT349lhFbpPMzA48l1vwTE1UQ4reetQsgSp4gJ3gIGa0ptuptm5KlSptcOxVNqsbcy8aQDMvFvlO2QuyUDudOb1LzLIQN4kE4px7HuNJqAP1HcfouukiZ"); + System.out.println("6WuAnpQKqFN08LDXYHpRqschoqGjuRUJKnsqzhnNNe1K1mlgA82oNat6no3niQSBzWzv1hfaNcekn4RYf5ZzWKh8FVUkEgiF07UVw7Eewh2C72SocPENL"); + System.out.println("8FtJoLjzFQ2SOK3QfHWJzUylzwdOKSjBzWpD5DfaRKd0lJ79Njcr0BpUMmvyYatF6NcIH9tpxtrLjQvF1HMwPh6Gno4vYwI2C5lrNMiiuyPH6"); + System.out.println("MQetwrtRtlRgBUAYcqBKHUlb2EFaU0y6DO2DxM6EgRxIIHKsfrGPvta3YKnAzXUHi0H0uE05E5vMGPmQ8z6lvDeLjdHjORIjzNZog7B8HXQgaADAoq3kuCb1"); + System.out.println("m4112CN4qvq2Pb7LDmCUb9sRIpYu0LAcoPKKHoPj5ctqk5AYuyJPhmspMGGSa5vay7xsdik6CrntGwO3SnZaDYzrvbxzMQvGQjWz6SytPnvjtSlQsIpKdTrOtDSphSLt"); + System.out.println("3nhu1Dbzkpw1NYXJtxrIEtO5eNNPpPzXWN04VelH5RhztyHlJ63dVnrWJMxWd8BtFo1kMSdZZ06e9lPbPr4Ekoy8KKm3QntMGuVpKX7zpiaEnP"); + System.out.println("StHaxGDRtItmFOoCdcWg5w5MaDDEWdMpooH3LBr0yGFU1l1qHt1hwBuABMC5A6AACHNbsozuftc78PMFtaZAuoXk8bJcmAvTJWm1RfyY3mJ4nsF8fhdc40J7"); + System.out.println("MUkWOnoG4E3zI1t9tlC9TJxBI7KUnt9FYaPkFVhaEFq26hdh9xq1HEOZ89NuFVCsHzNb1ttFOD0wRPbRgF80mQRUM7TTR2KSIKIbixDh0z5Al7fruneKfNx"); + System.out.println("F8XCIYrfJGkv9eIW4EFZNUkStO4P8zoJlYEoRDY1UWopxqViEX2nlKrWfWW2jemzJ3kgnDJSj3zWQMmE5zM83cTNpVGr6H3r3deyqwkcO"); + System.out.println("WsPSIdSv0MdasQscovpZdX2ArkXxbql2hVCIwvtIWzGLHlxWrVI8EQB0hRRm7op44PqfRcjnGqSvaeh0lxWKkkB1gI1YHzUwNIpjH1QncWKocWBVeWynm"); + System.out.println("89kQg7RZrjHzBDMxVpetbkeREp796Vi3eaT7rFpX4F3kfVSM7gyZAgEzv1hy0tqjz4ydoHW6vHG7z0NNCWRg08t76eb6AUzysKoyGysL38efkGXPVoA1Al9IrPF"); + System.out.println("qFtxIwYk4aEFrdvcPdQSRdtZmOSeS8j5ubjd2IiAr3caej6jIlcuIXceN2yjgIPdgXMNpKnIHs3KCB7O1QkeCYsgCNfaYf2tMULPY4xXjkWVpppELsArofrhC7uOz7xZ"); + System.out + .println( + "rMTVd0eDaJZX1oJ4fdBDjuld9Od87fQ0np08r0YIfXtu5GlfJZ39FrusMt46CSxY9yi8hWYFM5M6xKq1qe7rhCFqNpjmXlcewWPoJDcD7cD7y6ie2oXSzz2g0gCTMIYfeWEXAASj4vUgWqZ" + ); + System.out.println("s2YG8XOchpwXvmva5TEnE9UNTWh7ET6GBL6SXIStMBdTv36OwBaGXvJd1nkytdDxJvjxM3rhAZavi70kAKHXlUlA36uF0binFzJj1Kio6FyHVdXZeVULJ5AOaNsHmApT"); + System.out.println("eP6xmgWbJNHOlwrFNyNxUOye0azrjU8JOKGhjO5A50QKVZNEqPiDPEGoK96KorWFAfjoASh1wtqsjHmQQbbTuMfrrk9ijYsLUAWQD59cXQHDIe"); + System.out.println("rhZ34DXxt5bPWAL8cd5VANQiahf8NcLV6aY4ceFpnLFYsNsvhz6WtQbFqpVpW80JPt8tFyuZ2oBPCXE7faShgz5NdpC823L5QsrL"); + System.out.println("BKMPssKfh2i4MRGuUHp5WCarC6XLtwaozFpoxtLvvET3rcoYkdC6XPWKD2sx5ktluaDnRn7aMW9axAkNvSImsYz1ZnA52MKFRqk2B5UDwE8kWqxnKDHJ7ISFI"); + System.out + .println( + "IbrcVyI5ipEIwkQyt81ZGgKkFZcd2Mgz327nQOkaayZPGuUAE59j7kNlTwLlLNx8S89TdAWKMSOgtNNMo2jay0TeYQre2mncnwJRWz6NRWUgwzfT4SnZNNx1zEaelXpYoL9f96RoCZ0oFFaDlo4" + ); + System.out + .println( + "sqR7tDZl8BlprzkUGp9796VGqESKVzzGPe3TswPdrxGUiIW3rXD6CzEg5GTk1bZ4yQZgcauhFtB56ghnUHvCceeTJl3cxNv8EK6WXbccNA6OgiMXlOiRFb3BDCAAcqGOY5slRryx8kB" + ); + System.out.println("6nYM4E5El3rrd7MGcBqUg0rMwrtM3DItYqFXeZ9wDElgkr91XNCa4sT91ExEimGt6p7TV05WhXugsu1uoe9wZuHoJMjuxd19HSBcHdWhnrLgTqBNA9Vm2uN5"); + System.out + .println( + "i3EBDcduYI6YqPmm8jNbpCQhUd5OpXk0f3KmhfMJikxolUXowVN5DBEj0Ny7TcSrLbLkKN14KEuaWYYCRtIN43mDUZOIHd9H07ZTLGzi8IFl1QgSUahIum1ie41knKdROPD3HmQxxfB" + ); + System.out.println("xBlfg0GuDU99vGq6fGdj3cPTDlU7RH3s1FfB0HbORrNTDIPD2X2T80JekqX4lt4Ow799lctoF2bt7NniyZXKFNh20LTwjVZf1j0yJo7p7"); + System.out.println("oTXRpHSBRUQqYMG5dZrk2JMeRY1Y9Tt7yAZO5wvUGK4vkUvGgKQPhqVFqWo8IydR8Nkyun3EEhDCfZugvs6NzpzOKArKgmregI1iuWVXtl7Qfr"); + System.out.println("V7aN77kTYccgL6TNdueGwvzyn7m2rauYtstlLNeHefkPX4DEU3i8BlB3k0HB4iNlqWBdzqkoKcJzYmkID4hydJir5yKEsDjJf5McF91qvkumxE"); + System.out + .println("PUcJ8ngNSKvJRpewZa7HfHCUtyywXGKuzhGcT5AcnxhsCZLuagefgA2B644f9SZjiQ8O2wb4nV57cH0wkxHKWT9aDozDatnobMbRhrmarcGrxtCD4NbiGljgj8jfeJ7xyKIBhJ"); + System.out.println("yIEe5VNGe67Xu6syIjykviZ7XpMPEXRKjSIRDzcP6rSYGAHc6o3en4g8FZUKLk1Jow1geg8XMgubFkeWytM9EXkPR8QPfH8aOhYaWvy"); + System.out + .println( + "PKNg7k8Q1aO4p1hyyjZcO9hZr9yPWxVJ39iq7Zf18zGi7lGLzkYc2FN7pobyEUhRODPTAwDSzJtvZlaPiJK7AFkhLToYTnGLiqmmR7KmUGuJyXPzR5GVUyIeyLTm8ctvGcU9X6nfrB5mSupTaNhidc" + ); + System.out + .println( + "RG9URbiK9Evfy3Jufhmjrp5qeCRGfClbAyOLnC5R4juSUbhMAyQRXKpT7F399EVZ7Py4PPaytevtH44v9ZPugF8Df0ofikGXMYW3KMOGiZGrSUIKjIqJkA8m3kb8ggpznOwoLQub1pbhD8ybTAod" + ); + System.out + .println( + "QZFs37mrEQR2xLRpWdjiS1dB21DhACb9i87BTSk7cLNPFvnzxrLO7OOUCWPIBPoTB3aKvNivX2fZkYveFm1wkeMhRhTgRh1VC3KIyBs0MtJdpXu4mz2tE0wtLgY3eCqZRDX5QpVgtppqNBEncw" + ); + System.out.println("vs60cBQ3iAU4XVY3gj99tzuG2ldzs3CM0p6G7RqBgj5HYAxq13KYetcCRfLvTBb6HYOUqexc4Zz0jE8ZtaPpxlhDOi1qGQnXTaSCmoYHzdJcQaZM0P7YVxRY8f2qD1"); + System.out.println("vfNQ5Rx42eQHdB1Ir9Vt8VyoZy0m8UAeQ8ONiP2xMnEprLkZP4gQhybAsXuoWGuRaAneLg2WwobhKfGXIFI0op2Vr0jyPuonF07UPxZKtPm6uZyEr0XgJh2k"); + System.out.println("oubfWhHHwlv4v7oPv3hizGnZQAp1wQ0JqAboRDzJrDahwXANAvt3OxCYDAlUzlnuPvaCaw6C5FSaDHTL1vLw4hdbPMDbjFacE5DQvtqJwBq5zmD7WTep7JBOl0R4"); + System.out + .println("Mn51HyzC4dQgKu3BOCFElfwU10SXJAtl3b9i1OGtMaqrLC5W59U0A4LqUe5CDLGAngHB2Z1vm3CjRt3nCov3qcJ14HJN2lI20CZQwWR0EAgtzff7pLDmh7oxeDMjBFiDiN"); + System.out + .println( + "7C7VgqLf3bnK2wWDa3bw9nmTgQvNVu9zY2KHnTXFDbSl05de5TjtCLjADPJBq8nCcL9Ydg20TjKovuisOud5vh9TLs6VlHgYq5O4rmraiWohraw1mJMfw2f8GA24hwlWjiMYn7uVvtYFG" + ); + System.out.println("CzQPfiyB1yuaDcFvnAuYfUugM5aGPhmD3LiIq1sfufQgtRMYWfZCAoVVHHeZkt3wjNZZ06R8H3eVT4rslWbxMRpxacx37gVOzXlt5Hvjhfa"); + System.out + .println( + "HuU558zQwSLpclXOdpb0rTxVRjVt4wSymBzsqNggNLs8TV88W4sbknNTyGUScNa5vVrKQxw02USnr3yyt3bydLIXqTIljeexymZBHxLUyBRlkqota13rjjtW6Sy0BzwuNST9KrdBlAJO86cUO41" + ); + System.out.println("rLDf5QJv5zThdFDOpMt5zMkBWzwh1NehkvMuDmUCTrcwuV6lyiO0VlG6PMes6KzbeOHxISBYEbO9uB9EuEJlDKrHYo2RGS19DAvCiNBgVXMU"); + System.out.println("nlZRKmBjn7HFfJDhJAg6xHZ3mPfeA79Cq2KaYKjXr5HQ8OJFQ8KeuFJ5tsH3AR7ByH8fc8QOdPtL4dp2lrEx2xGXCXBUWATlZpkcQ12"); + System.out.println("7SoFDwvPNk00oLDRex9WyxDJBgt8fdGuDP5CNqDP812eNeKy62sdTPqf5r8BNpT6muVHQUxmSkDste1GbNClmllTptILw8zmc9UO5Jaui22XDxDtXrRai81rGz0Hidt"); + System.out.println("eu08ygUe3GUY1isZkDgspG5vE5h3oZfCeWfcQ3Sj3MHY6MGLo5vlEVagUddB4LXF3L2ca77gs97GciBIGNpMLXzQY0ZLm9OBzjDL7Zbw5wKkDMJITUon"); + System.out.println("4rejicxT3kibB3lJ4ioHyV9aUXC16QRHUkVGIT3VZXwlszZnuviTAwJb0XbBgCvIA7YnAoEgWZsX4fB7xD9DLDUooDzxDMOeRQIqq7VYovVdW1N4aBR"); + System.out + .println( + "7VCiuH2EmWl5Zqu700pw4NVirhVQvKTCOQGeI8U87CXVPTEAFHk1OqH4Yy8fTdCRpdS2cvVGJ09LdOR7ijdHAPrcMPydAgnBZEhYMxGEkp9Ogo8voYdXNlJcGM4eMSS2U2nYitFvmwO8VzX" + ); + System.out.println("haWLW6CRgBYHknITxQXNdiBcwzDmtG3QkbuMhB3ErezNQPTHIJ09BRSSswywAKbPsDvZlsME12ruT80DEBsuxrfzeeQshQ5C3KNhxy"); + System.out + .println( + "7weeTYQbSjQJef1KvldcUnzDwClTaJ7S38hRqs6RWkoXyP1uPNxMunq0PyWaGZVqsQ6DsmttvlTZvmNqg6dWtRUmhNiHzbCOzmh1RH46BARsMeq5tEpu8Swb7zjVTwsMovrMTO6aTD9spf9bPvuF" + ); + System.out + .println( + "TaA7NpbHnwDE3DpSrK6xfX2uuMxEDDGTO09GvZWyrMaOuzS1HqfRWR51S5xaYxBIez21X16oQ7sYgWCBlRcOXp7jDVMLP6tpU1KqAh5Wtw2qFACAAL1ijBYrQ5wN7T3huAeDkB6oP2Vj2yHS5fd" + ); + System.out + .println("bHXby99mvTIgkntkA4578xWM3yM9VwAtZhLtoa1n1xTiCBW5qVwLWr0YBXaoIxF8mPWCxnlnbyc5ncKou7PR20uUzRv23bNsniY49KuobpRNQAE00XO0COORjHsQCRP7MqON5"); + System.out.println("mLfsazkpLSaDfF3mIBLmw6rZtEotRxMHpeUGwP7Cy6hy6YijYA4SzEiax7ZDKyb7OsqXl6gGdKwGOAh9Q1s7QF1O7a2g6vSwre2tBLjNS2fZ1prqRVguXn"); + System.out.println("XDEdfMa6tfXiiDZhZmwjCYdjfxYpnwTkIZzb6Amyx6IPO9sDadItZH3sfkNlo0VHqpSKefoCcT4lRwUVaojJq6xDumsvR8OhEk1HJuYbL1Zy9aPya"); + System.out + .println("c4Io2lmF2GSKs6fmL8tCuqZABNnviuAqs8Zz5Coim2DJUUPwYF7d7eOWB3U1ZUMnxqJcrBSjwzz9hqxBUpdGWPWjI9Y2AbzHhJHdMQc7hhDplECQGPTzXs9H2xCkIg6UFPj"); + System.out + .println( + "RGCTZLFoTabCBFbM39YThKj6RNcaDfJiBAFTRHBi9u8XgXMLn7RHiCsTQZMe9KFFycAUkezE85fHeMWjjZBtbAP2HySmS7QJAXeybXHHjQoEj6sB6w4of3SoIplkYECVu8XaSsLWD2L34niL4Sdu" + ); + System.out + .println("bhmH6owrNBrmsodktcif8602F5wudyZuaIabEhfzIL8JSenPPRRwO3XZOvkLCBVJcgEXFmAtybAPn7hRh4j0XhTHcyrwTO54M020taH2iczCQJROnNvaTLHurbaY05ZJgNh"); + System.out + .println( + "3tZHUZekSxuxWTTrVnx4ZLGwVIzPz3R4D4jdXfaqIVOylFMrdWRO9nR6T7dZliqIxIbFlln0lq1G4uGwOIvc1DcShZ5fO9NeEwXuughYYxP6ZDaUePNVS6UsO33OaGXG0Xa8afAONA" + ); + System.out.println("bFpF0pmjr2t8wnrmzFb5n9RBvAJS3xMKeDJxz8WHFe4P9ivvRlaMmAzPBREBem9icsP7niPKf6OA1VWtYg65Rk5vu5wlDzeeCr6hugQaCPInzKe5e5e8pecRvMZyXO"); + System.out + .println( + "uAP8ZJ3A7AMmmqmn0c9iusyLwsUkM1CJlnz23L9gtInhZWXQ3gmTUZuwyQiIyS6He16E3VDrUvNSNjBnzZ2gUN0MCzhlZpA7C88yZauJ43rbvKX7W1YJYKH2Eyd3tIoDryQMBTGETBnN1koTy" + ); + System.out.println("A98NFPLFjlSaZNSIniqAurAeSLchx51R18K6DP9OEvGYXJeO0B9xhPOSP8IZKdAG8Flch3zobx5yT3cLZ55RNhnDzMsRFdDKhqGZ3Mkq"); + System.out.println("3H0DEXp6KYET2MzzW16NZD1NLoowlUCXhUEEynONtKtfwIqsaj6YCGJeCHXe8qJGWEsqRBG6SeB7FhkR4VKEkANEKBhfkhzf3E7duBx3VfTy3zS9oVA1Bq"); + System.out + .println( + "zgsvMQcCqmtGlwHSaYZYcK77mPZm86nWM5yrnPGon6DyP69R10sUecauTbNnfXXf0wWgbLL5v6ZbQd4CC2Pt32Ehd6VKOEm0jvKEO78TOm4fgrjPmQDo4G2UvG1cdBBVMQ9f7tfR7D214R" + ); + System.out + .println( + "zceRpzTcAths6qj7ZccShYupbj5uPusdEW3Vbo9rQAsTWtOyMgpM5UHzdqR9LH1Uw3qxfqDrUi9SMc0DmVdBsp1IUboEeWIZ50l5efuql6MOCWC8dD8sRuAHG238lUaF3nFXWCkbtt7DAkPMfaCdx3" + ); + System.out.println("m7ObHQeDkz2OKFo9OwdwxlzOLmQTgW5Gq5yvbKT9pFWmemCd8cdCDBPZelLuhY3smTeZuC6ReoXRe7NIdek5pS4tfZuHrlNIgvSgdygBVSaRt0bNQKoeyJjsDV7bBu"); + System.out + .println("WkbF1y5rBjIbl4JxhFgF8bmCTgUJ3mi0Pq8C2GL77pNpeSFQ5C4ioI7iy09pIWp9Q9iEHKiPpeiq7WdSn8x59nHOa9hyQAN4JmswadmpsdSo6W2NRceZDBNieCyZQ004ecCaZ7"); + System.out + .println("8MGN2HJyJD1TCNj5W6PSbOZmmwZYtz5gfgXenT7FzCteOjTB2VNmPewkeecbD3uk9paQwlJzga1Iob8lLk6jVJ27EvnAmFNokXeQ1NzzuvGZqsFqlpP2iWOpe3m0sad0mXyelt"); + System.out.println("scpmNB62x4GXDJ6ATcpZARMenY5cPM3HJ9dCTQWFZihNyZIIQfLkLYRzCJMj3N9nQv6xwj4Il8RqKTyQoWt67NaTMQ40e7f7WeR4w3alBLm4CXht"); + System.out.println("9TcqLaX06C2Ttgd8VzPRQRhK1ZcMPF91lh3uDVGfdxqR7QOQv9dcGidKBkggP4ZB4gkBFZSvcXH38NRiIpApWwdwPJzXmrz9SudT"); + System.out + .println( + "HTte7xlVfqI5fvik51Hx8fUUGjamP0SkdjthlxD7x9NSyU8dlGEW8vz50BirgaTAP4dzijNeb7GLxDHqmttBdr6qXhyxtziK1yhGYCLlA7uspu3siSwD7QCJwdOJwjKqUdQLQFtBPP8svab" + ); + System.out.println("ZoP0qBFPnrGwkXg8IsqjyoOYlnirliC6snQhuIYG7ScoVLRtULMmNuWwEP0ngY2V7N0W7VJbgIVEjBJfPA4CBOGwsJvRzFM8678bVQMYAQHgzxndKK9aG70l"); + System.out.println("v75jbTh8yomyLciwQNLrHQeTJkqbwfDfEPTHZnwlGQEqvkPhE1M0PpY0488vfWQgXiXEJ4hu55CN3dX4ebVTUeuijksn5Yf3j7T9FxYva9XxcgCMBDxi7z"); + System.out.println("gRz08kPkEOy5epvJFpgx5Hg9H788zzhHNxHIsWP6y567oDBlDB5Ud2PopXuKJ4bj21OzOCj6JKrlWLte3paqz90swEucMx0mRzRzYAQSTeiAjFnenfeX1"); + System.out.println("ILxUKT8SVyeoNvsO3DrVQK2WKgPLtXKmbCzbskRl6Vo1HSTvaOL6qLjULcr3ykHynmaU4PxNDsEB1g0vXpk16rwFFQjWdej3nMhqWt647TLe8J"); + System.out.println("3HSaq9kdPsY0XTdTuWtnvuqb8bksMmWUxVc04LAMf8HbzIFLZamGEAmCKQWK86UeBpT1INOrLXnPBfZXHZR06kkAGLSr9FxmB7xIGDMa"); + System.out.println("OlfCISuu3Ju2qoACq6DD8R7r3Wvi7axvLCFAu6d7FO2fBPGz7Ffp8wFXwSJG5QrchBJqhEn42J0Oc781Ilyr1nPpOgPkwK4ndZXIChuemeSMvfm"); + System.out.println("GTk7EiXhRuGxT1Y7jC1uI0Y9mYRCMtkp0j8Tc522JcNxqIYIJczQIAOehfFhK1CJmIDuFKQojq91IIEydfpozlE4qTxLS29tW5Oc6BR1TS1WnUKLBN7BPCPg"); + System.out.println("YDMItKARel7B1iI75bHR38quQTgT7DLF2LI1dBfUwaoCBsYnYKAcXjfYPkR2xfGJlm0ruVFD4zgzu0BK91Iv1iAQPED4ctDhU4udXiQLl"); + System.out.println("0Z9dQRZfTppoJ7t9BkXso3N9OmAvnxPV83tV5eKWpsN4wqkdApneB6O7uJCeqAaUSL57QXvvhRvPT6VV7wYFWcqtxqhuDejhV2yhvbZC7Xk8QtEtzhbFn"); + System.out.println("RHPpymxSqT6GaC3eCh9BwzXEg2uBV42Tg1jhKy2jpc6PcuyRkeRwGB6RX1dMy4dRlabGaeGQhe1tTaOjqQXUJNCX9cJQunqVUOsbgB85uYYEG6YMprkEf1ZXgiwVTDx2"); + System.out.println("YpjYDVj8z9NaxGTF8oZGoGv468Y5obfJlUvPieve4Im1SLI7o2gV6oKOBlQgncDInjT9EIoYvksGZa8BwI6XuaDkbt8GYjJ6WU8TKZkjrC"); } private static void a(String var0) { - if (var0.equals(a(16535, -2944))) { - System.out.println(a(16517, 22762)); + if (var0.equals("tak")) { + System.out.println("Correct password"); } else { - System.out.println(a(16566, 20267)); + System.out.println("Wrong Password"); } } - // $VF: Irreducible bytecode was duplicated to produce valid code static { String[] var5 = new String[85]; - int var3 = 0; - String var2 = "-\u0092Ù+\u000f\u0000áLÝ \u0019nÈY\\g@\u0095!\u00189\u001fOü-\u0097\u009de\u0086ÈP4\u0013ö\bïjw\u0099\u000b¾\u008eÕ\\9D®l³'Õ\u008cW\u0015pDï·\u0016\u009cãÄc\u0096Ö\u0012ÚËFÑ÷»]\u0091Ã¥µê\u009a\u0088ô\u0081ÉÎ.\u0090ze\u0013ø\\\u0007\u000e-k\u007fÃ\u0099¯\u008b¦zMý®þËÇ\"È¥¶\u0011à\u00063ûx+¦\u0097×7\u00ad*éo:Iº¬¨ì\u0017äíªv6\u008bO\u009f6ã+Ñ\u008cãdpånc\u001dÄQÕðò\u0080\u009få¾\u0098\u0087þ\u0095\u0082ûOª¼S\u007fLUæÅ~V\u008eãã~|²d\u0004Uüª5µ§hrk6\u0099¼åï\bü¯\u0013-\u001cë3×`\u0019\u0005æè\u0081Q5\u0093\u0014DáÝ\u0088ü\u0007.6ÌW%(\u009c^\tS\t\u007f\u0013\u008c¯âHz\u0092´\u0000Ô»×ñ¡Ê\u0007\u0083\u008cûn9:\u0091\t°>·UXU\u0095;\"3\u009cMê\u0080\u0092dyÆ\u0010\u000eKaÞCî>ú¤\u001aW3÷>\u0082\u001f\"éáÏFî>G/úÐn,\u009a\"Uî3\u0097\t\u000fX{¹¥C\u009d·\n\u001c4\u0095\u0002\u0001ÎÏðpÆf-uE'\u0001áûî áTk¨Ë9\u000bvkd9óÓKéo½éµ\u00034\u00addi:}\u008aÁϺ\"\u0089¶²\u0018\u0091Ú\u0095kpõ\u0002øElüh©ì\n\u008a\u0019\u0090øK/wÔ\u0091k\u001e WÍ7\u0081¬H7Â\u008c\u0004\u0013\u001aQ¸{±ä\u000f\u008aÃ0\t>°\u0015\u000fd¬\\\u001eI\u0083a\u008c\u0006n§ó\\`çz·\u0096\u0000°ÂÿCp\u000b£\u0010\u000e«4kR\u0014\u0005\u001fô\u008fó$e:m4\u0001Æ,-\u009e\u0085,\u00adê§K.ú¶\u001e],gQ0\u008f¹\u001cÊpÏ``¾x7s\u008fj:T«\bÉ\u0091½$=F3\u0093ye\u000bÂÅ\u0095\u001bré2O\u0014\u0003ú\u001bJ\u0090\u0000\u001c_Ï\\+ñØ·¹\u0096\u0015Û\u0015á)çuª\u0015¥n\u0095\u0016mþRxõôÈýÓ¤-\u008d8\u0080L©\u0015Õ\u0016þÀ\u0080ÅB$nä-\u0003ÜÄ\".Ëg\u001d\u001dT\u0099\u0007\u0013~\u0012Îháó¦ @iå\u0010ð\u008bõ\u0085$ò,YwóÑ\u0089í\u001eÊóÖ»¢\u0007\u008a`dH½È6ó¨\u0088\u008a,³FRµ9ÁZë\u001bD\u00ad\u0093\u0007ù'ÍoÂ\u000e\u001c\u0081°/¯4\u0088ó\u0007\u0007÷½\u009c\u008b\u0083M\u0094#\u001e£3\u0092)ü\u0007\u0013×\u0006ì\u0001Ùê~O\u008dNìUÎ\u008bM?]Íõ\u000b\u008eh\u008d\u0094o\u008e²aNIýQíÊGÑç&\f¢&Ò}T^ÿyÂÄ&£E¼pÅEÅ\u008fü\u00ad\u001fîó\"äÓøóQ,èi\u000f\u001dg\r§}\u008dCàEá2l<\u0097\t\u0018NV\u008d\u0099æ\u009b\rð]wòí\u0014`\u009b~\u008a[l<äî\u0096úeG\u0000\"\u009f4ò\u0018Õ)ó¼2#î\u0016å\\àT(N0N\b\u0005\u0083\u0007ùÐ\u001e\u00972T\u0084ù\u009dƯH{B\"§-\u001c\u0013ÞÌp(r?h Â\u0002\u0099o8 \n$í±:R{\u0012\u0005i¿GÛ®©ï\u0099áhí,\u0093«±ËÎG\u0081´BÍ_ÿC\u008bw\u008cþòK2È`Fc8mP}©âÏ(ØÃü$\u008cH4îoÜN\u007fk\u009c6ZfBéÒ\u00022Z°&R\u0088ìû\n¡ö¶I\u001cx\u008f\u00ad\u0097ë\u008fx°\u0097~\u0006{í«\u0088Tk¬{ó+I7Y?AÒ¢v0¯\u009e üv¯ãî~8¤7n_om0\u0091h>)Â\u00adeM\u0093º\u009f\u009c\u0015N}åqÙ6~jó\u001b\u008dÛ`àÕXéZÖÒ\u001bñ,&\u001bªÕX^\u0090#ôÿ\u0001o|³óë,ðHgáI\u0017|x ÿâñ\u00ad\u000b¬NS\u008e|B\u0000~\u009eÍ\f\u001eCFTaW\n\u0004§\u0016Ì\u0086ªäùÐ[2hPx/@ÛÛ@âfDññ)¬ûcaô\u001bÊ\u0083§º»«\u0018í@\u0093¡m\u0000N-I\b\u0083\u0090Qæ\u009dF¹Ë\u0006¨¶\u007fþu\u000f²«z\u0083~ÆD\u008c\u0096±\u0001±:à\u0000/l\u0082ßjêÐ×`þ'£½QWqª*\u0000Õd\u0083ªb«\u000efÎ=\u0082àÂ4²rUà·Îâ\u0099Rj¼Á4iÀY¤!L$ð\">\u00996\u0082\u0096\u0018vñö(×\r±\u001da2ö-\u0083¥Â\u0088b\u0000Dð\u000e\u009506÷\u0091mGy4GWCÒ±[¾['~r£ä\u0094b}ßcä)C(×KD\u0082úu_\u0086û·+Ü\u0095\u0002p§\u009eȾ\u0081X©\u000b\u0014\f\tÀe&\u0099âË'qß\u000eL±âhö¼®ï\f3ÑKíÏe\u001f¬âùüÞÈ\u00024$j4ìÖ$\u0010æDötü\u000e\u0003ÜN\u00adÈð¬MLëuÞb-¼kQ¾-y²2\u0093&)\u008d\u001cW\u0094\u0093^\\\u0092\u0013T\u000eåÛ¨\u0093Ä\u008fÆ>j·\u00ad5ññn\f\u000e\u0005çõ¿¾\u008fVp5-\u0090=>ýa\u0084Ïp¾#áoÛÃ8ÈÜèØ½\u008c\u0095\b\t\u0087!¥¼Æ«÷½\u008am÷»¦Á.ýÛ\u00adó\u0003Õøë<\u0086 woU~¥\u009aû1\"\u001c,õ¼¦MxnWÏ}»ze\u0080°2\u00940ñ¨íT(ÎÁn\u0091\u00030Çø#ÉÌ\u0090´\u0091\u0002Â\u008e³Rß.@\u0084\u0092ÀpFÂW\u0005¥Þ\u00ad\u000fT\u008dÖ\u0082°û\u0096E\u000f¹\u009b>ðIL@Ñ#Z1\u0018\u0006 r\u0006XsÝ\u0001'´\u009b»%ÕT_\u009aP\u0007u*\u0093ÿ4Ò.\u0006í!À>4\u0083«Ëò\u0018ä9\u0098ýà\u0002\u0016ç\"%w\u0083Ý¿\u008e&øõ'+²ß}d:¹\u000e\u000eT5@\u0005¦ÈñtaÒ\u0097\u008a\n87¡\u0087\u008c\u0096æRÙº\u0002g\u0017Zx¡Uàý>R&Ó.\u008c\u0084.Fïä\u0095B/5F\u009eiâjò!\u0003à]M3z\u001a\u0012ú,q\u0084j\u0088ÀJ\u0081u¿oïój\u001bR~Èå\u0006\u007f¿Z\u000bØ'\u0090at\u0080ÎmDó\u0007öÌ#\u0000/gm¦\u0096³o6\u0089¼ô4\u0001\u0085s`\u0086\u0099CÎ\n\u0010\u009c\u0018¥¸¥\u0080pT\u0016ç·\u0011\f\u0090\u0005W â¯1\u009ca}´¶+à©JÑ\"\u0016\u00832í\bã_\u00adæ¨`´Ð\u009bׯú;ªÊWc\u001b>7BÚ\u0001mfý\u0006\u0090}ȪK0.ìBIf\u008a\u0090(FÍŶÉäbYm\u0004íÆ\u009e\u008e¨Hÿ`IZ±\u0082ù\u008dö\u008e\u0015ÛvéK\u0005´ÖË\u009f4ý\u0015·4:\t\u0092ié\u0099\u0018{ì@ºKMúQæäÔä¼@Ç}èn\u0016O\u0017cA\u0083\u000fðò\u001b8¶ë<ÞÝ0\u0007Nè-]\b>5tçΠ[\u001aDò\u0084ü\u008b\u0092\u0082\u0082ùð\u009c|½rÑì»ý7\u0012\u0000Ãç«DÁ\f\u0091uáq7æMzRU]\u009b¯Ä\u009e+\u001e®\u0015Ê_À×X\u0005½\u0083\u0010µ\ry5\u0005\b6\u0080\u0018'\u0092Jí\u001a\u00192¹\u008et§\u0080¨«.H\u009dż\u00195=ÃE6e\u008aF4eïTµÈÓ\u001aBçië\u0092ç\u008f\u0016\u0099òó\u0013¶\rÊæ\u0082¹*Ì¥º¹\u0001+o\u0095 á¤zíÕ0þP5\u0005µr\u0096D]ù;Ô_¦_ÆÄºúþj\u008b\u0096|\n\u0080j\u0005~s¦\u0081¡\\æíz3\u009a\u0094íò´>^' \u001c2Ç\u007fXÆ\u0097èÀ{mt®_¨9\\×âè¥\u0089înP¤\u0086º6\u0098pÈ]\nìòo®L®\u0088.£4\u0087\u008cÒ!0÷\u0095àßÑ\u0097\u0083ùçÿ\u0084\u009eÉÍ¥ÀÌè@C|Á\u0004ï\u000fo\u008cõc|ÏYæL\u0085m?Y¯snas\u0095\u009e\u0081\u007fQ\u0080¹F#¯\u008a\u0096̰_M\u0082\u009e\u009eIp¡*i\u0095Cê-Ê\u0000Ã\u009còÑÐ\u0015}Î\u0010\u0095\u0006`nJ\u0011\"¾\u0003£[˦J!\u000b|´IÅtVÒ\u008f\u0016\u0014/\u0084Ö1wÀò!\u009c\u0016¨\u009e`¨î#\u009eu\"8é\u000e\u0084\u0089þ¡Añ\u008dÝ\t|Ë> ÿÀÏ&\u0018ûNô\u0081PÞ¬Um\u001aí»O÷\u0014¡\u0001ºBä¯ò÷\u009f\n\u0085å°y\bȪ\u0097Ôd£uEï\u008f\ràGÈÑÑËÂx4{1\u008a\u001e6WØOxº×s\u009dÛ7\u0010xo\u008e\u0092\bÏ}ê\u0093\u0010be·PpGÅÇù\u0000§1.Çó\u0087\u00adu\u0087Ó\u0006cñ±\u0013F#8Iû\"/\u008a[Ú+4\u0087¿x\u0099Z\u0082¦¹¶È³¨\u0000y\u0081Eµãñ\u0098\bY\u0002ò§gòLÞ\r×ÚlµMâJ\u001d\u0090G\u007f\u009e¤ÎáJ\u000bÛ3T(CÿþÔ¡\u0094\u008ci¹Ú=\u0016ÑZDÛ8\u000e´\u008dÌÖ÷\u000b\u0089ô\n9ê@$Í ~}@ZÇq\"CK\u0010\u008cTÂfHØ.0»\u0098ÜÓÎ\u0090¤k|3ñF{°ÉK@?[yp\u000eÓz4gø8=\u0003I^\u00136¹À\u008d\\ë\u0094pÅgjßD5\fÂa½V\u0089Dé\u008a \u0095\u000e\u0090-_AK£úm\u0086\u001a*ì+Em|,\u008fë\u0096\u001fÑü\u00177\u001agbà\u009d%>P\u0098lÐ\u0012²¡]}\\ÑhRºh\u000b\u000fi\u0002\u0003Kñ\u0011Å\u009e\u009a£9P\"£\u009e\u007fúÑu\fP\u0004\u0086Å$\u008dIìÖàáÞï\"\u0093À\u0083\u00805\u0094\u0092ßÁ#îÏÜ\u0083Ç\u0085Ðt¿î\u009e\u008dyó²\u008bÌ.\u000f\u0093ü_\u0001ÏÖ\u0099\u000bÕ6£¬-\u001e^#$°\u001e;a9K£\u0096{\u0014·ù\u0084/1\nv\u0004\u0084\u008a\u0007\u0018\u009c½¬s.&ûÉ?M´«M}RñÕv¶\u0002$\u0081q\u009b\u0017b\u009f\u0081ç]ÛòüºDª¶õ\r\u0096¼\u0093.n\u0012îã\u0084b\u0011a\u0091¿\u0086\u0089³øñà\u0099\u00ad\f6Ûß&f\u0007q\u0095\u0092ùùø=÷ö!2\u008fIÓ\u0092\u0095U!¶\u0098§V\u008c*uE³7ñ®ke¥¡©\f³³z¹à\u008f\u0006ú4_)\u009ehb\u0019\u0011ºC¼¨°Y\u0006Ðì®\u0090¾ºA|´xuo\u009cSÃÁ@PLQa\u001a7£µ»\u00077\u008aNV³wÞ(¼RgP\u001dÛÝXúf¦f\u008bB-ÒxÚýq}\u009bÁ\u001f¼õL\u0018G\feKÁÇ\u0000Ò%²¿÷\u001aU\u009d`\u001aVT¡,ÎRD\u0083\u0097/bTGH(7ØA\u0017b\u0081*\u001d1n+ä\u009f\u008bé\u0002\u0097½v¦K»^ÿOÍ3¾\u000e\u0098ã¶ÂY\u0085ÍY¼\u0004ÆH\u0096H\u001eî>\u001f\u0013'\"\u0088\u008c/¶¿\\lGÄj~\u008a\u0081Ñý\u0094^Ú\u0086'óê\u0096â\u008a¥«ÅÏVóÍ\u009eT$\u0006'\u0002(\u0012A\u0004¦\u0098äl¹\u008aJ\u0016UYÈ1}\u000fÆ\u0011\u000e\u0014}Âm\u0014¾Ù\u0019o\u0081·¼\u0084øJm+µJÓ^\\ýÞw\u000fÖ\u0014Ê\u0010ÁÂà\u001bYÞÊ£¶¾Ü¸\u0005±åðÔ=ÂÌÙ\u0091r¹*\u009b\u0095½\u0000u¦\u0001V\u0004¼\u0002Ù\u001f\u0080Ù\u0088Óý¸2\u0087Ë\u008fë(â\b\f\nN\u0013oª\u009a´}çÿ\u001a\u0011O´\u00ad×E\u0006L\u0092\u0089¾¨ljâÜ\u0001·Û@\u001c2\u009b8\u000b\u0013\u008b«1\u009f\u009e\u0096äĪϫ@k¿gf¦S¹¹òõµ\u0090³Ð\u0000{:è\u008a¡c0´g-wÝ\u0005\u0089Øiþ#±wÍÌí\u000fw\u001eåvBM|\u0011ϱH4\u0014R\u009dØíø\u0013\u000eÆ$ý\u0003Ú˸G&\u0017B\u001ccW\u0015Ñ@à5 ;\u0018SÛß\u0015\u0006:¦(ÔL«*·L50^¿\u009dͼä\u0000O0\tJ[ï<#°äåáõB\b)º\u0015\u000b\u0095]ÌHQ\u008dÄrÍë²¾êèé\u001f&©RâQÇL1\u008a\u0094>\u009b´?ÚÎÞw\rG\u0080C²í½\u000fÉ#ÜÛâ\u000e\u0089\u0098\u0090M\u008cõkÉàtz\u0087{7uú^Á\u0089P³\u0018Ú®Ý\u008bE5\u0095ú\u000e^´B«s\u000e\u0010 \bmºòTü»þ\u0085\"·£\u000e\u0011 ¨v.\u0006\u0018T¦È½=nv\u001açV\u008c ÁÔ|ãÛ\fû\u0005\u00911ýamÄí]®uRX3äç\u0016ù÷Vã.\u0091ò\tH \u0092\u001f©\u0098-*Ë´¼8Ò\u001d1\u0005\u001ff\u0003m-\u00037ª»:´\u0098Öh²\\#§W_ÿ£\r\u009c®Å û\u0001Qί\rù\fÛì±ö\u0001U\\Ó_ºré8\b\u0099\u009e:,´Ukz\u0094\u0007ùèÌè\u000fÅ\u00981\u0086yÛ\u0096¥Ô\u0088dÚÇüáUn\u009e\u0085«\u00ad$,ìUAj&¹EbÚ\u0080[\f\u0092i\u0019\u0017Ù?¾CÞ\u0002Cª¾\u0015ëáU¥Ø\u0096õ(\u009dTq7%\u0082\u009fXþùSrÂ\u0082VîÃ^>\u0099sBÛ'¿\u0097\u0006ø\u009a¬põñg\u00adl®\u0006E\u009c\u0092\nkçä V\u0017úW\u0018 \u0007 ÷ô7ôÉ\u0017ìÄY'ϲ\u001fq?§\r\u00ad\u001c}ñÇNÀ\u009eù¨À¼ÆÈã ¡µmõd2\u0010Ì\u0002Tç}\u0005h¤\u009bÏ{5Ä,/!\u0098û°Ì\u0000.xd\u0085\u008c ¦â\u009fîîÐ1r\u0002¶b¤vêïObKO#®#\u008f¬êbõ~+²\u0099é¦\u009dѾ<\u0012¿C\u0017©\u009d&\r¡\u0018\u0005²gÿ1\u008a\u0096\u001agmÁ`Î\u0095\u009cµD&\b²a'UÿíT\u001c{ø]¦Të*o\u0006\u009cÍJOÑ?g\u0090ß\u008eÁxÇl\u008bR¬¬î\u007fÒøG<\u0003h\u001eQ\u001en§ôµ1ÉØR\u0016\u0014¼Q¨\u001dÿØÑåÎw%\u0085ý¨\u008aF\u0016\u0091\u008fZ\u0096\u0000R\\WÉ-\u0001R¥\u0006Î\u0093\u0081FQ\u0089;\u0098\u0093BߨöW\u009bÀ\u0089ÃM\u001a\u0019ùR{\nü)¸\u0017CíiÝ¿x/LÍ\u00ad&©x\u008d\u001a\u0094á·\u001fw¦uºÞÝþ\u008c\u009fÚ\u0012i£Ô\u0086êLÐ\tn-Ý\u0000\u0005\u008d&2¹\u0096¶!!4\u009db\u001e{÷\u0003'\u0011»Þ0ú*õ\u0092¨\u008a\u0007\b;n@£øÐ\u009cøz¶Ë\\YË\u0087P_e},q\u0001\u0087\u0001¨ÓëÊÞ÷¹Ì÷åø\u0012÷®©vñ\u0091|R\u0013\u008c8íógá\u001d¶\u001c÷)¿½7Ô\u0001vØ+Ǫ\r}¾/\u0005o)ìUãÓb£\u0016W\\þMê\u008e\u0082¤D\u0095\u0002õ\u0006ØÊ#W[\u0099ï§Ô¯n»W2®\u0087÷\u008b&\u000e¬Í¤ÛÈøªs\u0081\u0002ʧ\u0090\u001b\u0081\u0005\u0099¾N¬¬x>Oä~H\u0087=\tjÄ3ÃÖ0c\"^vyp \u0017\u001e:ü õY8B\u00adYF,FÍß3U\u0092^.«\u0080ÿÈ\\Kؼ\t\u001fW!^¼ø\u0086¾Bò\u0092©ûQ裡íùÃOÍìж\u0012\u0016\u001d¯\u0011\u008dVÈ0´Ñ#¸\u009eR58Gï%ß\u0013\u0005ÔpE\u0093Jx)Í\u0013í\u0019çfï\u0094ÊÂ\tÿã:¿Bâ\u007f¾ÊTN Ò@\u0005m.ÓÔ»KìÛn¯\u0004\\\u0000Ó\u008fvÀ\u00014esQ]-Þ\u001d\u001eBfg²qC\u0094\u0083dz\u0090Í\u000b\\\u0099Ä\u001e\u008cC3\u0081Ñ\u0010¯«\u0005ßAðOà?bæS\u008c\u0086©\u000fH¢\u0080ä\u0089¹\u0007CÅ0=ª¾\u008f\u0095\u0092;\u0080\u009e\u009b\u0090\b\u0017 aE^\u0010\u0098>.Âý)¯ÚÉ®çQ]¢\u0006Eß^Á\u00025)e\u009eÞ\u008f\u0094*s¼KD¡¾\u0089\u001e\u00910VÅ\u009ct\u000bc\\òà»\\êi\n\u0098TË\u001eæ®6p\u0006\"\u0082Æi¤:ÉüàïØ\u0099[¹xv\f\u008b\u009c@Ù\"T}¶¿ÇKp\u0096\u0001ó\u0081û\u0013\u00149'´¥3ïÞã9\u0001çß²`ãdI\u0086©l9>ÛÌÉ¿W¼úÌYAÆÍw\"¬1'J1+cðJé\u0094\u0085r\u0081ÙÊ\u001bâæóÃ4\u0092t¹º\u0091¨Ý¡ë´¿w@\u0096,Ñâs\u0097Q:Oí\u0080é\u00124à¢Ö;ê\u0010Æ}ÜŬ£Æî·îpE?|\u0002\u0092\u001d\u0085Õ\u000bá,pÏö\u0012ú¯-\u00019è1Q\u0014Ï\u001a0\u001dÃ*$C\"\u009dÓ]\u0000±?ô'\u008a\u0013\u0098J.I\u0018Ï÷¤\u0093\u0006Æõ?à˪b\u007fÜ>+\u009ap%h¨oOþg߯Å\u0085Z£:\\\u0004\u009b`×\u009c\n\u0093\u0013\u0013\u0089s\u0090÷ÑKë\u0098ü\u0004e\u0004PD\u0006\u0086,Àò\u009b[j\rL>\u0092²iøÛº\u000b6Ìõ¿lié\u008fÝ\rÞ\u009bü\u0087\u0010\u0098ãe4søc\u000e»yó\f\r\u0014$\u0007«ûÀ2\u009e¾²u?'ä¾u/\u0080\u009e^ÇX´_þ¥Q7\u0086(»\u0084YåOõ^P\u00101\u0018 %Ùáù\u008eí(Q\u0004\u0006e8ÞJ\u000eGÙ4\u0006\u0081\u000fxXv\tûêR\u0012o*\u0092=Ré$ûqf¿íé\u000e\u000f~øØ\u000e'×H¤\\Ór\\\u0098Ëü' f|Ò³£$;¥M\rÜ\u001b'\u0010Õ°\u009a\u008dNK¼ö\u0097 `EÞ\u007f×ç\u0002\u009f¯·É\u0092§5\u0091B\u007f«º\u001aÑ\rËA\u0003ü\u0004m_Í}÷÷ÆS\u0015¸Ï'Ñt\u001fÆhV_rÃ`&éµM¼DèCÉ.½©6x\u008c\u001a*34\u0088\u0016Ò\nmËx\u0007VÛìXL\u0082ã´üñz\u008fþ\u0099*8vs1\u0090\u0091\u0098\u009et\u00076Ãñøm\u000b3½\u0000lL§%«$)\u000bëÌ8\u0083ëÞ½)\u0092½r\u0097\u0012\u0098ö\u0012O\u009aG»Ðä«¢\u0013P6²û\u008a\u0010%¨]P\u0089:ë¯údXöÍ&à§?\u0016LÀ°É¿\u0096ÍyùtÄ\u0091a\u009eæ#©>¹NÎóÆ=\u0099ëØ\u0017·<Ä4)\u0093\u0019\u0019°ÄM\u009c¨:.¦\u0086°ð\u0002\u009f@\u0081\u008cõ¦Px]áÞ\fßË\u0007P\u0013K\u0012Õ±kRý@\u0080Q\u008bú$\u00986\u008av\u0097÷ØÐÃwIß\u001e\u0098\u0017, ¤\u0089Fá\u0095¤ß\u0007´¾Ü<\u0007\u0017âä\u0011ññR\b÷©\u009e-Ï\u0006P\u001e\bWÁfááí^È+\u0001C\u001a\u0082cÎ\u0085ðôÄ|:'cÏ\u0094¾Oü@j\u0003Þ\u00000¨ÖRCGW|b\u0018Fw¼¾É\u0090©Ç|Â|\u0086\u001e\u0010\u0089»}ÅÖg¦S\u0097yÇ}·\u0002\u0016´\u009fLe²á1oÛ±{\n\u0015*tfâ\u009fh¬D\u0006\u0003\u00186úf\n\u0087]K.çIb\u008bÜ\u0017\u0091\u0092¨åÍL\u009fì\u0092AWX¥ÙD\u001f\u0081ÐØ÷o/F~\u008d\u009e+BxlÝz\bnüZk»¦X:\u0015©Ü'®\u0082¦ÒGF7Æ=ëLâ¤z7\u00adôBzü¦_\u0095É/ø)Àþ(»º^%j±ú]t³uñ8Çr\u008fJÑb+\u0001ðÍ\u0000£_®»=õVÈá6iQE\u0097!D¹ý{\u0081¸c¶\u0081#Ð3\u0011K\f¸ÀF©@Qäxò'Ô\u008a\u00ad\u0097ÁôAeMèß\u00adf2Ùö¥¸H\u009au\u0017\u0096\u0092ËåïP×ßWÌe[\u001a_)`¡»;pï&¿Ã=\u0089¡O]\u009e\r&ïA@\u00ad\u001f3¶yøÊ\u0000ïÆ²da\\Ðëo81z³\u001df¬ãø\u001c\u008b Ç(\u009c\u0012Ùéõåá½p\u0092k\u0096_Q\u0006¼ÆB4\u008cg\u0000?\u0087º\fÂÿFê\u0080ò\u0092\u0084\u0013\u0016\u0095sûèʧ1xÈ0Å\u0010;óÖ\r0ɲñÂ\u00854\u0098J\u0083I\u0017\u0084)ؤõ\u0089\t!<â»ÊKÉ?\u001a\u000e©Ø¤ãùm\u008f$Ã\u0089¼\u0013Â\u008b\u0089ªâ\u001eΣ¥aá·\u0088ï,G=\u0013_\u001bNÿ\u0097JÈÈ\bÌ8ã³ùéE\u001dÆ\u0002\u001dwÁ\u0089\u009axù:å¥\u0081Ê\u0096Ã\u0089Õ®æRöÝìO|ì\u000f\u0084\u001cÍ\u008bþ\u009cíÃF|§;¶3ÃS7\u0010\u001a\u001fÆ\u001eJí°HRÛ-\u0010\u0011\u00971·\u001fó>\u0081Û\u0094\u001eÍá?\u0001\u001eë\u0092%\u009d?vo'\u0086\u000fY-£x\u0000ÊdÆú\u0090uªÞE\u0093\u000b\u0089\u00ad{\u001fï\u008fÌ\u000f¨\u0086x\u008fýÔô²\u0003×#Ê\u001dCï\u0017(\u0088\u001eÍt\u000e0Ké]º\u0089\u008bY\u008c'\u001cõ¹è\u008d\u0018è\u0019}ùÇÐRÀ¼B\u0016pi\u0099s\u0087Í~U\u0090å6ø\u0013TM\u0093\u00adAy_Î)\u0089©ÀùKåC÷\u0080X·\u0092=ãcÍ\u0007d*\u0095\r pª_Óx\u008eæ¯\u009d ³ÿvç\u0002òWÊã\u0011¤\u00ad\u0017\u0092á\u0013\u007f\u00adg&$}¾s«ï\u001fͺ527\u0084{\u0080´Y\u0014¤Ü\u001f\u0080ÎBnj<\u009d°ìÐ{¾\u009b\u009d\u001bG\u009a×\u000bé\u0087\u0017\u0080Ñ\u0014\u0090ÿ\u0092\u0017q,\u0010\u0098\u0091£W¡0Ö`æ\u0019á\u001a\t6F\u008a\u0001ÀB\u0085\u0098@¦Ûλ¼~µ\u0016z\u0097þ$é\n\u0003Ö¥eðuçî@>ow(â=ÝFÝV¬TÒ¼¾\u0089.ɯËg^JCó¨q\\g+z{h\u000e¤z7£wÑ)´)\u008dÌf¥/õè/1:¶¯ÿ+Æ£Ö\t\u0089J²\u001d¼µK¸=f©ÎLSªÐ§_2¹\u0017 7^Ò3ó\u0012]UpþÃiAÙaC\u001bî\t¥Ë£\u009b\u0018~)\u008fÜ\u009cü~\u0094\u008c\fm'~ú\u0011\u0016ó\u0088vn;\u008c9Æ*º\u000fãnÅE\u000f\u0097½±Èw0\u0000\u008a\u0010oÆðk°õ±\u008aH\u001dë\u008f7\u0084ܼþüÎÿ²*úéFÓØ\u0086îm\\w¼®³w.±é\u0085\u0081Á\u008f¶\u0095\u0093DDw1¯5x8}¡OÿM_Û¦Z\u0014ar\u0012mB\u0019s\u001a\u0013¡\u0096\u000bA\u0015\u008b1â\\\\M9üÑ«å\u001bz´\u009d\u001f\u008dB\u0086A\bªý6»ã¨ë\u008bJ\u0086\n)KwR\r¶\u0005ä¬tC\u009cñ\u001eÒ\u0000\u007f&e9Òå]âÍ^\u008bù7ËC\u0007G\tX\u001d; s¬M\u007f/\u0010Ϭá\u008f,\u001eW3Å\u00ad\u008d®\u0098\u0004\u0085,<\u0087àJN¥\u0014s\u0096mþĤÉ\u008cÿW\u0012\u008e\u0007*>>\u0082â<Ãç»\u001dU\u008d½ \u0000,\u009fÚ¯ï¦æ0¦\\¯K(D¢\u008f§Ô¬\u0018xõÂZ\u008cøbn\u0098ü]E\u0084èÄ\u0099y\u0005R\u0094Bý¥#à\u0013ï\u0083¥V²v\u0099hkû¨Ñb4UD\u0006\t\u0004\u0085|J\u0084{Y©IuàK6\u008cRvE3\u0099³w\"æû´%\u001dÝK]Ú9ÞSi\u00adì\u0098Á\u009fÐdð|\u0018¨²M½\u008f»G\u0011CLK¼!\u0094±ð\u0086Õß\u001eÑ\u00adÌ5÷\u001d\u008c\u008cWs|ß+¡¾¯Ê\u009c¹U\u0007µ9\u008asêÈ;¿´-³nø\u008aq2miOüÖ©/½\u0096ÆZ\u0087¸ òPÅ\u0093/Na\u008cË\u0087æù\u000eÃ\u001by±\b+3óº.\ná}øCòv\u001f\u0007>UQ¢-\u0000K~\u0087^Y«\u0007\u0096O9ðþ\u0098\nZαò\u008f=_Å\u008fʦ\u008bâ\u0001óçÜÐ\u0099ÎgµzÛ=ýå\u0013WçH²\u0016\u0084ÏèßÑ!z\b\u0091ßB\u0017â\u00060W\u0005$*fýÉ\u0013¿þ]òÄ\u001c\u0018Q;à{\u000b\u007fÖ1d)j]Ë\u007fµ\u0001\u000f{ãòJ\u009cÝÖï\u0090\u001c\u009daìI\u000em£ß\u009d`#fõ7\u0016Îâév\\HXB íÒ£ßs°akdé²ÛC\u0013ebÐÑ\u0097§Ü\u009e-Ô\u008e\u0081Ñæ¸Ê8\u0094µxäù«}%ø}\u000bË\u0091g\u008aÂÕN\u0005up=k\n\u0011»E3å*ôðÛÅó¿V\u009b?éåEï]-Vò&âÁó¤\t»\t\u008bv\nòÐå\r8È\u0082\u0016bfY\u0015è«\u009a?l\u0010ïÓ\u0001«\u0097òkÏ(ö<\u001eAá\u000bØ\u0003TÒ\u0085\u007f\u008dÄ0}\u0083»f\u000b\"¦ãÄ\u008c\u008a>ÄÿWÚÌ\u0014\u0004\u009d¶\u001b\u000eX\u0084\u009dézh,{*y±Î\"³¢Sþ]\u0087R6\u001b¿ÓgEv×F/rUö\u00ad©[kÃk\u008cÆ:\u009f(}R\u0017Ù&t\u001dÊ\u008c\u0013)\u0080\u00036S)\u0091/\u008eÉ]\u009bÉ\u0004\u0005\u008c\u008d±²Ô\u0091¦f6\u0085\u0002·Ã[vµ\u0090/3\u008f\u0007¢%\u0085\u007f\u008e\u0003\u0095Ô¡\u0006Ü$!sø\u0004<¸ÓÀ{f\\õFåÐ~\u0002)Î\u008amÅp\u0084\u0017ÕX¡_al×Ðm\u0000\u0017.¾hÛèÓw\u0093´ÕñX\u008f\u007f9³Á-á\u0097EÁÍ2ý\u009cN\u0002´\u0010vùç²m$7\u00adtY¡à¯W\u0015Ê\u0096ÂȾß\u008eÁ´¥íý\u0084Õ3À]\u008f}=\u0092×¢\u001düì\u001f\u0002U\u0086\u0006b»r\u0094\u0096ý\u008eñ»\u009c\u0096Ìü\u0087Øýc\u0001¾±½î\u0080]¾\\\u0099A\\\u0013µD\u009cöå#!ë\u0084hïæKÖ¸µ\u001aß\u0007r\u0092\f:\u001cÂñ\u0093S=\u008e\u0003UÈu\u00adðW\u0013bR8M\u000e\u009dî\u001aæWj~\u008e¾,ömuÒ¥ìÓ\u0080\\hN5ÂÀkѬqÃ\u0005yE\u0097gHE«\u0085aÐg;ç\u0004r,ob¬À^¡ÇE?Ϲ\u0001Hkt\u0018\u0016l+ªµ¿\u0093j|\u008fUóöveÛ¢K\rÚ_%ª\u001f\u0091¤9á;,Ñé\u0093l\b;\u0006\u0093½¿ïßîDw¯¦\u0017ÁË/¢\u001d\n¶¿÷äîîÒ!\u008fËíýÚ¾û80lÊ\u0019R\u008cȵá\u0089®\u0080\u001e6¦ÄJý\u0086X¡\u0005\u0095LH\\T\u0080\u009fli\u0002,£\\¸áè8Ø\u0092ã&\u0004Ko`.\u009aêûçÂû¤^\u0090=ß¾·Lr\u0000årH\\\u0084.GÀøÜ#\u0087½8Eiî\u0011ºñ\u0019\u009eVÝ\u0007ÝþJ¾«@M\u009aÒß$s\nÌ(ä:á¦I¡qA\nMåYÆÁ8¥â\u001a䦽ø·'òù+Ä)sNìí±\u009b\u0018Þ\u0087%\u001c'¸¥°¨\u0087~\u0097\u008eþÕ\u0083ÕÞ?\u0086}p@8Áú+^·\\\u0090\u0018\u001cöÏ\u0097»ò9ÝB/\u0005wÓ®\u0017\u0014Fktÿ\u0006\u0007)\u0084´Ä.ỵó]\u008dð\u009d\"\u009cªy{\u0017N\u0084Ö½áª\u0018\u0019\u00926[\u008eD6½7\u009cÐ5\u0010|7¨\u001a\\º\u00109QKF\u009d\u009b¸ïqMÔì\u008c\u0086ÃòÓË-Åv$Ñlt\u0093\u001b§N\u001b¥{Éa#ìtéiJvbÓ¯6oâ>[\u0000Åm=\u00adAå||&\u0016\u0089£I\u009fñ\u000e\\eª÷9\u009d7Aj·Ä\u0081\u008c÷7\u008b\u0087/©ûã\u009a&\u0002Ev}\u0017\fÜÌã6\u008bÞòÁÿðYÕ®\ryÍ·AÂéþÿ×\u0013鹬\u0010©ÞG:n¨¿\u009bdrãÕËA·5å: ä\u001eU=\\ä\u0014wÅ\\ï\u0091ìð\u001d¯\u0099íçcS/x9£\u0093\u0007tpÐ#x\b'¹\b\u0005\u001fªËÂ\u008a\u000b\u0090\\j\u0085ñm$ÿAî\u008aré\"©zØ\\±ð\\£q:\u0001<Ê´ádA\u0086²)wÐ4a{²x\u0083[¡Qð\u009a^U¢²û¨\u0006 lh\u0003^ÛdúÜJÁ\u0017ö)X¦:@ÿ®Å\u0016M\u0017þ£¾\u00877l{\u009b8\u0087{&\u0097|§\u0007Öh\u0091¸\u0096R\u0010u\u0011y\u001a¡s!@pøù\u000e·V±Ö[\u0080\u0011\u0012\u0092Ûþ<\u001d\u0083Í\u0085\u001f\u0012*êß/¶è¾-\u001d²$\u009d¢·8MÊÞÕó0\n»>CÜ#Z_¾\u008f\u001c>Åp\u000e`]\u001f\u0088}þ¾\u0006\u00131\u0015$\u0099$\u0083ÜSUæ\u001e2Ò-uñÔo¼^*¶w¸G,\u0083L{`âlIÉ[¤k\u001f´S\fƶ\u00adúÁ\u0007º\u001eié\u0003\u000fÓ¿\u0004Ïfo>ÿ\u0085\t\u0003º®w\u0085\u0085)\u001cù.~\u0016'ÉË¿\u0007hå§Æ¹mxMJ1ìÂ%\u0089\u0003\"òÍPSJ\u001b\u009c! 'üðEǦ°4ä\u0082«\u0085\u000e»pܱ=\u0017\u001aê8\u0085®pä0\u0082h\u001bPó\u0000R3v÷úÆ\u0097>%Áq²0'\u0086êV¶\u0083æ\u0081:*ç![b\u0004T\u0089\u000fwÙ(p«a¤H\u0013\u0019V°x\u0093+:L.RÐ/¶S\u009b±\u0098\u0090R©\u0012\tÝë\u0016)\n´3¯\u0099\t\u0006védÂ\u0096\u009b\r\bñðGm\u009a\u000eÎãL¡Ð(\u0097\u0086\u0014þ\u001d\u001c!\fµ\u001d°d\u0090 \u001c·»\u0017Ë\u00ad¸³ÙräÖ0ÍËðΦó¬Éµaµ\u0087Ë\u009d\u007f£\\}+\\G>\u0016@C\u001c\u009b(ò\u001e\u001b&\u00965¢ÞZxV\u001bäûqaC\u008c\b°Ã\u0097f'~{¾\u0080\u00149n\u001br;g®q\u0088aï\u0003ñÍ\u0094[Qg+\u0085\u0099â\u0085\"\t\u000eNêk\u0086+Ù\f]mY\u0099è3s*.J}\u0094 89; - case 1 -> 65; - case 2 -> 88; - case 3 -> 36; - case 4 -> 112; - case 5 -> 91; - default -> 47; - }); - var6++; - if (var10001 == 0) { - var10004 = var10001; - var28 = var20; - } else { - if (var10001 <= var6) { - break; - } - - var28 = var20; - var10004 = var6; - } - } - } - - String var35 = new String(var20).intern(); - switch (var10001) { - case 0: - var5[var3++] = var35; - if ((var8 += var1) >= var4) { - a = var5; - b = new String[85]; - System.out.println(a(16530, 18598)); - return; - } - - var1 = var2.charAt(var8); - break; - default: - var5[var3++] = var35; - if ((var8 += var1) < var4) { - var1 = var2.charAt(var8); - continue label52; - } - - var2 = "ÿ.\u009fÌPÙöÒ\u0098°áÅGür²ifO_è¶[Ö.\u0000\u0004\u000f\u0013G®üÜ1Ô\u007f\u001aþàÁú1ÎçÔÃ\u0006\u00ad\u0096çµh\u0081\u008b+Ú\u009a\u009aû\u0005·6#¸¸\u0010¨\u0087¸=ÔÐ\u009b\"\u0099r\u008f9¾\u0005k\u0012© ¦ö!Ñ\u000b\u0087\t\u00ad\u0098Ô\u008dÁ\u001e<\u000e'\u0097ÓM\u0082¦üv\u0001n\u0091×\u0015_\u009e\u0004'o;ÎZ°\u000e\u008dpr·|z+ûþ0\u009e°×\u001fH¿D\u0082\u0003(<¬º/ð~~¼D4\u000f0\u0083[Â\u0012¢ ö\u008ag×@B¢þ1¢Áæ\u0094\u00adåvµINÉ\u0091X½ì\u00ad*7Õ\u001d(Å7ôfGñ}¦\u009e|\\\u0099Õí¯\u009dEáÔy´AkÏáEÔ\u0091Ó\u0096¥ú\u009fİØ\u009eƪ\u0098\u0004}Þ!"; - var4 = 234; - var1 = 'g'; - var8 = -1; - } - - var10000 = var2.substring(++var8, var8 + var1); - var10001 = 0; - } - } - } - - private static String a(int var0, int var1) { - int var2 = (var0 ^ 16532) & 65535; - if (b[var2] == null) { - char[] var3 = a[var2].toCharArray(); - - short var4 = switch (var3[0] & 0xFF) { - case 0 -> 227; - case 1 -> 224; - case 2 -> 211; - case 3 -> 23; - case 4 -> 237; - case 5 -> 168; - case 6 -> 119; - case 7 -> 34; - case 8 -> 98; - case 9 -> 33; - case 10 -> 132; - case 11 -> 0; - case 12 -> 135; - case 13 -> 223; - case 14 -> 175; - case 15 -> 147; - case 16 -> 37; - case 17 -> 235; - case 18 -> 35; - case 19 -> 183; - case 20 -> 127; - case 21 -> 167; - case 22 -> 232; - case 23 -> 4; - case 24 -> 75; - case 25 -> 173; - case 26 -> 134; - case 27 -> 69; - case 28 -> 158; - case 29 -> 25; - case 30 -> 30; - case 31 -> 106; - case 32 -> 188; - case 33 -> 203; - case 34 -> 59; - case 35 -> 117; - case 36 -> 105; - case 37 -> 250; - case 38 -> 121; - case 39 -> 17; - case 40 -> 169; - case 41 -> 70; - case 42 -> 207; - case 43 -> 84; - case 44 -> 81; - case 45 -> 182; - case 46 -> 231; - case 47 -> 123; - case 48 -> 142; - case 49 -> 191; - case 50 -> 96; - case 51 -> 28; - case 52 -> 149; - case 53 -> 7; - case 54 -> 13; - case 55 -> 243; - case 56 -> 196; - case 57 -> 72; - case 58 -> 165; - case 59 -> 46; - case 60 -> 63; - case 61 -> 86; - case 62 -> 215; - case 63 -> 1; - case 64 -> 93; - case 65 -> 31; - case 66 -> 16; - case 67 -> 6; - case 68 -> 5; - case 69 -> 36; - case 70 -> 50; - case 71 -> 176; - case 72 -> 107; - case 73 -> 71; - case 74 -> 78; - case 75 -> 24; - case 76 -> 111; - case 77 -> 32; - case 78 -> 229; - case 79 -> 58; - case 80 -> 65; - case 81 -> 210; - case 82 -> 61; - case 83 -> 8; - case 84 -> 190; - case 85 -> 140; - case 86 -> 15; - case 87 -> 151; - case 88 -> 153; - case 89 -> 213; - case 90 -> 76; - case 91 -> 95; - case 92 -> 234; - case 93 -> 97; - case 94 -> 236; - case 95 -> 2; - case 96 -> 226; - case 97 -> 29; - case 98 -> 125; - case 99 -> 185; - case 100 -> 247; - case 101 -> 143; - case 102 -> 186; - case 103 -> 21; - case 104 -> 56; - case 105 -> 244; - case 106 -> 57; - case 107 -> 137; - case 108 -> 27; - case 109 -> 103; - case 110 -> 18; - case 111 -> 136; - case 112 -> 133; - case 113 -> 251; - case 114 -> 184; - case 115 -> 248; - case 116 -> 225; - case 117 -> 180; - case 118 -> 44; - case 119 -> 219; - case 120 -> 19; - case 121 -> 88; - case 122 -> 53; - case 123 -> 195; - case 124 -> 115; - case 125 -> 130; - case 126 -> 200; - case 127 -> 254; - case 128 -> 194; - case 129 -> 90; - case 130 -> 114; - case 131 -> 160; - case 132 -> 166; - case 133 -> 202; - case 134 -> 12; - case 135 -> 92; - case 136 -> 209; - case 137 -> 41; - case 138 -> 189; - case 139 -> 146; - case 140 -> 9; - case 141 -> 253; - case 142 -> 221; - case 143 -> 205; - case 144 -> 113; - case 145 -> 49; - case 146 -> 187; - case 147 -> 131; - case 148 -> 22; - case 149 -> 124; - case 150 -> 239; - case 151 -> 208; - case 152 -> 157; - case 153 -> 218; - case 154 -> 91; - case 155 -> 94; - case 156 -> 148; - case 157 -> 201; - case 158 -> 67; - case 159 -> 171; - case 160 -> 170; - case 161 -> 242; - case 162 -> 154; - case 163 -> 55; - case 164 -> 99; - case 165 -> 62; - case 166 -> 40; - case 167 -> 139; - case 168 -> 174; - case 169 -> 249; - case 170 -> 11; - case 171 -> 110; - case 172 -> 43; - case 173 -> 77; - case 174 -> 38; - case 175 -> 222; - case 176 -> 42; - case 177 -> 199; - case 178 -> 141; - case 179 -> 197; - case 180 -> 155; - case 181 -> 45; - case 182 -> 83; - case 183 -> 198; - case 184 -> 193; - case 185 -> 228; - case 186 -> 10; - case 187 -> 217; - case 188 -> 60; - case 189 -> 47; - case 190 -> 162; - case 191 -> 238; - case 192 -> 3; - case 193 -> 100; - case 194 -> 245; - case 195 -> 138; - case 196 -> 102; - case 197 -> 82; - case 198 -> 85; - case 199 -> 80; - case 200 -> 246; - case 201 -> 129; - case 202 -> 241; - case 203 -> 52; - case 204 -> 68; - case 205 -> 233; - case 206 -> 204; - case 207 -> 14; - case 208 -> 164; - case 209 -> 144; - case 210 -> 89; - case 211 -> 112; - case 212 -> 108; - case 213 -> 212; - case 214 -> 172; - case 215 -> 150; - case 216 -> 230; - case 217 -> 192; - case 218 -> 74; - case 219 -> 128; - case 220 -> 145; - case 221 -> 39; - case 222 -> 104; - case 223 -> 161; - case 224 -> 79; - case 225 -> 177; - case 226 -> 216; - case 227 -> 118; - case 228 -> 255; - case 229 -> 152; - case 230 -> 126; - case 231 -> 26; - case 232 -> 156; - case 233 -> 120; - case 234 -> 64; - case 235 -> 109; - case 236 -> 163; - case 237 -> 87; - case 238 -> 179; - case 239 -> 181; - case 240 -> 54; - case 241 -> 206; - case 242 -> 51; - case 243 -> 66; - case 244 -> 252; - case 245 -> 178; - case 246 -> 73; - case 247 -> 122; - case 248 -> 159; - case 249 -> 48; - case 250 -> 20; - case 251 -> 214; - case 252 -> 220; - case 253 -> 101; - case 254 -> 240; - default -> 116; - }; - int var5 = (var1 & 0xFF) - var4; - if (var5 < 0) { - var5 += 256; - } - - int var6 = ((var1 & 65535) >>> 8) - var4; - if (var6 < 0) { - var6 += 256; - } - - for (int var7 = 0; var7 < var3.length; var7++) { - int var8 = var7 % 2; - char var10002 = var3[var7]; - if (var8 == 0) { - var3[var7] = (char)(var10002 ^ var5); - var5 = ((var5 >>> 3 | var5 << 5) ^ var3[var7]) & 0xFF; - } else { - var3[var7] = (char)(var10002 ^ var6); - var6 = ((var6 >>> 3 | var6 << 5) ^ var3[var7]) & 0xFF; - } - } - - b[var2] = new String(var3).intern(); - } - - return b[var2]; + a = var5; + System.out.println("hello world"); } } diff --git a/testData/results/custom-classes/zkm/EnhancedStringEncSomeStrings.dec b/testData/results/custom-classes/zkm/EnhancedStringEncSomeStrings.dec index 369139e6..f3a9f94e 100644 --- a/testData/results/custom-classes/zkm/EnhancedStringEncSomeStrings.dec +++ b/testData/results/custom-classes/zkm/EnhancedStringEncSomeStrings.dec @@ -1,376 +1,19 @@ public class a { private static final String[] a; - private static final String[] b; + private static final String[] b = new String[3]; public static void main(String[] var0) { - a(a(30479, -25658)); + a("placek"); } private static void a(String var0) { - if (!var0.equals(a(30478, -17804))) { - throw new RuntimeException(a(30477, 20135)); + if (!var0.equals("tak")) { + throw new RuntimeException("Wrong Password"); } } - // $VF: Irreducible bytecode was duplicated to produce valid code static { String[] var5 = new String[3]; - int var3 = 0; - String var2 = "Õ\u0082t\u009dÄ\u0081\u0003µ\u0092ö\u000eÜÜ\u0098\u0086\u0094\u0005TR\f¥¤¤)o"; - byte var4 = 25; - char var1 = 6; - int var0 = -1; - - while (true) { - char[] var16; - label38: { - char[] var10001 = var2.substring(++var0, var0 + var1).toCharArray(); - int var10003 = var10001.length; - int var6 = 0; - var16 = var10001; - int var10 = var10003; - char[] var23; - int var10004; - if (var10003 <= 1) { - var23 = var10001; - var10004 = var6; - } else { - var16 = var10001; - var10 = var10003; - if (var10003 <= var6) { - break label38; - } - - var23 = var10001; - var10004 = var6; - } - - while (true) { - var23[var10004] = (char)(var23[var10004] ^ switch (var6 % 7) { - case 0 -> 84; - case 1 -> 40; - case 2 -> 91; - case 3 -> 74; - case 4 -> 9; - case 5 -> 31; - default -> 60; - }); - var6++; - if (var10 == 0) { - var10004 = var10; - var23 = var16; - } else { - if (var10 <= var6) { - break; - } - - var23 = var16; - var10004 = var6; - } - } - } - - String var30 = new String(var16).intern(); - byte var14 = -1; - var5[var3++] = var30; - if ((var0 += var1) >= var4) { - a = var5; - b = new String[3]; - return; - } - - var1 = var2.charAt(var0); - } - } - - private static String a(int var0, int var1) { - int var2 = (var0 ^ 30479) & 65535; - if (b[var2] == null) { - char[] var3 = a[var2].toCharArray(); - - short var4 = switch (var3[0] & 0xFF) { - case 0 -> 177; - case 1 -> 28; - case 2 -> 194; - case 3 -> 248; - case 4 -> 244; - case 5 -> 236; - case 6 -> 133; - case 7 -> 9; - case 8 -> 169; - case 9 -> 88; - case 10 -> 182; - case 11 -> 118; - case 12 -> 82; - case 13 -> 59; - case 14 -> 120; - case 15 -> 127; - case 16 -> 243; - case 17 -> 6; - case 18 -> 137; - case 19 -> 45; - case 20 -> 239; - case 21 -> 36; - case 22 -> 147; - case 23 -> 69; - case 24 -> 104; - case 25 -> 202; - case 26 -> 62; - case 27 -> 255; - case 28 -> 220; - case 29 -> 119; - case 30 -> 21; - case 31 -> 94; - case 32 -> 122; - case 33 -> 163; - case 34 -> 247; - case 35 -> 51; - case 36 -> 31; - case 37 -> 87; - case 38 -> 221; - case 39 -> 204; - case 40 -> 5; - case 41 -> 37; - case 42 -> 201; - case 43 -> 11; - case 44 -> 41; - case 45 -> 115; - case 46 -> 211; - case 47 -> 42; - case 48 -> 206; - case 49 -> 117; - case 50 -> 67; - case 51 -> 148; - case 52 -> 142; - case 53 -> 181; - case 54 -> 191; - case 55 -> 80; - case 56 -> 46; - case 57 -> 39; - case 58 -> 102; - case 59 -> 130; - case 60 -> 116; - case 61 -> 58; - case 62 -> 40; - case 63 -> 93; - case 64 -> 7; - case 65 -> 48; - case 66 -> 184; - case 67 -> 74; - case 68 -> 90; - case 69 -> 173; - case 70 -> 152; - case 71 -> 25; - case 72 -> 198; - case 73 -> 135; - case 74 -> 91; - case 75 -> 3; - case 76 -> 77; - case 77 -> 253; - case 78 -> 151; - case 79 -> 222; - case 80 -> 149; - case 81 -> 160; - case 82 -> 111; - case 83 -> 228; - case 84 -> 64; - case 85 -> 0; - case 86 -> 216; - case 87 -> 166; - case 88 -> 76; - case 89 -> 212; - case 90 -> 251; - case 91 -> 43; - case 92 -> 162; - case 93 -> 188; - case 94 -> 34; - case 95 -> 225; - case 96 -> 109; - case 97 -> 242; - case 98 -> 78; - case 99 -> 183; - case 100 -> 165; - case 101 -> 86; - case 102 -> 73; - case 103 -> 83; - case 104 -> 53; - case 105 -> 71; - case 106 -> 32; - case 107 -> 18; - case 108 -> 8; - case 109 -> 159; - case 110 -> 85; - case 111 -> 81; - case 112 -> 235; - case 113 -> 14; - case 114 -> 17; - case 115 -> 146; - case 116 -> 144; - case 117 -> 30; - case 118 -> 27; - case 119 -> 112; - case 120 -> 15; - case 121 -> 171; - case 122 -> 129; - case 123 -> 195; - case 124 -> 131; - case 125 -> 226; - case 126 -> 241; - case 127 -> 232; - case 128 -> 107; - case 129 -> 213; - case 130 -> 210; - case 131 -> 140; - case 132 -> 205; - case 133 -> 68; - case 134 -> 136; - case 135 -> 238; - case 136 -> 200; - case 137 -> 89; - case 138 -> 56; - case 139 -> 54; - case 140 -> 143; - case 141 -> 12; - case 142 -> 110; - case 143 -> 50; - case 144 -> 215; - case 145 -> 234; - case 146 -> 13; - case 147 -> 95; - case 148 -> 100; - case 149 -> 190; - case 150 -> 193; - case 151 -> 20; - case 152 -> 175; - case 153 -> 185; - case 154 -> 155; - case 155 -> 180; - case 156 -> 168; - case 157 -> 70; - case 158 -> 179; - case 159 -> 125; - case 160 -> 1; - case 161 -> 84; - case 162 -> 207; - case 163 -> 150; - case 164 -> 10; - case 165 -> 218; - case 166 -> 139; - case 167 -> 250; - case 168 -> 170; - case 169 -> 108; - case 170 -> 197; - case 171 -> 79; - case 172 -> 209; - case 173 -> 33; - case 174 -> 47; - case 175 -> 63; - case 176 -> 105; - case 177 -> 233; - case 178 -> 246; - case 179 -> 26; - case 180 -> 22; - case 181 -> 121; - case 182 -> 249; - case 183 -> 66; - case 184 -> 65; - case 185 -> 23; - case 186 -> 199; - case 187 -> 29; - case 188 -> 230; - case 189 -> 172; - case 190 -> 245; - case 191 -> 123; - case 192 -> 124; - case 193 -> 61; - case 194 -> 16; - case 195 -> 217; - case 196 -> 227; - case 197 -> 24; - case 198 -> 134; - case 199 -> 126; - case 200 -> 96; - case 201 -> 92; - case 202 -> 208; - case 203 -> 72; - case 204 -> 44; - case 205 -> 52; - case 206 -> 240; - case 207 -> 128; - case 208 -> 138; - case 209 -> 141; - case 210 -> 132; - case 211 -> 38; - case 212 -> 106; - case 213 -> 113; - case 214 -> 187; - case 215 -> 167; - case 216 -> 252; - case 217 -> 189; - case 218 -> 49; - case 219 -> 161; - case 220 -> 158; - case 221 -> 174; - case 222 -> 196; - case 223 -> 231; - case 224 -> 229; - case 225 -> 223; - case 226 -> 214; - case 227 -> 237; - case 228 -> 60; - case 229 -> 153; - case 230 -> 57; - case 231 -> 154; - case 232 -> 186; - case 233 -> 4; - case 234 -> 101; - case 235 -> 98; - case 236 -> 103; - case 237 -> 192; - case 238 -> 19; - case 239 -> 35; - case 240 -> 55; - case 241 -> 2; - case 242 -> 156; - case 243 -> 99; - case 244 -> 203; - case 245 -> 224; - case 246 -> 178; - case 247 -> 75; - case 248 -> 219; - case 249 -> 114; - case 250 -> 157; - case 251 -> 176; - case 252 -> 164; - case 253 -> 254; - case 254 -> 145; - default -> 97; - }; - int var5 = (var1 & 0xFF) - var4; - if (var5 < 0) { - var5 += 256; - } - - int var6 = ((var1 & 65535) >>> 8) - var4; - if (var6 < 0) { - var6 += 256; - } - - for (int var7 = 0; var7 < var3.length; var7++) { - int var8 = var7 % 2; - char var10002 = var3[var7]; - if (var8 == 0) { - var3[var7] = (char)(var10002 ^ var5); - var5 = ((var5 >>> 3 | var5 << 5) ^ var3[var7]) & 0xFF; - } else { - var3[var7] = (char)(var10002 ^ var6); - var6 = ((var6 >>> 3 | var6 << 5) ^ var3[var7]) & 0xFF; - } - } - - b[var2] = new String(var3).intern(); - } - - return b[var2]; + a = var5; } } diff --git a/testData/results/custom-jars/SnakeGame-obf-zkm/d.dec b/testData/results/custom-jars/SnakeGame-obf-zkm/d.dec index 64d6d4c9..d546b602 100644 --- a/testData/results/custom-jars/SnakeGame-obf-zkm/d.dec +++ b/testData/results/custom-jars/SnakeGame-obf-zkm/d.dec @@ -11,7 +11,7 @@ public class d extends Thread { c g; public static boolean h; private static final String[] i; - private static final String[] j; + private static final String[] j = new String[2]; private static final long[] k; private static final Integer[] l; private static final long m; @@ -571,136 +571,71 @@ public class d extends Thread { // c5: return } - // $VF: Irreducible bytecode was duplicated to produce valid code static { String[] var13 = new String[2]; - int var11 = 0; - String var10 = "úÑ\u0017tµ9\u000b_ÉÛ.\u0003º·y³ß\u0081"; - byte var12 = 18; - char var9 = 6; - int var8 = -1; - + i = var13; + long var0 = 107634029375693L; + long[] var6 = new long[5]; + int var3 = 0; + String var4 = ":Q o5\u000eÁÍg¡ù¢Xɽ\u0088sÊUÂÈò©´"; + byte var5 = 24; + byte var2 = 0; + + label20: while (true) { - char[] var28; - label56: { - char[] var10001 = var10.substring(++var8, var8 + var9).toCharArray(); - int var10003 = var10001.length; - int var14 = 0; - var28 = var10001; - int var19 = var10003; - char[] var37; - int var10004; - if (var10003 <= 1) { - var37 = var10001; - var10004 = var14; - } else { - var28 = var10001; - var19 = var10003; - if (var10003 <= var14) { - break label56; - } - - var37 = var10001; - var10004 = var14; - } + int var10001 = var2; + var2 += 8; + byte[] var7 = var4.substring(var10001, var2).getBytes("ISO-8859-1"); + long[] var10000 = var6; + var10001 = var3++; + long var10002 = ((long)var7[0] & 255L) << 56 + | ((long)var7[1] & 255L) << 48 + | ((long)var7[2] & 255L) << 40 + | ((long)var7[3] & 255L) << 32 + | ((long)var7[4] & 255L) << 24 + | ((long)var7[5] & 255L) << 16 + | ((long)var7[6] & 255L) << 8 + | (long)var7[7] & 255L; + byte var10003 = -1; - while (true) { - var37[var10004] = (char)(var37[var10004] ^ switch (var14 % 7) { - case 0 -> 10; - case 1 -> 70; - case 2 -> 22; - case 3 -> 48; - case 4 -> 44; - case 5 -> 80; - default -> 36; - }); - var14++; - if (var19 == 0) { - var10004 = var19; - var37 = var28; - } else { - if (var19 <= var14) { - break; + while (true) { + long var19 = var10002 ^ var0; + switch (var10003) { + case 0: + var10000[var10001] = var19; + if (var2 >= var5) { + k = var6; + l = new Integer[5]; + m = 50L; + return; } - - var37 = var28; - var10004 = var14; - } - } - } - - String var44 = new String(var28).intern(); - int var23 = -1; - var13[var11++] = var44; - if ((var8 += var9) >= var12) { - i = var13; - j = new String[2]; - long var0 = 107634029375693L; - long[] var6 = new long[5]; - int var3 = 0; - String var4 = ":Q o5\u000eÁÍg¡ù¢Xɽ\u0088sÊUÂÈò©´"; - byte var5 = 24; - byte var2 = 0; - - label40: - while (true) { - var23 = var2; - var2 += 8; - byte[] var7 = var4.substring(var23, var2).getBytes("ISO-8859-1"); - long[] var10000 = var6; - var23 = var3++; - long var33 = ((long)var7[0] & 255L) << 56 - | ((long)var7[1] & 255L) << 48 - | ((long)var7[2] & 255L) << 40 - | ((long)var7[3] & 255L) << 32 - | ((long)var7[4] & 255L) << 24 - | ((long)var7[5] & 255L) << 16 - | ((long)var7[6] & 255L) << 8 - | (long)var7[7] & 255L; - byte var45 = -1; - - while (true) { - long var48 = var33 ^ var0; - switch (var45) { - case 0: - var10000[var23] = var48; - if (var2 >= var5) { - k = var6; - l = new Integer[5]; - m = 50L; - return; - } - break; - default: - var10000[var23] = var48; - if (var2 < var5) { - continue label40; - } - - var4 = ">\u0099\u0092í\u0001\n\u0081U\u008a±Å\u001bæLÈ-"; - var5 = 16; - var2 = 0; + break; + default: + var10000[var10001] = var19; + if (var2 < var5) { + continue label20; } - byte var26 = var2; - var2 += 8; - var7 = var4.substring(var26, var2).getBytes("ISO-8859-1"); - var10000 = var6; - var23 = var3++; - var33 = ((long)var7[0] & 255L) << 56 - | ((long)var7[1] & 255L) << 48 - | ((long)var7[2] & 255L) << 40 - | ((long)var7[3] & 255L) << 32 - | ((long)var7[4] & 255L) << 24 - | ((long)var7[5] & 255L) << 16 - | ((long)var7[6] & 255L) << 8 - | (long)var7[7] & 255L; - var45 = 0; - } + var4 = ">\u0099\u0092í\u0001\n\u0081U\u008a±Å\u001bæLÈ-"; + var5 = 16; + var2 = 0; } - } - var9 = var10.charAt(var8); + byte var16 = var2; + var2 += 8; + var7 = var4.substring(var16, var2).getBytes("ISO-8859-1"); + var10000 = var6; + var10001 = var3++; + var10002 = ((long)var7[0] & 255L) << 56 + | ((long)var7[1] & 255L) << 48 + | ((long)var7[2] & 255L) << 40 + | ((long)var7[3] & 255L) << 32 + | ((long)var7[4] & 255L) << 24 + | ((long)var7[5] & 255L) << 16 + | ((long)var7[6] & 255L) << 8 + | (long)var7[7] & 255L; + var10003 = 0; + } } } From fa3bfbb9b4673b7fb0dc2fc996b9038e19aa7948 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:04:32 +0700 Subject: [PATCH 10/15] String transformer doesn't work with Long encryption MPC --- .../core/other/composed/ComposedZelixTransformer.java | 2 +- .../core/other/impl/zkm/ZelixStringTransformer.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java index ae47974d..a9b96ecf 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedZelixTransformer.java @@ -44,7 +44,7 @@ public ComposedZelixTransformer(boolean experimental, Map classI InlineStaticFieldTransformer::new, UniversalNumberTransformer::new, - ZelixStringTransformer::new, +// ZelixStringTransformer::new, // Cleanup ComposedPeepholeCleanTransformer::new diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 1e41e59e..b5b29261 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -91,6 +91,7 @@ protected void transform() throws Exception { for (MethodNode method : classWrapper.methods()) { MethodContext methodContext = MethodContext.of(classWrapper, method); + DECRYPTION_MATCH.findAllMatches(methodContext).forEach(matchContext -> { int key1 = matchContext.captures().get("key1").insn().asInteger(); int key2 = matchContext.captures().get("key2").insn().asInteger(); @@ -146,7 +147,7 @@ protected void transform() throws Exception { context().removeCompiledClass(tmpClassWrapper); - if (isDecryptedFully.get()) + if (isDecryptedFully.get() ) cleanUpFunction(classWrapper); }); } From d4e46b8ac44b1149c3f677c521630307879d554c Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sun, 7 Sep 2025 20:28:42 +0700 Subject: [PATCH 11/15] improve --- .../impl/zkm/ZelixStringTransformer.java | 101 +++---- .../zkm/ZelixStringTwoCipherTransformer.java | 271 ------------------ 2 files changed, 53 insertions(+), 319 deletions(-) delete mode 100644 deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index b5b29261..fa097343 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -48,12 +48,6 @@ public class ZelixStringTransformer extends Transformer { MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node") ); - private static final Match STRING_ARRAY_MATCH = SequenceMatch.of( - NumberMatch.numInteger(), - OpcodeMatch.of(ANEWARRAY), - OpcodeMatch.of(ASTORE).capture("inject-insn") - ); - @Override protected void transform() throws Exception { Map encryptedClassWrapper = new HashMap<>(); @@ -113,17 +107,31 @@ protected void transform() throws Exception { }); } - if (classWrapper.isEnumClass()) { - SimpleArrayValue arrayValue = (SimpleArrayValue) sandBox.getInvocationUtil().invokeReference( - clazz.getMethod("getArr", "()[Ljava/lang/String;") - ); + SimpleArrayValue arrayValue = (SimpleArrayValue) sandBox.getInvocationUtil().invokeReference( + clazz.getMethod("getArr", "()[Ljava/lang/String;") + ); + + int length = arrayValue.getLength(); + MethodNode clinitMethod = classWrapper.findClInit().get(); + + VarInsnNode startArrayInsn = null; - int length = arrayValue.getLength(); + for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { + if (insn instanceof LabelNode) + break; + + if (insn.getOpcode() == Opcodes.INVOKESTATIC) + clinitMethod.instructions.remove(insn); + else if (insn instanceof VarInsnNode varInsnNode && + insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && + insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() + ) { + startArrayInsn = varInsnNode; + break; + } + } - MethodNode clinitMethod = classWrapper.findClInit().get(); - MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); - MatchContext match2 = STRING_ARRAY_MATCH.findFirstMatch(methodContext); - VarInsnNode fieldNode = (VarInsnNode) match2.captures().get("inject-insn").insn(); + if (startArrayInsn != null) { InsnList insnList = new InsnList(); for (int i = 0; i < length; i++) { @@ -131,13 +139,13 @@ protected void transform() throws Exception { String decryptedString = sandBox.vm().getOperations().readUtf8(value); // insert data - insnList.add(new VarInsnNode(Opcodes.ALOAD, fieldNode.var)); + insnList.add(new VarInsnNode(Opcodes.ALOAD, startArrayInsn.var)); insnList.add(AsmHelper.numberInsn(i)); insnList.add(new LdcInsnNode(decryptedString)); insnList.add(new InsnNode(Opcodes.AASTORE)); } - match2.insnContext().methodNode().instructions.insert(fieldNode, insnList); + clinitMethod.instructions.insert(startArrayInsn, insnList); markChange(); } @@ -147,7 +155,7 @@ protected void transform() throws Exception { context().removeCompiledClass(tmpClassWrapper); - if (isDecryptedFully.get() ) + if (isDecryptedFully.get()) cleanUpFunction(classWrapper); }); } @@ -196,19 +204,17 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit classNode.methods.add(clinit); - if (classWrapper.isEnumClass()) { - classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null)); + // create a tmp array for getting data + classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null)); - // add function - MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]); - getMethodNode.visitCode(); - getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, "arr", "[Ljava/lang/String;"); - getMethodNode.visitInsn(Opcodes.ARETURN); - getMethodNode.visitMaxs(1, 0); - getMethodNode.visitEnd(); + MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]); + getMethodNode.visitCode(); + getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, "arr", "[Ljava/lang/String;"); + getMethodNode.visitInsn(Opcodes.ARETURN); + getMethodNode.visitMaxs(1, 0); + getMethodNode.visitEnd(); - classNode.methods.add(getMethodNode); - } + classNode.methods.add(getMethodNode); // copy array JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); @@ -223,6 +229,11 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit } } + classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { + if (!classNode.methods.contains(method)) + classNode.methods.add(method); + }); + classNode.accept(cw); return cw.toByteArray(); } @@ -235,8 +246,6 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { cr.accept(classNode, 0); - String tmpClassName = "tmp/" + classWrapper.name(); - MethodNode clinitMethod = classNode.methods.stream() .filter(methodNode -> methodNode.name.equals("")) .findFirst() @@ -249,38 +258,34 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); AbstractInsnNode currentInsn = jumpInsnNode.label; + // mark a return opcode while (currentInsn != null) { if (currentInsn.getOpcode() == Opcodes.GOTO) { - if (classWrapper.isEnumClass()) { - MatchContext match2 = STRING_ARRAY_MATCH.findFirstMatch(methodContext); - VarInsnNode fieldNode = (VarInsnNode) match2.captures().get("inject-insn").insn(); - MethodNode methodNode = match2.insnContext().methodNode(); - methodNode.instructions.insertBefore(currentInsn, new VarInsnNode(Opcodes.ALOAD, fieldNode.var)); - methodNode.instructions.insertBefore(currentInsn, new FieldInsnNode(Opcodes.PUTSTATIC, tmpClassName, "arr", "[Ljava/lang/String;")); - } - - match.insnContext().methodNode().instructions.insert(currentInsn, new InsnNode(Opcodes.RETURN)); - match.insnContext().methodNode().instructions.remove(currentInsn); - break; + MethodNode methodNode = match.insnContext().methodNode(); + methodNode.instructions.insert(currentInsn, new InsnNode(Opcodes.RETURN)); + methodNode.instructions.remove(currentInsn); } currentInsn = currentInsn.getNext(); } - for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { if (insn instanceof LabelNode) break; if (insn.getOpcode() == Opcodes.INVOKESTATIC) clinitMethod.instructions.remove(insn); + else if (insn instanceof VarInsnNode varInsnNode && + insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && + insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() + ) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(Opcodes.ALOAD, varInsnNode.var)); + list.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, "arr", "[Ljava/lang/String;")); + clinitMethod.instructions.insert(insn, list); + } } - classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { - if (!classNode.methods.contains(method)) - classNode.methods.add(method); - }); - for (MethodNode methodNode : classNode.methods) { for (AbstractInsnNode insn : methodNode.instructions) { if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) { diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java deleted file mode 100644 index fd50675c..00000000 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTwoCipherTransformer.java +++ /dev/null @@ -1,271 +0,0 @@ -package uwu.narumi.deobfuscator.core.other.impl.zkm; - -import org.objectweb.asm.tree.*; -import uwu.narumi.deobfuscator.api.transformer.Transformer; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -public class ZelixStringTwoCipherTransformer extends Transformer { - - HashMap keyType1 = new HashMap<>(); - HashMap> keyType2 = new HashMap<>(); - HashMap staticArraySize = new HashMap<>(); - HashMap> offsets = new HashMap<>(); - - HashMap> encryptedStrings = new HashMap<>(); - - /* Written by https://github.com/Lampadina17 | OG 19/07/2024, Rewritten 09/08/2024 */ - @Override - protected void transform() throws Exception { - scopedClasses().forEach(classWrapper -> { - /* Extract key type 1 from hardcoded xor encryption */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.desc.equals("(Ljava/lang/String;)[C")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof IntInsnNode) - .filter(ain -> ain.getNext() instanceof InsnNode) - .filter(ain -> ain.getNext().getOpcode() == IXOR) - .map(IntInsnNode.class::cast) - .forEach(iin -> keyType1.put(classWrapper.name(), iin.operand))); - - /* Temporary variable */ - List key2 = new ArrayList<>(); - - /* Extract key type 2 from hardcoded switch case xor encryption */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.desc.equals("([C)Ljava/lang/String;")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof IntInsnNode) - .map(IntInsnNode.class::cast) - .forEach(iin -> key2.add((byte) iin.operand))); - - /* Store the key data by class */ - if (!key2.isEmpty()) keyType2.put(classWrapper.name(), key2); - - /* Retrieve array length (static block) */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> { - if (methodNode.instructions.getFirst() instanceof IntInsnNode && methodNode.instructions.getFirst().getNext() instanceof TypeInsnNode) { - staticArraySize.put(classWrapper.name(), ((IntInsnNode) methodNode.instructions.getFirst()).operand); - } - }); - - /* Temporary variable */ - List strings = new ArrayList<>(); - - /* Retrieve ciphered strings (static block) */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof LdcInsnNode) - .map(LdcInsnNode.class::cast) - .filter(ldc -> ldc.cst instanceof String) - .forEach(ldc -> strings.add((String) ldc.cst))); - - if (!strings.isEmpty()) encryptedStrings.put(classWrapper.name(), strings); - - /* Temporary Variable */ - List offsets = new ArrayList<>(); - - /* Retrieve weird zkm "offsets" */ - classWrapper.methods().stream() - .filter(methodNode -> methodNode.name.equals("")) - .forEach(methodNode -> - Arrays.stream(methodNode.instructions.toArray()) - .forEach(ain -> { - AbstractInsnNode prev = ain.getPrevious(); - if (prev != null && prev.getPrevious() != null && prev.previous() instanceof MethodInsnNode min && min.name.equals("length")) { - if (ain instanceof IntInsnNode iin) { - offsets.add(iin.operand); - } else if (ain instanceof InsnNode in && in.getOpcode() >= ICONST_M1 && in.getOpcode() <= ICONST_5) { - offsets.add(getValue(in)); - } - } - })); - - if (!offsets.isEmpty()) this.offsets.put(classWrapper.name(), offsets); - }); - - /* Decrypt and cleanup */ - scopedClasses().forEach(classWrapper -> { - classWrapper.methods().stream() - .forEach(methodNode -> { - List encrypted = encryptedStrings.get(classWrapper.name()); - List offsets = this.offsets.get(classWrapper.name()); - - if (encrypted != null && encrypted.size() == 2 && offsets != null && offsets.size() == 2) { - /* for classes that has big static block */ - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain.getOpcode() == AALOAD) - .filter(ain -> ain.getPrevious().getPrevious().getOpcode() == ALOAD) - .forEach(ain -> { - int index = 0; - if (ain.getPrevious() instanceof IntInsnNode iin) index = getValue(iin); - else if (ain.getPrevious() instanceof InsnNode in) index = getValue(in); - - List key2 = keyType2.get(classWrapper.name()); - - if (key2 != null) - try { - String[] decryptedStrings = ZKMCipher.StaticInit( - encrypted.get(0), - encrypted.get(1), - keyType1.get(classWrapper.name()), - shiftBytes(key2), - offsets.get(0), - offsets.get(1), - staticArraySize.get(classWrapper.name()), - key2.get(0)); - - /* Re-insert original string back to its place */ - methodNode.instructions.insert(ain, new LdcInsnNode(decryptedStrings[index])); - /* Cleanup */ - methodNode.instructions.remove(ain.getPrevious().getPrevious()); - methodNode.instructions.remove(ain.getPrevious()); - methodNode.instructions.remove(ain); - this.markChange(); - } catch (Exception e) { - } - }); - } else { - AtomicBoolean cleanup = new AtomicBoolean(false); - - List key2 = keyType2.get(classWrapper.name()); - - if (key2 != null) { - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain instanceof LdcInsnNode) - .map(LdcInsnNode.class::cast) - .filter(ldc -> ldc.cst instanceof String) - .forEach(ldc -> { - try { - ldc.cst = ZKMCipher.cipher2(ZKMCipher.cipher1((String) ldc.cst, keyType1.get(classWrapper.name())), shiftBytes(key2), key2.get(0)); - cleanup.set(true); - } catch (Exception e) { - } - }); - - if (cleanup.get()) - Arrays.stream(methodNode.instructions.toArray()) - .filter(ain -> ain.getOpcode() == SWAP) - .filter(ain -> ain.getNext() != null && ain.getNext() instanceof MethodInsnNode) - .filter(ain -> ain.getNext().getNext() != null && ain.getNext().getNext() instanceof MethodInsnNode) - .filter(ain -> ain.getNext().getNext().getNext() != null && ain.getNext().getNext().getNext().getOpcode() == SWAP) - .forEach(ain -> { - /* Do cleanup */ - methodNode.instructions.remove(ain.getNext().getNext().getNext()); - methodNode.instructions.remove(ain.getNext().getNext()); - methodNode.instructions.remove(ain.getNext()); - methodNode.instructions.remove(ain); - this.markChange(); - }); - } - } - }); - }); - LOGGER.info("Decrypted {} strings in {} classes", this.getChangesCount(), scopedClasses().size()); - } - - /* Convert arraylist to array and shift values, when a bug transform into a feature (Key type 2) */ - private byte[] shiftBytes(List input) { - byte[] keyBytes = new byte[input.size() - 1]; - - int j = 1; - for (int i = 0; i < keyBytes.length; i++) { - keyBytes[i] = input.get(j); - j++; - } - return keyBytes; - } - - public int getValue(AbstractInsnNode in) { - int opcode = in.getOpcode(); - return switch (opcode) { - case ICONST_M1 -> -1; - case ICONST_0 -> 0; - case ICONST_1 -> 1; - case ICONST_2 -> 2; - case ICONST_3 -> 3; - case ICONST_4 -> 4; - case ICONST_5 -> 5; - case SIPUSH, BIPUSH -> ((IntInsnNode) in).operand; - default -> throw new RuntimeException("Unsupported opcode"); - }; - } - - public static class ZKMCipher { - - public static char[] cipher1(final String var0, final int key) { // All old versions - final char[] input = var0.toCharArray(); - if (input.length < 2) { - input[0] ^= key; - } - return input; - } - - public static String cipher2(final char[] input, final byte[] keys, final int length) throws Exception { - if (keys.length != length) throw new Exception("Key is invalid"); - for (int i = 0; input.length > i; ++i) { - input[i] ^= (char) keys[i % length]; - } - return (new String(input)).intern(); - } - - public static String[] StaticInit(String encrypted1, String encrypted2, int key1, byte[] key2, int offset, int offset2, int arraysize, int length) throws Exception { - final String[] h2 = new String[arraysize]; - int n = 0; - String s; - int n2 = (s = encrypted1).length(); - int n3 = offset; - int n4 = -1; - - Label_0023: - while (true) { - while (true) { - ++n4; - final String s2 = s; - final int n5 = n4; - String s3 = s2.substring(n5, n5 + n3); - int n6 = -1; - while (true) { - final String a = ZKMCipher.cipher2(ZKMCipher.cipher1(s3, key1), key2, length); - switch (n6) { - default: { - h2[n++] = a; - if ((n4 += n3) < n2) { - n3 = s.charAt(n4); - continue Label_0023; - } - n2 = (s = encrypted2).length(); - n3 = offset2; - n4 = -1; - break; - } - case 0: { - h2[n++] = a; - if ((n4 += n3) < n2) { - n3 = s.charAt(n4); - break; - } - break Label_0023; - } - } - ++n4; - final String s4 = s; - final int n7 = n4; - s3 = s4.substring(n7, n7 + n3); - n6 = 0; - } - } - } - return h2; - } - } -} From 27089addffa2662aa8c28b7c1deaba978590a403 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Sun, 7 Sep 2025 21:51:37 +0700 Subject: [PATCH 12/15] cleanup --- .../impl/zkm/ZelixStringTransformer.java | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index fa097343..03d7c8ee 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -21,10 +21,7 @@ import uwu.narumi.deobfuscator.api.helper.AsmHelper; import uwu.narumi.deobfuscator.api.transformer.Transformer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -78,7 +75,7 @@ protected void transform() throws Exception { SandBox sandBox = new SandBox(context()); encryptedClassWrapper.forEach((classWrapper, tmpClassWrapper) -> { - AtomicBoolean isDecryptedFully = new AtomicBoolean(true); + AtomicBoolean shouldCleanArray = new AtomicBoolean(true); try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); @@ -102,11 +99,12 @@ protected void transform() throws Exception { matchContext.removeAll(); markChange(); } catch (Exception e) { - isDecryptedFully.set(false); + shouldCleanArray.set(false); } }); } + // TODO: Access directly array without getter SimpleArrayValue arrayValue = (SimpleArrayValue) sandBox.getInvocationUtil().invokeReference( clazz.getMethod("getArr", "()[Ljava/lang/String;") ); @@ -155,7 +153,7 @@ else if (insn instanceof VarInsnNode varInsnNode && context().removeCompiledClass(tmpClassWrapper); - if (isDecryptedFully.get()) + if (shouldCleanArray.get()) cleanUpFunction(classWrapper); }); } @@ -167,27 +165,19 @@ private void cleanUpFunction(ClassWrapper classWrapper) { MatchContext match = matches.get(matches.size() - 1); JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); - AbstractInsnNode currentInsn = jumpInsnNode.label; - List removedInsns = new ArrayList<>(); - LabelNode firstLabel = null; + Arrays.stream(clinitMethod.instructions.toArray()).filter(insn -> insn instanceof LabelNode).map(insn -> (LabelNode) insn).findFirst().ifPresent(firstLabel -> { + AbstractInsnNode currentInsn = jumpInsnNode.label; - for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { - if (insn instanceof LabelNode labelNode) { - firstLabel = labelNode; - break; + List removedInsns = new ArrayList<>(); + while (currentInsn != firstLabel) { + currentInsn = currentInsn.getPrevious(); + removedInsns.add(currentInsn); } - } - - if (firstLabel == null) - return; - while (currentInsn != firstLabel) { - currentInsn = currentInsn.getPrevious(); - removedInsns.add(currentInsn); - } + removedInsns.forEach(insn -> clinitMethod.instructions.remove(insn)); + }); - removedInsns.forEach(insn -> clinitMethod.instructions.remove(insn)); classWrapper.methods().removeIf(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")); } @@ -205,6 +195,7 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit classNode.methods.add(clinit); // create a tmp array for getting data + // TODO: Can we find a way to access direcly variable without getter? classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null)); MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]); @@ -229,7 +220,7 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit } } - classWrapper.findMethod(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")).ifPresent(method -> { + classWrapper.findMethod(this::isDecryptMethod).ifPresent(method -> { if (!classNode.methods.contains(method)) classNode.methods.add(method); }); @@ -238,6 +229,15 @@ private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit return cw.toByteArray(); } + private boolean isDecryptMethod(MethodNode methodNode) { + if (!methodNode.desc.equals("(II)Ljava/lang/String;")) + return false; + + if ((methodNode.access & (ACC_PRIVATE | ACC_STATIC)) == 0) + return false; + + return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH); + } private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { ClassReader cr = new ClassReader(classByte); @@ -273,9 +273,11 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { if (insn instanceof LabelNode) break; - if (insn.getOpcode() == Opcodes.INVOKESTATIC) + if (insn.getOpcode() == Opcodes.INVOKESTATIC) { clinitMethod.instructions.remove(insn); - else if (insn instanceof VarInsnNode varInsnNode && + } else if (insn instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && !typeInsnNode.desc.equals("java/lang/String")) { + typeInsnNode.desc = "java/lang/String"; + } else if (insn instanceof VarInsnNode varInsnNode && insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() ) { From 006eae645df6f9642da694513f0a94ae78924892 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Mon, 8 Sep 2025 08:50:35 +0700 Subject: [PATCH 13/15] improve --- .../impl/zkm/ZelixStringTransformer.java | 213 +++++++----------- .../other/impl/zkm/helper/ZelixAsmHelper.java | 50 ++++ .../helper/ZelixStringDecryptionMatch.java | 87 +++++++ 3 files changed, 220 insertions(+), 130 deletions(-) create mode 100644 deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java create mode 100644 deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java index 03d7c8ee..673e398a 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java @@ -4,7 +4,6 @@ import dev.xdark.ssvm.mirror.type.InstanceClass; import dev.xdark.ssvm.value.ObjectValue; import dev.xdark.ssvm.value.SimpleArrayValue; -import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; @@ -15,13 +14,16 @@ import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.JumpMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch; -import uwu.narumi.deobfuscator.api.asm.matcher.impl.NumberMatch; import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; import uwu.narumi.deobfuscator.api.execution.SandBox; import uwu.narumi.deobfuscator.api.helper.AsmHelper; import uwu.narumi.deobfuscator.api.transformer.Transformer; +import uwu.narumi.deobfuscator.core.other.impl.zkm.helper.ZelixAsmHelper; +import uwu.narumi.deobfuscator.core.other.impl.zkm.helper.ZelixStringDecryptionMatch; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -39,15 +41,11 @@ public class ZelixStringTransformer extends Transformer { JumpMatch.of(GOTO) ); - private static final Match DECRYPTION_MATCH = SequenceMatch.of( - NumberMatch.numInteger().capture("key1"), - NumberMatch.numInteger().capture("key2"), - MethodMatch.create().desc("(II)Ljava/lang/String;").capture("method-node") - ); + private static final Match DECRYPTION_MATCH = new ZelixStringDecryptionMatch(); @Override protected void transform() throws Exception { - Map encryptedClassWrapper = new HashMap<>(); + List encryptedClassWrapper = new ArrayList<>(); for (ClassWrapper classWrapper : scopedClasses()) { if (classWrapper.findClInit().isEmpty()) @@ -63,45 +61,51 @@ protected void transform() throws Exception { MatchContext match = matches.get(matches.size() - 1); // copy clinitMethod - byte[] clonedClass = cloneClassWithClinit(classWrapper, clinitMethod, match); - byte[] modifiedClass = modifyByteCode(classWrapper, clonedClass); + byte[] clonedClass = copyToTempClass(classWrapper, clinitMethod, match); - ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", modifiedClass); + ClassWrapper tmpClassWrapper = context().addCompiledClass("tmp/" + classWrapper.name() + ".class", clonedClass); if (tmpClassWrapper == null) continue; - encryptedClassWrapper.put(classWrapper, tmpClassWrapper); + encryptedClassWrapper.add(new ClassData(classWrapper, clinitMethod, tmpClassWrapper, match)); } SandBox sandBox = new SandBox(context()); - encryptedClassWrapper.forEach((classWrapper, tmpClassWrapper) -> { - AtomicBoolean shouldCleanArray = new AtomicBoolean(true); + + for (ClassData classData : encryptedClassWrapper) { + AtomicBoolean shouldCleanArray = new AtomicBoolean(false); try { - InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classWrapper.canonicalName()); - - for (MethodNode method : classWrapper.methods()) { - MethodContext methodContext = MethodContext.of(classWrapper, method); - - DECRYPTION_MATCH.findAllMatches(methodContext).forEach(matchContext -> { - int key1 = matchContext.captures().get("key1").insn().asInteger(); - int key2 = matchContext.captures().get("key2").insn().asInteger(); - MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); - - try { - String decryptedString = sandBox.getInvocationUtil().invokeStringReference( - clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), - Argument.int32(key1), - Argument.int32(key2) - ); - - matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString)); - matchContext.removeAll(); - markChange(); - } catch (Exception e) { - shouldCleanArray.set(false); - } - }); + InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classData.classWrapper.canonicalName()); + + for (MethodNode method : classData.classWrapper.methods()) { + MethodContext methodContext = MethodContext.of(classData.classWrapper, method); + + List matches = DECRYPTION_MATCH.findAllMatches(methodContext); + + if (!matches.isEmpty()) { + shouldCleanArray.set(true); + matches.forEach(matchContext -> { + int key1 = matchContext.captures().get("key1").insn().asInteger(); + int key2 = matchContext.captures().get("key2").insn().asInteger(); + MethodInsnNode decryptedMethod = matchContext.captures().get("method-node").insn().asMethodInsn(); + + try { + String decryptedString = sandBox.getInvocationUtil().invokeStringReference( + clazz.getMethod(decryptedMethod.name, decryptedMethod.desc), + Argument.int32(key1), + Argument.int32(key2) + ); + + matchContext.insnContext().methodNode().instructions.insert(matchContext.insn(), new LdcInsnNode(decryptedString)); + matchContext.removeAll(); + markChange(); + } catch (Exception e) { + shouldCleanArray.set(false); + } + }); + } + } // TODO: Access directly array without getter @@ -110,16 +114,15 @@ protected void transform() throws Exception { ); int length = arrayValue.getLength(); - MethodNode clinitMethod = classWrapper.findClInit().get(); VarInsnNode startArrayInsn = null; - for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { + for (AbstractInsnNode insn : classData.clinitMethod.instructions.toArray()) { if (insn instanceof LabelNode) break; if (insn.getOpcode() == Opcodes.INVOKESTATIC) - clinitMethod.instructions.remove(insn); + classData.clinitMethod.instructions.remove(insn); else if (insn instanceof VarInsnNode varInsnNode && insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() @@ -143,7 +146,7 @@ else if (insn instanceof VarInsnNode varInsnNode && insnList.add(new InsnNode(Opcodes.AASTORE)); } - clinitMethod.instructions.insert(startArrayInsn, insnList); + classData.clinitMethod.instructions.insert(startArrayInsn, insnList); markChange(); } @@ -151,22 +154,17 @@ else if (insn instanceof VarInsnNode varInsnNode && e.printStackTrace(); } - context().removeCompiledClass(tmpClassWrapper); + context().removeCompiledClass(classData.tempClassWrapper); if (shouldCleanArray.get()) - cleanUpFunction(classWrapper); - }); + cleanUpFunction(classData); + } } - private void cleanUpFunction(ClassWrapper classWrapper) { - MethodNode clinitMethod = classWrapper.findClInit().get(); - MethodContext methodContext = MethodContext.of(classWrapper, clinitMethod); - List matches = CLINIT_DETECTION.findAllMatches(methodContext); - MatchContext match = matches.get(matches.size() - 1); + private void cleanUpFunction(ClassData classData) { + JumpInsnNode jumpInsnNode = classData.match.captures().get("label").insn().asJump(); - JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); - - Arrays.stream(clinitMethod.instructions.toArray()).filter(insn -> insn instanceof LabelNode).map(insn -> (LabelNode) insn).findFirst().ifPresent(firstLabel -> { + Arrays.stream(classData.clinitMethod.instructions.toArray()).filter(insn -> insn instanceof LabelNode).map(insn -> (LabelNode) insn).findFirst().ifPresent(firstLabel -> { AbstractInsnNode currentInsn = jumpInsnNode.label; List removedInsns = new ArrayList<>(); @@ -175,88 +173,49 @@ private void cleanUpFunction(ClassWrapper classWrapper) { removedInsns.add(currentInsn); } - removedInsns.forEach(insn -> clinitMethod.instructions.remove(insn)); + removedInsns.forEach(insn -> classData.clinitMethod.instructions.remove(insn)); }); - classWrapper.methods().removeIf(methodNode -> methodNode.desc.equals("(II)Ljava/lang/String;")); + classData.classWrapper.methods().removeIf(ZelixAsmHelper::isStringDecryptMethod); } - private byte[] cloneClassWithClinit(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - ClassNode classNode = new ClassNode(); - + private byte[] copyToTempClass(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { String tmpClassName = "tmp/" + classWrapper.name(); - classNode.access = ACC_PUBLIC | ACC_STATIC; - classNode.name = tmpClassName; - classNode.version = classWrapper.classNode().version; - classNode.superName = "java/lang/Object"; - - classNode.methods.add(clinit); - - // create a tmp array for getting data - // TODO: Can we find a way to access direcly variable without getter? - classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, "arr", "[Ljava/lang/String;", null, null)); - - MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, "getArr", "()[Ljava/lang/String;", null, new String[0]); - getMethodNode.visitCode(); - getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, "arr", "[Ljava/lang/String;"); - getMethodNode.visitInsn(Opcodes.ARETURN); - getMethodNode.visitMaxs(1, 0); - getMethodNode.visitEnd(); - - classNode.methods.add(getMethodNode); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + ClassNode classNode = new ClassNode(); + classNode.visit( + classWrapper.classNode().version, + ACC_PUBLIC | ACC_SUPER, + "tmp/" + classWrapper.name(), + "", + "java/lang/Object", + new String[0] + ); + + MethodNode copiedClinit = AsmHelper.copyMethod(clinit); + classNode.methods.add(copiedClinit); + ZelixAsmHelper.createTempFieldForSSVM(classNode, tmpClassName, "arr", "getArr", "[Ljava/lang/String;"); // copy array JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); AbstractInsnNode node = jumpInsnNode.label; - while (node.getOpcode() != Opcodes.GOTO) { + while (node.getOpcode() != Opcodes.GOTO) { // is it safe? node = node.getNext(); - if (node instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) { - FieldNode fieldNode = classWrapper.findField(fieldInsnNode.name, fieldInsnNode.desc).get(); - classNode.fields.add(fieldNode); - } + if (node instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) + classWrapper.findField(fieldInsnNode.name, fieldInsnNode.desc).ifPresent(fieldNode -> classNode.fields.add(fieldNode)); } - classWrapper.findMethod(this::isDecryptMethod).ifPresent(method -> { - if (!classNode.methods.contains(method)) - classNode.methods.add(method); - }); - - classNode.accept(cw); - return cw.toByteArray(); - } + classWrapper.findMethod(ZelixAsmHelper::isStringDecryptMethod).ifPresent(method -> classNode.methods.add(AsmHelper.copyMethod(method))); - private boolean isDecryptMethod(MethodNode methodNode) { - if (!methodNode.desc.equals("(II)Ljava/lang/String;")) - return false; - - if ((methodNode.access & (ACC_PRIVATE | ACC_STATIC)) == 0) - return false; - - return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH); - } - - private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { - ClassReader cr = new ClassReader(classByte); - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - ClassNode classNode = new ClassNode(); - - cr.accept(classNode, 0); - - MethodNode clinitMethod = classNode.methods.stream() - .filter(methodNode -> methodNode.name.equals("")) - .findFirst() - .get(); - - MethodContext methodContext = MethodContext.of(classNode, clinitMethod); + MethodContext methodContext = MethodContext.of(classNode, copiedClinit); List matches = CLINIT_DETECTION.findAllMatches(methodContext); - MatchContext match = matches.get(matches.size() - 1); + MatchContext match2 = matches.get(matches.size() - 1); - JumpInsnNode jumpInsnNode = match.captures().get("label").insn().asJump(); - AbstractInsnNode currentInsn = jumpInsnNode.label; + JumpInsnNode jumpInsnNode2 = match2.captures().get("label").insn().asJump(); + AbstractInsnNode currentInsn = jumpInsnNode2.label; // mark a return opcode while (currentInsn != null) { @@ -269,12 +228,12 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { currentInsn = currentInsn.getNext(); } - for (AbstractInsnNode insn : clinitMethod.instructions.toArray()) { + for (AbstractInsnNode insn : copiedClinit.instructions.toArray()) { if (insn instanceof LabelNode) break; if (insn.getOpcode() == Opcodes.INVOKESTATIC) { - clinitMethod.instructions.remove(insn); + copiedClinit.instructions.remove(insn); } else if (insn instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && !typeInsnNode.desc.equals("java/lang/String")) { typeInsnNode.desc = "java/lang/String"; } else if (insn instanceof VarInsnNode varInsnNode && @@ -284,21 +243,15 @@ private byte[] modifyByteCode(ClassWrapper classWrapper, byte[] classByte) { InsnList list = new InsnList(); list.add(new VarInsnNode(Opcodes.ALOAD, varInsnNode.var)); list.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, "arr", "[Ljava/lang/String;")); - clinitMethod.instructions.insert(insn, list); + copiedClinit.instructions.insert(insn, list); } } - for (MethodNode methodNode : classNode.methods) { - for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) { - methodInsnNode.owner = "tmp/" + methodInsnNode.owner; - } else if (insn instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) { - fieldInsnNode.owner = "tmp/" + fieldInsnNode.owner; - } - } - } + ZelixAsmHelper.renameOwner(classNode, classWrapper); classNode.accept(cw); return cw.toByteArray(); } + + record ClassData(ClassWrapper classWrapper, MethodNode clinitMethod, ClassWrapper tempClassWrapper, MatchContext match) { } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java new file mode 100644 index 00000000..01f73936 --- /dev/null +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java @@ -0,0 +1,50 @@ +package uwu.narumi.deobfuscator.core.other.impl.zkm.helper; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; +import uwu.narumi.deobfuscator.api.asm.ClassWrapper; + +import java.util.Arrays; + +import static org.objectweb.asm.Opcodes.*; + +public class ZelixAsmHelper { + + public static void createTempFieldForSSVM(ClassNode classNode, String tmpClassName, String name, String getterName, String desc) { + classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, name, desc, null, null)); + + MethodNode getMethodNode = new MethodNode(ACC_PUBLIC | ACC_STATIC, getterName, "()" + desc, null, new String[0]); + getMethodNode.visitCode(); + getMethodNode.visitFieldInsn(Opcodes.GETSTATIC, tmpClassName, name, desc); + getMethodNode.visitInsn(Opcodes.ARETURN); + getMethodNode.visitMaxs(1, 0); + getMethodNode.visitEnd(); + + classNode.methods.add(getMethodNode); + } + + public static boolean isStringDecryptMethod(MethodNode methodNode) { + if (!methodNode.desc.equals("(II)Ljava/lang/String;")) + return false; + + if ((methodNode.access & (ACC_PRIVATE | ACC_STATIC)) == 0) + return false; + + return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH); + } + + // TODO: Can we find a way to access direcly variable without getter? + + public static void renameOwner(ClassNode classNode, ClassWrapper classWrapper) { + for (MethodNode methodNode : classNode.methods) { + for (AbstractInsnNode insn : methodNode.instructions) { + if (insn instanceof MethodInsnNode methodInsnNode && methodInsnNode.owner.equals(classWrapper.name())) { + methodInsnNode.owner = "tmp/" + methodInsnNode.owner; + } else if (insn instanceof FieldInsnNode fieldInsnNode && fieldInsnNode.owner.equals(classWrapper.name())) { + fieldInsnNode.owner = "tmp/" + fieldInsnNode.owner; + } + } + } + } + +} diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java new file mode 100644 index 00000000..302ad8f8 --- /dev/null +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java @@ -0,0 +1,87 @@ +package uwu.narumi.deobfuscator.core.other.impl.zkm.helper; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.OriginalSourceValue; +import uwu.narumi.deobfuscator.api.asm.ClassWrapper; +import uwu.narumi.deobfuscator.api.asm.MethodContext; +import uwu.narumi.deobfuscator.api.asm.matcher.Match; +import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; + +import java.util.Map; + +import static org.objectweb.asm.Opcodes.INVOKESTATIC; + +/** + * Better detection of Zelix string decryption calls. + * @author d-o-g + */ +public class ZelixStringDecryptionMatch extends Match { + + @Override + protected boolean test(MatchContext ctx) { + AbstractInsnNode insn = ctx.insn(); + if (!(insn instanceof MethodInsnNode call)) + return false; + + if (!"(II)Ljava/lang/String;".equals(call.desc)) { + return false; + } + + ClassWrapper classWrapper = ctx.insnContext().methodContext().classWrapper(); // never match if the method's owner is not equals to classwrapper's name + if (!call.owner.equals(classWrapper.name())) + return false; + + if (call.getOpcode() != INVOKESTATIC) { + return false; + } + + MethodContext mc = ctx.insnContext().methodContext(); + + Map> frms = mc.frames(); + Frame f = frms.get(insn); + if (f == null) { + return false; + } + + OriginalSourceValue arg2 = f.getStack(f.getStackSize() - 1); + OriginalSourceValue arg1 = f.getStack(f.getStackSize() - 2); + + AbstractInsnNode p1 = singleConstProducer(arg1); + AbstractInsnNode p2 = singleConstProducer(arg2); + if (p1 == null || p2 == null) { + return false; + } + + ctx.captures().put("key1", MatchContext.of(mc.at(p1))); + ctx.captures().put("key2", MatchContext.of(mc.at(p2))); + ctx.captures().put("method-node", ctx); + + ctx.collectedInsns().add(p1); + ctx.collectedInsns().add(p2); + ctx.collectedInsns().add(insn); + return true; + } + + private static AbstractInsnNode singleConstProducer(OriginalSourceValue sv) { + if (sv == null || sv.insns == null || sv.insns.isEmpty()) { + return null; + } + + AbstractInsnNode only = null; + for (AbstractInsnNode p : sv.insns) { + if (!p.isInteger()) { + return null; + } + + if (only == null) { + only = p; + } else if (only != p) { + return null; + } + } + + return only; + } +} From 012409ccb013bfb99a9c71d65108e3caf7b6e682 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:25:56 +0700 Subject: [PATCH 14/15] improve cleaning up --- ... => ZelixStringMultiArrayTransformer.java} | 89 ++++++++++++------- .../other/impl/zkm/helper/ZelixAsmHelper.java | 3 +- .../helper/ZelixStringDecryptionMatch.java | 7 +- 3 files changed, 59 insertions(+), 40 deletions(-) rename deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/{ZelixStringTransformer.java => ZelixStringMultiArrayTransformer.java} (76%) diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringMultiArrayTransformer.java similarity index 76% rename from deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java rename to deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringMultiArrayTransformer.java index 673e398a..c5ed7472 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/ZelixStringMultiArrayTransformer.java @@ -12,9 +12,7 @@ import uwu.narumi.deobfuscator.api.asm.matcher.Match; import uwu.narumi.deobfuscator.api.asm.matcher.MatchContext; import uwu.narumi.deobfuscator.api.asm.matcher.group.SequenceMatch; -import uwu.narumi.deobfuscator.api.asm.matcher.impl.JumpMatch; -import uwu.narumi.deobfuscator.api.asm.matcher.impl.MethodMatch; -import uwu.narumi.deobfuscator.api.asm.matcher.impl.OpcodeMatch; +import uwu.narumi.deobfuscator.api.asm.matcher.impl.*; import uwu.narumi.deobfuscator.api.execution.SandBox; import uwu.narumi.deobfuscator.api.helper.AsmHelper; import uwu.narumi.deobfuscator.api.transformer.Transformer; @@ -24,13 +22,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; /** * A comprehensive string transformer using temp class to deobfuscate * @author toidicakhia */ -public class ZelixStringTransformer extends Transformer { +public class ZelixStringMultiArrayTransformer extends Transformer { private static final Match CLINIT_DETECTION = SequenceMatch.of( OpcodeMatch.of(ILOAD), JumpMatch.of(IF_ICMPGE).capture("label"), @@ -73,7 +70,6 @@ protected void transform() throws Exception { SandBox sandBox = new SandBox(context()); for (ClassData classData : encryptedClassWrapper) { - AtomicBoolean shouldCleanArray = new AtomicBoolean(false); try { InstanceClass clazz = sandBox.getHelper().loadClass("tmp." + classData.classWrapper.canonicalName()); @@ -84,7 +80,7 @@ protected void transform() throws Exception { List matches = DECRYPTION_MATCH.findAllMatches(methodContext); if (!matches.isEmpty()) { - shouldCleanArray.set(true); + classData.canCleanUp = true; matches.forEach(matchContext -> { int key1 = matchContext.captures().get("key1").insn().asInteger(); int key2 = matchContext.captures().get("key2").insn().asInteger(); @@ -101,7 +97,7 @@ protected void transform() throws Exception { matchContext.removeAll(); markChange(); } catch (Exception e) { - shouldCleanArray.set(false); + classData.isCaughtFromException = true; } }); } @@ -114,6 +110,12 @@ protected void transform() throws Exception { ); int length = arrayValue.getLength(); + String[] decryptedArray = new String[length]; + for (int i = 0; i < length; i++) { + ObjectValue value = arrayValue.getReference(i); + String decryptedString = sandBox.vm().getOperations().readUtf8(value); + decryptedArray[i] = decryptedString; + } VarInsnNode startArrayInsn = null; @@ -121,42 +123,26 @@ protected void transform() throws Exception { if (insn instanceof LabelNode) break; - if (insn.getOpcode() == Opcodes.INVOKESTATIC) - classData.clinitMethod.instructions.remove(insn); - else if (insn instanceof VarInsnNode varInsnNode && + if (insn instanceof VarInsnNode varInsnNode && insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && - insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() + typeInsnNode.getPrevious() != null && typeInsnNode.getPrevious().isInteger() ) { startArrayInsn = varInsnNode; break; } } - if (startArrayInsn != null) { - InsnList insnList = new InsnList(); - - for (int i = 0; i < length; i++) { - ObjectValue value = arrayValue.getReference(i); - String decryptedString = sandBox.vm().getOperations().readUtf8(value); - - // insert data - insnList.add(new VarInsnNode(Opcodes.ALOAD, startArrayInsn.var)); - insnList.add(AsmHelper.numberInsn(i)); - insnList.add(new LdcInsnNode(decryptedString)); - insnList.add(new InsnNode(Opcodes.AASTORE)); - } - - classData.clinitMethod.instructions.insert(startArrayInsn, insnList); - markChange(); - } + inlineValueOnArray(classData, startArrayInsn, decryptedArray); + classData.canCleanUp = true; } catch (Exception e) { e.printStackTrace(); + classData.isCaughtFromException = true; } context().removeCompiledClass(classData.tempClassWrapper); - if (shouldCleanArray.get()) + if (classData.canCleanUp && !classData.isCaughtFromException) cleanUpFunction(classData); } } @@ -179,6 +165,25 @@ private void cleanUpFunction(ClassData classData) { classData.classWrapper.methods().removeIf(ZelixAsmHelper::isStringDecryptMethod); } + private void inlineValueOnArray(ClassData classData, VarInsnNode startArrayInsn, String[] decryptedArray) { + Match LOCAL_GET_ARRAY_MATCH = SequenceMatch.of( + OpcodeMatch.of(matchContext -> matchContext.insn() instanceof VarInsnNode varInsnNode && varInsnNode.getOpcode() == ALOAD && varInsnNode.var == startArrayInsn.var), + NumberMatch.numInteger().capture("idx"), + OpcodeMatch.of(AALOAD) + ); + + MethodContext methodContext = MethodContext.of(classData.classWrapper, classData.clinitMethod); + LOCAL_GET_ARRAY_MATCH.findAllMatches(methodContext).forEach(matchContext -> { + int idx = matchContext.captures().get("idx").insn().asInteger(); + String decryptedString = decryptedArray[idx]; + + matchContext.insnContext().methodNode().instructions.insertBefore(matchContext.insn(), new LdcInsnNode(decryptedString)); + matchContext.removeAll(); + + markChange(); + }); + } + private byte[] copyToTempClass(ClassWrapper classWrapper, MethodNode clinit, MatchContext match) { String tmpClassName = "tmp/" + classWrapper.name(); @@ -232,13 +237,13 @@ private byte[] copyToTempClass(ClassWrapper classWrapper, MethodNode clinit, Mat if (insn instanceof LabelNode) break; - if (insn.getOpcode() == Opcodes.INVOKESTATIC) { + if (insn.getOpcode() == Opcodes.INVOKESTATIC) copiedClinit.instructions.remove(insn); - } else if (insn instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && !typeInsnNode.desc.equals("java/lang/String")) { + if (insn instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && !typeInsnNode.desc.equals("java/lang/String")) { typeInsnNode.desc = "java/lang/String"; } else if (insn instanceof VarInsnNode varInsnNode && insn.getPrevious() instanceof TypeInsnNode typeInsnNode && typeInsnNode.getOpcode() == Opcodes.ANEWARRAY && typeInsnNode.desc.equals("java/lang/String") && - insn.getPrevious().getPrevious() != null && insn.getPrevious().getPrevious().isInteger() + typeInsnNode.getPrevious() != null && typeInsnNode.getPrevious().isInteger() ) { InsnList list = new InsnList(); list.add(new VarInsnNode(Opcodes.ALOAD, varInsnNode.var)); @@ -253,5 +258,21 @@ private byte[] copyToTempClass(ClassWrapper classWrapper, MethodNode clinit, Mat return cw.toByteArray(); } - record ClassData(ClassWrapper classWrapper, MethodNode clinitMethod, ClassWrapper tempClassWrapper, MatchContext match) { } + static class ClassData { + public ClassWrapper classWrapper; + public MethodNode clinitMethod; + public ClassWrapper tempClassWrapper; + public MatchContext match; + + public boolean canCleanUp = false; + public boolean isCaughtFromException = false; + + public ClassData(ClassWrapper classWrapper, MethodNode clinitMethod, ClassWrapper tempClassWrapper, MatchContext match) { + this.classWrapper = classWrapper; + this.clinitMethod = clinitMethod; + this.tempClassWrapper = tempClassWrapper; + this.match = match; + } + + } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java index 01f73936..c82b8ade 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixAsmHelper.java @@ -10,6 +10,7 @@ public class ZelixAsmHelper { + // TODO: Can we find a way to access direcly variable without getter? public static void createTempFieldForSSVM(ClassNode classNode, String tmpClassName, String name, String getterName, String desc) { classNode.fields.add(new FieldNode(ACC_PUBLIC | ACC_STATIC, name, desc, null, null)); @@ -33,8 +34,6 @@ public static boolean isStringDecryptMethod(MethodNode methodNode) { return Arrays.stream(methodNode.instructions.toArray()).anyMatch(abstractInsnNode -> abstractInsnNode.getOpcode() == TABLESWITCH); } - // TODO: Can we find a way to access direcly variable without getter? - public static void renameOwner(ClassNode classNode, ClassWrapper classWrapper) { for (MethodNode methodNode : classNode.methods) { for (AbstractInsnNode insn : methodNode.instructions) { diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java index 302ad8f8..5be1d888 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/zkm/helper/ZelixStringDecryptionMatch.java @@ -29,16 +29,15 @@ protected boolean test(MatchContext ctx) { return false; } - ClassWrapper classWrapper = ctx.insnContext().methodContext().classWrapper(); // never match if the method's owner is not equals to classwrapper's name - if (!call.owner.equals(classWrapper.name())) + MethodContext mc = ctx.insnContext().methodContext(); + + if (!call.owner.equals(mc.classWrapper().name())) return false; if (call.getOpcode() != INVOKESTATIC) { return false; } - MethodContext mc = ctx.insnContext().methodContext(); - Map> frms = mc.frames(); Frame f = frms.get(insn); if (f == null) { From cb0020bd1ebdc562e13733857b3d19b119246db7 Mon Sep 17 00:00:00 2001 From: toidicakhia <108525966+toidicakhia@users.noreply.github.com> Date: Tue, 9 Sep 2025 12:54:54 +0700 Subject: [PATCH 15/15] update test --- .../zkm/EnhancedStringEncManyStrings.dec | 5 +++++ .../zkm/EnhancedStringEncSomeStrings.dec | 5 +++++ .../custom-jars/SnakeGame-obf-zkm/d.dec | 19 ++++++++++++------- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec b/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec index 20f4016e..ef7e9c6c 100644 --- a/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec +++ b/testData/results/custom-classes/zkm/EnhancedStringEncManyStrings.dec @@ -159,6 +159,11 @@ public class a { static { String[] var5 = new String[85]; + boolean var3 = false; + String var2 = "-\u0092Ù+\u000f\u0000áLÝ \u0019nÈY\\g@\u0095!\u00189\u001fOü-\u0097\u009de\u0086ÈP4\u0013ö\bïjw\u0099\u000b¾\u008eÕ\\9D®l³'Õ\u008cW\u0015pDï·\u0016\u009cãÄc\u0096Ö\u0012ÚËFÑ÷»]\u0091Ã¥µê\u009a\u0088ô\u0081ÉÎ.\u0090ze\u0013ø\\\u0007\u000e-k\u007fÃ\u0099¯\u008b¦zMý®þËÇ\"È¥¶\u0011à\u00063ûx+¦\u0097×7\u00ad*éo:Iº¬¨ì\u0017äíªv6\u008bO\u009f6ã+Ñ\u008cãdpånc\u001dÄQÕðò\u0080\u009få¾\u0098\u0087þ\u0095\u0082ûOª¼S\u007fLUæÅ~V\u008eãã~|²d\u0004Uüª5µ§hrk6\u0099¼åï\bü¯\u0013-\u001cë3×`\u0019\u0005æè\u0081Q5\u0093\u0014DáÝ\u0088ü\u0007.6ÌW%(\u009c^\tS\t\u007f\u0013\u008c¯âHz\u0092´\u0000Ô»×ñ¡Ê\u0007\u0083\u008cûn9:\u0091\t°>·UXU\u0095;\"3\u009cMê\u0080\u0092dyÆ\u0010\u000eKaÞCî>ú¤\u001aW3÷>\u0082\u001f\"éáÏFî>G/úÐn,\u009a\"Uî3\u0097\t\u000fX{¹¥C\u009d·\n\u001c4\u0095\u0002\u0001ÎÏðpÆf-uE'\u0001áûî áTk¨Ë9\u000bvkd9óÓKéo½éµ\u00034\u00addi:}\u008aÁϺ\"\u0089¶²\u0018\u0091Ú\u0095kpõ\u0002øElüh©ì\n\u008a\u0019\u0090øK/wÔ\u0091k\u001e WÍ7\u0081¬H7Â\u008c\u0004\u0013\u001aQ¸{±ä\u000f\u008aÃ0\t>°\u0015\u000fd¬\\\u001eI\u0083a\u008c\u0006n§ó\\`çz·\u0096\u0000°ÂÿCp\u000b£\u0010\u000e«4kR\u0014\u0005\u001fô\u008fó$e:m4\u0001Æ,-\u009e\u0085,\u00adê§K.ú¶\u001e],gQ0\u008f¹\u001cÊpÏ``¾x7s\u008fj:T«\bÉ\u0091½$=F3\u0093ye\u000bÂÅ\u0095\u001bré2O\u0014\u0003ú\u001bJ\u0090\u0000\u001c_Ï\\+ñØ·¹\u0096\u0015Û\u0015á)çuª\u0015¥n\u0095\u0016mþRxõôÈýÓ¤-\u008d8\u0080L©\u0015Õ\u0016þÀ\u0080ÅB$nä-\u0003ÜÄ\".Ëg\u001d\u001dT\u0099\u0007\u0013~\u0012Îháó¦ @iå\u0010ð\u008bõ\u0085$ò,YwóÑ\u0089í\u001eÊóÖ»¢\u0007\u008a`dH½È6ó¨\u0088\u008a,³FRµ9ÁZë\u001bD\u00ad\u0093\u0007ù'ÍoÂ\u000e\u001c\u0081°/¯4\u0088ó\u0007\u0007÷½\u009c\u008b\u0083M\u0094#\u001e£3\u0092)ü\u0007\u0013×\u0006ì\u0001Ùê~O\u008dNìUÎ\u008bM?]Íõ\u000b\u008eh\u008d\u0094o\u008e²aNIýQíÊGÑç&\f¢&Ò}T^ÿyÂÄ&£E¼pÅEÅ\u008fü\u00ad\u001fîó\"äÓøóQ,èi\u000f\u001dg\r§}\u008dCàEá2l<\u0097\t\u0018NV\u008d\u0099æ\u009b\rð]wòí\u0014`\u009b~\u008a[l<äî\u0096úeG\u0000\"\u009f4ò\u0018Õ)ó¼2#î\u0016å\\àT(N0N\b\u0005\u0083\u0007ùÐ\u001e\u00972T\u0084ù\u009dƯH{B\"§-\u001c\u0013ÞÌp(r?h Â\u0002\u0099o8 \n$í±:R{\u0012\u0005i¿GÛ®©ï\u0099áhí,\u0093«±ËÎG\u0081´BÍ_ÿC\u008bw\u008cþòK2È`Fc8mP}©âÏ(ØÃü$\u008cH4îoÜN\u007fk\u009c6ZfBéÒ\u00022Z°&R\u0088ìû\n¡ö¶I\u001cx\u008f\u00ad\u0097ë\u008fx°\u0097~\u0006{í«\u0088Tk¬{ó+I7Y?AÒ¢v0¯\u009e üv¯ãî~8¤7n_om0\u0091h>)Â\u00adeM\u0093º\u009f\u009c\u0015N}åqÙ6~jó\u001b\u008dÛ`àÕXéZÖÒ\u001bñ,&\u001bªÕX^\u0090#ôÿ\u0001o|³óë,ðHgáI\u0017|x ÿâñ\u00ad\u000b¬NS\u008e|B\u0000~\u009eÍ\f\u001eCFTaW\n\u0004§\u0016Ì\u0086ªäùÐ[2hPx/@ÛÛ@âfDññ)¬ûcaô\u001bÊ\u0083§º»«\u0018í@\u0093¡m\u0000N-I\b\u0083\u0090Qæ\u009dF¹Ë\u0006¨¶\u007fþu\u000f²«z\u0083~ÆD\u008c\u0096±\u0001±:à\u0000/l\u0082ßjêÐ×`þ'£½QWqª*\u0000Õd\u0083ªb«\u000efÎ=\u0082àÂ4²rUà·Îâ\u0099Rj¼Á4iÀY¤!L$ð\">\u00996\u0082\u0096\u0018vñö(×\r±\u001da2ö-\u0083¥Â\u0088b\u0000Dð\u000e\u009506÷\u0091mGy4GWCÒ±[¾['~r£ä\u0094b}ßcä)C(×KD\u0082úu_\u0086û·+Ü\u0095\u0002p§\u009eȾ\u0081X©\u000b\u0014\f\tÀe&\u0099âË'qß\u000eL±âhö¼®ï\f3ÑKíÏe\u001f¬âùüÞÈ\u00024$j4ìÖ$\u0010æDötü\u000e\u0003ÜN\u00adÈð¬MLëuÞb-¼kQ¾-y²2\u0093&)\u008d\u001cW\u0094\u0093^\\\u0092\u0013T\u000eåÛ¨\u0093Ä\u008fÆ>j·\u00ad5ññn\f\u000e\u0005çõ¿¾\u008fVp5-\u0090=>ýa\u0084Ïp¾#áoÛÃ8ÈÜèØ½\u008c\u0095\b\t\u0087!¥¼Æ«÷½\u008am÷»¦Á.ýÛ\u00adó\u0003Õøë<\u0086 woU~¥\u009aû1\"\u001c,õ¼¦MxnWÏ}»ze\u0080°2\u00940ñ¨íT(ÎÁn\u0091\u00030Çø#ÉÌ\u0090´\u0091\u0002Â\u008e³Rß.@\u0084\u0092ÀpFÂW\u0005¥Þ\u00ad\u000fT\u008dÖ\u0082°û\u0096E\u000f¹\u009b>ðIL@Ñ#Z1\u0018\u0006 r\u0006XsÝ\u0001'´\u009b»%ÕT_\u009aP\u0007u*\u0093ÿ4Ò.\u0006í!À>4\u0083«Ëò\u0018ä9\u0098ýà\u0002\u0016ç\"%w\u0083Ý¿\u008e&øõ'+²ß}d:¹\u000e\u000eT5@\u0005¦ÈñtaÒ\u0097\u008a\n87¡\u0087\u008c\u0096æRÙº\u0002g\u0017Zx¡Uàý>R&Ó.\u008c\u0084.Fïä\u0095B/5F\u009eiâjò!\u0003à]M3z\u001a\u0012ú,q\u0084j\u0088ÀJ\u0081u¿oïój\u001bR~Èå\u0006\u007f¿Z\u000bØ'\u0090at\u0080ÎmDó\u0007öÌ#\u0000/gm¦\u0096³o6\u0089¼ô4\u0001\u0085s`\u0086\u0099CÎ\n\u0010\u009c\u0018¥¸¥\u0080pT\u0016ç·\u0011\f\u0090\u0005W â¯1\u009ca}´¶+à©JÑ\"\u0016\u00832í\bã_\u00adæ¨`´Ð\u009bׯú;ªÊWc\u001b>7BÚ\u0001mfý\u0006\u0090}ȪK0.ìBIf\u008a\u0090(FÍŶÉäbYm\u0004íÆ\u009e\u008e¨Hÿ`IZ±\u0082ù\u008dö\u008e\u0015ÛvéK\u0005´ÖË\u009f4ý\u0015·4:\t\u0092ié\u0099\u0018{ì@ºKMúQæäÔä¼@Ç}èn\u0016O\u0017cA\u0083\u000fðò\u001b8¶ë<ÞÝ0\u0007Nè-]\b>5tçΠ[\u001aDò\u0084ü\u008b\u0092\u0082\u0082ùð\u009c|½rÑì»ý7\u0012\u0000Ãç«DÁ\f\u0091uáq7æMzRU]\u009b¯Ä\u009e+\u001e®\u0015Ê_À×X\u0005½\u0083\u0010µ\ry5\u0005\b6\u0080\u0018'\u0092Jí\u001a\u00192¹\u008et§\u0080¨«.H\u009dż\u00195=ÃE6e\u008aF4eïTµÈÓ\u001aBçië\u0092ç\u008f\u0016\u0099òó\u0013¶\rÊæ\u0082¹*Ì¥º¹\u0001+o\u0095 á¤zíÕ0þP5\u0005µr\u0096D]ù;Ô_¦_ÆÄºúþj\u008b\u0096|\n\u0080j\u0005~s¦\u0081¡\\æíz3\u009a\u0094íò´>^' \u001c2Ç\u007fXÆ\u0097èÀ{mt®_¨9\\×âè¥\u0089înP¤\u0086º6\u0098pÈ]\nìòo®L®\u0088.£4\u0087\u008cÒ!0÷\u0095àßÑ\u0097\u0083ùçÿ\u0084\u009eÉÍ¥ÀÌè@C|Á\u0004ï\u000fo\u008cõc|ÏYæL\u0085m?Y¯snas\u0095\u009e\u0081\u007fQ\u0080¹F#¯\u008a\u0096̰_M\u0082\u009e\u009eIp¡*i\u0095Cê-Ê\u0000Ã\u009còÑÐ\u0015}Î\u0010\u0095\u0006`nJ\u0011\"¾\u0003£[˦J!\u000b|´IÅtVÒ\u008f\u0016\u0014/\u0084Ö1wÀò!\u009c\u0016¨\u009e`¨î#\u009eu\"8é\u000e\u0084\u0089þ¡Añ\u008dÝ\t|Ë> ÿÀÏ&\u0018ûNô\u0081PÞ¬Um\u001aí»O÷\u0014¡\u0001ºBä¯ò÷\u009f\n\u0085å°y\bȪ\u0097Ôd£uEï\u008f\ràGÈÑÑËÂx4{1\u008a\u001e6WØOxº×s\u009dÛ7\u0010xo\u008e\u0092\bÏ}ê\u0093\u0010be·PpGÅÇù\u0000§1.Çó\u0087\u00adu\u0087Ó\u0006cñ±\u0013F#8Iû\"/\u008a[Ú+4\u0087¿x\u0099Z\u0082¦¹¶È³¨\u0000y\u0081Eµãñ\u0098\bY\u0002ò§gòLÞ\r×ÚlµMâJ\u001d\u0090G\u007f\u009e¤ÎáJ\u000bÛ3T(CÿþÔ¡\u0094\u008ci¹Ú=\u0016ÑZDÛ8\u000e´\u008dÌÖ÷\u000b\u0089ô\n9ê@$Í ~}@ZÇq\"CK\u0010\u008cTÂfHØ.0»\u0098ÜÓÎ\u0090¤k|3ñF{°ÉK@?[yp\u000eÓz4gø8=\u0003I^\u00136¹À\u008d\\ë\u0094pÅgjßD5\fÂa½V\u0089Dé\u008a \u0095\u000e\u0090-_AK£úm\u0086\u001a*ì+Em|,\u008fë\u0096\u001fÑü\u00177\u001agbà\u009d%>P\u0098lÐ\u0012²¡]}\\ÑhRºh\u000b\u000fi\u0002\u0003Kñ\u0011Å\u009e\u009a£9P\"£\u009e\u007fúÑu\fP\u0004\u0086Å$\u008dIìÖàáÞï\"\u0093À\u0083\u00805\u0094\u0092ßÁ#îÏÜ\u0083Ç\u0085Ðt¿î\u009e\u008dyó²\u008bÌ.\u000f\u0093ü_\u0001ÏÖ\u0099\u000bÕ6£¬-\u001e^#$°\u001e;a9K£\u0096{\u0014·ù\u0084/1\nv\u0004\u0084\u008a\u0007\u0018\u009c½¬s.&ûÉ?M´«M}RñÕv¶\u0002$\u0081q\u009b\u0017b\u009f\u0081ç]ÛòüºDª¶õ\r\u0096¼\u0093.n\u0012îã\u0084b\u0011a\u0091¿\u0086\u0089³øñà\u0099\u00ad\f6Ûß&f\u0007q\u0095\u0092ùùø=÷ö!2\u008fIÓ\u0092\u0095U!¶\u0098§V\u008c*uE³7ñ®ke¥¡©\f³³z¹à\u008f\u0006ú4_)\u009ehb\u0019\u0011ºC¼¨°Y\u0006Ðì®\u0090¾ºA|´xuo\u009cSÃÁ@PLQa\u001a7£µ»\u00077\u008aNV³wÞ(¼RgP\u001dÛÝXúf¦f\u008bB-ÒxÚýq}\u009bÁ\u001f¼õL\u0018G\feKÁÇ\u0000Ò%²¿÷\u001aU\u009d`\u001aVT¡,ÎRD\u0083\u0097/bTGH(7ØA\u0017b\u0081*\u001d1n+ä\u009f\u008bé\u0002\u0097½v¦K»^ÿOÍ3¾\u000e\u0098ã¶ÂY\u0085ÍY¼\u0004ÆH\u0096H\u001eî>\u001f\u0013'\"\u0088\u008c/¶¿\\lGÄj~\u008a\u0081Ñý\u0094^Ú\u0086'óê\u0096â\u008a¥«ÅÏVóÍ\u009eT$\u0006'\u0002(\u0012A\u0004¦\u0098äl¹\u008aJ\u0016UYÈ1}\u000fÆ\u0011\u000e\u0014}Âm\u0014¾Ù\u0019o\u0081·¼\u0084øJm+µJÓ^\\ýÞw\u000fÖ\u0014Ê\u0010ÁÂà\u001bYÞÊ£¶¾Ü¸\u0005±åðÔ=ÂÌÙ\u0091r¹*\u009b\u0095½\u0000u¦\u0001V\u0004¼\u0002Ù\u001f\u0080Ù\u0088Óý¸2\u0087Ë\u008fë(â\b\f\nN\u0013oª\u009a´}çÿ\u001a\u0011O´\u00ad×E\u0006L\u0092\u0089¾¨ljâÜ\u0001·Û@\u001c2\u009b8\u000b\u0013\u008b«1\u009f\u009e\u0096äĪϫ@k¿gf¦S¹¹òõµ\u0090³Ð\u0000{:è\u008a¡c0´g-wÝ\u0005\u0089Øiþ#±wÍÌí\u000fw\u001eåvBM|\u0011ϱH4\u0014R\u009dØíø\u0013\u000eÆ$ý\u0003Ú˸G&\u0017B\u001ccW\u0015Ñ@à5 ;\u0018SÛß\u0015\u0006:¦(ÔL«*·L50^¿\u009dͼä\u0000O0\tJ[ï<#°äåáõB\b)º\u0015\u000b\u0095]ÌHQ\u008dÄrÍë²¾êèé\u001f&©RâQÇL1\u008a\u0094>\u009b´?ÚÎÞw\rG\u0080C²í½\u000fÉ#ÜÛâ\u000e\u0089\u0098\u0090M\u008cõkÉàtz\u0087{7uú^Á\u0089P³\u0018Ú®Ý\u008bE5\u0095ú\u000e^´B«s\u000e\u0010 \bmºòTü»þ\u0085\"·£\u000e\u0011 ¨v.\u0006\u0018T¦È½=nv\u001açV\u008c ÁÔ|ãÛ\fû\u0005\u00911ýamÄí]®uRX3äç\u0016ù÷Vã.\u0091ò\tH \u0092\u001f©\u0098-*Ë´¼8Ò\u001d1\u0005\u001ff\u0003m-\u00037ª»:´\u0098Öh²\\#§W_ÿ£\r\u009c®Å û\u0001Qί\rù\fÛì±ö\u0001U\\Ó_ºré8\b\u0099\u009e:,´Ukz\u0094\u0007ùèÌè\u000fÅ\u00981\u0086yÛ\u0096¥Ô\u0088dÚÇüáUn\u009e\u0085«\u00ad$,ìUAj&¹EbÚ\u0080[\f\u0092i\u0019\u0017Ù?¾CÞ\u0002Cª¾\u0015ëáU¥Ø\u0096õ(\u009dTq7%\u0082\u009fXþùSrÂ\u0082VîÃ^>\u0099sBÛ'¿\u0097\u0006ø\u009a¬põñg\u00adl®\u0006E\u009c\u0092\nkçä V\u0017úW\u0018 \u0007 ÷ô7ôÉ\u0017ìÄY'ϲ\u001fq?§\r\u00ad\u001c}ñÇNÀ\u009eù¨À¼ÆÈã ¡µmõd2\u0010Ì\u0002Tç}\u0005h¤\u009bÏ{5Ä,/!\u0098û°Ì\u0000.xd\u0085\u008c ¦â\u009fîîÐ1r\u0002¶b¤vêïObKO#®#\u008f¬êbõ~+²\u0099é¦\u009dѾ<\u0012¿C\u0017©\u009d&\r¡\u0018\u0005²gÿ1\u008a\u0096\u001agmÁ`Î\u0095\u009cµD&\b²a'UÿíT\u001c{ø]¦Të*o\u0006\u009cÍJOÑ?g\u0090ß\u008eÁxÇl\u008bR¬¬î\u007fÒøG<\u0003h\u001eQ\u001en§ôµ1ÉØR\u0016\u0014¼Q¨\u001dÿØÑåÎw%\u0085ý¨\u008aF\u0016\u0091\u008fZ\u0096\u0000R\\WÉ-\u0001R¥\u0006Î\u0093\u0081FQ\u0089;\u0098\u0093BߨöW\u009bÀ\u0089ÃM\u001a\u0019ùR{\nü)¸\u0017CíiÝ¿x/LÍ\u00ad&©x\u008d\u001a\u0094á·\u001fw¦uºÞÝþ\u008c\u009fÚ\u0012i£Ô\u0086êLÐ\tn-Ý\u0000\u0005\u008d&2¹\u0096¶!!4\u009db\u001e{÷\u0003'\u0011»Þ0ú*õ\u0092¨\u008a\u0007\b;n@£øÐ\u009cøz¶Ë\\YË\u0087P_e},q\u0001\u0087\u0001¨ÓëÊÞ÷¹Ì÷åø\u0012÷®©vñ\u0091|R\u0013\u008c8íógá\u001d¶\u001c÷)¿½7Ô\u0001vØ+Ǫ\r}¾/\u0005o)ìUãÓb£\u0016W\\þMê\u008e\u0082¤D\u0095\u0002õ\u0006ØÊ#W[\u0099ï§Ô¯n»W2®\u0087÷\u008b&\u000e¬Í¤ÛÈøªs\u0081\u0002ʧ\u0090\u001b\u0081\u0005\u0099¾N¬¬x>Oä~H\u0087=\tjÄ3ÃÖ0c\"^vyp \u0017\u001e:ü õY8B\u00adYF,FÍß3U\u0092^.«\u0080ÿÈ\\Kؼ\t\u001fW!^¼ø\u0086¾Bò\u0092©ûQ裡íùÃOÍìж\u0012\u0016\u001d¯\u0011\u008dVÈ0´Ñ#¸\u009eR58Gï%ß\u0013\u0005ÔpE\u0093Jx)Í\u0013í\u0019çfï\u0094ÊÂ\tÿã:¿Bâ\u007f¾ÊTN Ò@\u0005m.ÓÔ»KìÛn¯\u0004\\\u0000Ó\u008fvÀ\u00014esQ]-Þ\u001d\u001eBfg²qC\u0094\u0083dz\u0090Í\u000b\\\u0099Ä\u001e\u008cC3\u0081Ñ\u0010¯«\u0005ßAðOà?bæS\u008c\u0086©\u000fH¢\u0080ä\u0089¹\u0007CÅ0=ª¾\u008f\u0095\u0092;\u0080\u009e\u009b\u0090\b\u0017 aE^\u0010\u0098>.Âý)¯ÚÉ®çQ]¢\u0006Eß^Á\u00025)e\u009eÞ\u008f\u0094*s¼KD¡¾\u0089\u001e\u00910VÅ\u009ct\u000bc\\òà»\\êi\n\u0098TË\u001eæ®6p\u0006\"\u0082Æi¤:ÉüàïØ\u0099[¹xv\f\u008b\u009c@Ù\"T}¶¿ÇKp\u0096\u0001ó\u0081û\u0013\u00149'´¥3ïÞã9\u0001çß²`ãdI\u0086©l9>ÛÌÉ¿W¼úÌYAÆÍw\"¬1'J1+cðJé\u0094\u0085r\u0081ÙÊ\u001bâæóÃ4\u0092t¹º\u0091¨Ý¡ë´¿w@\u0096,Ñâs\u0097Q:Oí\u0080é\u00124à¢Ö;ê\u0010Æ}ÜŬ£Æî·îpE?|\u0002\u0092\u001d\u0085Õ\u000bá,pÏö\u0012ú¯-\u00019è1Q\u0014Ï\u001a0\u001dÃ*$C\"\u009dÓ]\u0000±?ô'\u008a\u0013\u0098J.I\u0018Ï÷¤\u0093\u0006Æõ?à˪b\u007fÜ>+\u009ap%h¨oOþg߯Å\u0085Z£:\\\u0004\u009b`×\u009c\n\u0093\u0013\u0013\u0089s\u0090÷ÑKë\u0098ü\u0004e\u0004PD\u0006\u0086,Àò\u009b[j\rL>\u0092²iøÛº\u000b6Ìõ¿lié\u008fÝ\rÞ\u009bü\u0087\u0010\u0098ãe4søc\u000e»yó\f\r\u0014$\u0007«ûÀ2\u009e¾²u?'ä¾u/\u0080\u009e^ÇX´_þ¥Q7\u0086(»\u0084YåOõ^P\u00101\u0018 %Ùáù\u008eí(Q\u0004\u0006e8ÞJ\u000eGÙ4\u0006\u0081\u000fxXv\tûêR\u0012o*\u0092=Ré$ûqf¿íé\u000e\u000f~øØ\u000e'×H¤\\Ór\\\u0098Ëü' f|Ò³£$;¥M\rÜ\u001b'\u0010Õ°\u009a\u008dNK¼ö\u0097 `EÞ\u007f×ç\u0002\u009f¯·É\u0092§5\u0091B\u007f«º\u001aÑ\rËA\u0003ü\u0004m_Í}÷÷ÆS\u0015¸Ï'Ñt\u001fÆhV_rÃ`&éµM¼DèCÉ.½©6x\u008c\u001a*34\u0088\u0016Ò\nmËx\u0007VÛìXL\u0082ã´üñz\u008fþ\u0099*8vs1\u0090\u0091\u0098\u009et\u00076Ãñøm\u000b3½\u0000lL§%«$)\u000bëÌ8\u0083ëÞ½)\u0092½r\u0097\u0012\u0098ö\u0012O\u009aG»Ðä«¢\u0013P6²û\u008a\u0010%¨]P\u0089:ë¯údXöÍ&à§?\u0016LÀ°É¿\u0096ÍyùtÄ\u0091a\u009eæ#©>¹NÎóÆ=\u0099ëØ\u0017·<Ä4)\u0093\u0019\u0019°ÄM\u009c¨:.¦\u0086°ð\u0002\u009f@\u0081\u008cõ¦Px]áÞ\fßË\u0007P\u0013K\u0012Õ±kRý@\u0080Q\u008bú$\u00986\u008av\u0097÷ØÐÃwIß\u001e\u0098\u0017, ¤\u0089Fá\u0095¤ß\u0007´¾Ü<\u0007\u0017âä\u0011ññR\b÷©\u009e-Ï\u0006P\u001e\bWÁfááí^È+\u0001C\u001a\u0082cÎ\u0085ðôÄ|:'cÏ\u0094¾Oü@j\u0003Þ\u00000¨ÖRCGW|b\u0018Fw¼¾É\u0090©Ç|Â|\u0086\u001e\u0010\u0089»}ÅÖg¦S\u0097yÇ}·\u0002\u0016´\u009fLe²á1oÛ±{\n\u0015*tfâ\u009fh¬D\u0006\u0003\u00186úf\n\u0087]K.çIb\u008bÜ\u0017\u0091\u0092¨åÍL\u009fì\u0092AWX¥ÙD\u001f\u0081ÐØ÷o/F~\u008d\u009e+BxlÝz\bnüZk»¦X:\u0015©Ü'®\u0082¦ÒGF7Æ=ëLâ¤z7\u00adôBzü¦_\u0095É/ø)Àþ(»º^%j±ú]t³uñ8Çr\u008fJÑb+\u0001ðÍ\u0000£_®»=õVÈá6iQE\u0097!D¹ý{\u0081¸c¶\u0081#Ð3\u0011K\f¸ÀF©@Qäxò'Ô\u008a\u00ad\u0097ÁôAeMèß\u00adf2Ùö¥¸H\u009au\u0017\u0096\u0092ËåïP×ßWÌe[\u001a_)`¡»;pï&¿Ã=\u0089¡O]\u009e\r&ïA@\u00ad\u001f3¶yøÊ\u0000ïÆ²da\\Ðëo81z³\u001df¬ãø\u001c\u008b Ç(\u009c\u0012Ùéõåá½p\u0092k\u0096_Q\u0006¼ÆB4\u008cg\u0000?\u0087º\fÂÿFê\u0080ò\u0092\u0084\u0013\u0016\u0095sûèʧ1xÈ0Å\u0010;óÖ\r0ɲñÂ\u00854\u0098J\u0083I\u0017\u0084)ؤõ\u0089\t!<â»ÊKÉ?\u001a\u000e©Ø¤ãùm\u008f$Ã\u0089¼\u0013Â\u008b\u0089ªâ\u001eΣ¥aá·\u0088ï,G=\u0013_\u001bNÿ\u0097JÈÈ\bÌ8ã³ùéE\u001dÆ\u0002\u001dwÁ\u0089\u009axù:å¥\u0081Ê\u0096Ã\u0089Õ®æRöÝìO|ì\u000f\u0084\u001cÍ\u008bþ\u009cíÃF|§;¶3ÃS7\u0010\u001a\u001fÆ\u001eJí°HRÛ-\u0010\u0011\u00971·\u001fó>\u0081Û\u0094\u001eÍá?\u0001\u001eë\u0092%\u009d?vo'\u0086\u000fY-£x\u0000ÊdÆú\u0090uªÞE\u0093\u000b\u0089\u00ad{\u001fï\u008fÌ\u000f¨\u0086x\u008fýÔô²\u0003×#Ê\u001dCï\u0017(\u0088\u001eÍt\u000e0Ké]º\u0089\u008bY\u008c'\u001cõ¹è\u008d\u0018è\u0019}ùÇÐRÀ¼B\u0016pi\u0099s\u0087Í~U\u0090å6ø\u0013TM\u0093\u00adAy_Î)\u0089©ÀùKåC÷\u0080X·\u0092=ãcÍ\u0007d*\u0095\r pª_Óx\u008eæ¯\u009d ³ÿvç\u0002òWÊã\u0011¤\u00ad\u0017\u0092á\u0013\u007f\u00adg&$}¾s«ï\u001fͺ527\u0084{\u0080´Y\u0014¤Ü\u001f\u0080ÎBnj<\u009d°ìÐ{¾\u009b\u009d\u001bG\u009a×\u000bé\u0087\u0017\u0080Ñ\u0014\u0090ÿ\u0092\u0017q,\u0010\u0098\u0091£W¡0Ö`æ\u0019á\u001a\t6F\u008a\u0001ÀB\u0085\u0098@¦Ûλ¼~µ\u0016z\u0097þ$é\n\u0003Ö¥eðuçî@>ow(â=ÝFÝV¬TÒ¼¾\u0089.ɯËg^JCó¨q\\g+z{h\u000e¤z7£wÑ)´)\u008dÌf¥/õè/1:¶¯ÿ+Æ£Ö\t\u0089J²\u001d¼µK¸=f©ÎLSªÐ§_2¹\u0017 7^Ò3ó\u0012]UpþÃiAÙaC\u001bî\t¥Ë£\u009b\u0018~)\u008fÜ\u009cü~\u0094\u008c\fm'~ú\u0011\u0016ó\u0088vn;\u008c9Æ*º\u000fãnÅE\u000f\u0097½±Èw0\u0000\u008a\u0010oÆðk°õ±\u008aH\u001dë\u008f7\u0084ܼþüÎÿ²*úéFÓØ\u0086îm\\w¼®³w.±é\u0085\u0081Á\u008f¶\u0095\u0093DDw1¯5x8}¡OÿM_Û¦Z\u0014ar\u0012mB\u0019s\u001a\u0013¡\u0096\u000bA\u0015\u008b1â\\\\M9üÑ«å\u001bz´\u009d\u001f\u008dB\u0086A\bªý6»ã¨ë\u008bJ\u0086\n)KwR\r¶\u0005ä¬tC\u009cñ\u001eÒ\u0000\u007f&e9Òå]âÍ^\u008bù7ËC\u0007G\tX\u001d; s¬M\u007f/\u0010Ϭá\u008f,\u001eW3Å\u00ad\u008d®\u0098\u0004\u0085,<\u0087àJN¥\u0014s\u0096mþĤÉ\u008cÿW\u0012\u008e\u0007*>>\u0082â<Ãç»\u001dU\u008d½ \u0000,\u009fÚ¯ï¦æ0¦\\¯K(D¢\u008f§Ô¬\u0018xõÂZ\u008cøbn\u0098ü]E\u0084èÄ\u0099y\u0005R\u0094Bý¥#à\u0013ï\u0083¥V²v\u0099hkû¨Ñb4UD\u0006\t\u0004\u0085|J\u0084{Y©IuàK6\u008cRvE3\u0099³w\"æû´%\u001dÝK]Ú9ÞSi\u00adì\u0098Á\u009fÐdð|\u0018¨²M½\u008f»G\u0011CLK¼!\u0094±ð\u0086Õß\u001eÑ\u00adÌ5÷\u001d\u008c\u008cWs|ß+¡¾¯Ê\u009c¹U\u0007µ9\u008asêÈ;¿´-³nø\u008aq2miOüÖ©/½\u0096ÆZ\u0087¸ òPÅ\u0093/Na\u008cË\u0087æù\u000eÃ\u001by±\b+3óº.\ná}øCòv\u001f\u0007>UQ¢-\u0000K~\u0087^Y«\u0007\u0096O9ðþ\u0098\nZαò\u008f=_Å\u008fʦ\u008bâ\u0001óçÜÐ\u0099ÎgµzÛ=ýå\u0013WçH²\u0016\u0084ÏèßÑ!z\b\u0091ßB\u0017â\u00060W\u0005$*fýÉ\u0013¿þ]òÄ\u001c\u0018Q;à{\u000b\u007fÖ1d)j]Ë\u007fµ\u0001\u000f{ãòJ\u009cÝÖï\u0090\u001c\u009daìI\u000em£ß\u009d`#fõ7\u0016Îâév\\HXB íÒ£ßs°akdé²ÛC\u0013ebÐÑ\u0097§Ü\u009e-Ô\u008e\u0081Ñæ¸Ê8\u0094µxäù«}%ø}\u000bË\u0091g\u008aÂÕN\u0005up=k\n\u0011»E3å*ôðÛÅó¿V\u009b?éåEï]-Vò&âÁó¤\t»\t\u008bv\nòÐå\r8È\u0082\u0016bfY\u0015è«\u009a?l\u0010ïÓ\u0001«\u0097òkÏ(ö<\u001eAá\u000bØ\u0003TÒ\u0085\u007f\u008dÄ0}\u0083»f\u000b\"¦ãÄ\u008c\u008a>ÄÿWÚÌ\u0014\u0004\u009d¶\u001b\u000eX\u0084\u009dézh,{*y±Î\"³¢Sþ]\u0087R6\u001b¿ÓgEv×F/rUö\u00ad©[kÃk\u008cÆ:\u009f(}R\u0017Ù&t\u001dÊ\u008c\u0013)\u0080\u00036S)\u0091/\u008eÉ]\u009bÉ\u0004\u0005\u008c\u008d±²Ô\u0091¦f6\u0085\u0002·Ã[vµ\u0090/3\u008f\u0007¢%\u0085\u007f\u008e\u0003\u0095Ô¡\u0006Ü$!sø\u0004<¸ÓÀ{f\\õFåÐ~\u0002)Î\u008amÅp\u0084\u0017ÕX¡_al×Ðm\u0000\u0017.¾hÛèÓw\u0093´ÕñX\u008f\u007f9³Á-á\u0097EÁÍ2ý\u009cN\u0002´\u0010vùç²m$7\u00adtY¡à¯W\u0015Ê\u0096ÂȾß\u008eÁ´¥íý\u0084Õ3À]\u008f}=\u0092×¢\u001düì\u001f\u0002U\u0086\u0006b»r\u0094\u0096ý\u008eñ»\u009c\u0096Ìü\u0087Øýc\u0001¾±½î\u0080]¾\\\u0099A\\\u0013µD\u009cöå#!ë\u0084hïæKÖ¸µ\u001aß\u0007r\u0092\f:\u001cÂñ\u0093S=\u008e\u0003UÈu\u00adðW\u0013bR8M\u000e\u009dî\u001aæWj~\u008e¾,ömuÒ¥ìÓ\u0080\\hN5ÂÀkѬqÃ\u0005yE\u0097gHE«\u0085aÐg;ç\u0004r,ob¬À^¡ÇE?Ϲ\u0001Hkt\u0018\u0016l+ªµ¿\u0093j|\u008fUóöveÛ¢K\rÚ_%ª\u001f\u0091¤9á;,Ñé\u0093l\b;\u0006\u0093½¿ïßîDw¯¦\u0017ÁË/¢\u001d\n¶¿÷äîîÒ!\u008fËíýÚ¾û80lÊ\u0019R\u008cȵá\u0089®\u0080\u001e6¦ÄJý\u0086X¡\u0005\u0095LH\\T\u0080\u009fli\u0002,£\\¸áè8Ø\u0092ã&\u0004Ko`.\u009aêûçÂû¤^\u0090=ß¾·Lr\u0000årH\\\u0084.GÀøÜ#\u0087½8Eiî\u0011ºñ\u0019\u009eVÝ\u0007ÝþJ¾«@M\u009aÒß$s\nÌ(ä:á¦I¡qA\nMåYÆÁ8¥â\u001a䦽ø·'òù+Ä)sNìí±\u009b\u0018Þ\u0087%\u001c'¸¥°¨\u0087~\u0097\u008eþÕ\u0083ÕÞ?\u0086}p@8Áú+^·\\\u0090\u0018\u001cöÏ\u0097»ò9ÝB/\u0005wÓ®\u0017\u0014Fktÿ\u0006\u0007)\u0084´Ä.ỵó]\u008dð\u009d\"\u009cªy{\u0017N\u0084Ö½áª\u0018\u0019\u00926[\u008eD6½7\u009cÐ5\u0010|7¨\u001a\\º\u00109QKF\u009d\u009b¸ïqMÔì\u008c\u0086ÃòÓË-Åv$Ñlt\u0093\u001b§N\u001b¥{Éa#ìtéiJvbÓ¯6oâ>[\u0000Åm=\u00adAå||&\u0016\u0089£I\u009fñ\u000e\\eª÷9\u009d7Aj·Ä\u0081\u008c÷7\u008b\u0087/©ûã\u009a&\u0002Ev}\u0017\fÜÌã6\u008bÞòÁÿðYÕ®\ryÍ·AÂéþÿ×\u0013鹬\u0010©ÞG:n¨¿\u009bdrãÕËA·5å: ä\u001eU=\\ä\u0014wÅ\\ï\u0091ìð\u001d¯\u0099íçcS/x9£\u0093\u0007tpÐ#x\b'¹\b\u0005\u001fªËÂ\u008a\u000b\u0090\\j\u0085ñm$ÿAî\u008aré\"©zØ\\±ð\\£q:\u0001<Ê´ádA\u0086²)wÐ4a{²x\u0083[¡Qð\u009a^U¢²û¨\u0006 lh\u0003^ÛdúÜJÁ\u0017ö)X¦:@ÿ®Å\u0016M\u0017þ£¾\u00877l{\u009b8\u0087{&\u0097|§\u0007Öh\u0091¸\u0096R\u0010u\u0011y\u001a¡s!@pøù\u000e·V±Ö[\u0080\u0011\u0012\u0092Ûþ<\u001d\u0083Í\u0085\u001f\u0012*êß/¶è¾-\u001d²$\u009d¢·8MÊÞÕó0\n»>CÜ#Z_¾\u008f\u001c>Åp\u000e`]\u001f\u0088}þ¾\u0006\u00131\u0015$\u0099$\u0083ÜSUæ\u001e2Ò-uñÔo¼^*¶w¸G,\u0083L{`âlIÉ[¤k\u001f´S\fƶ\u00adúÁ\u0007º\u001eié\u0003\u000fÓ¿\u0004Ïfo>ÿ\u0085\t\u0003º®w\u0085\u0085)\u001cù.~\u0016'ÉË¿\u0007hå§Æ¹mxMJ1ìÂ%\u0089\u0003\"òÍPSJ\u001b\u009c! 'üðEǦ°4ä\u0082«\u0085\u000e»pܱ=\u0017\u001aê8\u0085®pä0\u0082h\u001bPó\u0000R3v÷úÆ\u0097>%Áq²0'\u0086êV¶\u0083æ\u0081:*ç![b\u0004T\u0089\u000fwÙ(p«a¤H\u0013\u0019V°x\u0093+:L.RÐ/¶S\u009b±\u0098\u0090R©\u0012\tÝë\u0016)\n´3¯\u0099\t\u0006védÂ\u0096\u009b\r\bñðGm\u009a\u000eÎãL¡Ð(\u0097\u0086\u0014þ\u001d\u001c!\fµ\u001d°d\u0090 \u001c·»\u0017Ë\u00ad¸³ÙräÖ0ÍËðΦó¬Éµaµ\u0087Ë\u009d\u007f£\\}+\\G>\u0016@C\u001c\u009b(ò\u001e\u001b&\u00965¢ÞZxV\u001bäûqaC\u008c\b°Ã\u0097f'~{¾\u0080\u00149n\u001br;g®q\u0088aï\u0003ñÍ\u0094[Qg+\u0085\u0099â\u0085\"\t\u000eNêk\u0086+Ù\f]mY\u0099è3s*.J}\u0094= var5) { k = var6; l = new Integer[5]; @@ -611,9 +616,9 @@ public class d extends Thread { } break; default: - var10000[var10001] = var19; + var10000[var10001] = var20; if (var2 < var5) { - continue label20; + continue label35; } var4 = ">\u0099\u0092í\u0001\n\u0081U\u008a±Å\u001bæLÈ-"; @@ -621,9 +626,9 @@ public class d extends Thread { var2 = 0; } - byte var16 = var2; + byte var17 = var2; var2 += 8; - var7 = var4.substring(var16, var2).getBytes("ISO-8859-1"); + var7 = var4.substring(var17, var2).getBytes("ISO-8859-1"); var10000 = var6; var10001 = var3++; var10002 = ((long)var7[0] & 255L) << 56