From 80546203269949bb25fe26e0313a0938160625ec Mon Sep 17 00:00:00 2001 From: EpicPlayerA10 Date: Tue, 24 Sep 2024 17:13:53 +0200 Subject: [PATCH] test samples, some fixes and more documentation --- .gitattributes | 1 + .../analysis/OriginalSourceInterpreter.java | 7 ++- .../deobfuscator/TestDeobfuscation.java | 7 +++ .../composed/ComposedHP888Transformer.java | 6 +-- .../UnknownAttributeCleanTransformer.java | 27 +++++----- .../impl/hp888/HP888PackerTransformer.java | 46 ++++++++++++------ .../impl/hp888/HP888StringTransformer.java | 35 ++++++++----- .../universal/TryCatchRepairTransformer.java | 2 +- .../custom-classes/hp888/ExampleClass.class | Bin 0 -> 2053 bytes .../custom-classes/hp888/Strings.class | Bin 0 -> 1259 bytes .../custom-classes/hp888/ExampleClass.dec | Bin 0 -> 1185 bytes .../results/custom-classes/hp888/Strings.dec | Bin 0 -> 212 bytes 12 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 .gitattributes create mode 100644 testData/compiled/custom-classes/hp888/ExampleClass.class create mode 100644 testData/compiled/custom-classes/hp888/Strings.class create mode 100644 testData/results/custom-classes/hp888/ExampleClass.dec create mode 100644 testData/results/custom-classes/hp888/Strings.dec diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..de3e8995 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +testData/results/**/* text \ No newline at end of file diff --git a/deobfuscator-api/src/main/java/org/objectweb/asm/tree/analysis/OriginalSourceInterpreter.java b/deobfuscator-api/src/main/java/org/objectweb/asm/tree/analysis/OriginalSourceInterpreter.java index 00525505..d7064c1c 100644 --- a/deobfuscator-api/src/main/java/org/objectweb/asm/tree/analysis/OriginalSourceInterpreter.java +++ b/deobfuscator-api/src/main/java/org/objectweb/asm/tree/analysis/OriginalSourceInterpreter.java @@ -205,8 +205,11 @@ public OriginalSourceValue binaryOperation( OriginalSourceValue.ConstantValue constant2 = value2.getConstantValue(); if (constant1 != null && constant2 != null && constant1.get() instanceof Number constNum1 && constant2.get() instanceof Number constNum2) { - Number result = AsmMathHelper.mathBinaryOperation(constNum1, constNum2, insn.getOpcode()); - return new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result)); + try { + Number result = AsmMathHelper.mathBinaryOperation(constNum1, constNum2, insn.getOpcode()); + return new OriginalSourceValue(size, insn, null, OriginalSourceValue.ConstantValue.of(result)); + } catch (ArithmeticException ignored) { + } } } // Narumii end 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 1eb14463..326e9a9f 100644 --- a/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java +++ b/deobfuscator-impl/src/test/java/uwu/narumi/deobfuscator/TestDeobfuscation.java @@ -1,5 +1,6 @@ package uwu.narumi.deobfuscator; +import uwu.narumi.deobfuscator.core.other.composed.ComposedHP888Transformer; import uwu.narumi.deobfuscator.core.other.composed.ComposedZelixTransformer; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedPeepholeCleanTransformer; @@ -104,5 +105,11 @@ protected void registerAll() { List.of(ComposedZelixTransformer::new), Source.of("SnakeGame-obf-zkm") ); + + // Example HP888 classes. Without packer. + register("HP888", InputType.CUSTOM_CLASS, List.of(ComposedHP888Transformer::new), + Source.of("hp888/ExampleClass"), + Source.of("hp888/Strings") // Obfuscated strings + ); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java index c42a2a6e..380e0e2c 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/composed/ComposedHP888Transformer.java @@ -1,7 +1,6 @@ package uwu.narumi.deobfuscator.core.other.composed; import uwu.narumi.deobfuscator.api.transformer.ComposedTransformer; -import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralCleanTransformer; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralFlowTransformer; import uwu.narumi.deobfuscator.core.other.composed.general.ComposedGeneralRepairTransformer; import uwu.narumi.deobfuscator.core.other.impl.clean.peephole.DeadCodeCleanTransformer; @@ -9,6 +8,9 @@ import uwu.narumi.deobfuscator.core.other.impl.hp888.HP888StringTransformer; import uwu.narumi.deobfuscator.core.other.impl.universal.UniversalNumberTransformer; +/** + * Transformers for custom-made obfuscator by HP888. Used in projects like https://safemc.pl/ + */ public class ComposedHP888Transformer extends ComposedTransformer { public ComposedHP888Transformer(String packedEndOfFile) { @@ -16,7 +18,6 @@ public ComposedHP888Transformer(String packedEndOfFile) { HP888StringTransformer::new, () -> new HP888PackerTransformer(packedEndOfFile), HP888StringTransformer::new, - ComposedGeneralCleanTransformer::new, ComposedGeneralRepairTransformer::new, UniversalNumberTransformer::new, ComposedGeneralFlowTransformer::new, @@ -27,7 +28,6 @@ public ComposedHP888Transformer(String packedEndOfFile) { public ComposedHP888Transformer() { super( HP888StringTransformer::new, - ComposedGeneralCleanTransformer::new, ComposedGeneralRepairTransformer::new, UniversalNumberTransformer::new, ComposedGeneralFlowTransformer::new, diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/UnknownAttributeCleanTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/UnknownAttributeCleanTransformer.java index 3472399a..9b1e03d8 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/UnknownAttributeCleanTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/clean/UnknownAttributeCleanTransformer.java @@ -11,18 +11,21 @@ public class UnknownAttributeCleanTransformer extends Transformer { @Override protected void transform(ClassWrapper scope, Context context) throws Exception { - context - .classes(scope) - .forEach( - classWrapper -> { - changed |= classWrapper.classNode().attrs.removeIf(Attribute::isUnknown); - classWrapper - .methods() - .forEach(methodNode -> changed |= methodNode.attrs.removeIf(Attribute::isUnknown)); - classWrapper - .fields() - .forEach(fieldNode -> changed |= fieldNode.attrs.removeIf(Attribute::isUnknown)); - }); + context.classes(scope).forEach(classWrapper -> { + if (classWrapper.classNode().attrs != null) { + changed |= classWrapper.classNode().attrs.removeIf(Attribute::isUnknown); + } + classWrapper.methods().forEach(methodNode -> { + if (methodNode.attrs != null) { + changed |= methodNode.attrs.removeIf(Attribute::isUnknown); + } + }); + classWrapper.fields().forEach(fieldNode -> { + if (fieldNode.attrs != null) { + changed |= fieldNode.attrs.removeIf(Attribute::isUnknown); + } + }); + }); if (changed) { markChange(); diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java index 2a467847..1310b0c3 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888PackerTransformer.java @@ -15,50 +15,66 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +/** + * HP888 obfuscator has encrypted classes files (usually ending with the suffix ".mc" {@link #encryptedClassFilesSuffix}) + * that are decrypted and loaded when the jar starts. + */ public class HP888PackerTransformer extends Transformer { - private final String endOfEncryptedFile; + /** + * Suffix of encrypted class files + */ + private final String encryptedClassFilesSuffix; - public HP888PackerTransformer(String endOfEncryptedFile) { - this.endOfEncryptedFile = endOfEncryptedFile; + public HP888PackerTransformer(String encryptedClassFilesSuffix) { + this.encryptedClassFilesSuffix = encryptedClassFilesSuffix; } @Override protected void transform(ClassWrapper scope, Context context) throws Exception { Set toRemove = new HashSet<>(); HashMap newClasses = new HashMap<>(); - Cipher cipher = Cipher.getInstance("AES"); AtomicReference key = new AtomicReference<>(); - /* Firstly you must use HP888StringTransformer, so key would be decrypted, - and it only searches in loader classes so don't tell me its bad searching. */ + + /* Firstly you must use HP888StringTransformer, so key would be decrypted, + and it only searches in loader classes so don't tell me its bad searching. */ context.classes().stream().map(ClassWrapper::classNode).forEach(classNode -> classNode.methods.forEach(methodNode -> methodNode.instructions.forEach(abstractInsnNode -> { if (abstractInsnNode.isString() && abstractInsnNode.asString().endsWith("==")) { + // Find base64 key key.set(abstractInsnNode.asString()); } }))); + if (key.get().isEmpty()) { LOGGER.error("Key not found"); return; } + + // Decrypt encrypted classes + Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.getDecoder().decode(key.get()), "AES")); context.getFiles().forEach((file, bytes) -> { - if (file.endsWith(endOfEncryptedFile)) { - String cleanFileName = file.replace(endOfEncryptedFile, "").replace(".", "/"); + if (file.endsWith(encryptedClassFilesSuffix)) { + String cleanFileName = file.replace(encryptedClassFilesSuffix, "").replace(".", "/"); toRemove.add(file); try { - newClasses - .put - (cleanFileName, ClassHelper.loadClass(cleanFileName, - cipher.doFinal(bytes), - ClassReader.SKIP_FRAMES, - ClassWriter.COMPUTE_MAXS, - true)); + // Decrypt! + byte[] decrypted = cipher.doFinal(bytes); + + newClasses.put(cleanFileName, ClassHelper.loadClass(cleanFileName, + decrypted, + ClassReader.SKIP_FRAMES, + ClassWriter.COMPUTE_MAXS, + true + )); markChange(); } catch (Exception e) { LOGGER.error(e); } } }); + + // Cleanup toRemove.forEach(context.getFiles()::remove); context.getClasses().putAll(newClasses); } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java index 501a2854..b2626cc0 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/hp888/HP888StringTransformer.java @@ -11,12 +11,17 @@ import java.util.List; import java.util.Optional; +/** + * Strings are encrypted using a constant pool size of a provided class. + */ public class HP888StringTransformer extends Transformer { @Override protected void transform(ClassWrapper scope, Context context) throws Exception { context.classes().stream().map(ClassWrapper::classNode).forEach(classNode -> { List toRemove = new ArrayList<>(); + + // Find all encrypted strings classNode.methods.forEach(methodNode -> Arrays.stream(methodNode.instructions.toArray()) .filter(node -> node.getOpcode() == INVOKESTATIC) .filter(node -> node.getPrevious() != null && node.getPrevious().isString()) @@ -25,12 +30,20 @@ protected void transform(ClassWrapper scope, Context context) throws Exception { .filter(node -> node.desc.equals("(Ljava/lang/String;)Ljava/lang/String;")) .forEach(node -> findMethod(classNode, method -> method.name.equals(node.name) && method.desc.equals(node.desc)).ifPresent(method -> { String string = node.getPrevious().asString(); - int constantPoolSize = context.getClasses().get(getClassToPool(method).orElseThrow().getInternalName()).getConstantPool().getSize(); + + // Prepare data for decryption + ClassWrapper classForConstantPool = context.getClasses().get(getClassForConstantPool(method).orElseThrow().getInternalName()); + int constantPoolSize = classForConstantPool.getConstantPool().getSize(); String class0 = classNode.name; String class1 = classNode.name; + + // Decrypt! + String decryptedString = decrypt(string, constantPoolSize, class0.hashCode(), class1.hashCode()); + methodNode.instructions.remove(node.getPrevious()); - methodNode.instructions.set(node, new LdcInsnNode(decrypt(string, constantPoolSize, class0.hashCode(), class1.hashCode()))); + methodNode.instructions.set(node, new LdcInsnNode(decryptedString)); markChange(); + toRemove.add(method); }))); classNode.methods.removeAll(toRemove); @@ -38,7 +51,7 @@ protected void transform(ClassWrapper scope, Context context) throws Exception { }); } - private Optional getClassToPool(MethodNode methodNode) { + private Optional getClassForConstantPool(MethodNode methodNode) { return Arrays.stream(methodNode.instructions.toArray()) .filter(node -> node.getOpcode() == INVOKESTATIC) .map(AbstractInsnNode::next) @@ -52,14 +65,14 @@ private Optional getClassToPool(MethodNode methodNode) { } private String decrypt(String string, int constantPoolSize, int className0HashCode, int className1HashCode) { - char[] lllIlIIIllIllIIIllIlIllIl = string.toCharArray(); - int llIllIIIlllllIllIIlIIlIIl = 0; - for (char IlIIlIlllIIllIIIllIIIIIll : lllIlIIIllIllIIIllIlIllIl) { - IlIIlIlllIIllIIIllIIIIIll = (char) (IlIIlIlllIIllIIIllIIIIIll ^ constantPoolSize); - IlIIlIlllIIllIIIllIIIIIll = (char) (IlIIlIlllIIllIIIllIIIIIll ^ className0HashCode); - lllIlIIIllIllIIIllIlIllIl[llIllIIIlllllIllIIlIIlIIl] = (char) (IlIIlIlllIIllIIIllIIIIIll ^ className1HashCode); - ++llIllIIIlllllIllIIlIIlIIl; + char[] charArray = string.toCharArray(); + int i = 0; + for (char character : charArray) { + character = (char) (character ^ constantPoolSize); + character = (char) (character ^ className0HashCode); + charArray[i] = (char) (character ^ className1HashCode); + ++i; } - return new String(lllIlIIIllIllIIIllIlIllIl); + return new String(charArray); } } diff --git a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/TryCatchRepairTransformer.java b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/TryCatchRepairTransformer.java index 842e50a6..2c3975a0 100644 --- a/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/TryCatchRepairTransformer.java +++ b/deobfuscator-transformers/src/main/java/uwu/narumi/deobfuscator/core/other/impl/universal/TryCatchRepairTransformer.java @@ -45,7 +45,7 @@ else if (end.getNext() != null this.changed |= methodNode.exceptions.removeIf( exception -> methodNode.tryCatchBlocks.stream() - .noneMatch(tbce -> tbce.type.equals(exception))); + .noneMatch(tbce -> tbce.type != null && tbce.type.equals(exception))); }); if (changed) { diff --git a/testData/compiled/custom-classes/hp888/ExampleClass.class b/testData/compiled/custom-classes/hp888/ExampleClass.class new file mode 100644 index 0000000000000000000000000000000000000000..1e4a27d0bd2a07d05bffe3b0c583ca97448cbdc2 GIT binary patch literal 2053 zcma)-TUQ%Z6vzK36FMXeB@7|Tr4_KnB-|{57m{Ktf*s984Wb2aLl{GFaxs}yd1@DH z`Q#_4pQ1%g)>4WOyC+Jr47#y_zMM+-+0yB7?>|y`&RnMax;NHzrMLhtLtb18r3%=@ zR1y@p98Gno=A2rwTu8*aOwx^FqW{M)8DAyWiH@L*&QzR@N~65D{#D+pGHn%u8hQ~3 zz=t7$&{^;*h6Q@eYSli}@Dv7uh~Q}r=eg&{s9addjb|;V$+PA;)3Sum7Vj1{c-G9w ziA}eHB*h%1%#)jtLrwzN?S!$+taqjGNDA~O^LDXZbXEnFXlzTvIjB7E%gXlee_T<* ztx{IW9DH+Ixt0HONf~MVHl*Bs^yo!_b4f33IQrzyWT81+VjK$sLml9o`?boK=B{n2 zxDv!7kMqvJrJVzBT3uvV7 zuCiGo@o=)4&LPx zkdp|g1g{JijeIibT}GR^{c*Xlqa?<0Uf1=Oj&vi&0{*qi9xYtIT;8-<-Q?XPFpw&i ztxTh|Yt=l9o~qgSkqhmbW&FoTV>g z)t$7(S?ZxlIu?HV5AcVAd`f^29Ula!^&yBpYW>jAPaaC>I=>B|-~)R4n&K2{$K&54 z{17@fGfj-7XCI2$h M?N6y4dHhe;-wDVcU;qFB literal 0 HcmV?d00001 diff --git a/testData/compiled/custom-classes/hp888/Strings.class b/testData/compiled/custom-classes/hp888/Strings.class new file mode 100644 index 0000000000000000000000000000000000000000..93affa9849f478b3f80b92e36a7aaecc72815530 GIT binary patch literal 1259 zcmaJ>TTc^F5dKatoMl-kw3Jc-1uxiAu!2~Xo7M_iESIE6LU=GsS!r?ElHEmuPmSRh z_!E5bRbx_65>j9N5B?Gpan6=Y3F;=hXU?3NZ)U!kvp;@+I|ML{48w4xq^ET~O|N*; zB#~UzI7;ymDq$i{g$)N|XkJ&}sBv92*5Y$3>srp{a5IFDaRiKGTlFC?27l(vC2N}n zV{MciPQV5FX#^oLNMkv@U=-{L26rU7$lzuGYZO#?W9FQ+d@)nx?wL z;E6<+QwrJ}eZ z=G5i<;i!VkxFVwq-NfjontL6e*-7cDWo3%$s%COrWeEMlnd3TML8in zqDhG?r=p7@>BmBoVsI^|I3^h`|36iZr!+1Cj_Pz0oKZIy1RK<+Ou^7*w>DNZ)6sxX z;h~G`LRd^GNntUVjz)rnda_8JvG}{&=Zti?0VQPXCIHgA0V_8+ryJj6xxMp`gX!~MDrnWggB-c$1{4T v=r@C3Na8tJ3{l!Z6IJd*0L^p@{OBQR1g&Hd#5~&YlE~NS-sMSepab{=Klm=X literal 0 HcmV?d00001 diff --git a/testData/results/custom-classes/hp888/ExampleClass.dec b/testData/results/custom-classes/hp888/ExampleClass.dec new file mode 100644 index 0000000000000000000000000000000000000000..1065bcd7944b948b4d62d1e5ab36e42ba65df964 GIT binary patch literal 1185 zcmaJ>O>f&U4Bgqkg3Y0@FGiE&+U_!RK!9bJ_7?b{I&3UV{+L9!3kLu9iIg(SEixs7 z75ec#@|n({>YwU}F!<|jmJQ>x(KXaYIS0GbSH6Dl)d4NwZ^G{2AdUUWz^}pTS49Yp z6!~P`(Y^v>wp-a-S^n47NC57k!#6=~s@p&iRClLuV^@>zyA8aYPZ1F%?iy;%mpk1a z!P)wL)esHt^C!_VEXOi&$%_6UQRA#jfQ1BX~LwLuI9<6W0i{zz7LM|f3sT|jW0UDq#=Q=?t^&nmd`){e%c zC`JZCpTF_mFH=f24Ca&zcW9gwcrm@CTp(qPMH+3#m2H;Elk~%oDYTZL-8i5v=k^lq z2##|!x5`G!4t|Qa64>8VzNPgnei^w=kWnWN`(n)VbO@!i#PUb-Z^@HYH9i<@QRZAc z3M2j0*#1pD8_bLHh%}?~Vy^J)>;7`7H&w;?^4NZ;mg(@813bqPL1&PVodcKuD`H3m1MM~C$%!1ht UvL5aP5DR2hZenI0(1=