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.
pull/66/head
Graham 4 years ago
parent 4468766a9d
commit ce74765269
  1. 2
      deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt
  2. 99
      deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.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()
)

@ -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<MemberRef, String>()
private val methods = mutableMapOf<MemberRef, String>()
private var nextStaticClass: ClassNode? = null
private val staticClasses = mutableListOf<ClassNode>()
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
}
}
Loading…
Cancel
Save