Skip to content

Commit

Permalink
test samples, some fixes and more documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
EpicPlayerA10 committed Sep 24, 2024
1 parent 2d1e7ea commit b60da3e
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 44 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
testData/results/**/* diff
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
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;
import uwu.narumi.deobfuscator.core.other.impl.hp888.HP888PackerTransformer;
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) {
super(
HP888StringTransformer::new,
() -> new HP888PackerTransformer(packedEndOfFile),
HP888StringTransformer::new,
ComposedGeneralCleanTransformer::new,
ComposedGeneralRepairTransformer::new,
UniversalNumberTransformer::new,
ComposedGeneralFlowTransformer::new,
Expand All @@ -27,7 +28,6 @@ public ComposedHP888Transformer(String packedEndOfFile) {
public ComposedHP888Transformer() {
super(
HP888StringTransformer::new,
ComposedGeneralCleanTransformer::new,
ComposedGeneralRepairTransformer::new,
UniversalNumberTransformer::new,
ComposedGeneralFlowTransformer::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> toRemove = new HashSet<>();
HashMap<String, ClassWrapper> newClasses = new HashMap<>();
Cipher cipher = Cipher.getInstance("AES");
AtomicReference<String> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MethodNode> 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())
Expand All @@ -25,20 +30,28 @@ 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);
toRemove.clear();
});
}

private Optional<Type> getClassToPool(MethodNode methodNode) {
private Optional<Type> getClassForConstantPool(MethodNode methodNode) {
return Arrays.stream(methodNode.instructions.toArray())
.filter(node -> node.getOpcode() == INVOKESTATIC)
.map(AbstractInsnNode::next)
Expand All @@ -52,14 +65,14 @@ private Optional<Type> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added testData/results/custom-classes/hp888/Strings.dec
Binary file not shown.

0 comments on commit b60da3e

Please sign in to comment.