From ce74765269d89dbfa6d6749f0ccd6ff087e897ab Mon Sep 17 00:00:00 2001 From: Graham Date: Sat, 29 Feb 2020 23:16:31 +0000 Subject: [PATCH] Add static method scrambling transformer The client scrambles static fields and methods - moving each to a random class where it does not logically belong. This transformer moves all static methods to a new set of empty classes. The intention is that the human refactoring the deobfuscated client can move the static methods around with an IDE to classes where it makes sense. In theory we could use heuristics to move some methods around - e.g. ideas I have include: * If the method takes no arguments, move it to the same class as its return type. * If it's a void method that takes one argument, move it to the same class as its single argument. * Group related methods together based on where they are called. However, some of the heuristics are probably fairly complicated to implement. Furthermore, as they are heuristics, they will make mistakes. In the naive approach, the Static* classes generated by this transformer effectively act as a queue of methods that a human has yet to move to an appropriate class. If the Static* classes are ever emptied after refactoring, then we will know that all static methods have been moved to an appropriate class. We wouldn't be able to guarantee this if some methods were moved to Class* classes with heuristics. The heuristics would also be fairly complicated to implement. Therefore, the transformer does not use heuristics. Field scrambling support will be included in a future commit. --- .../java/dev/openrs2/deob/Deobfuscator.kt | 2 + .../transform/StaticScramblingTransformer.kt | 99 +++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt index a0549280..a7ff6e02 100644 --- a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt @@ -25,6 +25,7 @@ import dev.openrs2.deob.transform.OriginalNameTransformer import dev.openrs2.deob.transform.OverrideTransformer import dev.openrs2.deob.transform.RemapTransformer import dev.openrs2.deob.transform.ResetTransformer +import dev.openrs2.deob.transform.StaticScramblingTransformer import dev.openrs2.deob.transform.UnusedArgTransformer import dev.openrs2.deob.transform.VisibilityTransformer import java.nio.file.Files @@ -166,6 +167,7 @@ class Deobfuscator(private val input: Path, private val output: Path) { FinalTransformer(), ClassLiteralTransformer(), InvokeSpecialTransformer(), + StaticScramblingTransformer(), VisibilityTransformer(), OverrideTransformer() ) diff --git a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt new file mode 100644 index 00000000..fc0b7d0a --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt @@ -0,0 +1,99 @@ +package dev.openrs2.deob.transform + +import dev.openrs2.asm.ClassVersionUtils +import dev.openrs2.asm.MemberRef +import dev.openrs2.asm.classpath.ClassPath +import dev.openrs2.asm.classpath.Library +import dev.openrs2.asm.transform.Transformer +import dev.openrs2.deob.remap.TypedRemapper +import org.objectweb.asm.Opcodes +import org.objectweb.asm.tree.ClassNode +import org.objectweb.asm.tree.FieldInsnNode +import org.objectweb.asm.tree.MethodInsnNode +import org.objectweb.asm.tree.MethodNode + +class StaticScramblingTransformer : Transformer() { + private val fields = mutableMapOf() + private val methods = mutableMapOf() + private var nextStaticClass: ClassNode? = null + private val staticClasses = mutableListOf() + + private fun nextClass(): ClassNode { + var clazz = nextStaticClass + if (clazz != null && (clazz.fields.size + clazz.methods.size) < MAX_FIELDS_AND_METHODS) { + return clazz + } + + clazz = ClassNode() + clazz.version = Opcodes.V1_1 + clazz.access = Opcodes.ACC_PUBLIC or Opcodes.ACC_SUPER + clazz.name = "Static${staticClasses.size + 1}" + clazz.superName = "java/lang/Object" + clazz.interfaces = mutableListOf() + clazz.innerClasses = mutableListOf() + clazz.fields = mutableListOf() + clazz.methods = mutableListOf() + + staticClasses += clazz + nextStaticClass = clazz + + return clazz + } + + override fun preTransform(classPath: ClassPath) { + fields.clear() + methods.clear() + nextStaticClass = null + staticClasses.clear() + + for (library in classPath.libraries) { + // TODO(gpe): improve detection of the client library + if ("client" !in library) { + continue + } + + for (clazz in library) { + // TODO(gpe): exclude the JSObject class + if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { + continue + } + + clazz.methods.removeIf { method -> + if (method.access and Opcodes.ACC_STATIC == 0) { + return@removeIf false + } else if (method.access and Opcodes.ACC_NATIVE != 0) { + return@removeIf false + } else if (method.name in TypedRemapper.EXCLUDED_METHODS) { + return@removeIf false + } + + val staticClass = nextClass() + staticClass.methods.add(method) + staticClass.version = ClassVersionUtils.maxVersion(staticClass.version, clazz.version) + + methods[MemberRef(clazz, method)] = staticClass.name + return@removeIf true + } + } + + for (clazz in staticClasses) { + library.add(clazz) + } + } + } + + override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { + for (insn in method.instructions) { + when (insn) { + is FieldInsnNode -> insn.owner = fields.getOrDefault(MemberRef(insn), insn.owner) + is MethodInsnNode -> insn.owner = methods.getOrDefault(MemberRef(insn), insn.owner) + } + } + + return false + } + + companion object { + private const val MAX_FIELDS_AND_METHODS = 500 + } +}