Add OpaquePredicateTransformer and initial deobfuscator infrastructure

master
Graham 5 years ago
parent d001488424
commit 0007da4729
  1. 49
      asm/src/main/java/dev/openrs2/asm/FieldRef.java
  2. 12
      deob/pom.xml
  3. 57
      deob/src/main/java/dev/openrs2/deob/Deobfuscator.java
  4. 119
      deob/src/main/java/dev/openrs2/deob/transform/OpaquePredicateTransformer.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);
}
}

@ -12,4 +12,16 @@
<packaging>jar</packaging> <packaging>jar</packaging>
<name>OpenRS2 Deobfuscator</name> <name>OpenRS2 Deobfuscator</name>
<dependencies>
<dependency>
<groupId>dev.openrs2</groupId>
<artifactId>openrs2-asm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
</project> </project>

@ -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<Transformer> 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());
}
}
}
}

@ -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<FieldRef> 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<AbstractInsnNode> 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<AbstractInsnNode> 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);
}
}
Loading…
Cancel
Save