From 9e001e32e1aab8a49d36ba988a3d23df5d2596bc Mon Sep 17 00:00:00 2001 From: Graham Date: Thu, 11 Jan 2024 20:35:05 +0000 Subject: [PATCH] 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 --- .../bytecode/BytecodeDeobfuscatorModule.kt | 2 + .../ExceptionObfuscationTransformer.kt | 54 +++++++++++++++++++ .../transform/ExceptionTracingTransformer.kt | 28 +++------- 3 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionObfuscationTransformer.kt diff --git a/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/BytecodeDeobfuscatorModule.kt b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/BytecodeDeobfuscatorModule.kt index 132c0f9a..2dc39ce7 100644 --- a/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/BytecodeDeobfuscatorModule.kt +++ b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/BytecodeDeobfuscatorModule.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.CounterTransformer 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.FernflowerExceptionTransformer import org.openrs2.deob.bytecode.transform.FieldOrderTransformer @@ -56,6 +57,7 @@ public object BytecodeDeobfuscatorModule : AbstractModule() { binder.addBinding().to(PatcherTransformer::class.java) binder.addBinding().to(ResourceTransformer::class.java) binder.addBinding().to(OpaquePredicateTransformer::class.java) + binder.addBinding().to(ExceptionObfuscationTransformer::class.java) binder.addBinding().to(ExceptionTracingTransformer::class.java) binder.addBinding().to(MonitorTransformer::class.java) binder.addBinding().to(BitShiftTransformer::class.java) diff --git a/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionObfuscationTransformer.kt b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionObfuscationTransformer.kt new file mode 100644 index 00000000..98d0c75b --- /dev/null +++ b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionObfuscationTransformer.kt @@ -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() + } +} diff --git a/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionTracingTransformer.kt b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionTracingTransformer.kt index edf340fb..b47c02e1 100644 --- a/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionTracingTransformer.kt +++ b/deob-bytecode/src/main/kotlin/org/openrs2/deob/bytecode/transform/ExceptionTracingTransformer.kt @@ -11,22 +11,13 @@ import org.openrs2.asm.nextReal import org.openrs2.asm.transform.Transformer /** - * A [Transformer] responsible for removing two kinds of redundant exception - * handler. - * - * - [ZKM](http://www.zelix.com/klassmaster/)'s - * [exception obfuscation](https://www.zelix.com/klassmaster/featuresExceptionObfuscation.html), - * which inserts exception handlers that catch [RuntimeException]s and - * immediately re-throw them. The exception handlers are inserted in - * 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. + * A [Transformer] responsible for removing Jagex's exception tracing. 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 public class ExceptionTracingTransformer : Transformer() { @@ -42,8 +33,6 @@ public class ExceptionTracingTransformer : Transformer() { clazz: ClassNode, method: MethodNode ): Boolean { - var changed = false - for (match in CATCH_MATCHER.match(method)) { val foundTryCatch = method.tryCatchBlocks.removeIf { tryCatch -> tryCatch.type == "java/lang/RuntimeException" && tryCatch.handler.nextReal === match[0] @@ -52,11 +41,10 @@ public class ExceptionTracingTransformer : Transformer() { if (foundTryCatch) { match.forEach(method.instructions::remove) tracingTryCatches++ - changed = true } } - return changed + return false } override fun postTransform(classPath: ClassPath) {