Skip to content

Instantly share code, notes, and snippets.

@0xdeki
Last active April 2, 2020 13:12
Show Gist options
  • Save 0xdeki/be00a5c5c027dc23d878729840ef4808 to your computer and use it in GitHub Desktop.
Save 0xdeki/be00a5c5c027dc23d878729840ef4808 to your computer and use it in GitHub Desktop.
pretty sturdy mult finder for osrs
package io.deki.updater.asm.deob;
import io.deki.updater.asm.FieldMult;
import io.deki.updater.hook.FieldHook;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;
import java.util.*;
/**
* Created by Deki on 09.07.2019
* This class helps deobfuscate an integer/long obfuscation technique
* used by Jagex. It works by multiplying values of field calls with
* some large number, making it harder to read, especially due to the
* clever use of overflows. Knowing a field's multiplier is also required
* to get the proper values in ex. a cheat client. However, all obfuscators
* produce patterns by design and this is no exception. Namely one of the
* easier obfuscations to deobfuscate, we simply look for field calls
* multiplied by some number. To make our efforts harder, Jagex added
* unused/unreachable field calls with multiplier numbers that result
* in the wrong values, but the same fake number never appears more
* than once. This makes for an easily identifiable pattern which we
* can extract the values needed to get the right values from.
*/
public class MultFinder {
private static List<FieldMult> potentialMults, mults;
/**
* main function
* @param nodes List of classnodes from gamepack
*/
public static void mapMults(Collection<ClassNode> nodes) {
potentialMults = new ArrayList<>();
mults = new ArrayList<>();
for (ClassNode node : nodes) {
process(node);
}
}
/**
* Finds a specified hooks' mult in the mappings and sets it.
* FUN FACT: streams are better for big datasets than iterators,
* not sure if this counts as a big dataset though :)
* @param hook Field hook object used to store owner/name/mult
*/
public static void setMult(FieldHook hook) {
FieldMult mult = mults.stream().filter(m -> m.getOwner().equals(hook.getClazz())
&& m.getName().equals(hook.getField())).findFirst().orElse(null);
if (mult != null) hook.setMultiplier(mult.getMult());
}
/**
* Internal method to process each class node.
* We're looking for this pattern:
* GETFIELD/GETSTATIC/PUTFIELD
* LDC
* IMUL/LMUL
* (ldc and getter/setter can be swapped)
* @param node Class node to be processed
*/
private static void process(ClassNode node) {
//we want to loop through all methods
node.methods.forEach(methodNode -> methodNode.instructions.iterator().forEachRemaining(insn -> {
//we're looking for IMUL/LMULs, no need for the other ones
if (insn.opcode() != Opcodes.IMUL && insn.opcode() != Opcodes.LMUL) return;
//get the instruction from before the multiplication instruction
AbstractInsnNode first = insn.previous();
long constant = decodeLdc(first);
FieldInsnNode field = decodeField(first);
//here we split the path into two for optimal results (thanks spencer!):
//first option is if the pattern is GET/LDC/MUL
//second is LDC/GET/MUL
if (constant == -1) {
AbstractInsnNode second = first.previous();
constant = decodeLdc(second);
} else {
AbstractInsnNode second = first.previous();
field = decodeField(second);
}
//check if anything is null or if the mult is an even number (thanks spencer!)
if (field == null || constant == -1 || (constant & 1) == 0) return;
boolean decoder = field.opcode() == Opcodes.GETFIELD || field.opcode() == Opcodes.GETSTATIC;
//create a FieldMult object, just used for storing values
FieldMult mult = new FieldMult(field.owner, field.name, constant, decoder);
//check if we already mapped the mult in question
if (mults.contains(mult)) return;
//check if we already encountered this specific mult.
//jagex's obfuscator sprinkles in dummy constants, but these never occur more than once.
//if it's present more than once we're confident that it's the real deal.
if (potentialMults.contains(mult)) {
mults.add(mult);
return;
}
//add the field to encountered mults.
potentialMults.add(mult);
}));
}
private static long decodeLdc(AbstractInsnNode node) {
//if this isn't an ldc instruction it doesn't match our pattern and we don't need it
if (node == null || node.opcode() != Opcodes.LDC) return -1;
//at this point we know it's an ldc and we can store its value for later use.
//we're parsing the value as a string as the jvm gets angry if we just cast
return Long.parseLong("" + ((LdcInsnNode) node).cst);
}
private static FieldInsnNode decodeField(AbstractInsnNode node) {
//check if its a getfield/getstatic/putfield, if not we don't want it
if (node == null || (node.opcode() != Opcodes.GETFIELD && node.opcode() != Opcodes.GETSTATIC
&& node.opcode() != Opcodes.PUTFIELD && node.opcode() != Opcodes.PUTSTATIC)) return null;
//store this as a FieldInsnNode as I cba to cast it all the time
return (FieldInsnNode) node;
}
public static List<FieldMult> getMults() {
return mults;
}
}
@buracc
Copy link

buracc commented Jul 10, 2019

very good

@Pizzamaster59
Copy link

Cuck

@alexfaxe
Copy link

alexfaxe commented Apr 2, 2020

nice

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment