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