From 61f6191228bd0c196cababd3250fa28224dff91a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 11 Mar 2016 00:26:00 -0500 Subject: [PATCH] invoker injection. --- .../attributes/code/instructions/BiPush.java | 7 + .../code/instructions/InvokeVirtual.java | 8 + .../attributes/code/instructions/LDC2_W.java | 9 +- .../attributes/code/instructions/LDC_W.java | 5 + .../net/runelite/deob/injection/Inject.java | 203 +++++++++++++++++- .../runelite/deob/injection/InjectTest.java | 4 +- 6 files changed, 226 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/runelite/deob/attributes/code/instructions/BiPush.java b/src/main/java/net/runelite/deob/attributes/code/instructions/BiPush.java index 343e470dab..744b45a453 100644 --- a/src/main/java/net/runelite/deob/attributes/code/instructions/BiPush.java +++ b/src/main/java/net/runelite/deob/attributes/code/instructions/BiPush.java @@ -23,6 +23,13 @@ public class BiPush extends Instruction implements PushConstantInstruction { super(instructions, type, pc); } + + public BiPush(Instructions instructions, byte b) + { + super(instructions, InstructionType.BIPUSH, -1); + + this.b = b; + } @Override public void load(DataInputStream is) throws IOException diff --git a/src/main/java/net/runelite/deob/attributes/code/instructions/InvokeVirtual.java b/src/main/java/net/runelite/deob/attributes/code/instructions/InvokeVirtual.java index 950e5159b4..196485d73a 100644 --- a/src/main/java/net/runelite/deob/attributes/code/instructions/InvokeVirtual.java +++ b/src/main/java/net/runelite/deob/attributes/code/instructions/InvokeVirtual.java @@ -39,6 +39,14 @@ public class InvokeVirtual extends Instruction implements InvokeInstruction { super(instructions, type, pc); } + + public InvokeVirtual(Instructions instructions, Method method) + { + super(instructions, InstructionType.INVOKEVIRTUAL, -1); + + this.method = method; + length += 2; + } @Override public String toString() diff --git a/src/main/java/net/runelite/deob/attributes/code/instructions/LDC2_W.java b/src/main/java/net/runelite/deob/attributes/code/instructions/LDC2_W.java index e22afc4240..1187620a66 100644 --- a/src/main/java/net/runelite/deob/attributes/code/instructions/LDC2_W.java +++ b/src/main/java/net/runelite/deob/attributes/code/instructions/LDC2_W.java @@ -24,13 +24,20 @@ public class LDC2_W extends Instruction implements PushConstantInstruction { super(instructions, type, pc); } - + public LDC2_W(Instructions instructions, long value) { super(instructions, InstructionType.LDC2_W, -1); this.value = new net.runelite.deob.pool.Long(value); length += 2; } + + public LDC2_W(Instructions instructions, double value) + { + super(instructions, InstructionType.LDC2_W, -1); + this.value = new net.runelite.deob.pool.Double(value); + length += 2; + } public LDC2_W(Instructions instructions, PoolEntry value) { diff --git a/src/main/java/net/runelite/deob/attributes/code/instructions/LDC_W.java b/src/main/java/net/runelite/deob/attributes/code/instructions/LDC_W.java index 1c64dcfa1a..5db15217f1 100644 --- a/src/main/java/net/runelite/deob/attributes/code/instructions/LDC_W.java +++ b/src/main/java/net/runelite/deob/attributes/code/instructions/LDC_W.java @@ -41,6 +41,11 @@ public class LDC_W extends Instruction implements PushConstantInstruction { this(instructions, new net.runelite.deob.pool.Integer(value)); } + + public LDC_W(Instructions instructions, float value) + { + this(instructions, new net.runelite.deob.pool.Float(value)); + } @Override public void load(DataInputStream is) throws IOException diff --git a/src/main/java/net/runelite/deob/injection/Inject.java b/src/main/java/net/runelite/deob/injection/Inject.java index fa516c3f51..76133911a9 100644 --- a/src/main/java/net/runelite/deob/injection/Inject.java +++ b/src/main/java/net/runelite/deob/injection/Inject.java @@ -17,13 +17,20 @@ import net.runelite.deob.attributes.code.Instruction; import net.runelite.deob.attributes.code.InstructionType; import net.runelite.deob.attributes.code.Instructions; import net.runelite.deob.attributes.code.instructions.ALoad; +import net.runelite.deob.attributes.code.instructions.BiPush; +import net.runelite.deob.attributes.code.instructions.DLoad; +import net.runelite.deob.attributes.code.instructions.FLoad; import net.runelite.deob.attributes.code.instructions.GetField; import net.runelite.deob.attributes.code.instructions.GetStatic; +import net.runelite.deob.attributes.code.instructions.ILoad; import net.runelite.deob.attributes.code.instructions.IMul; +import net.runelite.deob.attributes.code.instructions.InvokeVirtual; import net.runelite.deob.attributes.code.instructions.LDC2_W; import net.runelite.deob.attributes.code.instructions.LDC_W; +import net.runelite.deob.attributes.code.instructions.LLoad; import net.runelite.deob.attributes.code.instructions.LMul; import net.runelite.deob.attributes.code.instructions.Return; +import net.runelite.deob.attributes.code.instructions.SiPush; import net.runelite.deob.signature.Type; import net.runelite.deob.pool.Class; import net.runelite.deob.pool.NameAndType; @@ -36,6 +43,7 @@ public class Inject private static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;"); private static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;"); private static final Type OBFUSCATED_GETTER = new Type("Lnet/runelite/mapping/ObfuscatedGetter;"); + private static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;"); private static java.lang.Class clientClass; @@ -93,6 +101,9 @@ public class Inject { Annotations an = cf.getAttributes().getAnnotations(); + if (an == null) + continue; + String obfuscatedName = cf.getName(); Annotation obfuscatedNameAnnotation = an.find(OBFUSCATED_NAME); if (obfuscatedNameAnnotation != null) @@ -124,14 +135,10 @@ public class Inject Type obType = toObType(f.getType()); Field otherf = other.findField(new NameAndType(obfuscatedName, obType)); - if (otherf == null) - otherf = other.findField(new NameAndType(obfuscatedName, obType)); assert otherf != null; assert f.isStatic() == otherf.isStatic(); - // - ClassFile targetClass = f.isStatic() ? vanilla.findClass("client") : other; // target class for getter java.lang.Class targetApiClass = f.isStatic() ? clientClass : implementingClass; // target api class for getter @@ -151,14 +158,48 @@ public class Inject if (an == null || an.find(EXPORT) == null) continue; // not an exported method + + // XXX static methods can be in any class not just 'other' so the below finding won't work. + // XXX static methods can also be totally inlined by the obfuscator and thus removed by the dead code remover, + // so exporting them maybe wouldn't work anyway? + assert !m.isStatic(); String exportedName = an.find(EXPORT).getElement().getString(); obfuscatedName = an.find(OBFUSCATED_NAME).getElement().getString(); - // XXX static methods don't have to be in the same class, so we should have @ObfuscatedClass or something - - Method otherm = other.findMethod(new NameAndType(obfuscatedName, toObSignature(m.getDescriptor()))); + Method otherm; + + Annotation obfuscatedSignature = an.find(OBFUSCATED_SIGNATURE); + + String garbage = null; + if (obfuscatedSignature != null) + { + String signatureString = obfuscatedSignature.getElements().get(0).getString(); + garbage = obfuscatedSignature.getElements().get(1).getString(); + + Signature signature = new Signature(signatureString); // parse signature + + // The obfuscated signature annotation is generated post rename unique, so class + // names in the signature match our class names and not theirs, so we toObSignature() it + otherm = other.findMethod(new NameAndType(obfuscatedName, toObSignature(signature))); + } + else + { + // No obfuscated signature annotation, so the annotation wasn't changed during deobfuscation. + // We should be able to just find it normally. + otherm = other.findMethod(new NameAndType(obfuscatedName, toObSignature(m.getDescriptor()))); + } + assert otherm != null; + + java.lang.reflect.Method apiMethod = findImportMethodOnApi(implementingClass, exportedName); // api method to invoke 'otherm' + if (apiMethod == null) + { + System.out.println("no api method"); + continue; + } + + injectInvoker(other, apiMethod, m, otherm, garbage); } } } @@ -287,4 +328,152 @@ public class Inject clazz.getMethods().addMethod(getterMethod); } + + private void injectInvoker(ClassFile clazz, java.lang.reflect.Method method, Method deobfuscatedMethod, Method invokeMethod, String garbage) + { + // clazz = clazz to add invoker to + // method = api method to override + // deobfuscatedMethod = deobfuscated method, used to get the deobfuscated signature + // invokeMethod = method to invoke, obfuscated + + assert clazz.findMethod(method.getName()) == null; + assert !invokeMethod.isStatic(); + assert invokeMethod.getMethods().getClassFile() == clazz; + + Type lastGarbageArgumentType = null; + + if (!deobfuscatedMethod.getDescriptor().equals(invokeMethod.getDescriptor())) + { + // allow for obfuscated method to have a single bogus signature at the end + assert deobfuscatedMethod.getDescriptor().size() + 1 == invokeMethod.getDescriptor().size(); + + List arguments = invokeMethod.getDescriptor().getArguments(); + lastGarbageArgumentType = arguments.get(arguments.size() - 1); + } + + Method invokerMethodSignature = new Method(clazz.getMethods(), method.getName(), deobfuscatedMethod.getDescriptor()); + invokerMethodSignature.setAccessFlags(Method.ACC_PUBLIC); + + Attributes methodAttributes = invokerMethodSignature.getAttributes(); + + // create code attribute + Code code = new Code(methodAttributes); + methodAttributes.addAttribute(code); + + Instructions instructions = code.getInstructions(); + List ins = instructions.getInstructions(); + + code.setMaxStack(1 + invokeMethod.getDescriptor().size()); // this + arguments + + // load function arguments onto the stack. + + int index = 0; + ins.add(new ALoad(instructions, index++)); // this + + for (int i = 0; i < deobfuscatedMethod.getDescriptor().size(); ++i) + { + Type type = deobfuscatedMethod.getDescriptor().getTypeOfArg(i); + + if (type.getArrayDims() > 0 || !type.isPrimitive()) + { + ins.add(new ALoad(instructions, index++)); + } + else + { + switch (type.getType()) + { + case "B": + case "C": + case "I": + case "S": + case "Z": + ins.add(new ILoad(instructions, index++)); + break; + case "D": + ins.add(new DLoad(instructions, index++)); + ++index; // takes two slots + break; + case "F": + ins.add(new FLoad(instructions, index++)); + break; + case "J": + ins.add(new LLoad(instructions, index++)); + ++index; + break; + default: + throw new RuntimeException("Unknown type"); + } + } + } + + if (lastGarbageArgumentType != null) + { + // function requires garbage value + + switch (lastGarbageArgumentType.getType()) + { + case "Z": + case "B": + case "C": + ins.add(new BiPush(instructions, Byte.parseByte(garbage))); + break; + case "S": + ins.add(new SiPush(instructions, Short.parseShort(garbage))); + break; + case "I": + ins.add(new LDC_W(instructions, Integer.parseInt(garbage))); + break; + case "D": + ins.add(new LDC2_W(instructions, Double.parseDouble(garbage))); + break; + case "F": + ins.add(new LDC_W(instructions, Float.parseFloat(garbage))); + break; + case "J": + ins.add(new LDC2_W(instructions, Long.parseLong(garbage))); + break; + default: + throw new RuntimeException("Unknown type"); + } + } + + ins.add(new InvokeVirtual(instructions, invokeMethod.getPoolMethod())); + + Type returnValue = invokeMethod.getDescriptor().getReturnValue(); + InstructionType returnType; + + if (returnValue.isPrimitive() && returnValue.getArrayDims() == 0) + { + switch (returnValue.getType()) + { + case "Z": + case "I": + returnType = InstructionType.IRETURN; + break; + case "J": + returnType = InstructionType.LRETURN; + break; + case "F": + returnType = InstructionType.FRETURN; + break; + case "D": + returnType = InstructionType.DRETURN; + break; + case "V": + returnType = InstructionType.RETURN; + break; + default: + assert false; + return; + } + } + else + { + returnType = InstructionType.ARETURN; + } + + ins.add(new Return(instructions, returnType)); + + clazz.getMethods().addMethod(invokerMethodSignature); + } } diff --git a/src/test/java/net/runelite/deob/injection/InjectTest.java b/src/test/java/net/runelite/deob/injection/InjectTest.java index a0495fbe03..3ffdee8d5f 100644 --- a/src/test/java/net/runelite/deob/injection/InjectTest.java +++ b/src/test/java/net/runelite/deob/injection/InjectTest.java @@ -11,8 +11,8 @@ import org.junit.Test; public class InjectTest { - private static final File DEOBFUSCATED = new File("d:/rs/07/adamin.jar"); - private static final File VANILLA = new File(MappingImporter.class.getResource("/gamepack_v16.jar").getFile()); + private static final File DEOBFUSCATED = new File("C:\\Users\\Adam\\.m2\\repository\\net\\runelite\\rs\\rs-client\\1.0-SNAPSHOT\\rs-client-1.0-SNAPSHOT.jar"); + private static final File VANILLA = new File(InjectTest.class.getResource("/gamepack_v16.jar").getFile()); private static final File OUT = new File("d:/rs/07/adamout.jar"); private ClassGroup deob, vanilla;