Separate ZKM and Jagex tracing exception transformers

This is a slightly nicer solution than running a combined transformer
until it reaches a fixed point: we know we'd only need two passes,
rather than an arbitrary number of passes.

The 667 client uses various exception types beyond RuntimeException in
ZKM exception obfuscation handlers, including Throwable, Exception,
EOFException and IOException. This separation also allows us to remove
the type constraint for ZKM handlers.

Signed-off-by: Graham <gpe@openrs2.org>
master
Graham 4 months ago
parent 514799920e
commit 9e001e32e1
  1. 2
      deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/BytecodeDeobfuscatorModule.kt
  2. 54
      deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionObfuscationTransformer.kt
  3. 28
      deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionTracingTransformer.kt

@ -12,6 +12,7 @@ import org.openrs2.deob.bytecode.transform.ConstantArgTransformer
import org.openrs2.deob.bytecode.transform.CopyPropagationTransformer import org.openrs2.deob.bytecode.transform.CopyPropagationTransformer
import org.openrs2.deob.bytecode.transform.CounterTransformer import org.openrs2.deob.bytecode.transform.CounterTransformer
import org.openrs2.deob.bytecode.transform.EmptyClassTransformer import org.openrs2.deob.bytecode.transform.EmptyClassTransformer
import org.openrs2.deob.bytecode.transform.ExceptionObfuscationTransformer
import org.openrs2.deob.bytecode.transform.ExceptionTracingTransformer import org.openrs2.deob.bytecode.transform.ExceptionTracingTransformer
import org.openrs2.deob.bytecode.transform.FernflowerExceptionTransformer import org.openrs2.deob.bytecode.transform.FernflowerExceptionTransformer
import org.openrs2.deob.bytecode.transform.FieldOrderTransformer import org.openrs2.deob.bytecode.transform.FieldOrderTransformer
@ -56,6 +57,7 @@ public object BytecodeDeobfuscatorModule : AbstractModule() {
binder.addBinding().to(PatcherTransformer::class.java) binder.addBinding().to(PatcherTransformer::class.java)
binder.addBinding().to(ResourceTransformer::class.java) binder.addBinding().to(ResourceTransformer::class.java)
binder.addBinding().to(OpaquePredicateTransformer::class.java) binder.addBinding().to(OpaquePredicateTransformer::class.java)
binder.addBinding().to(ExceptionObfuscationTransformer::class.java)
binder.addBinding().to(ExceptionTracingTransformer::class.java) binder.addBinding().to(ExceptionTracingTransformer::class.java)
binder.addBinding().to(MonitorTransformer::class.java) binder.addBinding().to(MonitorTransformer::class.java)
binder.addBinding().to(BitShiftTransformer::class.java) binder.addBinding().to(BitShiftTransformer::class.java)

@ -0,0 +1,54 @@
package org.openrs2.deob.bytecode.transform
import com.github.michaelbull.logging.InlineLogger
import jakarta.inject.Singleton
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.MethodNode
import org.openrs2.asm.classpath.ClassPath
import org.openrs2.asm.classpath.Library
import org.openrs2.asm.nextReal
import org.openrs2.asm.transform.Transformer
/**
* A [Transformer] responsible for removing [ZKM](http://www.zelix.com/klassmaster/)'s
* [exception obfuscation](https://www.zelix.com/klassmaster/featuresExceptionObfuscation.html),
* which inserts exception handlers that catch any type of exception and
* immediately re-throw them. The exception handlers are inserted in locations
* where there is no Java source code equivalent, confusing decompilers.
*/
@Singleton
public class ExceptionObfuscationTransformer : Transformer() {
private var handlers = 0
override fun preTransform(classPath: ClassPath) {
handlers = 0
}
override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean {
for (insn in method.instructions) {
if (insn.opcode != Opcodes.ATHROW) {
continue
}
val foundTryCatch = method.tryCatchBlocks.removeIf { tryCatch ->
tryCatch.handler.nextReal === insn
}
if (foundTryCatch) {
method.instructions.remove(insn)
handlers++
}
}
return false
}
override fun postTransform(classPath: ClassPath) {
logger.info { "Removed $handlers exception obfuscation handlers" }
}
private companion object {
private val logger = InlineLogger()
}
}

@ -11,22 +11,13 @@ import org.openrs2.asm.nextReal
import org.openrs2.asm.transform.Transformer import org.openrs2.asm.transform.Transformer
/** /**
* A [Transformer] responsible for removing two kinds of redundant exception * A [Transformer] responsible for removing Jagex's exception tracing. Jagex
* handler. * inserts a try/catch block around every method that catches
* * [RuntimeException]s, wraps them with a custom [RuntimeException]
* - [ZKM](http://www.zelix.com/klassmaster/)'s * implementation and re-throws them. The wrapped exception's message contains
* [exception obfuscation](https://www.zelix.com/klassmaster/featuresExceptionObfuscation.html), * the values of the method's arguments. While this is for debugging and not
* which inserts exception handlers that catch [RuntimeException]s and * obfuscation, it is clearly automatically-generated and thus we remove these
* immediately re-throw them. The exception handlers are inserted in * exception handlers too.
* locations where there is no Java source code equivalent, confusing
* decompilers.
*
* - Jagex inserts a try/catch block around every method that catches
* [RuntimeException]s, wraps them with a custom [RuntimeException]
* implementation and re-throws them. The wrapped exception's message
* contains the values of the method's arguments. While this is for debugging
* and not obfuscation, it is clearly automatically-generated and thus we
* remove these exception handlers too.
*/ */
@Singleton @Singleton
public class ExceptionTracingTransformer : Transformer() { public class ExceptionTracingTransformer : Transformer() {
@ -42,8 +33,6 @@ public class ExceptionTracingTransformer : Transformer() {
clazz: ClassNode, clazz: ClassNode,
method: MethodNode method: MethodNode
): Boolean { ): Boolean {
var changed = false
for (match in CATCH_MATCHER.match(method)) { for (match in CATCH_MATCHER.match(method)) {
val foundTryCatch = method.tryCatchBlocks.removeIf { tryCatch -> val foundTryCatch = method.tryCatchBlocks.removeIf { tryCatch ->
tryCatch.type == "java/lang/RuntimeException" && tryCatch.handler.nextReal === match[0] tryCatch.type == "java/lang/RuntimeException" && tryCatch.handler.nextReal === match[0]
@ -52,11 +41,10 @@ public class ExceptionTracingTransformer : Transformer() {
if (foundTryCatch) { if (foundTryCatch) {
match.forEach(method.instructions::remove) match.forEach(method.instructions::remove)
tracingTryCatches++ tracingTryCatches++
changed = true
} }
} }
return changed return false
} }
override fun postTransform(classPath: ClassPath) { override fun postTransform(classPath: ClassPath) {

Loading…
Cancel
Save