From 5af43165ff4547497d3e367df0230ee2cb08e52b Mon Sep 17 00:00:00 2001 From: Graham Date: Fri, 8 May 2020 23:58:13 +0100 Subject: [PATCH] Replace TypedRemapper constants with a more flexible system The new system will make it easier to port the deobfuscator to different revisions. There are two main changes: - The addition of a Profile class, which contains a list of excluded classes, methods and fields, and the maximum obfuscated name length. It is passed to Transformers that require it with dependency injection. - New ClassFilter and MemberFilter infrastructure. The MemberFilter class adds support for filtering fields and methods based on the owner and descriptor, in addition to the name. This makes the filters more precise than the previous system. It also supports globs, which makes it easy to filter whole groups of classes, fields and methods in one go. The Profile class uses a ClassFilter and MemberFilters to represent the list of excluded classes, methods and fields. A separate benefit is the addition of a separate entry points filter to the Profile class. Prior to this commit, many Transformers re-used the excluded method filter to find entry points, which is less precise (many of the excluded methods in 550 are not entry points). Support for filtering methods by owner and descriptor in addition to name allows the DEFAULT_PUBLIC_CTOR_CLASSES Set in VisibilityTransformer to be combined with the entry points filter. In the future it might be desirable to split the excluded method set into three separate sets: - One to represent methods that can't be renamed. - One to represent methods whose signature can't be changed. - One to represent methods that can't be removed. Signed-off-by: Graham --- .../dev/openrs2/asm/filter/ClassFilter.kt | 5 ++ .../main/java/dev/openrs2/asm/filter/Glob.kt | 55 ++++++++++++ .../dev/openrs2/asm/filter/GlobClassFilter.kt | 9 ++ .../openrs2/asm/filter/GlobMemberFilter.kt | 24 ++++++ .../dev/openrs2/asm/filter/MemberFilter.kt | 11 +++ .../openrs2/asm/filter/UnionClassFilter.kt | 9 ++ .../openrs2/asm/filter/UnionMemberFilter.kt | 9 ++ .../java/dev/openrs2/deob/Deobfuscator.kt | 9 +- .../dev/openrs2/deob/DeobfuscatorModule.kt | 3 + .../src/main/java/dev/openrs2/deob/Profile.kt | 45 ++++++++++ .../deob/filter/ReflectedConstructorFilter.kt | 47 ++++++++++ .../dev/openrs2/deob/remap/PrefixRemapper.kt | 5 +- .../dev/openrs2/deob/remap/TypedRemapper.kt | 86 +++++++++---------- .../deob/transform/ConstantArgTransformer.kt | 41 ++++----- .../deob/transform/RemapTransformer.kt | 6 +- .../transform/StaticScramblingTransformer.kt | 7 +- .../deob/transform/UnusedArgTransformer.kt | 12 ++- .../deob/transform/UnusedMethodTransformer.kt | 14 ++- .../deob/transform/VisibilityTransformer.kt | 31 +++---- 19 files changed, 324 insertions(+), 104 deletions(-) create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/Glob.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt create mode 100644 asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt create mode 100644 deob/src/main/java/dev/openrs2/deob/Profile.kt create mode 100644 deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt diff --git a/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt new file mode 100644 index 00000000..d2608600 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/ClassFilter.kt @@ -0,0 +1,5 @@ +package dev.openrs2.asm.filter + +interface ClassFilter { + fun matches(name: String): Boolean +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt b/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt new file mode 100644 index 00000000..076f3fce --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/Glob.kt @@ -0,0 +1,55 @@ +package dev.openrs2.asm.filter + +object Glob { + fun compile(pattern: String): Regex { + return compile(pattern, className = false) + } + + fun compileClass(pattern: String): Regex { + return compile(pattern, className = true) + } + + private fun compile(pattern: String, className: Boolean): Regex { + val regex = StringBuilder() + var star = false + var escape = false + + for (ch in pattern) { + check(!star || !escape) + + if (star) { + star = false + + if (ch == '*') { + regex.append(".*") + continue + } + + regex.append("[^/]*") + } else if (escape) { + regex.append(Regex.escape(ch.toString())) + continue + } + + when (ch) { + '*' -> if (className) { + star = true + } else { + regex.append(".*") + } + '\\' -> escape = true + else -> regex.append(Regex.escape(ch.toString())) + } + } + + if (star) { + regex.append(".*") + } + + require(!escape) { + "Unterminated escape sequence" + } + + return Regex(regex.toString()) + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt new file mode 100644 index 00000000..3845f9b1 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/GlobClassFilter.kt @@ -0,0 +1,9 @@ +package dev.openrs2.asm.filter + +class GlobClassFilter(vararg patterns: String) : ClassFilter { + private val patterns = patterns.map(Glob::compileClass).toList() + + override fun matches(name: String): Boolean { + return patterns.any { it.matches(name) } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt new file mode 100644 index 00000000..47f3eeeb --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/GlobMemberFilter.kt @@ -0,0 +1,24 @@ +package dev.openrs2.asm.filter + +import dev.openrs2.asm.MemberRef + +class GlobMemberFilter(vararg patterns: String) : MemberFilter { + private data class CompiledPattern(val owner: Regex, val name: Regex, val desc: Regex) + + private val patterns = patterns.map(::compile).toList() + + override fun matches(owner: String, name: String, desc: String): Boolean { + return patterns.any { it.owner.matches(owner) && it.name.matches(name) && it.desc.matches(desc) } + } + + companion object { + private fun compile(pattern: String): CompiledPattern { + val ref = MemberRef.fromString(pattern) + return CompiledPattern( + Glob.compileClass(ref.owner), + Glob.compile(ref.name), + Glob.compile(ref.desc) + ) + } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt new file mode 100644 index 00000000..394a6c11 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/MemberFilter.kt @@ -0,0 +1,11 @@ +package dev.openrs2.asm.filter + +import dev.openrs2.asm.MemberRef + +interface MemberFilter { + fun matches(owner: String, name: String, desc: String): Boolean + + fun matches(member: MemberRef): Boolean { + return matches(member.owner, member.name, member.desc) + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt new file mode 100644 index 00000000..17b8cd39 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/UnionClassFilter.kt @@ -0,0 +1,9 @@ +package dev.openrs2.asm.filter + +class UnionClassFilter(vararg filters: ClassFilter) : ClassFilter { + private val filters = filters.toList() + + override fun matches(name: String): Boolean { + return filters.any { it.matches(name) } + } +} diff --git a/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt b/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt new file mode 100644 index 00000000..2194fa93 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/filter/UnionMemberFilter.kt @@ -0,0 +1,9 @@ +package dev.openrs2.asm.filter + +class UnionMemberFilter(vararg filters: MemberFilter) : MemberFilter { + private val filters = filters.toList() + + override fun matches(owner: String, name: String, desc: String): Boolean { + return filters.any { it.matches(owner, name, desc) } + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt index 32a7554d..a3bb46f6 100644 --- a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.kt @@ -15,6 +15,7 @@ import javax.inject.Singleton @Singleton class Deobfuscator @Inject constructor( + private val profile: Profile, @DeobfuscatorQualifier private val transformers: Set<@JvmSuppressWildcards Transformer> ) { fun run(input: Path, output: Path) { @@ -56,10 +57,10 @@ class Deobfuscator @Inject constructor( // prefix remaining loader/unpacker classes (to avoid conflicts when we rename in the same classpath as the client) logger.info { "Prefixing loader and unpackclass class names" } - loader.remap(PrefixRemapper.create(loader, "loader_")) - glLoader.remap(PrefixRemapper.create(glLoader, "loader_")) - unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_")) - glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_")) + loader.remap(PrefixRemapper.create(loader, "loader_", profile.excludedClasses)) + glLoader.remap(PrefixRemapper.create(glLoader, "loader_", profile.excludedClasses)) + unpackClass.remap(PrefixRemapper.create(unpackClass, "unpackclass_", profile.excludedClasses)) + glUnpackClass.remap(PrefixRemapper.create(glUnpackClass, "unpackclass_", profile.excludedClasses)) // bundle libraries together into a common classpath val runtime = ClassLoader.getPlatformClassLoader() diff --git a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt index 7f91852f..e3bfc002 100644 --- a/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt +++ b/deob/src/main/java/dev/openrs2/deob/DeobfuscatorModule.kt @@ -40,6 +40,9 @@ object DeobfuscatorModule : AbstractModule() { install(BundlerModule) install(DeobfuscatorMapModule) + bind(Profile::class.java) + .toInstance(Profile.BUILD_550) + val binder = Multibinder.newSetBinder(binder(), Transformer::class.java, DeobfuscatorQualifier::class.java) binder.addBinding().to(OriginalPcSaveTransformer::class.java) binder.addBinding().to(OriginalNameTransformer::class.java) diff --git a/deob/src/main/java/dev/openrs2/deob/Profile.kt b/deob/src/main/java/dev/openrs2/deob/Profile.kt new file mode 100644 index 00000000..2d74e5a3 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/Profile.kt @@ -0,0 +1,45 @@ +package dev.openrs2.deob + +import dev.openrs2.asm.filter.ClassFilter +import dev.openrs2.asm.filter.GlobClassFilter +import dev.openrs2.asm.filter.GlobMemberFilter +import dev.openrs2.asm.filter.MemberFilter + +class Profile( + val excludedClasses: ClassFilter, + val excludedMethods: MemberFilter, + val excludedFields: MemberFilter, + val entryPoints: MemberFilter, + val maxObfuscatedNameLen: Int +) { + companion object { + val BUILD_550 = Profile( + excludedClasses = GlobClassFilter( + "client", + "jagex3/jagmisc/jagmisc", + "loader", + "unpack", + "unpackclass" + ), + excludedMethods = GlobMemberFilter( + "**. *", + "**. *", + "**.main *", + "**.providesignlink *", + "**.quit *" + ), + excludedFields = GlobMemberFilter( + "**.cache *" + ), + entryPoints = GlobMemberFilter( + "**. *", + "**.main *", + "**.providesignlink *", + "client. *", + "loader. *", + "unpackclass. *" + ), + maxObfuscatedNameLen = 2 + ) + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt b/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt new file mode 100644 index 00000000..f4f2ca47 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/filter/ReflectedConstructorFilter.kt @@ -0,0 +1,47 @@ +package dev.openrs2.deob.filter + +import com.github.michaelbull.logging.InlineLogger +import dev.openrs2.asm.ClassForNameUtils +import dev.openrs2.asm.classpath.ClassPath +import dev.openrs2.asm.filter.MemberFilter +import dev.openrs2.asm.hasCode +import org.objectweb.asm.tree.MethodNode + +class ReflectedConstructorFilter private constructor(private val classes: Set) : MemberFilter { + override fun matches(owner: String, name: String, desc: String): Boolean { + return classes.contains(owner) && name == "" + } + + companion object { + private val logger = InlineLogger() + + fun create(classPath: ClassPath): MemberFilter { + val classes = mutableSetOf() + + for (library in classPath.libraries) { + for (clazz in library) { + for (method in clazz.methods) { + processMethod(classPath, method, classes) + } + } + } + + logger.info { "Identified constructors invoked with reflection $classes" } + + return ReflectedConstructorFilter(classes) + } + + private fun processMethod(classPath: ClassPath, method: MethodNode, classes: MutableSet) { + if (!method.hasCode) { + return + } + + for (name in ClassForNameUtils.findClassNames(method)) { + val clazz = classPath[name] + if (clazz != null && !clazz.dependency) { + classes.add(name) + } + } + } + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt b/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt index c7c3e0f2..92ca5914 100644 --- a/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt +++ b/deob/src/main/java/dev/openrs2/deob/remap/PrefixRemapper.kt @@ -1,15 +1,16 @@ package dev.openrs2.deob.remap import dev.openrs2.asm.classpath.Library +import dev.openrs2.asm.filter.ClassFilter import org.objectweb.asm.commons.Remapper import org.objectweb.asm.commons.SimpleRemapper object PrefixRemapper { - fun create(library: Library, prefix: String): Remapper { + fun create(library: Library, prefix: String, excluded: ClassFilter): Remapper { val mapping = mutableMapOf() for (clazz in library) { - if (clazz.name in TypedRemapper.EXCLUDED_CLASSES) { + if (excluded.matches(clazz.name)) { mapping[clazz.name] = clazz.name } else { mapping[clazz.name] = prefix + clazz.name diff --git a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt index b6a70413..bcdc3816 100644 --- a/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt +++ b/deob/src/main/java/dev/openrs2/deob/remap/TypedRemapper.kt @@ -5,6 +5,9 @@ import dev.openrs2.asm.MemberDesc import dev.openrs2.asm.MemberRef import dev.openrs2.asm.classpath.ClassMetadata import dev.openrs2.asm.classpath.ClassPath +import dev.openrs2.asm.filter.ClassFilter +import dev.openrs2.asm.filter.MemberFilter +import dev.openrs2.deob.Profile import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.indefiniteArticle import org.objectweb.asm.Opcodes @@ -31,57 +34,38 @@ class TypedRemapper private constructor( companion object { private val logger = InlineLogger() - val EXCLUDED_CLASSES = setOf( - "client", - "jagex3/jagmisc/jagmisc", - "loader", - "unpack", - "unpackclass" - ) - val EXCLUDED_METHODS = setOf( - "", - "", - "main", - "providesignlink", - "quit" - ) - val EXCLUDED_FIELDS = setOf( - "cache" - ) - private val LIBRARY_PREFIX_REGEX = Regex("^(?:loader|unpackclass)_") - private const val MAX_OBFUSCATED_NAME_LEN = 2 - fun create(classPath: ClassPath): TypedRemapper { + fun create(classPath: ClassPath, profile: Profile): TypedRemapper { val inheritedFieldSets = classPath.createInheritedFieldSets() val inheritedMethodSets = classPath.createInheritedMethodSets() - val classes = createClassMapping(classPath) - val fields = createFieldMapping(classPath, inheritedFieldSets, classes) - val methods = createMethodMapping(classPath, inheritedMethodSets) + val classes = createClassMapping(classPath, profile.excludedClasses) + val fields = createFieldMapping(classPath, profile.excludedFields, inheritedFieldSets, classes) + val methods = createMethodMapping(classPath, profile.excludedMethods, inheritedMethodSets) - verifyMapping(classes) - verifyMemberMapping(fields) - verifyMemberMapping(methods) + verifyMapping(classes, profile.maxObfuscatedNameLen) + verifyMemberMapping(fields, profile.maxObfuscatedNameLen) + verifyMemberMapping(methods, profile.maxObfuscatedNameLen) return TypedRemapper(classes, fields, methods) } - private fun verifyMapping(mapping: Map) { + private fun verifyMapping(mapping: Map, maxObfuscatedNameLen: Int) { for ((key, value) in mapping) { - verifyMapping(key, value) + verifyMapping(key, value, maxObfuscatedNameLen) } } - private fun verifyMemberMapping(mapping: Map) { + private fun verifyMemberMapping(mapping: Map, maxObfuscatedNameLen: Int) { for ((key, value) in mapping) { - verifyMapping(key.name, value) + verifyMapping(key.name, value, maxObfuscatedNameLen) } } - private fun verifyMapping(name: String, mappedName: String) { + private fun verifyMapping(name: String, mappedName: String, maxObfuscatedNameLen: Int) { val originalName = name.replace(LIBRARY_PREFIX_REGEX, "") - if (originalName.length > MAX_OBFUSCATED_NAME_LEN && originalName != mappedName) { + if (originalName.length > maxObfuscatedNameLen && originalName != mappedName) { logger.warn { "Remapping probably unobfuscated name $originalName to $mappedName" } } } @@ -95,32 +79,33 @@ class TypedRemapper private constructor( return prefix + separator + prefixes.merge(prefix, 1, Integer::sum) } - private fun createClassMapping(classPath: ClassPath): Map { + private fun createClassMapping(classPath: ClassPath, excludedClasses: ClassFilter): Map { val mapping = mutableMapOf() val prefixes = mutableMapOf() for (clazz in classPath.libraryClasses) { - populateClassMapping(mapping, prefixes, clazz) + populateClassMapping(excludedClasses, mapping, prefixes, clazz) } return mapping } private fun populateClassMapping( + excludedClasses: ClassFilter, mapping: MutableMap, prefixes: MutableMap, clazz: ClassMetadata ): String { val name = clazz.name - if (mapping.containsKey(name) || !isClassRenamable(clazz)) { + if (mapping.containsKey(name) || !isClassRenamable(clazz, excludedClasses)) { return mapping.getOrDefault(name, name) } - val mappedName = generateClassName(mapping, prefixes, clazz) + val mappedName = generateClassName(excludedClasses, mapping, prefixes, clazz) mapping[name] = mappedName return mappedName } - private fun isClassRenamable(clazz: ClassMetadata): Boolean { - if (clazz.name in EXCLUDED_CLASSES || clazz.dependency) { + private fun isClassRenamable(clazz: ClassMetadata, excludedClasses: ClassFilter): Boolean { + if (excludedClasses.matches(clazz.name) || clazz.dependency) { return false } @@ -134,6 +119,7 @@ class TypedRemapper private constructor( } private fun generateClassName( + excludedClasses: ClassFilter, mapping: MutableMap, prefixes: MutableMap, clazz: ClassMetadata @@ -143,7 +129,7 @@ class TypedRemapper private constructor( val superClass = clazz.superClass if (superClass != null && superClass.name != "java/lang/Object") { - var superName = populateClassMapping(mapping, prefixes, superClass) + var superName = populateClassMapping(excludedClasses, mapping, prefixes, superClass) superName = superName.substring(superName.lastIndexOf('/') + 1) mappedName += generateName(prefixes, superName + "_Sub") } else if (clazz.`interface`) { @@ -157,6 +143,7 @@ class TypedRemapper private constructor( private fun createFieldMapping( classPath: ClassPath, + excludedFields: MemberFilter, disjointSet: DisjointSet, classMapping: Map ): Map { @@ -164,7 +151,7 @@ class TypedRemapper private constructor( val prefixes = mutableMapOf() for (partition in disjointSet) { - if (!isFieldRenamable(classPath, partition)) { + if (!isFieldRenamable(classPath, excludedFields, partition)) { continue } @@ -178,11 +165,15 @@ class TypedRemapper private constructor( return mapping } - private fun isFieldRenamable(classPath: ClassPath, partition: DisjointSet.Partition): Boolean { + private fun isFieldRenamable( + classPath: ClassPath, + excludedFields: MemberFilter, + partition: DisjointSet.Partition + ): Boolean { for (field in partition) { val clazz = classPath[field.owner]!! - if (field.name in EXCLUDED_FIELDS || clazz.dependency) { + if (excludedFields.matches(field) || clazz.dependency) { return false } } @@ -221,13 +212,14 @@ class TypedRemapper private constructor( private fun createMethodMapping( classPath: ClassPath, + excludedMethods: MemberFilter, disjointSet: DisjointSet ): Map { val mapping = mutableMapOf() var id = 0 for (partition in disjointSet) { - if (!isMethodRenamable(classPath, partition)) { + if (!isMethodRenamable(classPath, excludedMethods, partition)) { continue } @@ -240,11 +232,15 @@ class TypedRemapper private constructor( return mapping } - fun isMethodRenamable(classPath: ClassPath, partition: DisjointSet.Partition): Boolean { + fun isMethodRenamable( + classPath: ClassPath, + excludedMethods: MemberFilter, + partition: DisjointSet.Partition + ): Boolean { for (method in partition) { val clazz = classPath[method.owner]!! - if (method.name in EXCLUDED_METHODS || clazz.dependency) { + if (excludedMethods.matches(method) || clazz.dependency) { return false } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt index c6e2b835..63e0077b 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/ConstantArgTransformer.kt @@ -6,6 +6,8 @@ import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.Library import dev.openrs2.asm.copy import dev.openrs2.asm.deleteExpression +import dev.openrs2.asm.filter.MemberFilter +import dev.openrs2.asm.filter.UnionMemberFilter import dev.openrs2.asm.hasCode import dev.openrs2.asm.intConstant import dev.openrs2.asm.isPure @@ -15,12 +17,13 @@ import dev.openrs2.asm.stackMetadata import dev.openrs2.asm.toAbstractInsnNode import dev.openrs2.asm.transform.Transformer import dev.openrs2.deob.ArgRef +import dev.openrs2.deob.Profile import dev.openrs2.deob.analysis.IntBranch import dev.openrs2.deob.analysis.IntBranchResult.ALWAYS_TAKEN import dev.openrs2.deob.analysis.IntBranchResult.NEVER_TAKEN import dev.openrs2.deob.analysis.IntInterpreter import dev.openrs2.deob.analysis.IntValueSet -import dev.openrs2.deob.remap.TypedRemapper +import dev.openrs2.deob.filter.ReflectedConstructorFilter import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.removeFirstOrNull import org.objectweb.asm.Opcodes.GOTO @@ -43,14 +46,16 @@ import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.analysis.Analyzer +import javax.inject.Inject import javax.inject.Singleton @Singleton -class ConstantArgTransformer : Transformer() { +class ConstantArgTransformer @Inject constructor(private val profile: Profile) : Transformer() { private val pendingMethods = LinkedHashSet() private val arglessMethods = mutableSetOf>() private val argValues = mutableMapOf() private lateinit var inheritedMethodSets: DisjointSet + private lateinit var entryPoints: MemberFilter private var branchesSimplified = 0 private var constantsInlined = 0 @@ -59,6 +64,7 @@ class ConstantArgTransformer : Transformer() { arglessMethods.clear() argValues.clear() inheritedMethodSets = classPath.createInheritedMethodSets() + entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath)) branchesSimplified = 0 constantsInlined = 0 @@ -72,29 +78,24 @@ class ConstantArgTransformer : Transformer() { private fun queueEntryPoints(classPath: ClassPath) { for (partition in inheritedMethodSets) { - /* - * The set of non-renamable methods roughly matches up with the - * methods we want to consider as entry points. It includes methods - * which we override, which may be called by the standard library), - * the main() method (called by the JVM), providesignlink() (called - * with reflection) and (called by the JVM). - * - * It isn't perfect - it counts every method as an entry - * point, but strictly speaking we only need to count - * methods invoked with reflection as entry points (like - * VisibilityTransformer). However, it makes no difference in this - * case, as the obfuscator does not add dummy constant arguments to - * constructors. - * - * It also counts native methods as an entry point. This isn't - * problematic as they don't have an InsnList, so we skip them. - */ - if (!TypedRemapper.isMethodRenamable(classPath, partition)) { + if (isEntryPoint(classPath, partition)) { pendingMethods.addAll(partition) } } } + private fun isEntryPoint(classPath: ClassPath, partition: DisjointSet.Partition): Boolean { + for (method in partition) { + val clazz = classPath[method.owner]!! + + if (entryPoints.matches(method) || clazz.dependency) { + return true + } + } + + return false + } + private fun analyzeMethod(classPath: ClassPath, ref: MemberRef) { // find ClassNode/MethodNode val owner = classPath.getClassNode(ref.owner) ?: return diff --git a/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt index 8af2b09c..e8a768e1 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/RemapTransformer.kt @@ -2,12 +2,14 @@ package dev.openrs2.deob.transform import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.transform.Transformer +import dev.openrs2.deob.Profile import dev.openrs2.deob.remap.TypedRemapper +import javax.inject.Inject import javax.inject.Singleton @Singleton -class RemapTransformer : Transformer() { +class RemapTransformer @Inject constructor(private val profile: Profile) : Transformer() { override fun preTransform(classPath: ClassPath) { - classPath.remap(TypedRemapper.create(classPath)) + classPath.remap(TypedRemapper.create(classPath, profile)) } } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt index f1cc073b..36ecdc08 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/StaticScramblingTransformer.kt @@ -6,7 +6,7 @@ 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 dev.openrs2.deob.Profile import dev.openrs2.util.collect.DisjointSet import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode @@ -18,11 +18,12 @@ import org.objectweb.asm.tree.JumpInsnNode import org.objectweb.asm.tree.LabelNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode +import javax.inject.Inject import javax.inject.Singleton import kotlin.math.max @Singleton -class StaticScramblingTransformer : Transformer() { +class StaticScramblingTransformer @Inject constructor(private val profile: Profile) : Transformer() { private class FieldSet(val owner: ClassNode, val fields: List, val clinit: MethodNode?) { val dependencies = clinit?.instructions ?.filterIsInstance() @@ -165,7 +166,7 @@ class StaticScramblingTransformer : Transformer() { return@removeIf false } else if (method.access and Opcodes.ACC_NATIVE != 0) { return@removeIf false - } else if (method.name in TypedRemapper.EXCLUDED_METHODS) { + } else if (profile.excludedMethods.matches(clazz.name, method.name, method.desc)) { return@removeIf false } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt index 08f84d4d..9297e722 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/UnusedArgTransformer.kt @@ -8,6 +8,7 @@ import dev.openrs2.asm.hasCode import dev.openrs2.asm.removeArgument import dev.openrs2.asm.transform.Transformer import dev.openrs2.deob.ArgRef +import dev.openrs2.deob.Profile import dev.openrs2.deob.analysis.ConstSourceInterpreter import dev.openrs2.deob.analysis.ConstSourceValue import dev.openrs2.deob.remap.TypedRemapper @@ -20,10 +21,11 @@ import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.VarInsnNode import org.objectweb.asm.tree.analysis.Analyzer +import javax.inject.Inject import javax.inject.Singleton @Singleton -class UnusedArgTransformer : Transformer() { +class UnusedArgTransformer @Inject constructor(private val profile: Profile) : Transformer() { private val retainedArgs = mutableSetOf() private lateinit var inheritedMethodSets: DisjointSet private var deletedArgs = 0 @@ -71,7 +73,9 @@ class UnusedArgTransformer : Transformer() { } is MethodInsnNode -> { val invokePartition = inheritedMethodSets[MemberRef(insn)] - if (invokePartition == null || !TypedRemapper.isMethodRenamable(classPath, invokePartition)) { + if (invokePartition == null) { + continue@frame + } else if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, invokePartition)) { continue@frame } @@ -115,7 +119,7 @@ class UnusedArgTransformer : Transformer() { } val partition = inheritedMethodSets[MemberRef(insn)] - if (partition == null || !TypedRemapper.isMethodRenamable(classPath, partition)) { + if (partition == null || !TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) { continue } @@ -148,7 +152,7 @@ class UnusedArgTransformer : Transformer() { ): Boolean { // delete unused int args from the method itself val partition = inheritedMethodSets[MemberRef(clazz, method)]!! - if (!TypedRemapper.isMethodRenamable(classPath, partition)) { + if (!TypedRemapper.isMethodRenamable(classPath, profile.excludedMethods, partition)) { return false } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt index 20a038d1..49362ae1 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/UnusedMethodTransformer.kt @@ -6,23 +6,29 @@ import com.google.common.collect.Multimap import dev.openrs2.asm.MemberRef import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.Library +import dev.openrs2.asm.filter.MemberFilter +import dev.openrs2.asm.filter.UnionMemberFilter import dev.openrs2.asm.transform.Transformer -import dev.openrs2.deob.remap.TypedRemapper +import dev.openrs2.deob.Profile +import dev.openrs2.deob.filter.ReflectedConstructorFilter import dev.openrs2.util.collect.DisjointSet import dev.openrs2.util.collect.removeFirst import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode +import javax.inject.Inject import javax.inject.Singleton @Singleton -class UnusedMethodTransformer : Transformer() { +class UnusedMethodTransformer @Inject constructor(private val profile: Profile) : Transformer() { private lateinit var inheritedMethodSets: DisjointSet + private lateinit var excludedMethods: MemberFilter private val methodReferences = HashMultimap.create, MemberRef>() override fun preTransform(classPath: ClassPath) { inheritedMethodSets = classPath.createInheritedMethodSets() + excludedMethods = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath)) methodReferences.clear() } @@ -44,7 +50,9 @@ class UnusedMethodTransformer : Transformer() { val methods = clazz.methods.iterator() for (method in methods) { - if (method.access and Opcodes.ACC_NATIVE != 0 || method.name in TypedRemapper.EXCLUDED_METHODS) { + if (method.access and Opcodes.ACC_NATIVE != 0) { + continue + } else if (excludedMethods.matches(clazz.name, method.name, method.desc)) { continue } diff --git a/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt b/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt index cb6fbb7b..332aacc8 100644 --- a/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt +++ b/deob/src/main/java/dev/openrs2/deob/transform/VisibilityTransformer.kt @@ -3,12 +3,15 @@ package dev.openrs2.deob.transform import com.github.michaelbull.logging.InlineLogger import com.google.common.collect.HashMultimap import com.google.common.collect.Multimap -import dev.openrs2.asm.ClassForNameUtils import dev.openrs2.asm.MemberDesc import dev.openrs2.asm.MemberRef import dev.openrs2.asm.classpath.ClassPath import dev.openrs2.asm.classpath.Library +import dev.openrs2.asm.filter.MemberFilter +import dev.openrs2.asm.filter.UnionMemberFilter import dev.openrs2.asm.transform.Transformer +import dev.openrs2.deob.Profile +import dev.openrs2.deob.filter.ReflectedConstructorFilter import dev.openrs2.util.collect.DisjointSet import org.objectweb.asm.Opcodes import org.objectweb.asm.Type @@ -16,33 +19,26 @@ import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldInsnNode import org.objectweb.asm.tree.MethodInsnNode import org.objectweb.asm.tree.MethodNode +import javax.inject.Inject import javax.inject.Singleton @Singleton -class VisibilityTransformer : Transformer() { +class VisibilityTransformer @Inject constructor(private val profile: Profile) : Transformer() { private lateinit var inheritedFieldSets: DisjointSet private lateinit var inheritedMethodSets: DisjointSet + private lateinit var entryPoints: MemberFilter private val fieldReferences = HashMultimap.create, String>() private val methodReferences = HashMultimap.create, String>() - private val publicCtorClasses = mutableSetOf() override fun preTransform(classPath: ClassPath) { inheritedFieldSets = classPath.createInheritedFieldSets() inheritedMethodSets = classPath.createInheritedMethodSets() + entryPoints = UnionMemberFilter(profile.entryPoints, ReflectedConstructorFilter.create(classPath)) fieldReferences.clear() methodReferences.clear() - publicCtorClasses.clear() - publicCtorClasses.addAll(DEFAULT_PUBLIC_CTOR_CLASSES) } override fun transformCode(classPath: ClassPath, library: Library, clazz: ClassNode, method: MethodNode): Boolean { - for (name in ClassForNameUtils.findClassNames(method)) { - val loadedClass = classPath[name] - if (loadedClass != null && !loadedClass.dependency) { - publicCtorClasses.add(name) - } - } - for (insn in method.instructions) { when (insn) { is FieldInsnNode -> addReference(fieldReferences, inheritedFieldSets, MemberRef(insn), clazz.name) @@ -71,11 +67,8 @@ class VisibilityTransformer : Transformer() { if (member.name == "") { // the visibility flags don't really matter - we use package-private to match javac return 0 - } else if (member.owner in publicCtorClasses && member.name == "") { - // constructors invoked with reflection (including applets) must be public - return Opcodes.ACC_PUBLIC - } else if (member.name in PUBLIC_METHODS) { - // methods invoked with reflection must also be public + } else if (entryPoints.matches(member)) { + // entry points must be public return Opcodes.ACC_PUBLIC } } @@ -106,8 +99,6 @@ class VisibilityTransformer : Transformer() { } override fun postTransform(classPath: ClassPath) { - logger.info { "Identified constructors invoked with reflection $publicCtorClasses" } - var classesChanged = 0 var fieldsChanged = 0 var methodsChanged = 0 @@ -166,8 +157,6 @@ class VisibilityTransformer : Transformer() { companion object { private val logger = InlineLogger() private const val VISIBILITY_FLAGS = Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED or Opcodes.ACC_PRIVATE - private val DEFAULT_PUBLIC_CTOR_CLASSES = setOf("client", "loader", "unpackclass") - private val PUBLIC_METHODS = setOf("main", "providesignlink") private fun addReference( references: Multimap, String>,