From 0007da47299cf13e7a1e7b94a4b280e9d3d876bf Mon Sep 17 00:00:00 2001 From: Graham Date: Mon, 29 Jul 2019 14:39:33 +0100 Subject: [PATCH] Add OpaquePredicateTransformer and initial deobfuscator infrastructure --- .../main/java/dev/openrs2/asm/FieldRef.java | 49 ++++++++ deob/pom.xml | 12 ++ .../java/dev/openrs2/deob/Deobfuscator.java | 57 +++++++++ .../transform/OpaquePredicateTransformer.java | 119 ++++++++++++++++++ 4 files changed, 237 insertions(+) create mode 100644 asm/src/main/java/dev/openrs2/asm/FieldRef.java create mode 100644 deob/src/main/java/dev/openrs2/deob/Deobfuscator.java create mode 100644 deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer.java diff --git a/asm/src/main/java/dev/openrs2/asm/FieldRef.java b/asm/src/main/java/dev/openrs2/asm/FieldRef.java new file mode 100644 index 00000000..e2a488c1 --- /dev/null +++ b/asm/src/main/java/dev/openrs2/asm/FieldRef.java @@ -0,0 +1,49 @@ +package dev.openrs2.asm; + +import java.util.Objects; + +public final class FieldRef { + private final String owner, name, desc; + + public FieldRef(String owner, String name, String desc) { + this.owner = owner; + this.name = name; + this.desc = desc; + } + + public String getOwner() { + return owner; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + FieldRef fieldRef = (FieldRef) o; + return owner.equals(fieldRef.owner) && + name.equals(fieldRef.name) && + desc.equals(fieldRef.desc); + } + + @Override + public int hashCode() { + return Objects.hash(owner, name, desc); + } + + @Override + public String toString() { + return String.format("%s %s.%s", desc, owner, name); + } +} diff --git a/deob/pom.xml b/deob/pom.xml index 56d02627..97f2c811 100644 --- a/deob/pom.xml +++ b/deob/pom.xml @@ -12,4 +12,16 @@ jar OpenRS2 Deobfuscator + + + + dev.openrs2 + openrs2-asm + ${project.version} + + + ch.qos.logback + logback-classic + + diff --git a/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java new file mode 100644 index 00000000..6ed671c3 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/Deobfuscator.java @@ -0,0 +1,57 @@ +package dev.openrs2.deob; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; + +import dev.openrs2.asm.Library; +import dev.openrs2.asm.Transformer; +import dev.openrs2.deob.transform.OpaquePredicateTransformer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class Deobfuscator { + private static final Logger logger = LoggerFactory.getLogger(Deobfuscator.class); + + private static final List TRANSFORMERS = List.of( + new OpaquePredicateTransformer() + ); + + public static void main(String[] args) throws IOException { + var deobfuscator = new Deobfuscator(Paths.get("nonfree/code")); + deobfuscator.run(); + } + + private final Path input; + + public Deobfuscator(Path input) { + this.input = input; + } + + public void run() throws IOException { + var unpacker = Library.readJar(input.resolve("game_unpacker.dat")); + var loader = Library.readJar(input.resolve("loader.jar")); + var glLoader = Library.readJar(input.resolve("loader_gl.jar")); + var client = Library.readJar(input.resolve("runescape.jar")); + var glClient = Library.readPack(input.resolve("runescape_gl.pack200")); + + var libraries = Map.of( + "unpacker", unpacker, + "loader", loader, + "loader_gl", glLoader, + "runescape", client, + "runescape_gl", glClient + ); + + for (var entry : libraries.entrySet()) { + logger.info("Transforming library {}", entry.getKey()); + + for (var transformer : TRANSFORMERS) { + logger.info("Running transformer {}", transformer.getClass().getSimpleName()); + transformer.transform(entry.getValue()); + } + } + } +} diff --git a/deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer.java b/deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer.java new file mode 100644 index 00000000..1aa46755 --- /dev/null +++ b/deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer.java @@ -0,0 +1,119 @@ +package dev.openrs2.deob.transform; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import dev.openrs2.asm.FieldRef; +import dev.openrs2.asm.InsnMatcher; +import dev.openrs2.asm.Library; +import dev.openrs2.asm.Transformer; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class OpaquePredicateTransformer extends Transformer { + private static final Logger logger = LoggerFactory.getLogger(OpaquePredicateTransformer.class); + + private static final InsnMatcher FLOW_OBSTRUCTOR_INITIALIZER_MATCHER = InsnMatcher.compile("(GETSTATIC | ILOAD) IFEQ (((GETSTATIC ISTORE)? IINC ILOAD) | ((GETSTATIC | ILOAD) IFEQ ICONST GOTO ICONST)) PUTSTATIC"); + private static final InsnMatcher OPAQUE_PREDICATE_MATCHER = InsnMatcher.compile("(GETSTATIC | ILOAD) (IFEQ | IFNE)"); + private static final InsnMatcher STORE_MATCHER = InsnMatcher.compile("GETSTATIC ISTORE"); + + private final Set flowObstructors = new HashSet<>(); + private int opaquePredicates, stores; + + @Override + public void preTransform(Library library) { + flowObstructors.clear(); + opaquePredicates = 0; + stores = 0; + + for (var clazz : library) { + for (var method : clazz.methods) { + if ((method.access & (Opcodes.ACC_NATIVE | Opcodes.ACC_ABSTRACT)) == 0) { + findFlowObstructors(library, method); + } + } + } + + logger.info("Identified flow obstructors {}", flowObstructors); + } + + private void findFlowObstructors(Library library, MethodNode method) { + FLOW_OBSTRUCTOR_INITIALIZER_MATCHER.match(method).forEach(match -> { + /* add flow obstructor to set */ + var putstatic = (FieldInsnNode) match.get(match.size() - 1); + flowObstructors.add(new FieldRef(putstatic.owner, putstatic.name, putstatic.desc)); + + /* remove initializer */ + match.forEach(method.instructions::remove); + + /* remove field */ + var owner = library.get(putstatic.owner); + owner.fields.removeIf(field -> field.name.equals(putstatic.name) && field.desc.equals(putstatic.desc)); + }); + } + + private boolean isFlowObstructor(FieldInsnNode insn) { + return flowObstructors.contains(new FieldRef(insn.owner, insn.name, insn.desc)); + } + + private boolean isOpaquePredicate(MethodNode method, List match) { + var load = match.get(0); + + /* flow obstructor loaded directly? */ + if (load.getOpcode() == Opcodes.GETSTATIC) { + var getstatic = (FieldInsnNode) load; + return isFlowObstructor(getstatic); + } + + /* flow obstructor loaded via local variable? */ + var iload = (VarInsnNode) load; + return STORE_MATCHER.match(method).anyMatch(storeMatch -> { + var getstatic = (FieldInsnNode) storeMatch.get(0); + var istore = (VarInsnNode) storeMatch.get(1); + return isFlowObstructor(getstatic) && iload.var == istore.var; + }); + } + + private boolean isRedundantStore(List match) { + var getstatic = (FieldInsnNode) match.get(0); + return isFlowObstructor(getstatic); + } + + @Override + public void transformMethod(ClassNode clazz, MethodNode method) { + /* find and fix opaque predicates */ + OPAQUE_PREDICATE_MATCHER.match(method).filter(match -> isOpaquePredicate(method, match)).forEach(match -> { + var branch = (JumpInsnNode) match.get(1); + + if (branch.getOpcode() == Opcodes.IFEQ) { + /* branch is always taken */ + method.instructions.remove(match.get(0)); + branch.setOpcode(Opcodes.GOTO); + } else { /* IFNE */ + /* branch is never taken */ + match.forEach(method.instructions::remove); + } + + opaquePredicates++; + }); + + /* remove redundant stores */ + STORE_MATCHER.match(method).filter(this::isRedundantStore).forEach(match -> { + match.forEach(method.instructions::remove); + stores++; + }); + } + + @Override + public void postTransform(Library library) { + logger.info("Removed {} opaque predicates and {} redundant stores", opaquePredicates, stores); + } +}