hey this passes the test but doesnt compile. allow tracking more than one pops for stackctx.
This commit is contained in:
@@ -1,824 +0,0 @@
|
||||
package net.runelite.deob.deobfuscators;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import net.runelite.deob.ClassFile;
|
||||
import net.runelite.deob.ClassGroup;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
import net.runelite.deob.Field;
|
||||
import net.runelite.deob.attributes.code.Instruction;
|
||||
import net.runelite.deob.attributes.code.instruction.types.FieldInstruction;
|
||||
import net.runelite.deob.attributes.code.instruction.types.GetFieldInstruction;
|
||||
import net.runelite.deob.attributes.code.instruction.types.InvokeInstruction;
|
||||
import net.runelite.deob.attributes.code.instruction.types.PushConstantInstruction;
|
||||
import net.runelite.deob.attributes.code.instruction.types.SetFieldInstruction;
|
||||
import net.runelite.deob.attributes.code.instructions.IMul;
|
||||
import net.runelite.deob.execution.Execution;
|
||||
import net.runelite.deob.execution.Frame;
|
||||
import net.runelite.deob.execution.InstructionContext;
|
||||
import net.runelite.deob.execution.StackContext;
|
||||
|
||||
public class ModularArithmeticDeobfuscation implements Deobfuscator
|
||||
{
|
||||
private Set<Field> obfuscatedFields; // reliability of these sucks
|
||||
|
||||
static class Magic
|
||||
{
|
||||
Field field;
|
||||
int getter, setter;
|
||||
boolean unknownGetter, unknownSetter;
|
||||
}
|
||||
|
||||
static class Magics
|
||||
{
|
||||
Map<Field, Magic> magic = new HashMap<>();
|
||||
|
||||
Magic getMagic(Field field)
|
||||
{
|
||||
Magic m = magic.get(field);
|
||||
if (m != null)
|
||||
return m;
|
||||
|
||||
m = new Magic();
|
||||
m.field = field;
|
||||
magic.put(field, m);
|
||||
return m;
|
||||
}
|
||||
|
||||
void pass1()
|
||||
{
|
||||
int good = 0, bad = 0, calculated = 0, mismatch = 0;
|
||||
for (Magic m : new ArrayList<>(magic.values()))
|
||||
if (m.getter == 0 && m.setter == 0)
|
||||
{
|
||||
magic.remove(m.field);
|
||||
++bad;
|
||||
}
|
||||
else if (m.getter == 0)
|
||||
{
|
||||
m.unknownGetter = false;
|
||||
m.getter = modInverse(m.setter);
|
||||
++calculated;
|
||||
}
|
||||
else if (m.setter == 0)
|
||||
{
|
||||
m.unknownSetter = false;
|
||||
m.setter = modInverse(m.getter);
|
||||
++calculated;
|
||||
}
|
||||
else if (m.getter != modInverse(m.setter) || m.setter != modInverse(m.getter))
|
||||
{
|
||||
magic.remove(m.field);
|
||||
++mismatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
++good;
|
||||
}
|
||||
|
||||
System.out.println("Pass 1: Bad: " + bad + ", good: " + good + ", calculated " + calculated + ", mismatch: " + mismatch);
|
||||
}
|
||||
|
||||
void pass2()
|
||||
{
|
||||
int found = 0;
|
||||
for (Magic m : new ArrayList<>(magic.values()))
|
||||
{
|
||||
if (!m.unknownGetter && !m.unknownSetter && (m.setter != 0 || m.getter != 0))
|
||||
{
|
||||
++found;
|
||||
}
|
||||
}
|
||||
System.out.println("Pass 2: Calculated " + found);
|
||||
}
|
||||
|
||||
void merge(Magics other)
|
||||
{
|
||||
int merged = 0;
|
||||
for (Magic m : other.magic.values())
|
||||
{
|
||||
Field f = m.field;
|
||||
|
||||
if (!this.magic.containsKey(f))
|
||||
{
|
||||
this.magic.put(f, m);
|
||||
++merged;
|
||||
continue;
|
||||
}
|
||||
|
||||
System.err.println("field exists in both pass 1 and 2");
|
||||
}
|
||||
System.out.println("Merged " + merged);
|
||||
}
|
||||
}
|
||||
|
||||
private Field convertFieldFromPool(ClassGroup group, net.runelite.deob.pool.Field field)
|
||||
{
|
||||
ClassFile cf = group.findClass(field.getClassEntry().getName());
|
||||
if (cf == null)
|
||||
return null;
|
||||
return cf.findFieldDeep(field.getNameAndType());
|
||||
}
|
||||
|
||||
private List<net.runelite.deob.pool.Field> checkDown(InstructionContext context)
|
||||
{
|
||||
List<net.runelite.deob.pool.Field> fields = new ArrayList<>();
|
||||
|
||||
if (context.getInstruction() instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) context.getInstruction();
|
||||
fields.add(fi.getField());
|
||||
}
|
||||
|
||||
for (StackContext ctx : context.getPops())
|
||||
{
|
||||
InstructionContext i = ctx.getPushed();
|
||||
|
||||
fields.addAll(checkDown(i));
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private List<net.runelite.deob.pool.Field> checkUp(InstructionContext context)
|
||||
{
|
||||
List<net.runelite.deob.pool.Field> fields = new ArrayList<>();
|
||||
|
||||
if (context.getInstruction() instanceof InvokeInstruction)
|
||||
{
|
||||
// field = func(field * constant), the output of the function isn't directly related to the result of field * constant
|
||||
return fields;
|
||||
}
|
||||
|
||||
if (context.getInstruction() instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fi = (FieldInstruction) context.getInstruction();
|
||||
fields.add(fi.getField());
|
||||
}
|
||||
|
||||
for (StackContext ctx : context.getPushes())
|
||||
{
|
||||
InstructionContext i = ctx.getPopped();
|
||||
|
||||
if (i == null)
|
||||
continue;
|
||||
|
||||
fields.addAll(checkUp(i));
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/* check there are no other fields */
|
||||
private boolean checkFields(Magics goodMagics, ClassGroup group, Set<Field> obFields, net.runelite.deob.pool.Field imulField, InstructionContext context)
|
||||
{
|
||||
List<net.runelite.deob.pool.Field> fields = new ArrayList<>();
|
||||
fields.addAll(checkUp(context));
|
||||
fields.addAll(checkDown(context));
|
||||
|
||||
assert !fields.isEmpty();
|
||||
|
||||
for (net.runelite.deob.pool.Field f : fields)
|
||||
{
|
||||
if (f.equals(imulField))
|
||||
continue;
|
||||
|
||||
Field field = convertFieldFromPool(group, f);
|
||||
assert field != null;
|
||||
|
||||
if (!obFields.contains(field))
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<InstructionContext> getDown(InstructionContext context)
|
||||
{
|
||||
List<InstructionContext> instructions = new ArrayList<>();
|
||||
|
||||
instructions.add(context);
|
||||
|
||||
for (StackContext ctx : context.getPops())
|
||||
{
|
||||
InstructionContext i = ctx.getPushed();
|
||||
|
||||
instructions.addAll(getDown(i));
|
||||
}
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
private List<InstructionContext> getInstructions(InstructionContext context)
|
||||
{
|
||||
List<InstructionContext> instructions = new ArrayList<>();
|
||||
|
||||
instructions.add(context);
|
||||
|
||||
instructions.addAll(getDown(context));
|
||||
|
||||
return instructions;
|
||||
}
|
||||
|
||||
private Set<Field> getObfuscatedFields(Execution execution, ClassGroup group)
|
||||
{
|
||||
Set<Field> fields = new HashSet<>();
|
||||
|
||||
// XXX this detects field = field * constant as ob when field isn't
|
||||
|
||||
for (Frame frame : execution.processedFrames)
|
||||
{
|
||||
for (InstructionContext ctx : frame.getInstructions())
|
||||
{
|
||||
if (ctx.getInstruction() instanceof IMul)
|
||||
{
|
||||
Instruction one = ctx.getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = ctx.getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
GetFieldInstruction gf = null;
|
||||
if (one instanceof PushConstantInstruction && two instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
gf = (GetFieldInstruction) two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction && one instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
gf = (GetFieldInstruction) one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
continue;
|
||||
|
||||
// get Field from pool Field
|
||||
net.runelite.deob.pool.Field field = gf.getField();
|
||||
Field f = group.findClass(field.getClassEntry().getName()).findFieldDeep(field.getNameAndType());
|
||||
|
||||
assert f != null;
|
||||
|
||||
fields.add(f);
|
||||
}
|
||||
else if (ctx.getInstruction() instanceof SetFieldInstruction)
|
||||
{
|
||||
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
|
||||
|
||||
StackContext value = ctx.getPops().get(0); // what setfield pops as value
|
||||
if (!(value.getPushed().getInstruction() instanceof IMul))
|
||||
continue;
|
||||
|
||||
Instruction one = value.getPushed().getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = value.getPushed().getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
Instruction other = null;
|
||||
if (one instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
other = two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
other = one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
continue;
|
||||
|
||||
// get Field from pool Field
|
||||
net.runelite.deob.pool.Field field = sf.getField();
|
||||
Field f = group.findClass(field.getClassEntry().getName()).findFieldDeep(field.getNameAndType());
|
||||
|
||||
assert f != null;
|
||||
|
||||
fields.add(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private void detectSetters(Magics goodMagics, Magics workMagics, Execution execution, ClassGroup group, InstructionContext ctx)
|
||||
{
|
||||
if (!(ctx.getInstruction() instanceof SetFieldInstruction))
|
||||
return;
|
||||
|
||||
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
|
||||
|
||||
StackContext value = ctx.getPops().get(0); // what setfield pops as value
|
||||
if (!(value.getPushed().getInstruction() instanceof IMul))
|
||||
return;
|
||||
|
||||
Instruction one = value.getPushed().getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = value.getPushed().getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
Instruction other = null;
|
||||
if (one instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
other = two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
other = one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
return;
|
||||
|
||||
if (!checkFields(goodMagics, group, obfuscatedFields, sf.getField(), value.getPushed()))
|
||||
return;
|
||||
|
||||
//System.out.println("Setter " + sf.getField().getClassEntry().getName() + "." + sf.getField().getNameAndType().getName() + " -> " + pc.getConstant().toString());
|
||||
|
||||
int constant = Integer.parseInt(pc.getConstant().toString());
|
||||
try
|
||||
{
|
||||
modInverse(constant);
|
||||
}
|
||||
catch (ArithmeticException ex)
|
||||
{
|
||||
//System.err.println("Constant " + constant + " passed setter logic tests but is not inversable");
|
||||
//printWhatCalls(execution, frame.getMethod(), 0);
|
||||
return; // if the constant isn't inversable then it can't be the right one
|
||||
}
|
||||
|
||||
Field field = convertFieldFromPool(group, sf.getField());
|
||||
Magic magic = workMagics.getMagic(field);
|
||||
|
||||
if (!magic.unknownSetter)
|
||||
{
|
||||
if (magic.setter == 0)
|
||||
magic.setter = constant;
|
||||
else if (magic.setter != constant)
|
||||
{
|
||||
magic.setter = 0;
|
||||
magic.unknownSetter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detectGetters(Magics goodMagics, Magics workMagics, Execution execution, ClassGroup group, InstructionContext ctx)
|
||||
{
|
||||
if (!(ctx.getInstruction() instanceof IMul))
|
||||
return;
|
||||
|
||||
// check for push constant and for get field instruction
|
||||
Instruction one = ctx.getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = ctx.getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
GetFieldInstruction gf = null;
|
||||
if (one instanceof PushConstantInstruction && two instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
gf = (GetFieldInstruction) two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction && one instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
gf = (GetFieldInstruction) one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
return;
|
||||
|
||||
int constant = Integer.parseInt(pc.getConstant().toString());
|
||||
|
||||
StackContext push = ctx.getPushes().get(0); // result of imul operation
|
||||
InstructionContext popCtx = push.getPopped(); // instruction which popped the result of mul
|
||||
|
||||
if (popCtx == null)
|
||||
{
|
||||
return;
|
||||
//System.err.println("Stack ctx never popped! Pushed by " + push.getPushed().getInstruction());
|
||||
//int i = frame.getInstructions().indexOf(push.getPushed().getInstruction());
|
||||
//System.err.println("next ins is " + frame.getInstructions().get(i + 1).getInstruction());
|
||||
}
|
||||
|
||||
if (!checkFields(goodMagics, group, obfuscatedFields, gf.getField(), ctx))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
modInverse(constant);
|
||||
}
|
||||
catch (ArithmeticException ex)
|
||||
{
|
||||
//System.err.println("Constant " + constant + " passed getter logic tests but is not inversable");
|
||||
//printWhatCalls(execution, frame.getMethod(), 0);
|
||||
return; // if the constant isn't inversable then it can't be the right one
|
||||
}
|
||||
|
||||
// get Field from pool Field
|
||||
net.runelite.deob.pool.Field field = gf.getField();
|
||||
Field f = group.findClass(field.getClassEntry().getName()).findFieldDeep(field.getNameAndType());
|
||||
|
||||
Magic magic = workMagics.getMagic(f);
|
||||
|
||||
if (!magic.unknownGetter)
|
||||
{
|
||||
if (magic.getter == 0)
|
||||
magic.getter = constant;
|
||||
else if (magic.getter != constant)
|
||||
{
|
||||
magic.getter = 0;
|
||||
magic.unknownGetter = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detectCombined(Magics goodMagics, Magics workMagics, Execution execution, ClassGroup group, InstructionContext ctx)
|
||||
{
|
||||
// look for put involving one other field, assume constant is combined field getter/setter
|
||||
|
||||
if (!(ctx.getInstruction() instanceof SetFieldInstruction))
|
||||
return;
|
||||
|
||||
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
|
||||
Field thisField = convertFieldFromPool(group, sf.getField());
|
||||
|
||||
List<InstructionContext> ins = getInstructions(ctx);
|
||||
|
||||
Field other = null;
|
||||
int constant = 0;
|
||||
for (InstructionContext i : ins)
|
||||
if (i.getInstruction() instanceof FieldInstruction)
|
||||
{
|
||||
FieldInstruction fin = (FieldInstruction) i.getInstruction();
|
||||
if (fin.getField().equals(sf.getField()))
|
||||
continue;
|
||||
|
||||
if (other != null)
|
||||
return;
|
||||
|
||||
other = convertFieldFromPool(group, fin.getField());
|
||||
}
|
||||
else if (i.getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
PushConstantInstruction pci = (PushConstantInstruction) i.getInstruction();
|
||||
try
|
||||
{
|
||||
constant = Integer.parseInt(pci.getConstant().toString());
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (other == null || constant == 0)
|
||||
return;
|
||||
|
||||
if (goodMagics.magic.containsKey(thisField) && goodMagics.magic.containsKey(other))
|
||||
return;
|
||||
|
||||
if (!thisField.getType().toString().equals("I") || !other.getType().toString().equals("I"))
|
||||
return;
|
||||
|
||||
// thisField = operations with field/constant
|
||||
|
||||
//if (obfuscatedFields.contains(thisField) && obfuscatedFields.contains(other))
|
||||
{
|
||||
// constant is thisField setter * otherField getter
|
||||
|
||||
Magic thisMagic = goodMagics.magic.get(thisField);
|
||||
Magic otherMagic = goodMagics.magic.get(other);
|
||||
|
||||
if (thisMagic == null && otherMagic == null)
|
||||
{
|
||||
System.err.println("Combined fields with no known good magic");
|
||||
return;
|
||||
}
|
||||
|
||||
//if (thisMagic != null && otherMagic != null)
|
||||
//{
|
||||
// return; // check?
|
||||
//}
|
||||
|
||||
if (thisMagic == null)
|
||||
{
|
||||
//System.out.println("Combined 1");
|
||||
|
||||
// this = other * constant
|
||||
// constant = other getter * this setter
|
||||
// solve for this setter
|
||||
// this setter = constant * modInverse(other.getter)
|
||||
|
||||
int thisSetter = constant * modInverse(otherMagic.getter);
|
||||
|
||||
if (thisSetter == 1)
|
||||
{
|
||||
System.out.println(thisField.getFields().getClassFile().getName() + "." + thisField.getName() + " is not obd");
|
||||
// this means that this field isn't obbed
|
||||
obfuscatedFields.remove(thisField);
|
||||
otherMagic.setter = constant;
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Calculated setter for " + thisField.getFields().getClassFile().getName() + "." + thisField.getName() + " to be " + thisSetter);
|
||||
|
||||
Magic m = workMagics.getMagic(thisField);
|
||||
|
||||
if (!m.unknownSetter)
|
||||
if (m.setter != 0 && m.setter != thisSetter)
|
||||
{
|
||||
System.err.println("Calculated setter mismatch");
|
||||
m.unknownSetter = true;
|
||||
m.setter = 0;
|
||||
}
|
||||
|
||||
m.setter = thisSetter;
|
||||
}
|
||||
else if (otherMagic == null)
|
||||
{
|
||||
//System.out.println("Combined 2");
|
||||
|
||||
// this = other * constant
|
||||
// constant = other getter * this setter
|
||||
// solve for other getter
|
||||
// other getter = constant * modInverse(this setter)
|
||||
|
||||
int otherGetter = constant * modInverse(thisMagic.setter);
|
||||
|
||||
if (otherGetter == 1)
|
||||
{
|
||||
System.out.println(other.getFields().getClassFile().getName() + "." + other.getName() + " is not obd");
|
||||
obfuscatedFields.remove(other);
|
||||
thisMagic.getter = constant;
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("Calculated getter for " + other.getFields().getClassFile().getName() + "." + other.getName() + " to be " + otherGetter);
|
||||
|
||||
Magic m = workMagics.getMagic(other);
|
||||
|
||||
if (!m.unknownGetter)
|
||||
if (m.getter != 0 && m.getter != otherGetter)
|
||||
{
|
||||
System.err.println("Calculated getter mismatch");
|
||||
m.unknownGetter = true;
|
||||
m.getter = 0;
|
||||
}
|
||||
|
||||
m.getter = otherGetter;
|
||||
}
|
||||
}
|
||||
/*
|
||||
else if (obfuscatedFields.contains(thisField))
|
||||
{
|
||||
// constant is this fields setter
|
||||
System.out.println("Only one field is obd 1 " + thisField.getFields().getClassFile().getName() + "." + thisField.getName()
|
||||
+ ", " + other.getFields().getClassFile().getName() + "." + other.getName());
|
||||
}
|
||||
else if (obfuscatedFields.contains(other))
|
||||
{
|
||||
// constant is other fields getter
|
||||
System.out.println("Only one field is obd 2");
|
||||
}
|
||||
else
|
||||
{
|
||||
System.err.println("detected combined field with both fields non obfuscated. " + thisField.getFields().getClassFile().getName() + "." + thisField.getName()
|
||||
+ ", " + other.getFields().getClassFile().getName() + "." + other.getName());
|
||||
//return;
|
||||
}*/
|
||||
}
|
||||
|
||||
private void check(Magics magics)
|
||||
{
|
||||
int missing = 0, mismatch = 0, good = 0, half = 0;
|
||||
for (Field f : obfuscatedFields)
|
||||
{
|
||||
Magic magic = magics.magic.get(f);
|
||||
|
||||
if (magic == null)
|
||||
{
|
||||
System.err.println(f.getFields().getClassFile().getName() + "." + f.getName() + " is obfuscated, but no magic found");
|
||||
++missing;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (magic.getter != 0 && magic.setter != 0)
|
||||
{
|
||||
if (magic.getter != modInverse(magic.setter) || magic.setter != modInverse(magic.getter))
|
||||
{
|
||||
++mismatch;
|
||||
System.err.println(f.getFields().getClassFile().getName() + "." + f.getName() + " has mismatch, get " + magic.getter + ", set " + magic.setter + ", modInverse(get) " + modInverse(magic.getter) + ", modInverse(set) " + modInverse(magic.setter));
|
||||
}
|
||||
else
|
||||
{
|
||||
++good;
|
||||
//System.out.println(f.getFields().getClassFile().getName() + "." + f.getName() + " has get " + magic.getter + ", set " + magic.setter);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
++half;
|
||||
System.out.println(f.getFields().getClassFile().getName() + "." + f.getName() + " 2 has get " + magic.getter + ", set " + magic.setter);
|
||||
}
|
||||
}
|
||||
System.out.println("Check done missing: "+ missing + ", mismatch: " + mismatch + ", good: " + good + ", half: " + half);
|
||||
}
|
||||
|
||||
private void run(Magics magics /* known good */, Magics work, Execution execution, ClassGroup group)
|
||||
{
|
||||
obfuscatedFields = getObfuscatedFields(execution, group);
|
||||
|
||||
for (Frame frame : execution.processedFrames)
|
||||
{
|
||||
for (InstructionContext ctx : frame.getInstructions())
|
||||
{
|
||||
if (magics == null)
|
||||
{
|
||||
detectGetters(magics, work, execution, group, ctx);
|
||||
detectSetters(magics, work, execution, group, ctx);
|
||||
}
|
||||
else
|
||||
if (magics != null)
|
||||
detectCombined(magics, work, execution, group, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
//if (magics == null)
|
||||
//check(work);
|
||||
}
|
||||
|
||||
private static BigInteger modInverse(BigInteger val, int bits)
|
||||
{
|
||||
BigInteger shift = BigInteger.ONE.shiftLeft(bits);
|
||||
return val.modInverse(shift);
|
||||
}
|
||||
|
||||
private static int modInverse(int val)
|
||||
{
|
||||
return modInverse(BigInteger.valueOf(val), 32).intValue();
|
||||
}
|
||||
|
||||
private static long modInverse(long val)
|
||||
{
|
||||
return modInverse(BigInteger.valueOf(val), 64).longValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(ClassGroup group)
|
||||
{
|
||||
group.buildClassGraph();
|
||||
|
||||
Execution execution = new Execution(group);
|
||||
execution.populateInitialMethods();
|
||||
execution.run();
|
||||
|
||||
Magics work = new Magics();
|
||||
run(null, work, execution, group);
|
||||
work.pass1();
|
||||
// check(work);
|
||||
System.out.println("END OF PASS 1");
|
||||
|
||||
Magics magics = work;
|
||||
work = new Magics();
|
||||
run(magics, work, execution, group);
|
||||
work.pass2();
|
||||
|
||||
magics.merge(work);
|
||||
|
||||
check(magics);
|
||||
|
||||
replace(execution, group, magics);
|
||||
}
|
||||
|
||||
private void replace(Execution execution, ClassGroup group, Magics magics)
|
||||
{
|
||||
Set<Instruction> done = new HashSet<>();
|
||||
int replaced = 0;
|
||||
for (Frame frame : execution.processedFrames)
|
||||
{
|
||||
for (InstructionContext ctx : frame.getInstructions())
|
||||
{
|
||||
if (ctx.getInstruction() instanceof IMul)
|
||||
{
|
||||
Instruction one = ctx.getPops().get(0).getPushed().getInstruction();
|
||||
Instruction two = ctx.getPops().get(1).getPushed().getInstruction();
|
||||
|
||||
PushConstantInstruction pc = null;
|
||||
GetFieldInstruction gf = null;
|
||||
if (one instanceof PushConstantInstruction && two instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one;
|
||||
gf = (GetFieldInstruction) two;
|
||||
}
|
||||
else if (two instanceof PushConstantInstruction && one instanceof GetFieldInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two;
|
||||
gf = (GetFieldInstruction) one;
|
||||
}
|
||||
|
||||
if (pc == null)
|
||||
continue;
|
||||
|
||||
Magic m = magics.magic.get(this.convertFieldFromPool(group, gf.getField()));
|
||||
if (m == null)
|
||||
{
|
||||
System.out.println("No magc for field " + gf.getField());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (done.contains(ctx.getInstruction()))
|
||||
continue;
|
||||
done.add(ctx.getInstruction());
|
||||
|
||||
int constant = Integer.parseInt(pc.getConstant().toString());
|
||||
|
||||
// we have field * constant
|
||||
|
||||
// eg constant is 42 * getter do * modInverse(getter) to get result
|
||||
|
||||
//assert m.setter == modInverse(m.getter);
|
||||
int newConstant = constant * m.setter;
|
||||
|
||||
Instruction i2 = pc.setConstant(new net.runelite.deob.pool.Integer(newConstant));
|
||||
assert i2 == (Instruction) pc;
|
||||
if (newConstant != 1)
|
||||
System.out.println("new constant: " + newConstant);
|
||||
else
|
||||
++replaced;
|
||||
}
|
||||
else if (ctx.getInstruction() instanceof SetFieldInstruction)
|
||||
{
|
||||
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
|
||||
StackContext value = ctx.getPops().get(0); // what setfield pops as value
|
||||
|
||||
if (value.getPushed().getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
// field = constant
|
||||
PushConstantInstruction pi = (PushConstantInstruction) value.getPushed().getInstruction();
|
||||
|
||||
Magic m = magics.magic.get(this.convertFieldFromPool(group, sf.getField()));
|
||||
if (m == null)
|
||||
continue;
|
||||
|
||||
int constant = Integer.parseInt(pi.getConstant().toString());
|
||||
|
||||
if (done.contains(ctx.getInstruction()))
|
||||
continue;
|
||||
done.add(ctx.getInstruction());
|
||||
|
||||
// field = setter * value, solve for value by * modInverse(setter)
|
||||
int newConstant = constant * m.getter;
|
||||
Instruction i2 = pi.setConstant(new net.runelite.deob.pool.Integer(newConstant));
|
||||
assert i2 == (Instruction) pi;
|
||||
++replaced;
|
||||
}
|
||||
else if (value.getPushed().getInstruction() instanceof IMul)
|
||||
{
|
||||
InstructionContext imul = value.getPushed();
|
||||
|
||||
StackContext one = imul.getPops().get(0), two = imul.getPops().get(1);
|
||||
|
||||
PushConstantInstruction pc;
|
||||
if (one.getPushed().getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) one.getPushed().getInstruction();
|
||||
}
|
||||
else if (two.getPushed().getInstruction() instanceof PushConstantInstruction)
|
||||
{
|
||||
pc = (PushConstantInstruction) two.getPushed().getInstruction();
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int constant = Integer.parseInt(pc.getConstant().toString());
|
||||
|
||||
Magic m = magics.magic.get(this.convertFieldFromPool(group, sf.getField()));
|
||||
if (m == null)
|
||||
continue;
|
||||
|
||||
if (done.contains(ctx.getInstruction()))
|
||||
continue;
|
||||
done.add(ctx.getInstruction());
|
||||
|
||||
// field = expression * constant
|
||||
int newConstant = constant * m.getter;
|
||||
Instruction i2 = pc.setConstant(new net.runelite.deob.pool.Integer(newConstant));
|
||||
assert i2 == (Instruction) pc;
|
||||
++replaced;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("Replaced " + replaced + " constants");
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import net.runelite.deob.ClassGroup;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
@@ -42,7 +43,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
System.out.println("Total changed " + count);
|
||||
}
|
||||
|
||||
private MultiplicationExpression parseExpression(InstructionContext ctx)
|
||||
private MultiplicationExpression parseExpression(Execution e, InstructionContext ctx)
|
||||
{
|
||||
MultiplicationExpression me = new MultiplicationExpression();
|
||||
|
||||
@@ -59,6 +60,12 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
return me;
|
||||
}
|
||||
|
||||
if (ctx.getInstruction() instanceof IMul)
|
||||
{
|
||||
if (!this.isOnlyPath(e, ctx))
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
for (StackContext sctx : ctx.getPops())
|
||||
{
|
||||
InstructionContext i = sctx.getPushed();
|
||||
@@ -91,7 +98,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
// chained imul, append to me
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i);
|
||||
MultiplicationExpression other = parseExpression(e, i);
|
||||
|
||||
me.instructions.addAll(other.instructions);
|
||||
me.subexpressions.addAll(other.subexpressions);
|
||||
@@ -106,7 +113,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
// imul using result of iadd or isub. evaluate expression
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i);
|
||||
MultiplicationExpression other = parseExpression(e, i);
|
||||
|
||||
// subexpr
|
||||
//if (other != null)
|
||||
@@ -129,7 +136,8 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
// find other branch of the dup instruction
|
||||
// sctx = what dup pushed, find other
|
||||
StackContext otherCtx = dup.getOtherBranch(sctx); // other side of dup
|
||||
InstructionContext otherCtxI = otherCtx.getPopped(); // would insert imul here?
|
||||
//InstructionContext otherCtxI = otherCtx.getPopped(); // would insert imul here?
|
||||
InstructionContext otherCtxI = otherCtx.getPopped().get(0); // is this irght?
|
||||
|
||||
if (otherCtxI.getInstruction() instanceof IMul)
|
||||
{
|
||||
@@ -143,7 +151,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
StackContext orig = dup.getOriginal(sctx); // original
|
||||
try
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(orig.getPushed());
|
||||
MultiplicationExpression other = parseExpression(e, orig.getPushed());
|
||||
// this expression is used elsewhere like 'pushConstant' so any changes
|
||||
// done to it affect that, too. so multiply it by existing values?
|
||||
if (orig.getPushed().getInstruction() instanceof IAdd || orig.getPushed().getInstruction() instanceof ISub)
|
||||
@@ -182,7 +190,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
// this is an iadd/sub
|
||||
else if (ctx.getInstruction() instanceof IAdd || ctx.getInstruction() instanceof ISub)
|
||||
{
|
||||
MultiplicationExpression other = parseExpression(i); // parse this side of the add/sub
|
||||
MultiplicationExpression other = parseExpression(e, i); // parse this side of the add/sub
|
||||
|
||||
//if (other != null)
|
||||
me.subexpressions.add(other);
|
||||
@@ -216,39 +224,46 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
}
|
||||
|
||||
// for each instruction ctx in ths expression, see if it !equals any other for each ins?
|
||||
//
|
||||
// private List<InstructionContext> getInsInExpr(InstructionContext ctx, Set<Instruction> set)
|
||||
// {
|
||||
// List<InstructionContext> l = new ArrayList<>();
|
||||
//
|
||||
// if (ctx == null || set.contains(ctx.getInstruction()))
|
||||
// return l;
|
||||
//
|
||||
// set.add(ctx.getInstruction());
|
||||
//
|
||||
// l.add(ctx);
|
||||
// for (StackContext s : ctx.getPops())
|
||||
// l.addAll(getInsInExpr(s.getPushed(), set));
|
||||
// for (StackContext s : ctx.getPushes())
|
||||
// l.addAll(getInsInExpr(s.getPopped(), set));
|
||||
//
|
||||
// return l;
|
||||
// }
|
||||
|
||||
private List<InstructionContext> getInsInExpr(InstructionContext ctx, Set<Instruction> set)
|
||||
{
|
||||
List<InstructionContext> l = new ArrayList<>();
|
||||
|
||||
if (ctx == null || set.contains(ctx.getInstruction()))
|
||||
return l;
|
||||
|
||||
set.add(ctx.getInstruction());
|
||||
|
||||
l.add(ctx);
|
||||
for (StackContext s : ctx.getPops())
|
||||
l.addAll(getInsInExpr(s.getPushed(), set));
|
||||
for (StackContext s : ctx.getPushes())
|
||||
l.addAll(getInsInExpr(s.getPopped(), set));
|
||||
|
||||
return l;
|
||||
}
|
||||
|
||||
private boolean isOnlyPath(Execution execution, InstructionContext ctx)
|
||||
public static boolean isOnlyPath(Execution execution, InstructionContext ctx)
|
||||
{
|
||||
assert ctx.getInstruction() instanceof IMul;
|
||||
Collection<InstructionContext> ins = execution.getInstructonContexts(ctx.getInstruction());
|
||||
for (InstructionContext i : ins)
|
||||
//for (Frame f : execution.processedFrames)
|
||||
// if (f.getMethod() == frame.getMethod())
|
||||
// for (InstructionContext i : f.getInstructions())
|
||||
//if (i.getInstruction() == ctx.getInstruction())
|
||||
{
|
||||
if (!i.equals(ctx))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (!i.equals(ctx))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (StackContext sctx : i.getPushes())
|
||||
if (sctx.getPopped().size() > 1)
|
||||
return false;
|
||||
///if (i.getPushes().size() > 1)
|
||||
// return false;
|
||||
// if (!Objects.equals(i.getPushes().get(0).getPopped(), ctx.getPushes().get(0).getPopped()))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -267,7 +282,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
int mcount = 0;
|
||||
|
||||
for (Frame frame : e.processedFrames)
|
||||
outer:
|
||||
//outer:
|
||||
for (InstructionContext ictx : frame.getInstructions())
|
||||
{
|
||||
Instruction instruction = ictx.getInstruction();
|
||||
@@ -305,7 +320,7 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
MultiplicationExpression expression;
|
||||
try
|
||||
{
|
||||
expression = parseExpression(ictx);
|
||||
expression = parseExpression(e, ictx);
|
||||
}
|
||||
catch (IllegalStateException ex)
|
||||
{
|
||||
@@ -319,11 +334,11 @@ public class MultiplicationDeobfuscator implements Deobfuscator
|
||||
// continue;
|
||||
|
||||
// there can only be one path to here, or else combinging would change code logic
|
||||
List<InstructionContext> ilist = this.getInsInExpr(ictx, new HashSet());
|
||||
for (InstructionContext i2 : ilist)
|
||||
if (i2.getInstruction() instanceof IMul)
|
||||
if (!isOnlyPath(e, i2))
|
||||
continue outer;
|
||||
// List<InstructionContext> ilist = this.getInsInExpr(ictx, new HashSet());
|
||||
// for (InstructionContext i2 : ilist)
|
||||
// if (i2.getInstruction() instanceof IMul)
|
||||
// if (!isOnlyPath(e, i2))
|
||||
// continue outer;
|
||||
|
||||
|
||||
if (done.contains(instruction))
|
||||
|
||||
@@ -30,6 +30,8 @@ public class Frame
|
||||
private Variables variables;
|
||||
private List<InstructionContext> instructions = new ArrayList<>(); // instructions executed in this frame
|
||||
private MultiValueMap<InstructionContext, Instruction> visited = new MultiValueMap<>(); // shared
|
||||
|
||||
public static long num;
|
||||
|
||||
public Frame(Execution execution, Method method)
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ public class InstructionContext
|
||||
private List<StackContext> pushes = new ArrayList<>(); // stack contexts pushed by instruction execution
|
||||
private List<VariableContext> reads = new ArrayList<>(); // lvt reads
|
||||
private List<Method> invokes = new ArrayList<>(); // invokes
|
||||
public long frameNum;
|
||||
|
||||
public InstructionContext(Instruction i, Frame f)
|
||||
{
|
||||
@@ -28,7 +29,7 @@ public class InstructionContext
|
||||
{
|
||||
for (StackContext c : ctx)
|
||||
{
|
||||
c.setPopped(this); // now we know which instruction popped this, record it
|
||||
c.addPopped(this); // now we know which instruction popped this, record it
|
||||
pops.add(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.List;
|
||||
public class StackContext
|
||||
{
|
||||
public InstructionContext pushed; // instruction which pushed this
|
||||
public InstructionContext popped; // instruction which popped this
|
||||
public List<InstructionContext> poppeds = new ArrayList<>(); // instructions which popped this
|
||||
public Type type; // type of this
|
||||
public boolean removed;
|
||||
public int encryption; // if this value is encrypted, this is the key to get the real value
|
||||
@@ -34,14 +34,18 @@ public class StackContext
|
||||
return pushed;
|
||||
}
|
||||
|
||||
public InstructionContext getPopped()
|
||||
public List<InstructionContext> getPopped()
|
||||
{
|
||||
return popped;
|
||||
return poppeds;
|
||||
}
|
||||
|
||||
public void setPopped(InstructionContext popped)
|
||||
public void addPopped(InstructionContext popped)
|
||||
{
|
||||
this.popped = popped;
|
||||
//assert !this.poppeds.contains(popped);
|
||||
if (!this.poppeds.contains(popped))
|
||||
this.poppeds.add(popped);
|
||||
//assert this.popped == null;
|
||||
//this.popped = popped;
|
||||
}
|
||||
|
||||
public Type getType()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.runelite.deob.deobfuscators.arithmetic;
|
||||
|
||||
import java.util.Collection;
|
||||
import net.runelite.deob.ClassGroup;
|
||||
import net.runelite.deob.ClassGroupFactory;
|
||||
import net.runelite.deob.Deobfuscator;
|
||||
@@ -7,26 +8,19 @@ import net.runelite.deob.attributes.Code;
|
||||
import net.runelite.deob.attributes.code.Instruction;
|
||||
import net.runelite.deob.attributes.code.Instructions;
|
||||
import net.runelite.deob.attributes.code.instructions.Dup_X1;
|
||||
import net.runelite.deob.attributes.code.instructions.Goto;
|
||||
import net.runelite.deob.attributes.code.instructions.IAdd;
|
||||
import net.runelite.deob.attributes.code.instructions.IConst_0;
|
||||
import net.runelite.deob.attributes.code.instructions.IConst_1;
|
||||
import net.runelite.deob.attributes.code.instructions.IConst_2;
|
||||
import net.runelite.deob.attributes.code.instructions.IConst_3;
|
||||
import net.runelite.deob.attributes.code.instructions.IDiv;
|
||||
import net.runelite.deob.attributes.code.instructions.ILoad;
|
||||
import net.runelite.deob.attributes.code.instructions.IMul;
|
||||
import net.runelite.deob.attributes.code.instructions.IStore;
|
||||
import net.runelite.deob.attributes.code.instructions.IStore_0;
|
||||
import net.runelite.deob.attributes.code.instructions.IStore_1;
|
||||
import net.runelite.deob.attributes.code.instructions.IStore_2;
|
||||
import net.runelite.deob.attributes.code.instructions.If;
|
||||
import net.runelite.deob.attributes.code.instructions.If0;
|
||||
import net.runelite.deob.attributes.code.instructions.LDC_W;
|
||||
import net.runelite.deob.attributes.code.instructions.NOP;
|
||||
import net.runelite.deob.attributes.code.instructions.Pop;
|
||||
import net.runelite.deob.attributes.code.instructions.VReturn;
|
||||
import net.runelite.deob.execution.Execution;
|
||||
import net.runelite.deob.execution.InstructionContext;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -303,6 +297,15 @@ public class MultiplicationDeobfuscatorTest
|
||||
|
||||
assert constant4.getConstantAsInt() * constant5.getConstantAsInt() == 1;
|
||||
|
||||
{
|
||||
Collection<InstructionContext> ctxs = e.getInstructonContexts(body[3]);
|
||||
assert ctxs.size() == 1;
|
||||
|
||||
InstructionContext ictx = ctxs.iterator().next();
|
||||
boolean onlyPath = MultiplicationDeobfuscator.isOnlyPath(e, ictx);
|
||||
Assert.assertFalse(onlyPath);
|
||||
}
|
||||
|
||||
Deobfuscator d = new MultiplicationDeobfuscator();
|
||||
d.run(group);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user