forked from openrs2/openrs2
parent
d001488424
commit
0007da4729
@ -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); |
||||
} |
||||
} |
@ -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…
Reference in new issue