Port Injector to main layout

Adds deobfuscator
Adds injected-client
Adds injector-plugin
Adds runescape-client

Replaces RL's apis

Small bug with sprites atm, will be resolved soon. tired af.

Builds, probably
This commit is contained in:
zeruth
2019-06-08 05:43:03 -04:00
parent eafb024f16
commit e4d6e9fe13
1111 changed files with 135441 additions and 44733 deletions

View File

@@ -0,0 +1,225 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob;
import com.google.common.base.Stopwatch;
import java.io.File;
import java.io.IOException;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.execution.Execution;
import net.runelite.deob.deobfuscators.transformers.GetPathTransformer;
import net.runelite.deob.deobfuscators.CastNull;
import net.runelite.deob.deobfuscators.constparam.ConstantParameter;
import net.runelite.deob.deobfuscators.EnumDeobfuscator;
import net.runelite.deob.deobfuscators.FieldInliner;
import net.runelite.deob.deobfuscators.IllegalStateExceptions;
import net.runelite.deob.deobfuscators.Lvt;
import net.runelite.deob.deobfuscators.Order;
import net.runelite.deob.deobfuscators.RenameUnique;
import net.runelite.deob.deobfuscators.RuntimeExceptions;
import net.runelite.deob.deobfuscators.UnreachedCode;
import net.runelite.deob.deobfuscators.UnusedClass;
import net.runelite.deob.deobfuscators.UnusedFields;
import net.runelite.deob.deobfuscators.UnusedMethods;
import net.runelite.deob.deobfuscators.UnusedParameters;
import net.runelite.deob.deobfuscators.arithmetic.ModArith;
import net.runelite.deob.deobfuscators.arithmetic.MultiplicationDeobfuscator;
import net.runelite.deob.deobfuscators.arithmetic.MultiplyOneDeobfuscator;
import net.runelite.deob.deobfuscators.arithmetic.MultiplyZeroDeobfuscator;
import net.runelite.deob.deobfuscators.cfg.ControlFlowDeobfuscator;
import net.runelite.deob.deobfuscators.exprargorder.ExprArgOrder;
import net.runelite.deob.deobfuscators.menuaction.MenuActionDeobfuscator;
import net.runelite.deob.deobfuscators.transformers.ClientErrorTransformer;
import net.runelite.deob.deobfuscators.transformers.MaxMemoryTransformer;
import net.runelite.deob.deobfuscators.transformers.OpcodesTransformer;
import net.runelite.deob.deobfuscators.transformers.ReflectionTransformer;
import net.runelite.deob.util.JarUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Deob
{
private static final Logger logger = LoggerFactory.getLogger(Deob.class);
public static final int OBFUSCATED_NAME_MAX_LEN = 2;
private static final boolean CHECK_EXEC = false;
public static void main(String[] args) throws IOException
{
if (args == null || args.length < 2)
{
System.err.println("Syntax: input_jar output_jar");
System.exit(-1);
}
//logger.info("Deobfuscator revision {}", DeobProperties.getRevision());
Stopwatch stopwatch = Stopwatch.createStarted();
ClassGroup group = JarUtil.loadJar(new File(args[0]));
for (ClassFile f : group.getClasses())
{
f.getAnnotations().clearAnnotations();
for (Method m : f.getMethods())
{
Annotations an = m.getAnnotations();
an.clearAnnotations();
}
for (Field fi : f.getFields())
{
Annotations an = fi.getAnnotations();
if (an.find(new Type("Ljavax/inject/Inject;")) == null)
{
an.clearAnnotations();
}
else
{
logger.info("Class {}, field {} has inject", f.getClassName(), fi.getName());
}
}
}
// remove except RuntimeException
run(group, new RuntimeExceptions());
run(group, new ControlFlowDeobfuscator());
run(group, new RenameUnique());
// remove unused methods - this leaves Code with no instructions,
// which is not valid, so unused methods is run after
run(group, new UnreachedCode());
run(group, new UnusedMethods());
// remove illegal state exceptions, frees up some parameters
run(group, new IllegalStateExceptions());
// remove constant logically dead parameters
run(group, new ConstantParameter());
// remove unhit blocks
run(group, new UnreachedCode());
run(group, new UnusedMethods());
// remove unused parameters
run(group, new UnusedParameters());
// remove unused fields
run(group, new UnusedFields());
run(group, new FieldInliner());
// order uses class name order for sorting fields/methods,
// so run it before removing classes below
run(group, new Order());
run(group, new UnusedClass());
runMath(group);
run(group, new ExprArgOrder());
run(group, new Lvt());
run(group, new CastNull());
run(group, new EnumDeobfuscator());
new OpcodesTransformer().transform(group);
//run(group, new PacketHandlerOrder());
//run(group, new PacketWriteDeobfuscator());
run(group, new MenuActionDeobfuscator());
new GetPathTransformer().transform(group);
new ClientErrorTransformer().transform(group);
new ReflectionTransformer().transform(group);
new MaxMemoryTransformer().transform(group);
//new RuneliteBufferTransformer().transform(group);
JarUtil.saveJar(group, new File(args[1]));
stopwatch.stop();
logger.info("Done in {}", stopwatch);
}
public static boolean isObfuscated(String name)
{
return name.length() <= OBFUSCATED_NAME_MAX_LEN || name.startsWith("method") || name.startsWith("vmethod") || name.startsWith("field") || name.startsWith("class");
}
private static void runMath(ClassGroup group)
{
ModArith mod = new ModArith();
mod.run(group);
int last = -1, cur;
while ((cur = mod.runOnce()) > 0)
{
new MultiplicationDeobfuscator().run(group);
// do not remove 1 * field so that ModArith can detect
// the change in guessDecreasesConstants()
new MultiplyOneDeobfuscator(true).run(group);
new MultiplyZeroDeobfuscator().run(group);
if (last == cur)
{
break;
}
last = cur;
}
// now that modarith is done, remove field * 1
new MultiplyOneDeobfuscator(false).run(group);
mod.annotateEncryption();
}
private static void run(ClassGroup group, Deobfuscator deob)
{
Stopwatch stopwatch = Stopwatch.createStarted();
deob.run(group);
stopwatch.stop();
logger.info("{} took {}", deob.getClass().getSimpleName(), stopwatch);
// check code is still correct
if (CHECK_EXEC)
{
Execution execution = new Execution(group);
execution.populateInitialMethods();
execution.run();
}
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.annotation.Element;
import net.runelite.asm.signature.Signature;
import java.util.List;
public class DeobAnnotations
{
public static final Type OBFUSCATED_NAME = new Type("Lnet/runelite/mapping/ObfuscatedName;");
public static final Type EXPORT = new Type("Lnet/runelite/mapping/Export;");
public static final Type IMPLEMENTS = new Type("Lnet/runelite/mapping/Implements;");
public static final Type OBFUSCATED_GETTER = new Type("Lnet/runelite/mapping/ObfuscatedGetter;");
public static final Type OBFUSCATED_SIGNATURE = new Type("Lnet/runelite/mapping/ObfuscatedSignature;");
public static final Type HOOK = new Type("Lnet/runelite/mapping/Hook;");
public static Signature getObfuscatedSignature(Method m)
{
String str = getAnnotationValue(m.getAnnotations(), OBFUSCATED_SIGNATURE);
if (str == null)
{
return null;
}
return new Signature(str);
}
public static Type getObfuscatedType(Field f)
{
String str = getAnnotationValue(f.getAnnotations(), OBFUSCATED_SIGNATURE);
if (str == null)
{
return null;
}
return new Type(str);
}
public static String getObfuscatedName(Annotations an)
{
return getAnnotationValue(an, OBFUSCATED_NAME);
}
public static String getExportedName(Annotations an)
{
return getAnnotationValue(an, EXPORT);
}
public static String getImplements(ClassFile cf)
{
return getAnnotationValue(cf.getAnnotations(), IMPLEMENTS);
}
public static String getHookName(Annotations an)
{
return getAnnotationValue(an, HOOK);
}
public static Number getObfuscatedGetter(Field field)
{
if (field == null || field.getAnnotations() == null)
{
return null;
}
Annotation an = field.getAnnotations().find(OBFUSCATED_GETTER);
if (an == null)
{
return null;
}
return (Number) an.getElement().getValue();
}
public static String getObfuscatedValue(Method method)
{
if (method == null || method.getAnnotations() == null)
{
return null;
}
Annotation an = method.getAnnotations().find(OBFUSCATED_SIGNATURE);
if (an == null)
{
return null;
}
List<Element> elements = an.getElements();
if (elements == null || elements.size() < 2)
{
return null;
}
return (String) elements.get(1).getValue();
}
private static String getAnnotationValue(Annotations an, Type type)
{
if (an == null)
{
return null;
}
Annotation a = an.find(type);
if (a == null)
{
return null;
}
return a.getElement().getString();
}
}

View File

@@ -0,0 +1,41 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class DeobProperties
{
public static String getRevision() throws IOException
{
Properties properties = new Properties();
InputStream resourceAsStream = DeobProperties.class.getResourceAsStream("/deob.properties");
properties.load(resourceAsStream);
return "420";
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob;
import net.runelite.asm.ClassGroup;
public interface Deobfuscator
{
void run(ClassGroup group);
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob;
import net.runelite.asm.ClassGroup;
public interface Transformer
{
void transform(ClassGroup group);
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.c2s;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IsaacCipherFinder
{
private static final Logger logger = LoggerFactory.getLogger(IsaacCipherFinder.class);
private static final int GOLDEN_RATIO = 0x9E3779B9; // 2^32 / phi
private final ClassGroup group;
private ClassFile isaacCipher;
private Method getNext;
public IsaacCipherFinder(ClassGroup group)
{
this.group = group;
}
public ClassFile getIsaacCipher()
{
return isaacCipher;
}
public Method getGetNext()
{
return getNext;
}
public void find()
{
Method highest = null;
int count = 0;
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
int i = find(m, code);
if (i > count)
{
count = i;
highest = m;
}
}
}
assert highest != null;
isaacCipher = highest.getClassFile();
// find nextInt
for (Method method : isaacCipher.getMethods())
{
if (method.getDescriptor().size() == 0 && method.getDescriptor().getReturnValue().equals(Type.INT))
{
getNext = method;
}
}
logger.debug("Found cipher {}, getNext {}", isaacCipher, getNext);
}
private int find(Method method, Code code)
{
if (code == null)
{
return 0;
}
int gr = 0;
for (Instruction i : code.getInstructions().getInstructions())
{
if (i instanceof PushConstantInstruction)
{
PushConstantInstruction pci = (PushConstantInstruction) i;
if (pci.getConstant().equals(GOLDEN_RATIO))
{
++gr;
}
}
}
return gr;
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.c2s;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RWOpcodeFinder
{
private static final Logger logger = LoggerFactory.getLogger(RWOpcodeFinder.class);
private final ClassGroup group;
private Method readOpcode;
private Method writeOpcode;
public RWOpcodeFinder(ClassGroup group)
{
this.group = group;
}
public Method getReadOpcode()
{
return readOpcode;
}
public Method getWriteOpcode()
{
return writeOpcode;
}
public ClassFile getSecretBuffer()
{
assert writeOpcode.getClassFile() == readOpcode.getClassFile();
return writeOpcode.getClassFile();
}
public ClassFile getBuffer()
{
return getSecretBuffer().getParent();
}
public void find()
{
IsaacCipherFinder ic = new IsaacCipherFinder(group);
ic.find();
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
find(ic, m, code);
}
}
logger.debug("Found readOpcode {}, writeOpcode {}", readOpcode, writeOpcode);
}
private void find(IsaacCipherFinder ic, Method method, Code code)
{
if (code == null)
{
return;
}
for (Instruction i : code.getInstructions().getInstructions())
{
if (i instanceof InvokeInstruction)
{
if (((InvokeInstruction) i).getMethods().contains(ic.getGetNext()))
{
if (method.getDescriptor().size() == 0)
{
// read opcode
readOpcode = method;
}
else
{
// write opcode
writeOpcode = method;
}
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.clientver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.objectweb.asm.ClassReader;
public class ClientVersion
{
private final File jar;
public ClientVersion(File jar)
{
this.jar = jar;
}
public int getVersion() throws IOException
{
try (JarFile jar = new JarFile(this.jar))
{
for (Enumeration<JarEntry> it = jar.entries(); it.hasMoreElements();)
{
JarEntry entry = it.nextElement();
if (!entry.getName().equals("client.class"))
continue;
InputStream in = jar.getInputStream(entry);
ClassReader reader = new ClassReader(in);
VersionClassVisitor v = new VersionClassVisitor();
reader.accept(v, 0);
return v.getVersion();
}
}
return -1;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.clientver;
import java.io.File;
import java.io.IOException;
public class ClientVersionMain
{
public static void main(String[] args) throws IOException
{
File jar = new File(args[0]);
ClientVersion cv = new ClientVersion(jar);
System.out.println(cv.getVersion());
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.clientver;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class VersionClassVisitor extends ClassVisitor
{
private VersionMethodVisitor vmv = new VersionMethodVisitor();
public VersionClassVisitor()
{
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(int access,
String name,
String desc,
String signature,
String[] exceptions)
{
if (!name.equals("init"))
return null;
return vmv;
}
public int getVersion()
{
return vmv.getVersion();
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.clientver;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class VersionMethodVisitor extends MethodVisitor
{
private int state = 0;
private int version = -1;
VersionMethodVisitor()
{
super(Opcodes.ASM5);
}
@Override
public void visitIntInsn(int opcode, int operand)
{
if (state == 2)
{
version = operand;
++state;
}
if (operand == 765 || operand == 503)
{
++state;
}
}
public int getVersion()
{
return version;
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.AConstNull;
import net.runelite.asm.attributes.code.instructions.CheckCast;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CastNull implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(CastNull.class);
private int removed;
private final List<Instruction> interesting = new ArrayList<>();
private final List<Instruction> notInteresting = new ArrayList<>();
private void visit(InstructionContext ictx)
{
if (!(ictx.getInstruction() instanceof CheckCast))
return;
if (notInteresting.contains(ictx.getInstruction()) || interesting.contains(ictx.getInstruction()))
return;
StackContext sctx = ictx.getPops().get(0);
if (sctx.getPushed().getInstruction() instanceof AConstNull)
{
interesting.add(ictx.getInstruction());
}
else
{
interesting.remove(ictx.getInstruction());
notInteresting.add(ictx.getInstruction());
}
}
private void visit(MethodContext ctx)
{
Instructions ins = ctx.getMethod().getCode().getInstructions();
interesting.forEach(ins::remove);
removed += interesting.size();
interesting.clear();
notInteresting.clear();
}
@Override
public void run(ClassGroup group)
{
Execution execution = new Execution(group);
execution.addExecutionVisitor(i -> visit(i));
execution.addMethodContextVisitor(i -> visit(i));
execution.populateInitialMethods();
execution.run();
logger.info("Removed {} casts on null", removed);
}
}

View File

@@ -0,0 +1,262 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.ILoad;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EnumDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(EnumDeobfuscator.class);
private static final net.runelite.asm.pool.Method ENUM_INIT = new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("java/lang/Enum"),
"<init>",
new Signature("(Ljava/lang/String;I)V")
);
@Override
public void run(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
if (!isEnum(cf) || cf.isEnum())
{
continue;
}
logger.info("Converting {} to an enum", cf.getName());
makeEnum(cf);
}
}
private boolean isEnum(ClassFile cf)
{
if (cf.getInterfaces().getMyInterfaces().size() != 1
|| cf.getInterfaces().getInterfaces().size() != 1)
{
return false;
}
if (!interfaceIsEnum(cf.getInterfaces().getMyInterfaces().get(0)))
{
return false;
}
// number of non static methods should be 1
long count = cf.getMethods()
.stream()
.filter(m -> !m.isStatic())
.filter(m -> !m.getName().startsWith("<"))
.count();
if (count != 1)
{
return false;
}
return true;
}
private boolean interfaceIsEnum(ClassFile cf)
{
assert cf.isInterface();
if (cf.getMethods().size() != 1)
{
return false;
}
Method method = cf.getMethods().get(0);
return method.getDescriptor().toString().equals("()I"); // ordinal
}
private void makeEnum(ClassFile cf)
{
cf.setEnum(); // make class an enum
// enums super class is java/lang/Enum
assert cf.getParentClass().getName().equals("java/lang/Object");
cf.setSuperName("java/lang/Enum");
// all static fields of the type of the class become enum members
for (Field field : cf.getFields())
{
if (field.isStatic() && field.getType().equals(new Type("L" + cf.getName() + ";")))
{
field.setEnum();
}
}
for (Method method : cf.getMethods())
{
if (!method.getName().equals("<init>"))
{
continue;
}
// Add string as first argument, which is the field name,
// and ordinal as second argument
Signature signature = new Signature.Builder()
.setReturnType(method.getDescriptor().getReturnValue())
.addArgument(Type.STRING)
.addArgument(Type.INT)
.addArguments(method.getDescriptor().getArguments())
.build();
method.setDescriptor(signature);
// Remove instructions up to invokespecial
Instructions ins = method.getCode().getInstructions();
Instruction i;
do
{
i = ins.getInstructions().get(0);
ins.remove(i);
}
while (i.getType() != InstructionType.INVOKESPECIAL);
ins.addInstruction(0, new ALoad(ins, 0)); // load this
ins.addInstruction(1, new ALoad(ins, 1)); // load constant name
ins.addInstruction(2, new ILoad(ins, 2)); // ordinal
ins.addInstruction(3, new InvokeSpecial(ins, ENUM_INIT)); // invoke enum constructor
// Shift all indexes after this up +2 because of the new String and int argument
for (int j = 4; j < ins.getInstructions().size(); ++j)
{
i = ins.getInstructions().get(j);
if (i instanceof LVTInstruction)
{
LVTInstruction lvt = ((LVTInstruction) i);
int idx = lvt.getVariableIndex();
if (idx != 0)
{
lvt.setVariableIndex(idx + 2);
}
}
}
}
// Order of fields being set in clinit, which is the order
// the enum fields are actually in
List<Field> order = new ArrayList<>();
for (Method method : cf.getMethods())
{
if (!method.getName().equals("<clinit>"))
{
continue;
}
Instructions ins = method.getCode().getInstructions();
int count = 0;
// sometimes there is new new invokespecial invokespecial putfield
// for eg enum member field30(1, 2, String.class, new class5());
boolean seenDup = false;
for (int j = 0; j < ins.getInstructions().size(); ++j)
{
Instruction i = ins.getInstructions().get(j);
if (i.getType() == InstructionType.DUP && !seenDup)
{
// XXX this should actually be the field name, but it seems to have no effect on fernflower
ins.addInstruction(j + 1, new LDC(ins, "runelite"));
ins.addInstruction(j + 2, new LDC(ins, count++));
seenDup = true;
}
else if (i.getType() == InstructionType.INVOKESPECIAL)
{
Instruction next = ins.getInstructions().get(j + 1);
// check if this is the invokespecial on the enum, putstatic comes next
if (next.getType() == InstructionType.PUTSTATIC)
{
InvokeSpecial is = (InvokeSpecial) i;
PutStatic ps = (PutStatic) next;
net.runelite.asm.pool.Method pmethod = new net.runelite.asm.pool.Method(
is.getMethod().getClazz(),
is.getMethod().getName(),
new Signature.Builder()
.setReturnType(is.getMethod().getType().getReturnValue())
.addArgument(Type.STRING)
.addArgument(Type.INT)
.addArguments(is.getMethod().getType().getArguments())
.build()
);
is.setMethod(pmethod);
Field field = ps.getMyField();
assert field != null;
order.add(field);
seenDup = false;
}
}
}
}
// Enum fields must be first. Also they are in order in clinit.
// Sort fields
Collections.sort(cf.getFields(), (f1, f2) ->
{
int idx1 = order.indexOf(f1);
int idx2 = order.indexOf(f2);
if (idx1 == -1)
{
idx1 = Integer.MAX_VALUE;
}
if (idx2 == -1)
{
idx2 = Integer.MAX_VALUE;
}
return Integer.compare(idx1, idx2);
});
}
}

View File

@@ -0,0 +1,178 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FieldInliner implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(FieldInliner.class);
private ClassGroup group;
private Multimap<Field, FieldInstruction> fieldInstructions = HashMultimap.create();
private List<Field> fields = new ArrayList<>();
private void findFieldIns()
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
if (code == null)
continue;
for (Instruction i : code.getInstructions().getInstructions())
{
if (!(i instanceof FieldInstruction))
continue;
FieldInstruction sf = (FieldInstruction) i;
if (sf.getMyField() == null)
continue;
fieldInstructions.put(sf.getMyField(), sf);
}
}
}
}
private void makeConstantValues()
{
for (ClassFile cf : group.getClasses())
{
for (Field f : cf.getFields())
{
if (!f.isStatic() || !f.getType().equals(Type.STRING))
continue;
Object constantValue = f.getValue();
if (constantValue != null)
continue;
List<FieldInstruction> sfis = fieldInstructions.get(f).stream().filter(f2 -> f2 instanceof SetFieldInstruction).collect(Collectors.toList());
if (sfis.size() != 1)
continue;
SetFieldInstruction sfi = (SetFieldInstruction) sfis.get(0);
Instruction ins = (Instruction) sfi;
Method mOfSet = ins.getInstructions().getCode().getMethod();
if (!mOfSet.getName().equals("<clinit>"))
continue;
// get prev instruction and change to a constant value
Instructions instructions = mOfSet.getCode().getInstructions();
int idx = instructions.getInstructions().indexOf(ins);
assert idx != -1;
Instruction prev = instructions.getInstructions().get(idx - 1);
if (!(prev instanceof PushConstantInstruction))
continue;
PushConstantInstruction pci = (PushConstantInstruction) prev;
constantValue = pci.getConstant();
f.setValue(constantValue);
fields.add(f);
boolean b = instructions.getInstructions().remove(prev);
assert b;
b = instructions.getInstructions().remove(ins);
assert b;
}
}
}
public int inlineUse()
{
int count = 0;
for (Field f : fields)
{
// replace getfield with constant push
List<FieldInstruction> fins = fieldInstructions.get(f).stream().filter(f2 -> f2 instanceof GetFieldInstruction).collect(Collectors.toList());
Object value = f.getValue();
for (FieldInstruction fin : fins)
{
// remove fin, add push constant
Instruction i = (Instruction) fin;
Instruction pushIns = new LDC(i.getInstructions(), value);
List<Instruction> instructions = i.getInstructions().getInstructions();
int idx = instructions.indexOf(i);
assert idx != -1;
i.getInstructions().remove(i);
instructions.add(idx, pushIns);
++count;
}
f.getClassFile().removeField(f);
}
return count;
}
@Override
public void run(ClassGroup group)
{
this.group = group;
findFieldIns();
makeConstantValues();
int count = inlineUse();
logger.info("Inlined " + count + " fields");
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instructions.AThrow;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.New;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class IllegalStateExceptions implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(IllegalStateExceptions.class);
private int count;
private Set<Instruction> interesting = new HashSet<>();
private List<InstructionContext> toRemove = new ArrayList<>();
/* find if, new, ..., athrow, replace with goto */
private void findInteresting(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code c = m.getCode();
if (c == null)
continue;
Instructions instructions = c.getInstructions();
List<Instruction> ilist = instructions.getInstructions();
for (int i = 0; i < ilist.size(); ++i)
{
Instruction ins = ilist.get(i);
if (!(ins instanceof ComparisonInstruction)) // the if
continue;
Instruction ins2 = ilist.get(i + 1);
if (!(ins2 instanceof New))
continue;
New new2 = (New) ins2;
net.runelite.asm.pool.Class clazz = new2.getNewClass();
if (!clazz.getName().contains("java/lang/IllegalStateException"))
continue;
interesting.add(ins);
}
}
}
}
private void visit(InstructionContext ic)
{
if (interesting.contains(ic.getInstruction()))
{
toRemove.add(ic);
}
}
private void visit(MethodContext ctx)
{
for (InstructionContext ictx : toRemove)
processOne(ictx);
toRemove.clear();
}
private void processOne(InstructionContext ic)
{
Instruction ins = ic.getInstruction();
Instructions instructions = ins.getInstructions();
if (instructions == null)
return;
List<Instruction> ilist = instructions.getInstructions();
JumpingInstruction jumpIns = (JumpingInstruction) ins;
assert jumpIns.getJumps().size() == 1;
Instruction to = jumpIns.getJumps().get(0);
// remove stack of if.
if (ins instanceof If)
{
ic.removeStack(1);
}
ic.removeStack(0);
int i = ilist.indexOf(ins);
assert i != -1;
// remove up to athrow
while (!(ins instanceof AThrow))
{
instructions.remove(ins);
ins = ilist.get(i); // don't need to ++i because
}
// remove athrow
instructions.remove(ins);
// insert goto
assert ilist.contains(to);
Goto g = new Goto(instructions, instructions.createLabelFor(to));
ilist.add(i, g);
++count;
}
@Override
public void run(ClassGroup group)
{
findInteresting(group);
Execution execution = new Execution(group);
execution.addExecutionVisitor(i -> visit(i));
execution.addMethodContextVisitor(i -> visit(i));
execution.populateInitialMethods();
execution.run();
logger.info("Removed " + count + " illegal state exceptions");
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.deob.Deobfuscator;
import net.runelite.deob.deobfuscators.lvt.Mappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This deobfuscator is only required for fernflower which has a difficult time
* when the same lvt index is used for variables of differing types (like object
* and int), see IDEABKL-7230.
*
* @author Adam
*/
public class Lvt implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(Lvt.class);
private int count = 0;
private void process(Method method)
{
Code code = method.getCode();
if (code == null)
{
return;
}
Mappings mappings = new Mappings(code.getMaxLocals());
for (Instruction ins : code.getInstructions().getInstructions())
{
if (!(ins instanceof LVTInstruction))
{
continue;
}
LVTInstruction lv = (LVTInstruction) ins;
Integer newIdx = mappings.remap(lv.getVariableIndex(), lv.type());
if (newIdx == null)
{
continue;
}
assert newIdx != lv.getVariableIndex();
Instruction newIns = lv.setVariableIndex(newIdx);
assert ins == newIns;
++count;
}
}
@Override
public void run(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
process(m);
}
}
logger.info("Remapped {} lvt indexes", count);
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.execution.Execution;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sort fields and methods based first on the name order of the classes, and
* then based on the order the executor encounters them.
*
* @author Adam
*/
public class Order implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(Order.class);
private Execution execution;
private final Map<String, Integer> nameIndices = new HashMap<>();
@Override
public void run(ClassGroup group)
{
execution = new Execution(group);
execution.staticStep = true;
execution.populateInitialMethods();
execution.run();
for (int i = 0; i < group.getClasses().size(); i++)
{
ClassFile cf = group.getClasses().get(i);
String className = DeobAnnotations.getObfuscatedName(cf.getAnnotations());
nameIndices.put(className, i);
}
int sortedMethods = 0, sortedFields = 0;
for (ClassFile cf : group.getClasses())
{
List<Method> m = cf.getMethods();
Collections.sort(m, this::compareMethod);
sortedMethods += m.size();
// field order of enums is mostly handled in EnumDeobfuscator
if (!cf.isEnum())
{
List<Field> f = cf.getFields();
Collections.sort(f, this::compareFields);
sortedFields += f.size();
}
}
logger.info("Sorted {} methods and {} fields", sortedMethods, sortedFields);
}
// static fields, member fields, clinit, init, methods, static methods
private int compareMethod(Method m1, Method m2)
{
int i1 = getType(m1), i2 = getType(m2);
if (i1 != i2)
{
return Integer.compare(i1, i2);
}
int nameIdx1 = getNameIdx(m1.getAnnotations());
int nameIdx2 = getNameIdx(m2.getAnnotations());
if (nameIdx1 != nameIdx2)
{
return Integer.compare(nameIdx1, nameIdx2);
}
return compareOrder(m1, m2);
}
private int compareFields(Field f1, Field f2)
{
int i1 = getType(f1), i2 = getType(f2);
if (i1 != i2)
{
return Integer.compare(i1, i2);
}
int nameIdx1 = getNameIdx(f1.getAnnotations());
int nameIdx2 = getNameIdx(f2.getAnnotations());
if (nameIdx1 != nameIdx2)
{
return Integer.compare(nameIdx1, nameIdx2);
}
return compareOrder(f1, f2);
}
private int getNameIdx(Annotations annotations)
{
String name = DeobAnnotations.getObfuscatedName(annotations);
Integer nameIdx = nameIndices.get(name);
return nameIdx != null ? nameIdx : -1;
}
private int getType(Method m)
{
if (m.getName().equals("<clinit>"))
{
return 1;
}
if (m.getName().equals("<init>"))
{
return 2;
}
if (!m.isStatic())
{
return 3;
}
return 4;
}
private int getType(Field f)
{
if (f.isStatic())
{
return 1;
}
return 2;
}
private int compareOrder(Object o1, Object o2)
{
Integer i1, i2;
i1 = execution.getOrder(o1);
i2 = execution.getOrder(o2);
if (i1 == null)
{
i1 = Integer.MAX_VALUE;
}
if (i2 == null)
{
i2 = Integer.MAX_VALUE;
}
if (!i1.equals(i2))
{
return Integer.compare(i1, i2);
}
// Fall back to number of accesses
i1 = execution.getAccesses(o1);
i2 = execution.getAccesses(o2);
if (i1 == null)
{
i1 = Integer.MAX_VALUE;
}
if (i2 == null)
{
i2 = Integer.MAX_VALUE;
}
return Integer.compare(i1, i2);
}
}

View File

@@ -0,0 +1,693 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import com.google.common.primitives.Ints;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.MappableInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.IfEq;
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.Frame;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Deobfuscator;
import net.runelite.deob.deobfuscators.transformers.buffer.BufferFinder;
import net.runelite.deob.deobfuscators.packethandler.PacketLengthFinder;
import net.runelite.deob.deobfuscators.packethandler.PacketRead;
import net.runelite.deob.deobfuscators.packethandler.PacketTypeFinder;
import static net.runelite.deob.deobfuscators.transformers.OpcodesTransformer.RUNELITE_OPCODES;
import net.runelite.deob.s2c.HandlerFinder;
import net.runelite.deob.s2c.PacketHandler;
import net.runelite.deob.s2c.PacketHandlers;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketHandlerOrder implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(PacketHandlerOrder.class);
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
private static final MessageDigest sha256;
static
{
try
{
sha256 = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException ex)
{
throw new RuntimeException(ex);
}
}
@Override
public void run(ClassGroup group)
{
// This is run on the deobfuscated jar, so there are no symbols yet...
// Find packetType and buffer classes
PacketTypeFinder ptf = new PacketTypeFinder(group);
ptf.find();
BufferFinder bf = new BufferFinder(group);
bf.find();
HandlerFinder hf = new HandlerFinder(group, ptf.getPacketType());
PacketHandlers handlers = hf.findHandlers();
logger.info("Found {} packet handlers", handlers.getHandlers().size());
for (PacketHandler handler : handlers.getHandlers())
{
Execution e = hf.getExecution();
e.reset();
e.staticStep = true;
e.step = false;
e.noInvoke = true;
// exception processing won't do non-local jumps, so
// depending on whether methods are inlined or not
// it may jump completely out of the handler into the
// catch all for all packet handling
// just disable exception execution
e.noExceptions = true;
assert e.frames.isEmpty();
Frame f = handler.jumpFrame.dup();
assert f.isExecuting();
f.getMethodCtx().reset();
e.clearExecutionVisitor();
e.addExecutionVisitor(ictx ->
{
if (ictx.getInstruction() instanceof MappableInstruction)
{
if (ictx.getInstruction().getType() != InstructionType.INVOKESTATIC)
{
if (!handler.mappable.contains(ictx.getInstruction()))
{
handler.mappable.add(ictx.getInstruction());
}
}
}
if (ictx.getInstruction().getType() == InstructionType.INVOKEVIRTUAL)
{
InvokeInstruction ii = (InvokeInstruction) ictx.getInstruction();
// check if the invoke is on buffer/packetbuffer classes
boolean matches = ii.getMethods().stream()
.filter(m -> m.getDescriptor().size() == 0)
.map(method -> method.getClassFile())
.anyMatch(cf -> cf == bf.getBuffer() || cf == bf.getPacketBuffer());
if (matches)
{
Method method = ii.getMethods().get(0);
Signature signature = method.getDescriptor();
Type returnValue = signature.getReturnValue();
assert ictx.getPops().size() == 1; // buffer reference
InstructionContext bufferCtx = ictx.getPops().get(0).getPushed();
if (bufferCtx.getInstruction().getType() != InstructionType.GETSTATIC)
{
return; // sometimes buffer is passed to a function and then invoked.
}
PacketRead packetRead = new PacketRead(returnValue, bufferCtx.getInstruction(), ictx);
if (!handler.reads.contains(packetRead))
{
handler.reads.add(packetRead);
}
}
}
if (ictx.getInstruction().getType() == InstructionType.INVOKEVIRTUAL
|| ictx.getInstruction().getType() == InstructionType.INVOKESPECIAL
|| ictx.getInstruction().getType() == InstructionType.INVOKEINTERFACE)
{
InvokeInstruction ii = (InvokeInstruction) ictx.getInstruction();
// read methods are scrambled so cant count them
if (!handler.hasPacketRead(ictx.getInstruction()))
{
handler.methodInvokes.addAll(ii.getMethods());
}
}
if (ictx.getInstruction() instanceof SetFieldInstruction)
{
SetFieldInstruction sfi = (SetFieldInstruction) ictx.getInstruction();
Field field = sfi.getMyField();
if (field != null)
{
handler.fieldWrite.add(field);
}
}
if (ictx.getInstruction() instanceof GetFieldInstruction)
{
GetFieldInstruction gfi = (GetFieldInstruction) ictx.getInstruction();
Field field = gfi.getMyField();
if (field != null)
{
handler.fieldRead.add(field);
}
}
if (ictx.getInstruction() instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) ictx.getInstruction();
if (!lvt.store())
{
// get lvt access order
Frame frame = ictx.getFrame();
int order = frame.getNextOrder();
if (!handler.lvtOrder.containsKey(lvt.getVariableIndex()))
{
handler.lvtOrder.put(lvt.getVariableIndex(), order);
}
}
}
if (ictx.getInstruction() instanceof PushConstantInstruction)
{
PushConstantInstruction pci = (PushConstantInstruction) ictx.getInstruction();
handler.constants.add(pci.getConstant());
}
});
logger.debug("Beginning execution of opcode {}", handler.getOpcode());
e.run();
logger.info("Executed opcode {}: {} mappable instructions", handler.getOpcode(), handler.mappable.size());
handler.findReorderableReads();
}
List<PacketHandler> unsortedHandlers = new ArrayList<>(handlers.getHandlers());
List<PacketHandler> sortedHandlers = new ArrayList<>(handlers.getHandlers()).stream()
.sorted((PacketHandler p1, PacketHandler p2) ->
{
int c = compareReads(p1.reads, p2.reads);
if (c != 0)
{
return c;
}
if (p1.methodInvokes.size() != p2.methodInvokes.size())
{
return Integer.compare(p1.methodInvokes.size(), p2.methodInvokes.size());
}
if (p1.fieldRead.size() != p2.fieldRead.size())
{
return Integer.compare(p1.fieldRead.size(), p2.fieldRead.size());
}
if (p1.fieldWrite.size() != p2.fieldWrite.size())
{
return Integer.compare(p1.fieldWrite.size(), p2.fieldWrite.size());
}
int i = Integer.compare(p1.mappable.size(), p2.mappable.size());
if (i != 0)
{
return i;
}
int s1 = hashConstants(p1.constants), s2 = hashConstants(p2.constants);
if (s1 != s2)
{
return Integer.compare(s1, s2);
}
logger.warn("Unable to differentiate {} from {}", p1, p2);
return 0;
})
.map(s -> s.clone())
.collect(Collectors.toList());
assert sortedHandlers.size() == handlers.getHandlers().size();
for (PacketHandler handler : sortedHandlers)
{
handler.sortedReads = new ArrayList<>(handler.reads);
Collections.sort(handler.sortedReads, (PacketRead p1, PacketRead p2) ->
{
LVTInstruction l1 = (LVTInstruction) p1.getStore();
LVTInstruction l2 = (LVTInstruction) p2.getStore();
if (l1 == null && l2 == null)
{
return 0;
}
if (l1 == null)
{
return 1;
}
if (l2 == null)
{
return -1;
}
if (l1.getVariableIndex() == l2.getVariableIndex())
{
return 0;
}
Integer i1 = handler.lvtOrder.get(l1.getVariableIndex());
Integer i2 = handler.lvtOrder.get(l2.getVariableIndex());
assert i1 != null;
assert i2 != null;
int i = Integer.compare(i1, i2);
if (i == 0)
{
logger.warn("Cannot differentiate {} from {}", p1, p2);
}
return i;
});
Collections.reverse(handler.sortedReads);
}
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
assert runeliteOpcodes != null : "Opcodes class must exist";
for (PacketHandler handler : sortedHandlers)
{
logger.info("Handler {} mappable {} reads {} invokes {} freads {} fwrites {}",
handler.getOpcode(), handler.mappable.size(), handler.reads.size(), handler.methodInvokes.size(),
handler.fieldRead.size(), handler.fieldWrite.size());
final String fieldName = "PACKET_SERVER_" + handler.getOpcode();
// Add opcode fields
if (runeliteOpcodes.findField(fieldName) == null)
{
Field opField = new Field(runeliteOpcodes, fieldName, Type.INT);
// ACC_FINAL causes javac to inline the fields, which prevents
// the mapper from doing field mapping
opField.setAccessFlags(ACC_PUBLIC | ACC_STATIC);
// setting a non-final static field value
// doesn't work with fernflower
opField.setValue(handler.getOpcode());
runeliteOpcodes.addField(opField);
// add initialization
Method clinit = runeliteOpcodes.findMethod("<clinit>");
assert clinit != null;
Instructions instructions = clinit.getCode().getInstructions();
instructions.addInstruction(0, new LDC(instructions, handler.getOpcode()));
instructions.addInstruction(1, new PutStatic(instructions, opField));
}
}
// Find unique methods
List<Method> methods = unsortedHandlers.stream()
.map(ph -> ph.getMethod())
.distinct()
.collect(Collectors.toList());
for (Method m : methods)
{
List<PacketHandler> unsortedMethodHandlers = unsortedHandlers.stream()
.filter(ph -> ph.getMethod() == m)
.collect(Collectors.toList());
List<PacketHandler> sortedMethodHandlers = sortedHandlers.stream()
.filter(ph -> ph.getMethod() == m)
.collect(Collectors.toList());
assert unsortedMethodHandlers.size() == sortedMethodHandlers.size();
for (int i = 0; i < sortedMethodHandlers.size(); ++i)
{
PacketHandler unsorted = unsortedMethodHandlers.get(i);
PacketHandler sortedh = sortedMethodHandlers.get(i);
// Set opcode/jump from sorted -> unsorted
If jump = (If) unsorted.getJump();
PushConstantInstruction pci = (PushConstantInstruction) unsorted.getPush();
assert unsorted.getOpcode() == ((Number) pci.getConstant()).intValue();
Instructions instructions = unsorted.getMethod().getCode().getInstructions();
final String fieldName = "PACKET_SERVER_" + sortedh.getOpcode();
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(RUNELITE_OPCODES),
fieldName,
Type.INT
);
instructions.replace(unsorted.getPush(), new GetStatic(instructions, field));
assert jump.getType() == InstructionType.IF_ICMPEQ || jump.getType() == InstructionType.IF_ICMPNE;
Label startLabel = instructions.createLabelFor(sortedh.getStart());
if (jump.getType() == InstructionType.IF_ICMPEQ)
{
instructions.replace(jump, new IfICmpEq(instructions, startLabel));
}
else if (jump.getType() == InstructionType.IF_ICMPNE)
{
// insert a jump after to go to sortedh start
int idx = instructions.getInstructions().indexOf(jump);
assert idx != -1;
instructions.addInstruction(idx + 1, new Goto(instructions, startLabel));
}
else
{
throw new IllegalStateException();
}
}
}
insertSortedReads(group, sortedHandlers);
insertPacketLength(group, ptf);
}
private void insertSortedReads(ClassGroup group, List<PacketHandler> handlers)
{
outer:
for (PacketHandler handler : handlers)
{
Method method = handler.getMethod();
Instructions instructions = method.getCode().getInstructions();
List<Instruction> ins = instructions.getInstructions();
Instruction afterRead = handler.getAfterRead();
if (afterRead == null)
{
continue;
}
for (PacketRead read : handler.reads)
{
if (read.getStore() == null)
{
continue outer;
}
}
List<Instruction> follow = follow(instructions, handler.getStart(), afterRead);
if (follow == null)
{
continue;
}
for (Instruction i : follow)
{
if (i instanceof ComparisonInstruction)
{
continue outer;
}
}
Label afterReadLabel = instructions.createLabelFor(afterRead);
int idx = ins.indexOf(handler.getStart());
assert idx != -1;
if (handler.getStart() instanceof Label)
{
++idx;
}
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(findClient(group).getName()),
RUNELITE_PACKET,
Type.BOOLEAN
);
instructions.addInstruction(idx, new GetStatic(instructions, field));
++idx;
instructions.addInstruction(idx, new IfEq(instructions, instructions.createLabelFor(ins.get(idx))));
++idx;
List<Instruction> toCopy = new ArrayList<>();
for (Instruction i : follow)
{
assert !(i instanceof JumpingInstruction);
if (i instanceof Label)
{
continue;
}
toCopy.add(i);
}
// Remove getstatic/invoke/store for packet reads
for (PacketRead read : handler.reads)
{
boolean b = toCopy.remove(read.getGetBuffer());
assert b;
b = toCopy.remove(read.getInvoke());
assert b;
b = toCopy.remove(read.getStore());
assert b;
}
// Add sorted reads
for (PacketRead read : handler.sortedReads)
{
toCopy.add(0, read.getGetBuffer());
// replace invoke instruction with typed read
InvokeInstruction ii = (InvokeInstruction) read.getInvoke();
net.runelite.asm.pool.Method invokeMethod;
if (read.getType().equals(Type.BYTE))
{
invokeMethod = new net.runelite.asm.pool.Method(
ii.getMethod().getClazz(),
"runeliteReadByte",
new Signature("()B")
);
}
else if (read.getType().equals(Type.SHORT))
{
invokeMethod = new net.runelite.asm.pool.Method(
ii.getMethod().getClazz(),
"runeliteReadShort",
new Signature("()S")
);
}
else if (read.getType().equals(Type.INT))
{
invokeMethod = new net.runelite.asm.pool.Method(
ii.getMethod().getClazz(),
"runeliteReadInt",
new Signature("()I")
);
}
else if (read.getType().equals(Type.STRING))
{
invokeMethod = new net.runelite.asm.pool.Method(
ii.getMethod().getClazz(),
"runeliteReadString",
new Signature("()Ljava/lang/String;")
);
}
else
{
throw new UnsupportedOperationException("Unknown type " + read.getType());
}
toCopy.add(1, new InvokeVirtual(instructions, invokeMethod));
toCopy.add(2, read.getStore());
}
for (Instruction i : toCopy)
{
instructions.addInstruction(idx++, i.clone());
}
instructions.addInstruction(idx, new Goto(instructions, afterReadLabel));
++idx;
}
}
private int compareReads(List<PacketRead> r1, List<PacketRead> r2)
{
List<Type> t1 = r1.stream()
.map(pr -> pr.getType())
.sorted(this::compareType)
.collect(Collectors.toList());
List<Type> t2 = r2.stream()
.map(pr -> pr.getType())
.sorted(this::compareType)
.collect(Collectors.toList());
if (t1.size() != t2.size())
{
return Integer.compare(t1.size(), t2.size());
}
for (int i = 0; i < t1.size(); ++i)
{
Type type1 = t1.get(i), type2 = t2.get(i);
int cmp = compareType(type1, type2);
if (cmp != 0)
{
return cmp;
}
}
return 0;
}
private int compareType(Type t1, Type t2)
{
if (t1.getDimensions() != t2.getDimensions())
{
return Integer.compare(t1.getDimensions(), t2.getDimensions());
}
return t1.toString().compareTo(t2.toString());
}
public static int hashConstants(List<Object> constants)
{
sha256.reset();
for (Object o : constants)
{
if (o instanceof Number)
{
sha256.update(((Number) o).byteValue());
}
else if (o instanceof String)
{
String s = (String) o;
sha256.update(s.getBytes());
}
}
byte[] b = sha256.digest();
return Ints.fromByteArray(b);
}
private void insertPacketLength(ClassGroup group, PacketTypeFinder ptf)
{
PacketLengthFinder pfl = new PacketLengthFinder(group, ptf);
pfl.find();
GetStatic getArray = pfl.getGetArray();
PutStatic ps = pfl.getStore(); // instruction to store packet length
Instructions instructions = ps.getInstructions();
List<Instruction> ins = instructions.getInstructions();
Label getArrayLabel = instructions.createLabelFor(getArray);
Label storeLabel = instructions.createLabelFor(ps);
int idx = ins.indexOf(getArray);
assert idx != -1;
--idx; // to go before label, which must exist
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(findClient(group).getName()),
RUNELITE_PACKET,
Type.BOOLEAN
);
instructions.addInstruction(idx++, new GetStatic(instructions, field));
instructions.addInstruction(idx++, new IfEq(instructions, getArrayLabel));
instructions.addInstruction(idx++, new LDC(instructions, -2)); // 2 byte length
instructions.addInstruction(idx++, new Goto(instructions, storeLabel));
}
private ClassFile findClient(ClassGroup group)
{
// "client" in vainlla but "Client" in deob..
ClassFile cf = group.findClass("client");
return cf != null ? cf : group.findClass("Client");
}
private List<Instruction> follow(Instructions instructions, Instruction start, Instruction end)
{
List<Instruction> list = new ArrayList<>();
int idx = instructions.getInstructions().indexOf(start);
assert idx != -1;
for (;;)
{
Instruction i = instructions.getInstructions().get(idx);
// end is the following instruction post read.. not included
if (i == end)
{
break;
}
if (i instanceof Goto)
{
Goto g = (Goto) i;
Label to = g.getTo();
idx = instructions.getInstructions().indexOf(to);
assert idx != -1;
continue;
}
list.add(i);
if (i instanceof ComparisonInstruction)
{
return list;
}
++idx;
}
return list;
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.signature.util.VirtualMethods;
import net.runelite.deob.Deob;
import net.runelite.deob.Deobfuscator;
import net.runelite.deob.util.NameMappings;
import java.util.List;
public class RenameUnique implements Deobfuscator
{
private Renamer renamer;
private void generateClassNames(NameMappings map, ClassGroup group)
{
int i = 0;
for (ClassFile cf : group.getClasses())
{
if (cf.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
continue;
map.map(cf.getPoolClass(), "class" + i++);
}
}
private void generateFieldNames(NameMappings map, ClassGroup group)
{
int i = 0;
for (ClassFile cf : group.getClasses())
for (Field field : cf.getFields())
{
if (field.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
continue;
map.map(field.getPoolField(), "field" + i++);
}
}
private void generateMethodNames(NameMappings map, ClassGroup group)
{
int i = 0;
for (ClassFile cf : group.getClasses())
for (Method method : cf.getMethods())
{
if (method.getName().length() > Deob.OBFUSCATED_NAME_MAX_LEN)
continue;
List<Method> virtualMethods = VirtualMethods.getVirtualMethods(method);
assert !virtualMethods.isEmpty();
String name;
if (virtualMethods.size() == 1)
name = "method" + i++;
else
name = "vmethod" + i++;
for (Method m : virtualMethods)
map.map(m.getPoolMethod(), name);
}
}
@Override
public void run(ClassGroup group)
{
group.buildClassGraph();
group.lookup();
NameMappings mappings = new NameMappings();
this.generateClassNames(mappings, group);
this.generateFieldNames(mappings, group);
this.generateMethodNames(mappings, group);
renamer = new Renamer(mappings);
renamer.run(group);
}
}

View File

@@ -0,0 +1,339 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import com.google.common.base.Strings;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Interfaces;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.code.Exceptions;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.LocalVariable;
import net.runelite.asm.attributes.code.Parameter;
import net.runelite.asm.signature.Signature;
import net.runelite.asm.signature.util.VirtualMethods;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.Deobfuscator;
import net.runelite.deob.util.NameMappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Renamer implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(Renamer.class);
private final NameMappings mappings;
public Renamer(NameMappings mappings)
{
this.mappings = mappings;
}
private void renameClass(ClassFile on, ClassFile old, String name)
{
if (on.getParentClass().getName().equals(old.getName()))
{
on.setParentClass(new net.runelite.asm.pool.Class(name));
}
Interfaces interfaces = on.getInterfaces();
List<net.runelite.asm.pool.Class> interfaceList = interfaces.getInterfaces();
for (net.runelite.asm.pool.Class inter : interfaceList)
{
if (inter.getName().equals(old.getName()))
{
int idx = interfaceList.indexOf(inter);
interfaceList.remove(idx);
interfaceList.add(idx, new net.runelite.asm.pool.Class(name));
break;
}
}
}
private void renameClass(ClassGroup group, ClassFile cf, String name)
{
for (ClassFile c : group.getClasses())
{
// rename on child interfaces and classes
renameClass(c, cf, name);
for (Method method : c.getMethods())
{
// rename on instructions. this includes method calls and field accesses.
if (method.getCode() != null)
{
Code code = method.getCode();
// rename on instructions
for (Instruction i : code.getInstructions().getInstructions())
{
i.renameClass(cf.getName(), name);
}
// rename on exception handlers
Exceptions exceptions = code.getExceptions();
exceptions.renameClass(cf, name);
}
// rename on parameters
Signature.Builder builder = new Signature.Builder();
Signature signature = method.getDescriptor();
for (int i = 0; i < signature.size(); ++i)
{
Type type = signature.getTypeOfArg(i);
if (type.getInternalName().equals(cf.getName()))
{
builder.addArgument(Type.getType("L" + name + ";", type.getDimensions()));
}
else
{
builder.addArgument(type);
}
}
// rename return type
if (signature.getReturnValue().getInternalName().equals(cf.getName()))
{
builder.setReturnType(Type.getType("L" + name + ";", signature.getReturnValue().getDimensions()));
}
else
{
builder.setReturnType(signature.getReturnValue());
}
Signature newSignature = builder.build();
if (!method.getDescriptor().equals(newSignature))
{
//Signature was updated. Annotate it
if (method.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE) == null)
{
//Signature was not previously renamed
method.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", method.getDescriptor().toString());
}
}
method.setDescriptor(newSignature);
// rename on exceptions thrown
if (method.getExceptions() != null)
{
method.getExceptions().renameClass(cf, name);
}
}
// rename on fields
for (Field field : c.getFields())
{
if (field.getType().getInternalName().equals(cf.getName()))
{
if (field.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE) == null)
{
//Signature was updated. Annotate it
field.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", field.getType().toString());
}
field.setType(Type.getType("L" + name + ";", field.getType().getDimensions()));
}
}
}
if (cf.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
{
cf.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", cf.getName());
}
group.renameClass(cf, name);
}
private void regeneratePool(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code c = m.getCode();
if (c == null)
{
continue;
}
c.getInstructions().regeneratePool();
}
}
}
@Override
public void run(ClassGroup group)
{
group.buildClassGraph();
group.lookup();
int classes = 0, fields = 0, methods = 0, parameters = 0;
// rename fields
for (ClassFile cf : group.getClasses())
{
for (Field field : cf.getFields())
{
String newName = mappings.get(field.getPoolField());
if (newName == null)
{
continue;
}
if (field.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
{
field.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", field.getName());
}
field.setName(newName);
++fields;
}
}
// rename methods
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
String newName = mappings.get(method.getPoolMethod());
String[] newParams = mappings.getP(method.getPoolMethod());
// rename on obfuscated signature
Annotation an = method.getAnnotations().find(DeobAnnotations.OBFUSCATED_SIGNATURE);
if (an != null)
{
Signature obfuscatedSig = new Signature(an.getElement().getString());
Signature updatedSig = renameSignature(obfuscatedSig);
an.getElement().setValue(updatedSig.toString());
}
if (newName == null)
{
continue;
}
List<Method> virtualMethods = VirtualMethods.getVirtualMethods(method);
assert !virtualMethods.isEmpty();
for (Method m : virtualMethods)
{
List<Parameter> oldParams = method.getParameters();
for (Parameter p : oldParams)
{
int index = oldParams.indexOf(p);
if (newParams == null)
{
break;
}
else if (newParams.length < 1 || index > newParams.length - 1 || Strings.isNullOrEmpty(newParams[index]))
{
break;
}
LocalVariable oldVar = p.getLocalVariable();
LocalVariable newVar = new LocalVariable(
newParams[index],
oldVar.getDesc(),
oldVar.getSignature(),
oldVar.getStart(),
oldVar.getEnd(),
oldVar.getIndex()
);
p.setLocalVariable(newVar);
++parameters;
}
if (m.getAnnotations().find(DeobAnnotations.OBFUSCATED_NAME) == null)
{
m.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_NAME, "value", m.getName());
}
m.setName(newName);
}
methods += virtualMethods.size();
}
}
for (ClassFile cf : group.getClasses())
{
String newName = mappings.get(cf.getPoolClass());
if (newName == null)
{
continue;
}
renameClass(group, cf, newName);
++classes;
}
this.regeneratePool(group);
logger.info("Renamed {} classes, {} fields, {} methods, and {} parameters", classes, fields, methods, parameters);
}
private Type renameType(Type t)
{
if (t.isPrimitive())
{
return t;
}
String className = t.getInternalName();
String newName = mappings.get(new net.runelite.asm.pool.Class(className));
if (newName == null)
{
return t;
}
Type type = Type.getType("L" + newName + ";", t.getDimensions());
logger.debug("Renamed {} -> {}", t, type);
return type;
}
private Signature renameSignature(Signature s)
{
Signature.Builder builder = new Signature.Builder()
.setReturnType(renameType(s.getReturnValue()));
for (Type t : s.getArguments())
{
builder.addArgument(renameType(t));
}
return builder.build();
}
}

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RuntimeExceptions implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(RuntimeExceptions.class);
@Override
public void run(ClassGroup group)
{
boolean foundInit = false;
int i = 0;
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code c = m.getCode();
if (c == null)
continue;
// Keep one handler in the client so the deobfuscator
// keeps the client error handling related methods
if (cf.getName().equals("client") && m.getName().equals("init"))
{
foundInit = true;
continue;
}
for (net.runelite.asm.attributes.code.Exception e : new ArrayList<>(c.getExceptions().getExceptions()))
{
if (e.getCatchType() != null && e.getCatchType().getName().equals("java/lang/RuntimeException"))
{
c.getExceptions().remove(e);
++i;
}
}
}
}
if (!foundInit)
{
throw new IllegalStateException("client.init(...) method seems to be missing!");
}
logger.info("Remove {} exception handlers", i);
}
}

View File

@@ -0,0 +1,113 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.execution.Execution;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnreachedCode implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(UnreachedCode.class);
private Execution execution;
private int removeUnused(Method m)
{
Instructions ins = m.getCode().getInstructions();
int count = 0;
List<Instruction> insCopy = new ArrayList<>(ins.getInstructions());
for (int j = 0; j < insCopy.size(); ++j)
{
Instruction i = insCopy.get(j);
if (!execution.executed.contains(i))
{
// if this is an exception handler, the exception handler is never used...
for (net.runelite.asm.attributes.code.Exception e : new ArrayList<>(m.getCode().getExceptions().getExceptions()))
{
if (e.getStart().next() == i)
{
e.setStart(ins.createLabelFor(insCopy.get(j + 1)));
if (e.getStart().next() == e.getEnd().next())
{
m.getCode().getExceptions().remove(e);
continue;
}
}
if (e.getHandler().next() == i)
{
m.getCode().getExceptions().remove(e);
}
}
if (i instanceof Label)
continue;
ins.remove(i);
++count;
}
}
return count;
}
@Override
public void run(ClassGroup group)
{
group.buildClassGraph();
execution = new Execution(group);
execution.populateInitialMethods();
execution.run();
int count = 0;
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
if (m.getCode() == null)
continue;
count += removeUnused(m);
}
}
logger.info("Removed {} unused instructions", count);
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnusedClass implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(UnusedClass.class);
@Override
public void run(ClassGroup group)
{
int count = 0;
for (ClassFile cf : new ArrayList<>(group.getClasses()))
{
if (!cf.getFields().isEmpty())
{
continue;
}
if (!cf.getMethods().isEmpty())
{
continue;
}
if (isImplemented(group, cf))
{
continue;
}
group.removeClass(cf);
++count;
}
logger.info("Removed {} classes", count);
}
private boolean isImplemented(ClassGroup group, ClassFile iface)
{
for (ClassFile cf : group.getClasses())
{
if (cf.getInterfaces().getMyInterfaces().contains(iface))
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnusedFields implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(UnusedFields.class);
private final Set<Field> used = new HashSet<>();
private void checkForFieldUsage(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
for (Method m : cf.getMethods())
{
Code code = m.getCode();
if (code == null)
continue;
for (Instruction ins : code.getInstructions().getInstructions())
{
if (ins instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) ins;
used.add(fi.getMyField());
}
}
}
}
@Override
public void run(ClassGroup group)
{
checkForFieldUsage(group);
int count = 0;
for (ClassFile cf : group.getClasses())
for (Field f : new ArrayList<>(cf.getFields()))
if (!used.contains(f))
{
cf.removeField(f);
++count;
}
logger.info("Removed " + count + " unused fields");
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.deob.Deob;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnusedMethods implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(UnusedMethods.class);
private final Set<Method> methods = new HashSet<>();
@Override
public void run(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
run(method);
}
}
int count = 0;
for (ClassFile cf : group.getClasses())
{
boolean extendsApplet = extendsApplet(cf);
for (Method method : new ArrayList<>(cf.getMethods()))
{
// constructors can't be renamed, but are obfuscated
if (!Deob.isObfuscated(method.getName()) && !method.getName().equals("<init>"))
{
continue;
}
if (extendsApplet && method.getName().equals("<init>"))
{
continue;
}
if (!methods.contains(method))
{
logger.debug("Removing unused method {}", method);
cf.removeMethod(method);
++count;
}
}
}
logger.info("Removed {} methods", count);
}
private void run(Method method)
{
Code code = method.getCode();
if (code == null)
{
return;
}
for (Instruction i : code.getInstructions().getInstructions())
{
if (!(i instanceof InvokeInstruction))
{
continue;
}
InvokeInstruction ii = (InvokeInstruction) i;
methods.addAll(ii.getMethods());
}
}
private static boolean extendsApplet(ClassFile cf)
{
if (cf.getParent() != null)
{
return extendsApplet(cf.getParent());
}
return cf.getSuperName().equals("java/applet/Applet");
}
}

View File

@@ -0,0 +1,316 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.signature.Signature;
import net.runelite.asm.signature.util.VirtualMethods;
import net.runelite.deob.Deob;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UnusedParameters implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(UnusedParameters.class);
private final Map<List<Method>, Collection<Integer>> unused = new HashMap<>();
private final Multimap<Instruction, InstructionContext> invokes = HashMultimap.create();
private void visit(InstructionContext ictx)
{
Instruction i = ictx.getInstruction();
if (!(i instanceof InvokeInstruction))
{
return;
}
invokes.put(i, ictx);
}
private void buildUnused(ClassGroup group)
{
unused.clear();
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
if (!Deob.isObfuscated(m.getName()))
{
continue;
}
List<Method> ms = VirtualMethods.getVirtualMethods(m);
Collection<Integer> u = this.findUnusedParameters(ms);
if (!u.isEmpty())
{
unused.put(ms, u);
}
}
}
}
private boolean shouldRemove(Method m, int parameter)
{
Signature obSig = DeobAnnotations.getObfuscatedSignature(m);
if (obSig == null)
{
return false;
}
return parameter + 1 == obSig.size();
}
private int processUnused(Execution execution, ClassGroup group)
{
int count = 0;
for (List<Method> m : unused.keySet())
{
Collection<Integer> u = unused.get(m);
int offset = m.size() == 1 && m.get(0).isStatic() ? 0 : 1;
for (int unusedParameter : u)
{
if (!shouldRemove(m.get(0), unusedParameter))
{
continue;
}
Signature signature = m.get(0).getDescriptor();
int lvtIndex = this.getLvtIndex(signature, offset, unusedParameter);
/* removing the parameter can't cause collisions on other (overloaded) methods because prior to this we rename
* all classes/fields/methods to have unique names.
*/
logger.debug("Removing parameter {} at index {} from {}", unusedParameter, lvtIndex, m);
removeParameter(group, m, signature, execution, unusedParameter, lvtIndex);
break;
}
++count;
}
return count;
}
private Set<Integer> findUnusedParameters(Method method)
{
int offset = method.isStatic() ? 0 : 1;
Signature signature = method.getDescriptor();
List<Integer> unusedParams = new ArrayList<>();
for (int variableIndex = 0, lvtIndex = offset;
variableIndex < signature.size();
lvtIndex += signature.getTypeOfArg(variableIndex++).getSize())
{
List<? extends Instruction> lv = method.findLVTInstructionsForVariable(lvtIndex);
if (lv == null || lv.isEmpty())
{
unusedParams.add(variableIndex);
}
}
return ImmutableSet.copyOf(unusedParams);
}
@SuppressWarnings("empty-statement")
private int getLvtIndex(Signature signature, int offset, int parameter)
{
// get lvt index for parameter
int lvtIndex = offset;
for (int variableIndex = 0;
variableIndex < parameter;
lvtIndex += signature.getTypeOfArg(variableIndex++).getSize());
return lvtIndex;
}
public Collection<Integer> findUnusedParameters(Collection<Method> methods)
{
Set<Integer> list = null;
for (Method m : methods)
{
Set<Integer> p = findUnusedParameters(m);
if (list == null)
{
list = p;
}
else
{
list = Sets.intersection(list, p);
}
}
List<Integer> l = new ArrayList<>(list);
Collections.sort(l);
Collections.reverse(l);
return l;
}
public void removeParameter(ClassGroup group, List<Method> methods, Signature signature, Execution execution, int paramIndex, int lvtIndex)
{
int slots = signature.getTypeOfArg(paramIndex).getSize();
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code c = m.getCode();
if (c == null)
{
continue;
}
for (Instruction i : new ArrayList<>(c.getInstructions().getInstructions()))
{
if (!(i instanceof InvokeInstruction))
{
continue;
}
InvokeInstruction ii = (InvokeInstruction) i;
if (!ii.getMethods().stream().anyMatch(me -> methods.contains(me)))
{
continue;
}
ii.removeParameter(paramIndex); // remove parameter from instruction
Collection<InstructionContext> ics = invokes.get(i);
assert ics != null;
if (ics != null)
{
for (InstructionContext ins : ics)
{
int pops = signature.size() - paramIndex - 1; // index from top of stack of parameter. 0 is the last parameter
StackContext sctx = ins.getPops().get(pops);
if (sctx.getPushed().getInstruction().getInstructions() == null)
{
continue;
}
ins.removeStack(pops); // remove parameter from stack
}
}
}
}
}
for (Method method : methods)
{
if (method.getCode() != null)
// adjust lvt indexes to get rid of idx in the method
{
for (Instruction ins : method.getCode().getInstructions().getInstructions())
{
if (ins instanceof LVTInstruction)
{
LVTInstruction lins = (LVTInstruction) ins;
int i = lins.getVariableIndex();
assert i != lvtIndex; // current unused variable detection just looks for no accesses
// reassign
if (i > lvtIndex)
{
assert i > 0;
assert i >= lvtIndex + slots;
Instruction newIns = lins.setVariableIndex(i - slots);
assert ins == newIns;
}
}
}
}
}
for (Method method : methods)
{
method.getDescriptor().remove(paramIndex);
}
}
private int count;
@Override
public void run(ClassGroup group)
{
int i;
int pnum = 1;
do
{
group.buildClassGraph();
invokes.clear();
this.buildUnused(group);
Execution execution = new Execution(group);
execution.addExecutionVisitor(ictx -> visit(ictx));
execution.populateInitialMethods();
execution.run();
i = this.processUnused(execution, group);
count += i;
break;
}
while (i > 0);
logger.info("Removed {} unused parameters", count);
}
public int getCount()
{
return count;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
// a constant associated to a field
class AssociatedConstant
{
Number value;
boolean other; // whether or not another field is involved
boolean constant; // whether or not the constant is a constant field assignment
boolean getter; // whether or not this is associated as a getter
boolean setter; // whether or not this is associated as a setter
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.math.BigInteger;
public class DMath
{
public static BigInteger modInverse(BigInteger val, int bits)
{
try
{
BigInteger shift = BigInteger.ONE.shiftLeft(bits);
return val.modInverse(shift);
}
catch (ArithmeticException e)
{
return val;
}
}
public static int modInverse(int val)
{
return modInverse(BigInteger.valueOf(val), 32).intValue();
}
public static long modInverse(long val)
{
return modInverse(BigInteger.valueOf(val), 64).longValue();
}
public static Number modInverse(Number value)
{
if (value instanceof Integer)
{
return modInverse((int) value);
}
else if (value instanceof Long)
{
return modInverse((long) value);
}
else
{
throw new IllegalArgumentException();
}
}
public static boolean isInversable(int val)
{
try
{
modInverse(val);
return true;
}
catch (ArithmeticException ex)
{
return false;
}
}
private static boolean isInversable(long val)
{
try
{
modInverse(val);
return true;
}
catch (ArithmeticException ex)
{
return false;
}
}
public static boolean isInversable(Number value)
{
if (value instanceof Integer)
{
return isInversable((int) value);
}
else if (value instanceof Long)
{
return isInversable((long) value);
}
else
{
throw new IllegalArgumentException();
}
}
public static Number multiply(Number one, Number two)
{
assert one.getClass() == two.getClass();
if (one instanceof Integer)
{
return (int) one * (int) two;
}
else if (one instanceof Long)
{
return (long) one * (long) two;
}
else
{
throw new IllegalArgumentException();
}
}
public static boolean equals(Number one, int two)
{
if (one instanceof Long)
{
return equals(one, ((long) two) & 0xffffffff);
}
return one.intValue() == two;
}
public static boolean equals(Number one, long two)
{
if (one instanceof Integer)
{
return equals(one, (int) two);
}
return one.longValue() == two;
}
}

View File

@@ -0,0 +1,275 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import com.google.common.collect.Lists;
import java.util.List;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
import net.runelite.asm.attributes.code.instructions.Dup;
import net.runelite.asm.attributes.code.instructions.Dup2_X1;
import net.runelite.asm.attributes.code.instructions.Dup_X1;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.attributes.code.instructions.Pop;
import net.runelite.asm.attributes.code.instructions.Pop2;
import net.runelite.asm.attributes.code.instructions.Swap;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Removes the duplication of multiplication instructions to make
* MultiplicationDeobfuscator easier
*
* @author Adam
*/
public class DupDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(DupDeobfuscator.class);
private int count;
private void visit(InstructionContext i)
{
if (!(i.getInstruction() instanceof DupInstruction))
{
return;
}
DupInstruction di = (DupInstruction) i.getInstruction();
List<StackContext> sctxs = di.getDuplicated(i); // stack values being duplicated
for (StackContext sctx : sctxs)
{
InstructionContext ic = sctx.getPushed();
if (ic.getInstruction() instanceof IMul)
{
if (i.getInstruction() instanceof Dup)
{
logger.debug("Dup instruction {} duplicates multiplication result {}", i, ic);
undup(i);
++count;
return;
}
if (i.getInstruction() instanceof Dup_X1)
{
logger.debug("Dup_X1 instruction {} duplicates multiplication result {}", i, ic);
undup_x1(i);
++count;
return;
}
logger.warn("Dup instruction {} pops imul", i);
}
else if (ic.getInstruction() instanceof LMul)
{
if (i.getInstruction() instanceof Dup2_X1)
{
logger.debug("Dup_X2 instruction {} duplicates multiplication result {}", i, ic);
undup2_x1(i);
++count;
return;
}
logger.warn("Dup instruction {} pops lmul", i);
}
}
// find if mul pops anything duplicated
sctxs = di.getCopies(i);
for (StackContext sctx : sctxs)
{
for (InstructionContext ic : sctx.getPopped())
{
if (ic.getInstruction() instanceof IMul)
{
if (i.getInstruction() instanceof Dup)
{
logger.debug("imul {} pops dup instruction {}", ic, i);
undup(i);
++count;
return;
}
if (i.getInstruction() instanceof Dup_X1)
{
logger.debug("imul {} pops dup x1 instruction {}", ic, i);
undup_x1(i);
++count;
return;
}
logger.warn("imul pops dup instruction {}", i);
}
else if (ic.getInstruction() instanceof LMul)
{
if (i.getInstruction() instanceof Dup2_X1)
{
logger.debug("imul {} pops dup2 x1 instruction {}", ic, i);
undup2_x1(i);
++count;
return;
}
logger.warn("lmul pops dup instruction {}", i);
}
}
}
}
private void undup(InstructionContext ictx)
{
assert ictx.getInstruction() instanceof Dup;
Instructions instructions = ictx.getInstruction().getInstructions();
StackContext duplicated = ictx.getPops().get(0);
int idx = instructions.getInstructions().indexOf(ictx.getInstruction());
assert idx != -1;
// replace dup with duplicated instructions
instructions.remove(ictx.getInstruction());
// insert copy
copy(duplicated, instructions, idx);
}
private void undup_x1(InstructionContext ictx)
{
assert ictx.getInstruction() instanceof Dup_X1;
Instructions instructions = ictx.getInstruction().getInstructions();
StackContext duplicated = ictx.getPops().get(0);
// replace dup_x1 with swap
int idx = instructions.replace(ictx.getInstruction(), new Swap(instructions));
// copy imul and insert after idx
copy(duplicated, instructions, idx + 1);
}
private void undup2_x1(InstructionContext ictx)
{
assert ictx.getInstruction() instanceof Dup2_X1;
assert ictx.getPops().size() == 2; // only support this form
// I L -> L I L
Instructions instructions = ictx.getInstruction().getInstructions();
// can't swap a long on the stack, so
int idx = instructions.getInstructions().indexOf(ictx.getInstruction());
assert idx != -1;
instructions.remove(ictx.getInstruction()); // remove dup2_x1
instructions.addInstruction(idx++, new Pop2(instructions)); // pop long
instructions.addInstruction(idx++, new Pop(instructions)); // pop int
// insert copy of long
idx = copy(ictx.getPops().get(0), instructions, idx);
// insert copy of int
idx = copy(ictx.getPops().get(1), instructions, idx);
// insert copy of long
/* idx = */ copy(ictx.getPops().get(0), instructions, idx);
}
/**
* copy the instruction which pushed sctx and insert into instructions
* starting at index idx
*/
private int copy(StackContext sctx, Instructions instructions, int idx)
{
InstructionContext ictx = sctx.getPushed();
if (ictx.getInstruction() instanceof DupInstruction)
{
// we only care about one path
DupInstruction di = (DupInstruction) ictx.getInstruction();
sctx = di.getOriginal(sctx);
ictx = sctx.getPushed();
}
// copy required instructions
// the first thing popped was pushed last, so reverse
for (StackContext s : Lists.reverse(ictx.getPops()))
{
idx = copy(s, instructions, idx);
}
// copy instruction
Instruction i = ictx.getInstruction();
logger.debug("Duplicating instruction {} at index {}", i, idx);
i = i.clone();
instructions.addInstruction(idx, i);
return idx + 1; // move on to next instruction
}
private void visit(MethodContext mctx)
{
for (InstructionContext ictx : mctx.getInstructionContexts())
{
if (ictx.getInstruction().getInstructions() == null)
{
// already removed?
continue;
}
visit(ictx);
}
}
@Override
public void run(ClassGroup group)
{
Execution e = new Execution(group);
e.addMethodContextVisitor(m -> visit(m));
e.populateInitialMethods();
e.run();
logger.info("Replaced {} dup instructions", count);
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.pool.Field;
class Encryption
{
private final Map<Field, Pair> fields = new HashMap<>();
void addPair(Pair pair)
{
assert pair.field != null;
Pair existing = fields.get(pair.field);
if (existing != null)
{
// if this is a subsequent guess, then the new guess
// is multiplied into the old guess
fields.put(pair.field, new Pair(pair, existing));
}
else
{
fields.put(pair.field, pair);
}
}
Pair getField(Field field)
{
return fields.get(field);
}
}

View File

@@ -0,0 +1,165 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import static java.lang.Math.abs;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static net.runelite.deob.deobfuscators.arithmetic.DMath.multiply;
class FieldInfo
{
public final Set<Number> getters = new HashSet<>();
public final Set<Number> setters = new HashSet<>();
public final Set<AssociatedConstant> constants = new HashSet<>();
boolean guessDecreasesConstants(Pair guess)
{
if (getters.isEmpty() && setters.isEmpty())
{
return false;
}
if (guess.getter.longValue() == -1 && guess.setter.longValue() == -1)
{
// special case - when guess is -1/-1, checking the abs(total)
// is not helpful. instead look for both:
// associated non-constant of value -1
// no non-constant non-other values (indicative of field being
// obfuscated still since if it used elsewhere it would have
// an associated constant)
//
// this is really only her to fight:
// client.java: field975 = field974;
// client.java: field974 = field971;
// when field974 is only used in combination with field975
// and is initialized to -1. This causes causes guess()
// to guess a negated getter which then causes
// client.java: field975 = field974 * -1;
// client.java: field974 = field971 * -1;
// And it becomes difficult to prevent both fields being
// multiplied by -1 on each round. field975 has some other
// uses.
// only change if there is a -1 in the non constant
// and also there is a non constant non other too
List<Number> value2 = constants.stream()
.filter(i -> !i.constant)
.map(i -> i.value)
.collect(Collectors.toList());
List<Number> value = constants.stream()
.filter(i -> !i.other)
.filter(i -> !i.constant)
.map(i -> i.value)
.collect(Collectors.toList());
return value.isEmpty() && value2.contains(-1);
}
// remove possibe getters that are:
// - a constant - they don't necessarilly decrease when deobfuscated
// - are multiplied into another field
// - are both a getter and setter
Collection<Number> gettersFiltered = getters.stream()
.filter(number -> isOkay(number))
.collect(Collectors.toSet());
Collection<Number> settersFiltered = setters.stream()
.filter(number -> isOkay(number))
.collect(Collectors.toSet());
if (!gettersFiltered.isEmpty())
{
long before = gettersFiltered.stream()
.mapToLong(number -> abs(downsample(number).longValue()))
.sum();
long after = gettersFiltered.stream()
.mapToLong(number -> abs(downsample(multiply(number, guess.setter)).longValue()))
.sum();
assert before >= 0;
assert after >= 0;
// If the total value of the getters is more, assume
// the guess is bad. Ideally the values go to 1, so
// 'after' will be small.
if (after > before)
{
return false;
}
}
if (!settersFiltered.isEmpty())
{
long beforeSetter = settersFiltered.stream()
.mapToLong(number -> abs(downsample(number).longValue()))
.sum();
long afterSetter = settersFiltered.stream()
.mapToLong(number -> abs(downsample(multiply(number, guess.getter)).longValue()))
.sum();
assert beforeSetter >= 0;
assert afterSetter >= 0;
if (afterSetter > beforeSetter)
{
return false;
}
}
return true;
}
private boolean isOkay(Number number)
{
for (AssociatedConstant c : constants)
{
if (c.value.equals(number)
&& !c.constant
&& !c.other
&& !(c.setter && c.getter))
{
return true;
}
}
return false;
}
// summation of longs probably overflows,
// so only use the most significant 32 bits
private Number downsample(Number number)
{
if (number instanceof Long)
{
long l = (Long) number;
return l >>> 32;
}
return number;
}
}

View File

@@ -0,0 +1,795 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import com.google.common.collect.ImmutableSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.ArrayLoad;
import net.runelite.asm.attributes.code.instruction.types.ArrayStoreInstruction;
import net.runelite.asm.attributes.code.instruction.types.DivisionInstruction;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.IAdd;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.IShR;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.If0;
import net.runelite.asm.attributes.code.instructions.LAdd;
import net.runelite.asm.attributes.code.instructions.LCmp;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.attributes.code.instructions.LSub;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.Deobfuscator;
import static net.runelite.deob.deobfuscators.arithmetic.DMath.modInverse;
import static net.runelite.deob.deobfuscators.arithmetic.DMath.multiply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ModArith implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(ModArith.class);
private ClassGroup group;
private Execution execution;
private final Map<Field, FieldInfo> fieldInfo = new HashMap<>();
private List<Pair> pairs = new ArrayList<>();
private Encryption encryption = new Encryption();
private FieldInfo getFieldInfo(Field field)
{
FieldInfo f = fieldInfo.get(field);
if (f == null)
{
f = new FieldInfo();
fieldInfo.put(field, f);
}
return f;
}
private static List<InstructionContext> getInsInExpr(InstructionContext ctx, Set<Instruction> set, boolean imul)
{
List<InstructionContext> l = new ArrayList<>();
if (ctx == null || set.contains(ctx.getInstruction()))
{
return l;
}
set.add(ctx.getInstruction());
if (imul)
{
if (!(ctx.getInstruction() instanceof IMul) & !(ctx.getInstruction() instanceof LMul))
{
l.add(ctx);
return l;
}
}
else
{
// invoke and array store pops are unrelated to each other
if (ctx.getInstruction() instanceof InvokeInstruction
|| ctx.getInstruction() instanceof ArrayStoreInstruction
|| ctx.getInstruction() instanceof ArrayLoad
|| ctx.getInstruction() instanceof If
|| ctx.getInstruction() instanceof If0
|| ctx.getInstruction() instanceof LCmp
|| ctx.getInstruction() instanceof DivisionInstruction
|| ctx.getInstruction() instanceof IShR)
{
return l;
}
l.add(ctx);
}
for (StackContext s : ctx.getPops())
{
l.addAll(getInsInExpr(s.getPushed(), set, imul));
}
for (StackContext s : ctx.getPushes())
{
for (InstructionContext i : s.getPopped())
{
l.addAll(getInsInExpr(i, set, imul));
}
}
return l;
}
// find associated constants with each field
private void findConstants(MethodContext mctx)
{
for (InstructionContext ctx : mctx.getInstructionContexts())
{
if (ctx.getInstruction() instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) ctx.getInstruction();
if (fi.getMyField() == null)
{
continue;
}
if ((!fi.getField().getType().equals(Type.INT)
&& !fi.getField().getType().equals(Type.LONG))
|| fi.getField().getType().isArray())
{
continue;
}
FieldInfo fieldInfo = getFieldInfo(fi.getMyField());
List<InstructionContext> l = getInsInExpr(ctx, new HashSet(), false);
boolean other = false; // check if this contains another field
boolean getter = false, setter = false;
for (InstructionContext i : l)
{
if (i.getInstruction() instanceof FieldInstruction)
{
FieldInstruction fi2 = (FieldInstruction) i.getInstruction();
Field myField = fi2.getMyField();
if (myField != null && myField != fi.getMyField())
{
Type t = myField.getType();
if (t.equals(fi.getMyField().getType()))
{
other = true;
}
}
else if (myField != null && myField == fi.getMyField())
{
if (fi2 instanceof SetFieldInstruction)
{
setter = true;
}
else
{
getter = true;
}
}
}
}
// check if this is a constant assignment
boolean constant = false;
if (fi instanceof SetFieldInstruction)
{
InstructionContext pushedsfi = ctx.getPops().get(0).getPushed(); // value being set
pushedsfi = pushedsfi.resolve(ctx.getPops().get(0));
if (pushedsfi.getInstruction() instanceof LDC)
{
constant = true;
}
}
for (InstructionContext i : l)
{
if (i.getInstruction() instanceof LDC)
{
PushConstantInstruction w = (PushConstantInstruction) i.getInstruction();
if (w.getConstant() instanceof Integer || w.getConstant() instanceof Long)
{
AssociatedConstant n = new AssociatedConstant();
n.value = (Number) w.getConstant();
n.other = other;
n.constant = constant;
n.getter = getter;
n.setter = setter;
fieldInfo.constants.add(n);
}
}
}
}
}
}
// find potential getters/setters for each field
private void findUses(MethodContext mctx)
{
for (InstructionContext ctx : mctx.getInstructionContexts())
{
if (ctx.getInstruction() instanceof IMul || ctx.getInstruction() instanceof LMul)
{
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;
}
Field field = gf.getMyField();
if (field == null)
{
continue;
}
FieldInfo fieldInfo = getFieldInfo(field);
// parse the full multiplication expression to
// get all associated constants
List<InstructionContext> insInExpr = getInsInExpr(ctx, new HashSet(), true);
for (InstructionContext ctx2 : insInExpr)
{
if (!(ctx2.getInstruction() instanceof PushConstantInstruction))
{
continue;
}
PushConstantInstruction pci3 = (PushConstantInstruction) ctx2.getInstruction();
Number value = (Number) pci3.getConstant();
// field * constant
if (value instanceof Integer || value instanceof Long)
{
fieldInfo.getters.add(value);
}
}
}
else if (ctx.getInstruction() instanceof SetFieldInstruction)
{
SetFieldInstruction sf = (SetFieldInstruction) ctx.getInstruction();
Field field = sf.getMyField();
if (field == null)
{
continue;
}
FieldInfo fieldInfo = getFieldInfo(field);
InstructionContext pushedsfi = ctx.getPops().get(0).getPushed(); // value being set
pushedsfi = pushedsfi.resolve(ctx.getPops().get(0));
if (!(pushedsfi.getInstruction() instanceof IMul) && !(pushedsfi.getInstruction() instanceof LMul)
&& !(pushedsfi.getInstruction() instanceof IAdd) && !(pushedsfi.getInstruction() instanceof LAdd)
&& !(pushedsfi.getInstruction() instanceof ISub) && !(pushedsfi.getInstruction() instanceof LSub))
{
if (pushedsfi.getInstruction() instanceof LDC)
{
PushConstantInstruction ldc = (PushConstantInstruction) pushedsfi.getInstruction();
if (ldc.getConstant() instanceof Integer || ldc.getConstant() instanceof Long)
{
Number i = (Number) ldc.getConstant();
// field = constant
fieldInfo.setters.add(i);
}
}
continue;
}
Instruction one = pushedsfi.getPops().get(0).getPushed().getInstruction();
Instruction two = pushedsfi.getPops().get(1).getPushed().getInstruction();
// field = field + imul
if (pushedsfi.getInstruction() instanceof IAdd)
{
if (one instanceof IMul && two instanceof GetFieldInstruction)
{
one = pushedsfi.getPops().get(0).getPushed().getPops().get(0).getPushed().getInstruction();
two = pushedsfi.getPops().get(0).getPushed().getPops().get(1).getPushed().getInstruction();
}
}
// if both one and two are constants then one of them must not be a setter
PushConstantInstruction pc = null;
if (one instanceof PushConstantInstruction
&& !(two instanceof PushConstantInstruction))
{
pc = (PushConstantInstruction) one;
}
else if (two instanceof PushConstantInstruction
&& !(one instanceof PushConstantInstruction))
{
pc = (PushConstantInstruction) two;
}
if (pc == null)
{
continue;
}
Number value2 = (Number) pc.getConstant();
// field = something * constant
if (value2 instanceof Integer || value2 instanceof Long)
{
fieldInfo.setters.add(value2);
}
}
}
}
private Pair guess(Field field, Collection<Number> constants)
{
if (constants.isEmpty())
{
return null;
}
// multiply each by each,
// lowest number wins
Number s1 = 0, s2 = 0;
Number smallest = 0;
for (Number i : constants)
{
for (Number i2 : constants)
{
if (DMath.equals(i, 0) || DMath.equals(i2, 0)
|| DMath.equals(i, 1) || DMath.equals(i2, 1))
{
continue;
}
if (i.equals(i2))
{
// it is never i*i. if there is only one constant,
// prefer i*inverse(i) if possible by setting i2
// to 1
if (i2 instanceof Integer)
{
i2 = 1;
}
else if (i2 instanceof Long)
{
i2 = 1L;
}
}
Number result = multiply(i, i2);
if (DMath.equals(result, 0))
{
continue;
}
boolean smaller;
if (smallest.longValue() == 0L
// abs(MIN_VALUE) is MIN_VALUE - it is not the smallest
|| (result instanceof Long && Long.MIN_VALUE == result.longValue())
|| (result instanceof Integer && Integer.MIN_VALUE == result.intValue()))
{
smaller = false;
}
else if (i instanceof Long)
{
smaller = Math.abs((long) result) < Math.abs((long) smallest);
}
else
{
smaller = Math.abs((int) result) < Math.abs((int) smallest);
}
if (DMath.equals(smallest, 0) || DMath.equals(result, 1) || smaller)
{
s1 = i;
s2 = i2;
smallest = result;
}
}
}
if (!DMath.equals(smallest, 1))
{
// smallest s1*s2 is not 1
if (DMath.isInversable(smallest))
{
// s1*s2=smallest
// divide both sides by inverse(smallest)
// to make the right hand side 1
// pick s1/s2 depending on which one isn't inversible
if (DMath.isInversable(s1))
{
s2 = multiply(s2, modInverse(smallest));
smallest = 1; // rhs goes to one
assert multiply(s1, s2).intValue() == 1;
}
else if (DMath.isInversable(s2))
{
s1 = multiply(s1, modInverse(smallest));
smallest = 1;
assert multiply(s1, s2).intValue() == 1;
}
else
{
return null;
}
}
else
{
// s1*s2 is not 1 and smallest is not inversible
// calculate a new s1 or s2 if one is inversible
if (DMath.isInversable(s1))
{
// assume this is s1 * (s2 * constant) = 1 * constant
Number newTwo = modInverse(s1); // we guess s2 is actually this
// check if s2 * constant = old value of s2
if (multiply(newTwo, smallest).equals(s2))
{
s2 = newTwo; // s2 changes to new value
smallest = 1; // rhs goes to 1
assert multiply(s1, s2).intValue() == 1;
}
}
else if (DMath.isInversable(s2))
{
Number newOne = modInverse(s2);
if (multiply(newOne, smallest).equals(s1))
{
s1 = newOne;
smallest = 1;
assert multiply(s1, s2).intValue() == 1;
}
}
else
{
return null;
}
}
}
// Now that we have two constants, figure out which one is
// the getter and which is the setter
boolean g = isGetterOrSetter(field, true, s1),
g2 = isGetterOrSetter(field, true, s2);
boolean inverse = false;
if (g == g2)
{
// both are getters
inverse = true;
g = isGetterOrSetter(field, false, s1);
g2 = isGetterOrSetter(field, false, s2);
}
if (g == g2)
{
// special case, when the guessted value is correct but
// negated, setters and getters will both contain -1,
// and the guess will be -1/-1.
if (s1.longValue() == -1L && s2.longValue() == -1L)
{
Pair p = new Pair();
p.field = field.getPoolField();
p.getter = s1;
p.setter = s2;
return p;
}
// both are also setters
}
else
{
Pair p = new Pair();
p.field = field.getPoolField();
if (g != inverse)
{
p.getter = s1;
p.setter = s2;
}
else
{
p.getter = s2;
p.setter = s1;
}
return p;
}
return null;
}
// figure out if value is a getter or setter
private boolean isGetterOrSetter(Field field, boolean getter, Number value)
{
FieldInfo fieldInfo = getFieldInfo(field);
Collection<Number> c;
if (getter)
{
c = fieldInfo.getters;
}
else
{
c = fieldInfo.setters;
}
if (c == null)
{
return false;
}
if (c.contains(value))
{
return true;
}
return false;
}
private void guess()
{
for (ClassFile cf : group.getClasses())
{
for (Field f : cf.getFields())
{
FieldInfo fieldInfo = getFieldInfo(f);
Collection<AssociatedConstant> col = fieldInfo.constants; // all constants in instructions associated with the field
if (col.isEmpty())
{
continue;
}
Type type = f.getType();
assert type.equals(Type.INT) || type.equals(Type.LONG);
Class typeOfField = type.equals(Type.INT) ? Integer.class : Long.class;
// filter collect constants of the correct type
Collection<AssociatedConstant> col2 = col.stream()
.filter(i -> i.value.getClass() == typeOfField)
.collect(Collectors.toList());
// filer out ones that have another field in the expression
List<Number> noOther = col2.stream()
.filter(i -> !i.other && !i.constant)
.map(i -> i.value)
.distinct()
.collect(Collectors.toList());
List<Number> other = col2.stream()
.filter(i -> i.other || i.constant)
.map(i -> i.value)
.collect(Collectors.toList());
other.addAll(noOther);
other = ImmutableSet.copyOf(other).asList();
// guess with constants not associated with other fields
Pair p = this.guess(f, noOther);
if (p == null)
{
p = this.guess(f, other); // fall back to all constants
}
// check that this guess doesn't increase constants
if (p != null && !fieldInfo.guessDecreasesConstants(p))
{
continue;
}
if (p != null)
{
pairs.add(p);
}
}
}
}
@Override
public void run(ClassGroup group)
{
this.group = group;
}
private void insertGetterSetterMuls(Encryption encr)
{
// after getfield insert imul * setter
// before setfield insert imul * getter
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
if (code == null)
{
continue;
}
Instructions ins = code.getInstructions();
List<Instruction> ilist = ins.getInstructions();
for (int i = 0; i < ilist.size(); ++i)
{
Instruction in = ilist.get(i);
if (in instanceof SetFieldInstruction)
{
SetFieldInstruction sfi = (SetFieldInstruction) in;
Field f = sfi.getMyField();
if (f == null)
{
continue;
}
Pair p = encr.getField(f.getPoolField());
if (p == null)
{
continue;
}
// insert push getter
// insert imul
if (p.getType() == Integer.class)
{
ilist.add(i++, new LDC(ins, (int) p.getter));
ilist.add(i++, new IMul(ins));
}
else if (p.getType() == Long.class)
{
ilist.add(i++, new LDC(ins, (long) p.getter));
ilist.add(i++, new LMul(ins));
}
else
{
throw new IllegalStateException();
}
}
else if (in instanceof GetFieldInstruction)
{
GetFieldInstruction sfi = (GetFieldInstruction) in;
Field f = sfi.getMyField();
if (f == null)
{
continue;
}
Pair p = encr.getField(f.getPoolField());
if (p == null)
{
continue;
}
// add after:
// push setter
// imul
if (p.getType() == Integer.class)
{
ilist.add(++i, new LDC(ins, (int) p.setter));
ilist.add(++i, new IMul(ins));
}
else if (p.getType() == Long.class)
{
ilist.add(++i, new LDC(ins, (long) p.setter));
ilist.add(++i, new LMul(ins));
}
else
{
throw new IllegalStateException();
}
}
}
}
}
}
public int runOnce()
{
group.buildClassGraph();
pairs.clear();
fieldInfo.clear();
execution = new Execution(group);
execution.addMethodContextVisitor(i -> findUses(i));
execution.addMethodContextVisitor(i -> findConstants(i));
execution.populateInitialMethods();
execution.run();
guess();
int i = 0;
Encryption encr = new Encryption();
for (Pair pair : pairs)
{
logger.debug("Processing {} getter {} setter {}", pair.field.getName(), pair.getter, pair.setter);
encr.addPair(pair);
encryption.addPair(pair); // sum total
++i;
}
logger.info("Done processing {}", i);
if (i > 0)
{
insertGetterSetterMuls(encr);
}
return i;
}
public void annotateEncryption()
{
for (ClassFile cf : group.getClasses())
{
for (Field f : cf.getFields())
{
Pair pair = encryption.getField(f.getPoolField());
if (pair == null)
{
continue;
}
if (pair.getter.longValue() == 1L)
{
continue;
}
String ename = pair.getType() == Long.class
? "longValue"
: "intValue";
f.getAnnotations().addAnnotation(DeobAnnotations.OBFUSCATED_GETTER, ename, pair.getter);
}
}
}
Encryption getEncryption()
{
return encryption;
}
}

View File

@@ -0,0 +1,401 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
import net.runelite.asm.attributes.code.instruction.types.GetFieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.BiPush;
import net.runelite.asm.attributes.code.instructions.IAdd;
import net.runelite.asm.attributes.code.instructions.IInc;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.ISub;
import net.runelite.asm.attributes.code.instructions.LAdd;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.attributes.code.instructions.LSub;
import net.runelite.asm.attributes.code.instructions.SiPush;
import net.runelite.asm.attributes.code.instructions.Swap;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.execution.VariableContext;
import net.runelite.asm.execution.Variables;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiplicationDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(MultiplicationDeobfuscator.class);
private ClassGroup group;
@Override
public void run(ClassGroup group)
{
this.group = group;
int i;
int count = 0;
while ((i = runOnce()) > 0)
{
logger.info("Replaced " + i + " constants");
count += i;
}
logger.info("Total changed " + count);
}
public static MultiplicationExpression parseExpression(InstructionContext ctx, Class want)
{
MultiplicationExpression me = new MultiplicationExpression();
if (ctx.getInstruction() instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) ctx.getInstruction();
// loading a variable
if (!lvt.store())
{
int idx = lvt.getVariableIndex(); // var index
Variables vars = ctx.getVariables(); // variables at time of execution
VariableContext vctx = vars.get(idx); // get the variable
if (vctx.getRead().size() == 1) // ?
{
InstructionContext storeCtx = vctx.getInstructionWhichStored(); // this is an istore
if (storeCtx.getInstruction() instanceof LVTInstruction)
{
// invoking funcs can put stuff in lvt
LVTInstruction storelvt = (LVTInstruction) storeCtx.getInstruction();
if (storelvt instanceof IInc)
throw new IllegalStateException();
assert storelvt.store();
InstructionContext pushed = storeCtx.getPops().get(0).getPushed();
return parseExpression(pushed, want);
}
}
}
}
if (ctx.getInstruction() instanceof PushConstantInstruction)
{
if (ctx.getInstruction() instanceof BiPush || ctx.getInstruction() instanceof SiPush)
{
throw new IllegalStateException();
}
me.instructions.add(ctx);
return me;
}
for (StackContext sctx : ctx.getPops())
{
if (ctx.getInstruction().getClass() == want)
{
if (!isOnlyPath(ctx, sctx))
continue;
}
InstructionContext i = sctx.getPushed();
// if this instruction is imul, look at pops
if (ctx.getInstruction().getClass() == want)
{
if (i.getInstruction() instanceof Swap)
{
logger.debug("Resolving swap");
Swap swap = (Swap) i.getInstruction();
sctx = swap.getOriginal(sctx);
i = sctx.getPushed();
}
if (i.getInstruction() instanceof PushConstantInstruction)
{
// bipush/sipush are always not obfuscated
if (i.getInstruction() instanceof BiPush || i.getInstruction() instanceof SiPush)
continue;
// a constant of imul
me.instructions.add(i);
}
else if (i.getInstruction().getClass() == want)
{
// chained imul, append to me
try
{
MultiplicationExpression other = parseExpression(i, want);
if (other.dupmagic != null)
{
assert me.dupmagic == null;
me.dupmagic = other.dupmagic;
}
me.instructions.addAll(other.instructions);
me.dupedInstructions.addAll(other.dupedInstructions);
me.subexpressions.addAll(other.subexpressions);
}
catch (IllegalStateException ex)
{
// this is ok? just don't include it?
}
}
else if (i.getInstruction() instanceof IAdd || i.getInstruction() instanceof ISub
|| i.getInstruction() instanceof LAdd || i.getInstruction() instanceof LSub)
{
// imul using result of iadd or isub. evaluate expression
try
{
MultiplicationExpression other = parseExpression(i, want);
assert other.dupmagic == null;
// subexpr
me.subexpressions.add(other);
}
catch (IllegalStateException ex)
{
assert me.subexpressions.isEmpty();
// subexpression is too complex. we can still simplify the top level though
}
}
else if (i.getInstruction() instanceof DupInstruction)
{
DupInstruction dup = (DupInstruction) i.getInstruction();
// 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().get(0); // what popped other side of dup. is this right?
if (otherCtxI.getInstruction().getClass() == want)
{
//assert otherCtxI.getInstruction() instanceof IMul;
InstructionContext pushConstant = otherCtxI.getPops().get(0).getPushed(); // other side of that imul
assert pushConstant.getInstruction() instanceof LDC;
me.dupmagic = pushConstant;
StackContext orig = dup.getOriginal(sctx); // original
try
{
MultiplicationExpression other = parseExpression(orig.getPushed(), want);
// 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
|| orig.getPushed().getInstruction() instanceof LAdd || orig.getPushed().getInstruction() instanceof LSub)
{
me.subexpressions.add(other);
}
else
{
assert other.dupmagic == null;
me.instructions.addAll(other.instructions);
me.dupedInstructions.addAll(other.instructions);
me.subexpressions.addAll(other.subexpressions);
}
}
catch (IllegalStateException ex)
{
assert me.subexpressions.isEmpty();
}
}
}
else if (i.getInstruction() instanceof GetFieldInstruction)
{
me.fieldInstructions.add(i);
// non constant, ignore
}
else
{
//System.out.println("imul pops something I don't know " + i.getInstruction());
}
}
// this is an iadd/sub
else if (ctx.getInstruction() instanceof IAdd || ctx.getInstruction() instanceof ISub
|| ctx.getInstruction() instanceof LAdd || ctx.getInstruction() instanceof LSub)
{
MultiplicationExpression other = parseExpression(i, want); // parse this side of the add/sub
me.subexpressions.add(other);
}
else
{
//System.out.println(ctx.getInstruction() + " pops something I dont know " + i.getInstruction());
}
}
if (me.instructions.isEmpty() && me.subexpressions.isEmpty())
throw new IllegalStateException();
return me;
}
// one = imul, two = other executeion of instruction of one, sctx = popped by imul
private static boolean ictxEqualsDir(InstructionContext one, InstructionContext two, StackContext sctx)
{
assert one.getInstruction() == two.getInstruction();
if (one.getInstruction() != two.getInstruction())
return false;
assert one.getPops().contains(sctx);
int i = one.getPops().indexOf(sctx);
StackContext theirsctx = two.getPops().get(i);
if (sctx.getPushed().getInstruction() != theirsctx.getPushed().getInstruction())
return false;
return true;
}
// ctx = imul, sctx = popped by imul
public static boolean isOnlyPath(InstructionContext ctx, StackContext sctx)
{
assert ctx.getInstruction() instanceof IMul || ctx.getInstruction() instanceof LMul;
Collection<InstructionContext> ins = ctx.getFrame().getMethodCtx().getInstructonContexts(ctx.getInstruction());
for (InstructionContext i : ins)
{
if (sctx == null)
{
if (!i.equals(ctx))
{
return false;
}
for (StackContext sctx2 : i.getPushes())
{
if (sctx2.getPopped().size() > 1)
{
return false;
}
}
}
else
{
// everything which pushed the parameters must be the same
if (!ictxEqualsDir(ctx, i, sctx))
{
return false;
}
// everything which pops the result must be the same
Instruction poppedIns = null;
for (StackContext s : i.getPushes())
for (InstructionContext i2 : s.getPopped())
{
if (poppedIns == null)
poppedIns = i2.getInstruction();
else if (poppedIns != i2.getInstruction())
return false;
}
}
}
return true;
}
private Set<Instruction> done = new HashSet<>();
private void visit(MethodContext ctx)
{
for (InstructionContext ictx : ctx.getInstructionContexts())
{
Instruction instruction = ictx.getInstruction();
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
{
continue;
}
MultiplicationExpression expression;
try
{
expression = parseExpression(ictx, instruction.getClass());
}
catch (IllegalStateException ex)
{
continue;
}
if (expression == null)
{
continue;
}
if (done.contains(instruction))
{
continue;
}
done.add(instruction);
assert instruction instanceof IMul || instruction instanceof LMul;
if (instruction instanceof IMul)
{
count += expression.simplify(1);
}
else if (instruction instanceof LMul)
{
count += expression.simplify(1L);
}
else
{
throw new IllegalStateException();
}
}
}
int count;
private int runOnce()
{
group.buildClassGraph();
count = 0;
Execution e = new Execution(group);
e.addMethodContextVisitor(m -> visit(m));
e.populateInitialMethods();
e.run();
return count;
}
}

View File

@@ -0,0 +1,134 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.Field;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.execution.InstructionContext;
public class MultiplicationExpression
{
List<InstructionContext> instructions = new ArrayList<>(), // push constant instructions that are being multiplied
dupedInstructions = new ArrayList<>(),
fieldInstructions = new ArrayList<>();
List<MultiplicationExpression> subexpressions = new ArrayList<>(); // for distributing, each subexpr is * by this
InstructionContext dupmagic; // inverse of what is distributed to subexpressions gets set here
int simplify(Number start)
{
int count = 0;
Number result = start;
// calculate result
for (InstructionContext i : instructions)
{
PushConstantInstruction pci = (PushConstantInstruction) i.getInstruction();
Number value = (Number) pci.getConstant();
result = DMath.multiply(result, value);
}
if (dupmagic != null)
{
// mul dupmagic by result of dup ins?
PushConstantInstruction pci = (PushConstantInstruction) dupmagic.getInstruction();
Number value = (Number) pci.getConstant();
for (InstructionContext ic : dupedInstructions)
{
PushConstantInstruction pci2 = (PushConstantInstruction) ic.getInstruction();
Number value2 = (Number) pci2.getConstant();
value = DMath.multiply(value, value2);
}
Instruction newIns = pci.setConstant(value);
assert newIns == (Instruction) pci;
}
// multiply subexpressions by result
if (!subexpressions.isEmpty())
{
for (MultiplicationExpression me : subexpressions)
{
count += me.simplify(result);
}
if (dupmagic != null)
{
PushConstantInstruction pci = (PushConstantInstruction) dupmagic.getInstruction();
Number value = (Number) pci.getConstant();
value = DMath.multiply(value, DMath.modInverse(result));
pci.setConstant(value);
}
// constant has been distributed, outer numbers all go to 1
if (result instanceof Long)
result = 1L;
else
result = 1;
}
// set result on ins
for (InstructionContext i : instructions)
{
PushConstantInstruction pci = (PushConstantInstruction) i.getInstruction();
Instruction newIns = pci.setConstant(result);
++count;
assert newIns == pci;
// rest of the results go to 1
if (result instanceof Long)
result = 1L;
else
result = 1;
}
return count;
}
public boolean hasFieldOtherThan(Field field)
{
for (InstructionContext i : this.fieldInstructions)
{
FieldInstruction fi = (FieldInstruction) i.getInstruction();
if (fi.getMyField() != null && fi.getMyField() != field)
return true;
}
for (MultiplicationExpression ex : this.subexpressions)
if (ex.hasFieldOtherThan(field))
return true;
return false;
}
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.util.List;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiplyOneDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(MultiplyOneDeobfuscator.class);
private final boolean onlyConstants;
private int count;
public MultiplyOneDeobfuscator(boolean onlyConstants)
{
this.onlyConstants = onlyConstants;
}
private void visit(MethodContext mctx)
{
for (InstructionContext ictx : mctx.getInstructionContexts())
{
Instruction instruction = ictx.getInstruction();
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
{
continue;
}
Instructions ins = ictx.getInstruction().getInstructions();
if (ins == null)
{
continue;
}
List<Instruction> ilist = ins.getInstructions();
if (!ilist.contains(ictx.getInstruction()))
{
continue; // already done
}
StackContext one = ictx.getPops().get(0);
StackContext two = ictx.getPops().get(1);
StackContext other = null;
int removeIdx = -1;
if (one.getPushed().getInstruction() instanceof PushConstantInstruction
&& DMath.equals((Number) ((PushConstantInstruction) one.getPushed().getInstruction()).getConstant(), 1))
{
removeIdx = 0;
other = two;
}
else if (two.getPushed().getInstruction() instanceof PushConstantInstruction
&& DMath.equals((Number) ((PushConstantInstruction) two.getPushed().getInstruction()).getConstant(), 1))
{
removeIdx = 1;
other = one;
}
if (removeIdx == -1)
{
continue;
}
if (onlyConstants && !(other.getPushed().getInstruction() instanceof PushConstantInstruction))
{
continue;
}
if (!MultiplicationDeobfuscator.isOnlyPath(ictx, removeIdx == 0 ? one : two))
{
continue;
}
ictx.removeStack(removeIdx); // remove 1
ins.remove(instruction); // remove mul
++count;
}
}
@Override
public void run(ClassGroup group)
{
Execution e = new Execution(group);
e.addMethodContextVisitor(i -> visit(i));
e.populateInitialMethods();
e.run();
logger.info("Removed " + count + " 1 multiplications");
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import java.util.List;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.LMul;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiplyZeroDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(MultiplyZeroDeobfuscator.class);
private int count;
private void visit(MethodContext mctx)
{
for (InstructionContext ictx : mctx.getInstructionContexts())
{
Instruction instruction = ictx.getInstruction();
Instructions ins = instruction.getInstructions();
if (ins == null)
{
continue;
}
if (!(instruction instanceof IMul) && !(instruction instanceof LMul))
{
continue;
}
List<Instruction> ilist = ins.getInstructions();
StackContext one = ictx.getPops().get(0);
StackContext two = ictx.getPops().get(1);
Instruction ione = one.getPushed().getInstruction(),
itwo = two.getPushed().getInstruction();
boolean remove = false;
if (ione instanceof PushConstantInstruction)
{
PushConstantInstruction pci = (PushConstantInstruction) ione;
Number value = (Number) pci.getConstant();
if (DMath.equals(value, 0))
{
remove = true;
}
}
if (itwo instanceof PushConstantInstruction)
{
PushConstantInstruction pci = (PushConstantInstruction) itwo;
Number value = (Number) pci.getConstant();
if (DMath.equals(value, 0))
{
remove = true;
}
}
if (remove == false)
{
continue;
}
if (!ilist.contains(instruction))
{
continue; // already done
}
if (!MultiplicationDeobfuscator.isOnlyPath(ictx, null))
{
continue;
}
// remove both, remove imul, push 0
ictx.removeStack(1);
ictx.removeStack(0);
if (instruction instanceof IMul)
{
ins.replace(instruction, new LDC(ins, 0));
}
else if (instruction instanceof LMul)
{
ins.replace(instruction, new LDC(ins, 0L));
}
else
{
throw new IllegalStateException();
}
++count;
}
}
@Override
public void run(ClassGroup group)
{
Execution e = new Execution(group);
e.addMethodContextVisitor(i -> visit(i));
e.populateInitialMethods();
e.run();
logger.info("Removed " + count + " 0 multiplications");
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.arithmetic;
import net.runelite.asm.pool.Field;
class Pair
{
Field field;
Number getter, setter;
public Pair()
{
}
public Pair(Pair one, Pair two)
{
assert one.getType() == two.getType();
assert one.field != null;
assert two.field != null;
assert one.field.equals(two.field);
getter = DMath.multiply(one.getter, two.getter);
setter = DMath.multiply(one.setter, two.setter);
field = one.field;
}
public Class getType()
{
assert getter.getClass() == setter.getClass();
return getter.getClass();
}
}

View File

@@ -0,0 +1,164 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.cfg;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.attributes.code.Instruction;
public class Block
{
private int id = -1;
private boolean jumptarget; // block is unconditionally jumped to
/**
* blocks which jump here
*/
private final List<Block> prev = new ArrayList<>();
/**
* blocks which this jumps to
*/
private final List<Block> next = new ArrayList<>();
/**
* block which flows directly into this block
*/
private Block flowsFrom;
/**
* block which this directly flows into
*/
private Block flowsInto;
/**
* instructions in this block
*/
private final List<Instruction> instructions = new ArrayList<>();
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public boolean isJumptarget()
{
return jumptarget;
}
public void setJumptarget(boolean jumptarget)
{
this.jumptarget = jumptarget;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("Block ID ").append(id).append("\n");
if (flowsFrom != null)
{
sb.append(" flows from ").append(flowsFrom.id).append("\n");
}
for (Instruction i : instructions)
{
sb.append(" ").append(i.toString()).append("\n");
}
if (flowsInto != null)
{
sb.append(" flows into ").append(flowsInto.id).append("\n");
}
sb.append("\n");
return sb.toString();
}
public void addInstruction(int idx, Instruction i)
{
assert !instructions.contains(i);
instructions.add(idx, i);
}
public void addInstruction(Instruction i)
{
assert !instructions.contains(i);
instructions.add(i);
}
public List<Instruction> getInstructions()
{
return instructions;
}
public void addPrev(Block block)
{
if (!prev.contains(block))
{
prev.add(block);
}
}
public List<Block> getPrev()
{
return prev;
}
public void addNext(Block block)
{
if (!next.contains(block))
{
next.add(block);
}
}
public List<Block> getNext()
{
return next;
}
public Block getFlowsFrom()
{
return flowsFrom;
}
public void setFlowsFrom(Block flowsFrom)
{
this.flowsFrom = flowsFrom;
}
public Block getFlowsInto()
{
return flowsInto;
}
public void setFlowsInto(Block flowsInto)
{
this.flowsInto = flowsInto;
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.cfg;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Exception;
import net.runelite.asm.attributes.code.Exceptions;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ControlFlowDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(ControlFlowDeobfuscator.class);
private int insertedJump;
private int placedBlocks;
private int removedJumps;
@Override
public void run(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
if (code == null || !code.getExceptions().getExceptions().isEmpty())
{
continue;
}
split(code);
run(code);
runJumpLabel(code);
}
}
logger.info("Inserted {} jumps, reordered {} blocks, and removed {} jumps. jump delta {}",
insertedJump, placedBlocks, removedJumps, insertedJump - removedJumps);
}
/**
* Add gotos at the end of blocks without terminal instructions
*
* @param code
*/
private void split(Code code)
{
Instructions ins = code.getInstructions();
Exceptions exceptions = code.getExceptions();
ControlFlowGraph graph = new ControlFlowGraph.Builder().build(code);
List<Exception> exc = new ArrayList<>(exceptions.getExceptions());
exceptions.clear(); // Must clear this before ins.clear() runs
ins.clear();
// insert jumps where blocks flow into others
for (Block block : graph.getBlocks())
{
if (block.getFlowsInto() == null)
{
continue;
}
Block into = block.getFlowsInto();
assert into.getFlowsFrom() == block;
Instruction first = into.getInstructions().get(0);
Label label;
if (!(first instanceof Label))
{
label = new Label(null);
into.addInstruction(0, label);
}
else
{
label = (Label) first;
}
Goto g = new Goto(null, label);
block.addInstruction(g);
block.setFlowsInto(null);
into.setFlowsFrom(null);
++insertedJump;
}
// Readd instructions from modified blocks
for (Block block : graph.getBlocks())
{
for (Instruction i : block.getInstructions())
{
assert i.getInstructions() == null;
i.setInstructions(ins); // I shouldn't have to do this here
ins.addInstruction(i);
}
}
// Readd exceptions
for (Exception ex : exc)
{
exceptions.add(ex);
}
}
private int compareBlock(Block o1, Block o2)
{
// higher numbers have the lowest priority
if (o1.isJumptarget() && !o2.isJumptarget())
{
return -1;
}
if (o2.isJumptarget() && !o1.isJumptarget())
{
return 1;
}
return 0;
}
private void run(Code code)
{
Instructions ins = code.getInstructions();
Exceptions exceptions = code.getExceptions();
ControlFlowGraph graph = new ControlFlowGraph.Builder().build(code);
for (Block block : graph.getBlocks())
{
assert block.getFlowsFrom() == null;
assert block.getFlowsInto() == null;
}
if (logger.isDebugEnabled()) // graph.toString() is expensive
{
logger.debug(graph.toString());
}
List<Exception> originalExceptions = new ArrayList<>(exceptions.getExceptions());
// Clear existing exceptions and instructions as we are going to
// rebuild them
exceptions.clear();
ins.clear();
List<Block> done = new ArrayList<>();
Queue<Block> queue = new PriorityQueue<>(this::compareBlock);
// add initial code block
queue.add(graph.getHead());
while (!queue.isEmpty())
{
Block block = queue.remove();
if (done.contains(block))
{
continue;
}
done.add(block);
++placedBlocks;
logger.debug("Placed block {}", block.getId());
List<Block> next = block.getNext();
if (next.isEmpty() == false)
{
// jumps are added in order their instructions are reached by ControlFlowGraph,
// so the last jump is the goto.
//
// removing this line causes the priority queue (due to implementation detail on how
// it handles objects with equal priority) to try to optimize for block closeness
// (how close blocks which are neighbors are to each other in bytecode).
// I get a jump delta of ~+14k with this on 143, vs ~-47k when priotiziing optimizing
// out jumps. I can't tell which is better.
next.get(next.size() - 1).setJumptarget(true);
}
// add next reachable blocks
for (Block bl : next)
{
queue.add(bl);
}
for (Instruction i : block.getInstructions())
{
assert i.getInstructions() == null;
i.setInstructions(ins); // I shouldn't have to do this here
ins.addInstruction(i);
}
}
}
/**
* remove jumps followed immediately by the label they are jumping to
*
* @param code
*/
private void runJumpLabel(Code code)
{
Instructions ins = code.getInstructions();
List<Instruction> instructions = ins.getInstructions();
for (int i = 0; i < instructions.size() - 1; ++i)
{
Instruction i1 = instructions.get(i),
i2 = instructions.get(i + 1);
if (!(i1 instanceof Goto))
{
continue;
}
Goto g = (Goto) i1;
assert g.getJumps().size() == 1;
if (g.getJumps().get(0) != i2)
{
continue;
}
ins.remove(i1); // remove jump
++removedJumps;
// i now points to i2, so next loop we go to next instruction
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.cfg;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
public class ControlFlowGraph
{
private Map<Label, Block> blocks = new HashMap<>();
private List<Block> allBlocks = new ArrayList<>();
private final Block head;
public ControlFlowGraph(Block head)
{
this.head = head;
}
public Block getBlock(Label label)
{
return blocks.get(label);
}
public Collection<Block> getBlocks()
{
return allBlocks;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
for (Block b : allBlocks)
{
sb.append(b.toString());
}
return sb.toString();
}
public Block getHead()
{
return head;
}
public static class Builder
{
private final Map<Label, Block> blocks = new HashMap<>();
private final List<Block> allBlocks = new ArrayList<>();
public ControlFlowGraph build(Code code)
{
int id = 0;
Block head = new Block(),
cur = head;
allBlocks.add(head);
for (Instruction i : code.getInstructions().getInstructions())
{
if (i instanceof Label)
{
// blocks always begin at labels, so create initial blocks
Block block = new Block();
blocks.put((Label) i, block);
allBlocks.add(block);
}
}
for (Instruction i : code.getInstructions().getInstructions())
{
if (i instanceof Label)
{
Block next = blocks.get((Label) i);
assert next != null;
if (next.getId() == -1)
{
next.setId(id++);
}
if (next != cur)
{
Instruction last = cur.getInstructions().isEmpty()
? null
: cur.getInstructions().get(cur.getInstructions().size() - 1);
if (last == null || !last.isTerminal())
{
assert next.getFlowsFrom() == null;
assert cur.getFlowsInto() == null;
// previous block flows directly into next
next.setFlowsFrom(cur);
cur.setFlowsInto(next);
}
cur = next;
}
}
cur.addInstruction(i);
if (i instanceof JumpingInstruction)
{
JumpingInstruction ji = (JumpingInstruction) i;
for (Label l : ji.getJumps())
{
Block next = blocks.get(l);
if (next.getId() == -1)
{
next.setId(id++);
}
cur.addNext(next);
next.addPrev(cur);
}
}
}
assert head != null : "no instructions in code";
assert head.getFlowsFrom() == null;
ControlFlowGraph cfg = new ControlFlowGraph(head);
cfg.blocks = blocks;
cfg.allBlocks = allBlocks;
return cfg;
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.constparam;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
class ConstantMethodParameter
{
List<Method> methods; // methods this is a parameter for
int paramIndex;
int lvtIndex;
List<Number> values = new ArrayList<>(); // possible values for the parameter
List<Instruction> operations = new ArrayList<>(); // conditional jumps based on the parameter
Boolean result; // result of the jumps (branch taken or not)
boolean invalid;
@Override
public int hashCode()
{
int hash = 3;
hash = 47 * hash + Objects.hashCode(this.methods);
hash = 47 * hash + this.lvtIndex;
return hash;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final ConstantMethodParameter other = (ConstantMethodParameter) obj;
if (!Objects.equals(this.methods, other.methods))
{
return false;
}
if (this.lvtIndex != other.lvtIndex)
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,483 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.constparam;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.annotation.Element;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.ComparisonInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.If0;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConstantParameter implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(ConstantParameter.class);
private Map<ConstantMethodParameter, ConstantMethodParameter> parameters = new HashMap<>();
private Multimap<Method, ConstantMethodParameter> mparams = HashMultimap.create();
private void checkMethodsAreConsistent(List<Method> methods)
{
Method prev = null;
for (Method m : methods)
{
if (prev != null)
{
assert prev.getDescriptor().equals(m.getDescriptor());
assert prev.isStatic() == m.isStatic();
}
prev = m;
}
}
private ConstantMethodParameter getCMPFor(List<Method> methods, int paramIndex, int lvtIndex)
{
ConstantMethodParameter cmp = new ConstantMethodParameter();
cmp.methods = methods;
cmp.paramIndex = paramIndex;
cmp.lvtIndex = lvtIndex;
ConstantMethodParameter exists = parameters.get(cmp);
if (exists != null)
{
// existing mparams for each method of methods can have different 'methods'
// due to invokespecial on virtual methods.
return exists;
}
parameters.put(cmp, cmp);
for (Method m : methods)
{
mparams.put(m, cmp);
}
return cmp;
}
// find constant values passed as parameters
private void findConstantParameter(List<Method> methods, InstructionContext invokeCtx)
{
checkMethodsAreConsistent(methods);
Method method = methods.get(0); // all methods must have the same signature etc
int offset = method.isStatic() ? 0 : 1;
List<StackContext> pops = invokeCtx.getPops();
outer:
// object is popped first, then param 1, 2, 3, etc. double and long take two slots.
for (int lvtOffset = offset, parameterIndex = 0;
parameterIndex < method.getDescriptor().size();
lvtOffset += method.getDescriptor().getTypeOfArg(parameterIndex++).getSize())
{
// get(0) == first thing popped which is the last parameter,
// get(descriptor.size() - 1) == first parameter
StackContext ctx = pops.get(method.getDescriptor().size() - 1 - parameterIndex);
ConstantMethodParameter cmp = getCMPFor(methods, parameterIndex, lvtOffset);
if (cmp.invalid)
{
continue;
}
if (ctx.getPushed().getInstruction() instanceof PushConstantInstruction)
{
PushConstantInstruction pc = (PushConstantInstruction) ctx.getPushed().getInstruction();
if (!(pc.getConstant() instanceof Number))
{
cmp.invalid = true;
continue;
}
Number number = (Number) pc.getConstant();
if (!cmp.values.contains(number))
{
cmp.values.add((Number) pc.getConstant());
}
}
else
{
cmp.invalid = true;
}
}
}
// find constant valuess passed to parameters
private void findParameters(InstructionContext ins)
{
if (!(ins.getInstruction() instanceof InvokeInstruction))
{
return;
}
List<Method> methods = ((InvokeInstruction) ins.getInstruction()).getMethods();
if (methods.isEmpty())
{
return;
}
findConstantParameter(methods, ins);
}
private List<ConstantMethodParameter> findParametersForMethod(Method m)
{
Collection<ConstantMethodParameter> c = mparams.get(m);
if (c == null)
{
return Collections.EMPTY_LIST;
}
return new ArrayList<>(c);
}
// compare known values against a jump. also invalidates constant param
// if lvt is reassigned or a comparison is made against a non constant
private void findDeadParameters(MethodContext mctx)
{
mctx.getInstructionContexts().forEach(this::findDeadParameters);
}
private void findDeadParameters(InstructionContext ins)
{
List<ConstantMethodParameter> parameters = this.findParametersForMethod(ins.getFrame().getMethod());
for (ConstantMethodParameter parameter : parameters)
{
int lvtIndex = parameter.lvtIndex;
if (parameter.invalid)
{
continue;
}
if (ins.getInstruction() instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) ins.getInstruction();
if (lvt.getVariableIndex() != lvtIndex)
{
continue;
}
if (lvt.store() || ins.getInstruction().getType() == InstructionType.IINC)
{
parameter.invalid = true;
continue; // value changes at some point, parameter is used
}
// check what pops the parameter is a comparison
assert ins.getPushes().size() == 1;
StackContext sctx = ins.getPushes().get(0);
if (sctx.getPopped().size() != 1
|| !(sctx.getPopped().get(0).getInstruction() instanceof ComparisonInstruction))
{
parameter.invalid = true;
continue;
}
}
if (!(ins.getInstruction() instanceof ComparisonInstruction))
{
continue;
}
// assume that this will always be variable index #paramIndex comp with a constant.
ComparisonInstruction comp = (ComparisonInstruction) ins.getInstruction();
StackContext one, two = null;
if (comp instanceof If0)
{
one = ins.getPops().get(0);
}
else if (comp instanceof If)
{
one = ins.getPops().get(0);
two = ins.getPops().get(1);
}
else
{
throw new RuntimeException("Unknown comp ins");
}
// find if one is a lvt ins
LVTInstruction lvt = null;
StackContext other = null;
if (one.getPushed().getInstruction() instanceof LVTInstruction)
{
lvt = (LVTInstruction) one.getPushed().getInstruction();
other = two;
}
else if (two != null && two.getPushed().getInstruction() instanceof LVTInstruction)
{
lvt = (LVTInstruction) two.getPushed().getInstruction();
other = one;
}
assert lvt == null || !lvt.store();
if (lvt == null || lvt.getVariableIndex() != lvtIndex)
{
continue;
}
Number otherValue = null;
if (two != null) // two is null for if0
{
if (!(other.getPushed().getInstruction() instanceof PushConstantInstruction))
{
parameter.invalid = true;
continue;
}
PushConstantInstruction pc = (PushConstantInstruction) other.getPushed().getInstruction();
otherValue = (Number) pc.getConstant();
}
for (Number value : parameter.values)
{
// the result of the comparison doesn't matter, only that it always goes the same direction for every invocation
boolean result = doLogicalComparison(value, comp, otherValue);
// XXX this should check that the particular if always takes the same path,
// not that all ifs for a specific parameter always take the same path
if (parameter.result != null && parameter.result != result)
{
parameter.invalid = true;
}
else
{
parameter.operations.add(ins.getInstruction());
parameter.result = result;
}
}
}
}
private boolean doLogicalComparison(Number value, ComparisonInstruction comparison, Number otherValue)
{
Instruction ins = (Instruction) comparison;
assert (comparison instanceof If0) == (otherValue == null);
switch (ins.getType())
{
case IFEQ:
return value.equals(0);
case IFNE:
return !value.equals(0);
case IFLT:
return value.intValue() < 0;
case IFGE:
return value.intValue() >= 0;
case IFGT:
return value.intValue() > 0;
case IFLE:
return value.intValue() <= 0;
case IF_ICMPEQ:
return value.equals(otherValue);
case IF_ICMPNE:
return !value.equals(otherValue);
case IF_ICMPLT:
return value.intValue() < otherValue.intValue();
case IF_ICMPGE:
return value.intValue() >= otherValue.intValue();
case IF_ICMPGT:
return value.intValue() > otherValue.intValue();
case IF_ICMPLE:
return value.intValue() <= otherValue.intValue();
default:
throw new RuntimeException("Unknown constant comparison instructuction");
}
}
// remove logically dead comparisons
private int removeDeadOperations(MethodContext mctx)
{
int count = 0;
for (ConstantMethodParameter cmp : parameters.values())
{
if (cmp.invalid)
{
continue;
}
if (!cmp.methods.contains(mctx.getMethod()))
{
continue;
}
// only annotate garbage value of last param
if (cmp.paramIndex + 1 == mctx.getMethod().getDescriptor().size())
{
annotateObfuscatedSignature(cmp);
}
for (Instruction ins : cmp.operations) // comparisons
{
if (ins.getInstructions() == null || ins.getInstructions().getCode().getMethod() != mctx.getMethod())
{
continue;
}
InstructionContext ctx = mctx.getInstructonContexts(ins).toArray(new InstructionContext[0])[0];
boolean branch = cmp.result; // branch that is always taken
if (ins.getInstructions() == null)
{
continue; // ins already removed?
}
Instructions instructions = ins.getInstructions();
// remove the if
if (ctx.getInstruction() instanceof If)
{
ctx.removeStack(1);
}
ctx.removeStack(0);
int idx = instructions.getInstructions().indexOf(ins);
if (idx == -1)
{
continue; // already removed?
}
++count;
Instruction to;
if (branch)
{
JumpingInstruction jumpIns = (JumpingInstruction) ins;
assert jumpIns.getJumps().size() == 1;
to = jumpIns.getJumps().get(0);
}
else
{
// just go to next instruction
to = instructions.getInstructions().get(idx + 1);
}
assert to.getInstructions() == instructions;
assert ins != to;
assert instructions.getInstructions().contains(to);
instructions.remove(ins);
assert instructions.getInstructions().contains(to);
if (branch)
{
Goto gotoins = new Goto(instructions, instructions.createLabelFor(to));
// insert goto
instructions.getInstructions().add(idx, gotoins);
}
}
}
return count;
}
private void annotateObfuscatedSignature(ConstantMethodParameter parameter)
{
for (Method m : parameter.methods)
{
Object value = parameter.values.get(0);
Annotations annotations = m.getAnnotations();
Annotation obfuscatedSignature = annotations.find(DeobAnnotations.OBFUSCATED_SIGNATURE);
if (obfuscatedSignature != null && obfuscatedSignature.getElements().size() == 2)
{
// already annotated
continue;
}
if (obfuscatedSignature == null)
{
obfuscatedSignature = annotations.addAnnotation(DeobAnnotations.OBFUSCATED_SIGNATURE, "signature", m.getDescriptor().toString());
}
// Add garbage value
Element element = new Element(obfuscatedSignature);
element.setName("garbageValue");
element.setValue(value.toString());
obfuscatedSignature.addElement(element);
}
}
private int count;
@Override
public void run(ClassGroup group)
{
Execution execution = new Execution(group);
execution.addExecutionVisitor(i -> findParameters(i));
execution.populateInitialMethods();
execution.run();
execution = new Execution(group);
execution.addMethodContextVisitor(mc -> findDeadParameters(mc));
execution.populateInitialMethods();
execution.run();
execution = new Execution(group);
execution.addMethodContextVisitor(m -> count += removeDeadOperations(m));
execution.populateInitialMethods();
execution.run();
logger.info("Removed {} logically dead conditional jumps", count);
}
}

View File

@@ -0,0 +1,544 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.exprargorder;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import static net.runelite.asm.attributes.code.InstructionType.IADD;
import static net.runelite.asm.attributes.code.InstructionType.IF_ACMPEQ;
import static net.runelite.asm.attributes.code.InstructionType.IF_ACMPNE;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPEQ;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPNE;
import static net.runelite.asm.attributes.code.InstructionType.IMUL;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.AConstNull;
import net.runelite.asm.attributes.code.instructions.IAdd;
import net.runelite.asm.attributes.code.instructions.IInc;
import net.runelite.asm.attributes.code.instructions.IMul;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.IfACmpEq;
import net.runelite.asm.attributes.code.instructions.IfACmpNe;
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
import net.runelite.asm.attributes.code.instructions.IfICmpNe;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ExprArgOrder implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(ExprArgOrder.class);
private final List<Instruction> exprIns = new ArrayList<>();
private final Map<Instruction, Expression> exprs = new HashMap<>();
private int count;
static boolean isCommutative(InstructionType type)
{
switch (type)
{
case IADD:
case IMUL:
case IF_ICMPEQ:
case IF_ICMPNE:
case IF_ACMPEQ:
case IF_ACMPNE:
return true;
default:
return false;
}
}
private Instruction newInstruction(Instructions ins, Instruction old, InstructionType type)
{
assert isCommutative(type) : "type is " + type;
switch (type)
{
case IADD:
return new IAdd(ins);
case IMUL:
return new IMul(ins);
case IF_ICMPEQ:
{
If i = (If) old;
return new IfICmpEq(ins, i.getJumps().get(0));
}
case IF_ICMPNE:
{
If i = (If) old;
return new IfICmpNe(ins, i.getJumps().get(0));
}
case IF_ACMPEQ:
{
If i = (If) old;
return new IfACmpEq(ins, i.getJumps().get(0));
}
case IF_ACMPNE:
{
If i = (If) old;
return new IfACmpNe(ins, i.getJumps().get(0));
}
default:
throw new IllegalStateException();
}
}
private void parseExpr(Expression expr, InstructionContext ctx)
{
InstructionType type = ctx.getInstruction().getType();
List<StackContext> pops = ctx.getPops();
for (StackContext sctx : pops)
{
InstructionContext i = sctx.getPushed();
if (isCommutative(type) && i.getInstruction().getType() == type)
{
parseExpr(expr, i);
}
else if (isCommutative(type))
{
Expression sub = new Expression(i);
parseExpr(sub, i);
expr.addComExpr(sub);
}
else
{
Expression sub = new Expression(i);
parseExpr(sub, i);
expr.addExpr(sub);
}
exprIns.remove(i.getInstruction());
exprs.remove(i.getInstruction()); // remove sub expr
}
}
private void visit(InstructionContext ctx)
{
Instruction ins = ctx.getInstruction();
if (ins instanceof IAdd || ins instanceof IMul
|| ins instanceof IfICmpEq || ins instanceof IfICmpNe
|| ins instanceof IfACmpEq || ins instanceof IfACmpNe)
{
Expression expression = new Expression(ctx);
parseExpr(expression, ctx);
if (!exprs.containsKey(ins))
{
exprIns.add(ins);
exprs.put(ins, expression);
}
}
}
private boolean canRemove(MethodContext mctx, Instructions ins, Instruction i)
{
Set<InstructionContext> ctxs = new HashSet<>(mctx.getInstructonContexts(i));
if (!alwaysPoppedBySameInstruction(ctxs, i) || !alwaysPopsFromSameInstructions(ctxs, i))
{
return false;
}
if (i instanceof InvokeInstruction)
{
// don't ever order lhs/rhs if an invoke is involved?
// func1() + func2() vs func2() + func1() is not the same thing
return false;
}
int idx = ins.getInstructions().indexOf(i);
if (idx == -1)
{
return false;
}
for (InstructionContext ictx : ctxs)
{
for (StackContext sctx : ictx.getPops())
{
Instruction pushed = sctx.getPushed().getInstruction();
int idx2 = ins.getInstructions().indexOf(pushed);
if (idx2 == -1)
{
return false;
}
assert idx > idx2;
// If there is a lvt store (incl iinc!) between the two
// instructions, we can't move them
for (int j = idx2; j <= idx; ++j)
{
Instruction i2 = ins.getInstructions().get(j);
if (i2 instanceof LVTInstruction)
{
if (((LVTInstruction) i2).store())
{
return false;
}
}
if (i2 instanceof IInc)
{
return false;
}
}
if (!canRemove(mctx, ins, pushed))
{
return false;
}
}
}
return true;
}
private void remove(Instructions ins, InstructionContext ic)
{
ins.remove(ic.getInstruction());
for (StackContext sc : ic.getPops())
{
assert sc.getPopped().size() == 1;
remove(ins, sc.getPushed());
}
}
private void insert(Instructions ins, InstructionContext ic, Instruction before)
{
Instruction i = ic.getInstruction();
assert i.getInstructions() == null;
int idx = ins.getInstructions().indexOf(before);
assert idx != -1;
i.setInstructions(ins);
ins.addInstruction(idx, i);
}
public static int hash(Method method, InstructionContext ic)
{
MessageDigest sha256;
try
{
sha256 = MessageDigest.getInstance("SHA-256");
}
catch (NoSuchAlgorithmException ex)
{
throw new RuntimeException(ex);
}
hash(method, sha256, ic);
byte[] res = sha256.digest();
return Ints.fromByteArray(res);
}
private static void hash(Method method, MessageDigest sha256, InstructionContext ic)
{
Instruction i = ic.getInstruction();
// this relies on all push constants are converted to ldc..
sha256.update((byte) i.getType().getCode());
if (i instanceof PushConstantInstruction)
{
PushConstantInstruction pci = (PushConstantInstruction) i;
Object constant = pci.getConstant();
if (constant instanceof Number)
{
long l = ((Number) constant).longValue();
sha256.update(Longs.toByteArray(l));
}
}
else if (i instanceof LVTInstruction)
{
int idx = ((LVTInstruction) i).getVariableIndex();
Signature signature = method.getDescriptor();
int lvt = method.isStatic() ? 0 : 1;
for (Type type : signature.getArguments())
{
if (idx == lvt)
{
// Accessing a method parameter
sha256.update(Ints.toByteArray(idx));
break;
}
lvt += type.getSize();
}
}
for (StackContext sctx : ic.getPops())
{
hash(method, sha256, sctx.getPushed());
}
}
private boolean alwaysPopsFromSameInstructions(Set<InstructionContext> instructonContexts, Instruction i)
{
InstructionContext ictx = instructonContexts.iterator().next();
for (InstructionContext i2 : instructonContexts)
{
if (!i2.equals(ictx))
{
// this instruction doesn't always pop the same thing
return false;
}
}
return true;
}
private boolean alwaysPoppedBySameInstruction(Set<InstructionContext> instructonContexts, Instruction i)
{
Set<Instruction> c = new HashSet<>();
for (InstructionContext i2 : instructonContexts)
{
i2.getPushes().stream()
.flatMap(sctx -> sctx.getPopped().stream())
.map(ic -> ic.getInstruction())
.forEach(i3 -> c.add(i3));
}
return c.size() <= 1;
}
public static int compare(Method method, InstructionType type, InstructionContext ic1, InstructionContext ic2)
{
Instruction i1 = ic1.getInstruction();
Instruction i2 = ic2.getInstruction();
if (type == IF_ICMPEQ || type == IF_ICMPNE
|| type == IADD || type == IMUL)
{
if (!(i1 instanceof PushConstantInstruction)
&& (i2 instanceof PushConstantInstruction))
{
return 1;
}
if (i1 instanceof PushConstantInstruction
&& !(i2 instanceof PushConstantInstruction))
{
return -1;
}
}
if (type == IF_ACMPEQ || type == IF_ACMPNE)
{
if (!(i1 instanceof AConstNull)
&& (i2 instanceof AConstNull))
{
return 1;
}
if (i1 instanceof AConstNull
&& !(i2 instanceof AConstNull))
{
return -1;
}
}
int hash1 = hash(method, ic1);
int hash2 = hash(method, ic2);
if (hash1 == hash2)
{
logger.debug("Unable to differentiate {} from {}", ic1, ic2);
}
return Integer.compare(hash1, hash2);
}
public static int compare(Method method, InstructionType type, Expression expr1, Expression expr2)
{
Instruction i1 = expr1.getHead().getInstruction();
Instruction i2 = expr2.getHead().getInstruction();
if (type == IF_ICMPEQ || type == IF_ICMPNE
|| type == IADD || type == IMUL)
{
if (!(i1 instanceof PushConstantInstruction)
&& (i2 instanceof PushConstantInstruction))
{
return 1;
}
if (i1 instanceof PushConstantInstruction
&& !(i2 instanceof PushConstantInstruction))
{
return -1;
}
}
if (type == IF_ACMPEQ || type == IF_ACMPNE)
{
if (!(i1 instanceof AConstNull)
&& (i2 instanceof AConstNull))
{
return 1;
}
if (i1 instanceof AConstNull
&& !(i2 instanceof AConstNull))
{
return -1;
}
}
int hash1 = hash(method, expr1.getHead());
int hash2 = hash(method, expr2.getHead());
if (hash1 == hash2)
{
logger.debug("Unable to differentiate {} from {}", expr1.getHead(), expr2.getHead());
}
return Integer.compare(hash1, hash2);
}
private void insert(Instructions ins, Instruction next, Expression expression)
{
int count = 0;
if (expression.sortedExprs != null)
{
for (Expression sub : expression.sortedExprs)
{
if (count == 2)
{
Instruction newOp;
if (isCommutative(expression.getType()))
{
// there might be >2 sortedExprs so we can't reuse instructions
newOp = newInstruction(ins, expression.getHead().getInstruction(), expression.getType());
}
else
{
newOp = expression.getHead().getInstruction();
assert newOp.getInstructions() == null;
newOp.setInstructions(ins);
}
int idx = ins.getInstructions().indexOf(next);
assert idx != -1;
ins.addInstruction(idx, newOp);
count = 1;
}
insert(ins, next, sub);
++count;
}
}
List<Expression> reverseExpr = new ArrayList<>(expression.getExprs());
Collections.reverse(reverseExpr);
for (Expression sub : reverseExpr)
{
insert(ins, next, sub);
}
insert(ins, expression.getHead(), next);
}
private void visit(MethodContext ctx)
{
Instructions ins = ctx.getMethod().getCode().getInstructions();
for (Instruction i : exprIns)
{
Expression expression = exprs.get(i);
assert expression != null;
if (!canRemove(ctx, ins, i))
{
continue;
}
int idx = ins.getInstructions().indexOf(expression.getHead().getInstruction());
assert idx != -1;
// get next instruction
Instruction next = ins.getInstructions().get(idx + 1);;
// remove expression
remove(ins, expression.getHead());
// sort expression
expression.sort(ctx);
// insert expression
insert(ins, next, expression);
++count;
}
exprIns.clear();
exprs.clear();
}
@Override
public void run(ClassGroup group)
{
Execution execution = new Execution(group);
execution.addExecutionVisitor(i -> visit(i));
execution.addMethodContextVisitor(i -> visit(i));
execution.populateInitialMethods();
execution.run();
logger.info("Reordered {} expressions", count);
}
}

View File

@@ -0,0 +1,100 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.exprargorder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.MethodContext;
public class Expression
{
private final InstructionContext head;
private final List<Expression> exprs = new ArrayList<>();
private final List<Expression> comExprs = new ArrayList<>();
public List<Expression> sortedExprs;
private int exprHash;
public Expression(InstructionContext head)
{
this.head = head;
}
public InstructionType getType()
{
return head.getInstruction().getType();
}
public InstructionContext getHead()
{
return head;
}
public void addExpr(Expression expr)
{
exprs.add(expr);
}
public List<Expression> getExprs()
{
return Collections.unmodifiableList(exprs);
}
public void addComExpr(Expression expr)
{
comExprs.add(expr);
}
public List<Expression> getComExprs()
{
return Collections.unmodifiableList(comExprs);
}
public void sort(MethodContext ctx)
{
for (Expression e : comExprs)
{
e.sort(ctx);
}
for (Expression e : exprs)
{
e.sort(ctx);
}
sortedExprs = new ArrayList<>(comExprs);
Collections.sort(sortedExprs, (e1, e2) -> ExprArgOrder.compare(ctx.getMethod(), getType(), e1, e2));
Collections.reverse(sortedExprs);
int hash = 0;
for (Expression e : sortedExprs)
{
hash ^= e.exprHash;
}
exprHash = hash;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.lvt;
public enum LVTType
{
INT,
LONG,
OBJECT
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.lvt;
import java.util.Objects;
public class MapKey
{
private final int idx;
private final LVTType type;
public MapKey(int idx, LVTType type)
{
this.idx = idx;
this.type = type;
}
@Override
public int hashCode()
{
int hash = 7;
hash = 89 * hash + this.idx;
hash = 89 * hash + Objects.hashCode(this.type);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final MapKey other = (MapKey) obj;
if (this.idx != other.idx)
{
return false;
}
if (this.type != other.type)
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.lvt;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.attributes.code.instruction.types.LVTInstructionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Mappings
{
private static final Logger logger = LoggerFactory.getLogger(Mappings.class);
private final int maxVariables;
private int offset;
private Map<Integer, LVTType> map = new HashMap<>();
private Map<MapKey, Integer> newIdxMap = new HashMap<>();
public Mappings(int maxVariables)
{
this.maxVariables = maxVariables;
}
private static LVTType toLvtType(LVTInstructionType type)
{
switch (type)
{
case DOUBLE:
case LONG:
return LVTType.LONG;
case FLOAT:
case INT:
return LVTType.INT;
case OBJECT:
return LVTType.OBJECT;
default:
throw new IllegalArgumentException("Unknown type " + type);
}
}
public Integer remap(int idx, LVTInstructionType type)
{
LVTType seen = map.get(idx);
if (seen == null)
{
map.put(idx, toLvtType(type));
}
else if (toLvtType(type) != seen)
{
MapKey key = new MapKey(idx, toLvtType(type));
Integer newIdx = newIdxMap.get(key);
if (newIdx == null)
{
newIdx = maxVariables + offset;
newIdxMap.put(key, newIdx);
logger.debug("Mapping {} -> {}", idx, newIdx);
offset += type.getSlots();
}
return newIdx;
}
return null;
}
}

View File

@@ -0,0 +1,356 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.deob.DeobAnnotations;
import net.runelite.mapping.Import;
import net.runelite.rs.api.RSClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnnotationIntegrityChecker
{
private static final Logger logger = LoggerFactory.getLogger(AnnotationIntegrityChecker.class);
public static final java.lang.Class<?> CLIENT_CLASS = RSClient.class;
public static final String API_PACKAGE_BASE = "rs.api.RS";
private final ClassGroup one;
private final ClassGroup two;
private final ParallelExecutorMapping mapping;
private int errors;
private int warnings;
public AnnotationIntegrityChecker(ClassGroup one, ClassGroup two, ParallelExecutorMapping mapping)
{
this.one = one;
this.two = two;
this.mapping = mapping;
}
public int getErrors()
{
return errors;
}
public int getWarnings()
{
return warnings;
}
public void run()
{
for (ClassFile cf : one.getClasses())
{
ClassFile other = (ClassFile) mapping.get(cf);
List<Field> exf1 = getExportedFields(cf);
List<Method> exm1 = getExportedMethods(cf);
for (Field f1 : exf1)
{
boolean isImported = isImported(cf, f1.getName(), f1.isStatic());
Field f2;
if (other == null)
{
if (!f1.isStatic() && isImported)
{
++errors;
logger.error("No other class for {} which contains imported field {}", cf, f1);
}
continue;
}
if (f1.isStatic())
{
f2 = findExportedFieldStatic(two, DeobAnnotations.getExportedName(f1.getAnnotations()));
}
else
{
f2 = findExportedField(other, DeobAnnotations.getExportedName(f1.getAnnotations()));
}
if (f2 == null)
{
if (isImported)
{
logger.error("Missing IMPORTED field on {} named {}",
other,
DeobAnnotations.getExportedName(f1.getAnnotations()));
++errors;
}
else
{
logger.warn("Missing exported field on {} named {}",
other,
DeobAnnotations.getExportedName(f1.getAnnotations()));
++warnings;
}
}
}
for (Method m1 : exm1)
{
boolean isImported = isImported(cf, m1.getName(), m1.isStatic());
Method m2;
if (other == null)
{
if (!m1.isStatic() && isImported)
{
++errors;
logger.error("No other class for {} which contains imported method {}", cf, m1);
}
continue;
}
if (m1.isStatic())
{
m2 = findExportedMethodStatic(two, DeobAnnotations.getExportedName(m1.getAnnotations()));
}
else
{
m2 = findExportedMethod(other, DeobAnnotations.getExportedName(m1.getAnnotations()));
}
if (m2 == null)
{
if (isImported)
{
logger.error("Missing IMPORTED method on {} named {} ({})",
other,
DeobAnnotations.getExportedName(m1.getAnnotations()),
m1);
++errors;
}
else
{
logger.warn("Missing exported method on {} named {} ({})",
other,
DeobAnnotations.getExportedName(m1.getAnnotations()),
m1);
++warnings;
}
}
}
}
checkAnnotationCounts();
}
private void checkAnnotationCounts()
{
for (ClassFile cf : two.getClasses())
{
for (Field f : cf.getFields())
{
int num = this.getNumberOfExports(f.getAnnotations());
if (num > 1)
{
logger.warn("Field {} has more than 1 export", f);
++errors;
}
}
for (Method m : cf.getMethods())
{
int num = this.getNumberOfExports(m.getAnnotations());
if (num > 1)
{
logger.warn("Method {} has more than 1 export", m);
++errors;
}
}
}
}
/**
* Determine if the api imports a given exported field or method by
* namee
*
* @param cf Class file field/method is on
* @param name Exported name of field/method
* @param isStatic Whether or not field/method is static
* @return
*/
private boolean isImported(ClassFile cf, String name, boolean isStatic)
{
Class<?> clazz;
if (isStatic)
{
// Use client
clazz = CLIENT_CLASS;
}
else
{
// Find interface for class
String iface = DeobAnnotations.getImplements(cf);
if (iface == null)
{
return false;
}
try
{
clazz = Class.forName(API_PACKAGE_BASE + iface);
}
catch (ClassNotFoundException ex)
{
return false; // this is okay
}
}
for (java.lang.reflect.Method method : clazz.getDeclaredMethods())
{
Import im = method.getAnnotation(Import.class);
if (im != null && im.value().equals(name))
{
return true;
}
}
return false;
}
private List<Field> getExportedFields(ClassFile clazz)
{
List<Field> list = new ArrayList<>();
for (Field f : clazz.getFields())
{
if (DeobAnnotations.getExportedName(f.getAnnotations()) != null)
{
list.add(f);
}
}
return list;
}
private List<Method> getExportedMethods(ClassFile clazz)
{
List<Method> list = new ArrayList<>();
for (Method m : clazz.getMethods())
{
if (DeobAnnotations.getExportedName(m.getAnnotations()) != null)
{
list.add(m);
}
}
return list;
}
private int getNumberOfExports(Annotations an)
{
int count = 0;
for (Annotation a : an.getAnnotations())
{
if (a.getType().equals(DeobAnnotations.EXPORT))
{
++count;
}
}
return count;
}
private Field findExportedField(ClassFile clazz, String name)
{
for (Field f : getExportedFields(clazz))
{
if (DeobAnnotations.getExportedName(f.getAnnotations()).equals(name))
{
return f;
}
}
return null;
}
private Field findExportedFieldStatic(ClassGroup group, String name)
{
for (ClassFile cf : group.getClasses())
{
for (Field f : cf.getFields())
{
if (f.isStatic())
{
if (name.equals(DeobAnnotations.getExportedName(f.getAnnotations())))
{
return f;
}
}
}
}
return null;
}
private Method findExportedMethodStatic(ClassGroup group, String name)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
if (m.isStatic())
{
if (name.equals(DeobAnnotations.getExportedName(m.getAnnotations())))
{
return m;
}
}
}
}
return null;
}
private Method findExportedMethod(ClassFile clazz, String name)
{
for (Method m : getExportedMethods(clazz))
{
if (DeobAnnotations.getExportedName(m.getAnnotations()).equals(name))
{
return m;
}
}
return null;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.annotation.Element;
import net.runelite.deob.DeobAnnotations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnnotationMapper
{
private static final Logger logger = LoggerFactory.getLogger(AnnotationMapper.class);
private final ClassGroup source, target;
private final ParallelExecutorMapping mapping;
public AnnotationMapper(ClassGroup source, ClassGroup target, ParallelExecutorMapping mapping)
{
this.source = source;
this.target = target;
this.mapping = mapping;
}
public void run()
{
int count = 0;
for (ClassFile c : source.getClasses())
{
ClassFile other = (ClassFile) mapping.get(c);
count += run(c, other);
}
logger.info("Copied {} annotations", count);
}
private int run(ClassFile from, ClassFile to)
{
int count = 0;
if (hasCopyableAnnotation(from.getAnnotations()))
{
if (to != null)
{
count += copyAnnotations(from.getAnnotations(), to.getAnnotations());
}
else
{
logger.warn("Class {} has copyable annotations but there is no mapped class", from);
}
}
for (Field f : from.getFields())
{
if (!hasCopyableAnnotation(f.getAnnotations()))
continue;
Field other = (Field) mapping.get(f);
if (other == null)
{
logger.warn("Unable to map annotated field {} named {}", f, DeobAnnotations.getExportedName(f.getAnnotations()));
continue;
}
count += copyAnnotations(f.getAnnotations(), other.getAnnotations());
}
for (Method m : from.getMethods())
{
if (!hasCopyableAnnotation(m.getAnnotations()))
continue;
Method other = (Method) mapping.get(m);
if (other == null)
{
logger.warn("Unable to map annotated method {} named {}", m, DeobAnnotations.getExportedName(m.getAnnotations()));
continue;
}
count += copyAnnotations(m.getAnnotations(), other.getAnnotations());
}
return count;
}
private int copyAnnotations(Annotations from, Annotations to)
{
int count = 0;
if (from.getAnnotations() == null)
return count;
for (Annotation a : from.getAnnotations())
{
if (isCopyable(a))
{
Annotation annotation = new Annotation(to);
annotation.setType(a.getType());
to.addAnnotation(annotation);
for (Element e : a.getElements())
{
Element element = new Element(annotation);
element.setName(e.getName());
element.setValue(e.getValue());
annotation.addElement(element);
}
++count;
}
}
return count;
}
private boolean hasCopyableAnnotation(Annotations a)
{
for (Annotation an : a.getAnnotations())
if (isCopyable(an))
return true;
return false;
}
private boolean isCopyable(Annotation a)
{
return a.getType().equals(DeobAnnotations.EXPORT)
|| a.getType().equals(DeobAnnotations.IMPLEMENTS)
|| a.getType().equals(DeobAnnotations.HOOK);
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
public class ClassGroupMapper
{
private final ClassGroup one, two;
private final Map<ClassFile, ClassFile> map = new HashMap<>();
public ClassGroupMapper(ClassGroup one, ClassGroup two)
{
this.one = one;
this.two = two;
}
public void map()
{
for (ClassFile cf1 : one.getClasses())
for (ClassFile cf2 : two.getClasses())
{
if (!MappingExecutorUtil.isMaybeEqual(cf1, cf2))
continue;
ClassMapper m = new ClassMapper(cf1, cf2);
if (!m.same())
continue;
map.put(cf1, cf2);
}
}
public Map<ClassFile, ClassFile> getMap()
{
return map;
}
public ClassFile get(ClassFile c)
{
return map.get(c);
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.Multiset;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Type;
import net.runelite.asm.signature.Signature;
public class ClassMapper
{
private final ClassFile one, two;
public ClassMapper(ClassFile one, ClassFile two)
{
this.one = one;
this.two = two;
}
private Multiset<Type> fieldCardinalities(ClassFile cf)
{
List<Type> t = cf.getFields().stream()
.filter(f -> !f.isStatic())
.map(f -> f.getType())
.collect(Collectors.toList());
return ImmutableMultiset.copyOf(t);
}
private Multiset<Signature> methodCardinalities(ClassFile cf)
{
List<Signature> t = cf.getMethods().stream()
.filter(m -> !m.isStatic())
.filter(m -> !m.getName().startsWith("<"))
.map(m -> m.getDescriptor())
.collect(Collectors.toList());
return ImmutableMultiset.copyOf(t);
}
public boolean same()
{
Multiset<Type> c1 = fieldCardinalities(one), c2 = fieldCardinalities(two);
if (!c1.equals(c2))
{
return false;
}
Multiset<Signature> s1 = methodCardinalities(one);
Multiset<Signature> s2 = methodCardinalities(two);
if (!s1.equals(s2))
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.signature.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConstructorMapper
{
private static final Logger logger = LoggerFactory.getLogger(ConstructorMapper.class);
private final ClassGroup source, target;
private final ParallelExecutorMapping mapping;
public ConstructorMapper(ClassGroup source, ClassGroup target, ParallelExecutorMapping mapping)
{
this.source = source;
this.target = target;
this.mapping = mapping;
}
private Type toOtherType(Type type)
{
if (type.isPrimitive())
{
return type;
}
ClassFile cf = source.findClass(type.getInternalName());
if (cf == null)
{
return type;
}
ClassFile other = (ClassFile) mapping.get(cf);
if (other == null)
{
logger.debug("Unable to map other type due to no class mapping for {}", cf);
return null;
}
return new Type("L" + other.getName() + ";");
}
private Signature toOtherSignature(Signature s)
{
Signature.Builder builder = new Signature.Builder()
.setReturnType(toOtherType(s.getReturnValue()));
for (Type t : s.getArguments())
{
Type other = toOtherType(t);
if (other == null)
{
return null;
}
builder.addArgument(other);
}
return builder.build();
}
/**
* Map constructors based on the class mapping of the given mapping
*/
public void mapConstructors()
{
for (ClassFile cf : source.getClasses())
{
ClassFile other = (ClassFile) mapping.get(cf);
if (other == null)
{
continue;
}
for (Method m : cf.getMethods())
{
if (!m.getName().equals("<init>"))
{
continue;
}
Signature otherSig = toOtherSignature(m.getDescriptor());
if (otherSig == null)
{
continue;
}
logger.debug("Converted signature {} -> {}", m.getDescriptor(), otherSig);
Method m2 = other.findMethod(m.getName(), otherSig);
if (m2 == null)
{
logger.warn("Unable to find other constructor for {}, looking for signature {} on class {}", m, otherSig, other);
continue;
}
ParallelExecutorMapping p = MappingExecutorUtil.map(m, m2);
p.map(null, m, m2);
mapping.merge(p);
}
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.Collection;
import net.runelite.asm.Method;
public class ExecutionMapper
{
// method1 maps to one of methods2, find out based on mapping
private Method method1;
private Collection<Method> methods2;
public ExecutionMapper(Method method1, Collection<Method> methods2)
{
this.method1 = method1;
this.methods2 = methods2;
}
public ParallelExecutorMapping run()
{
ParallelExecutorMapping highest = null;
boolean multiple = false;
for (Method m : methods2)
{
ParallelExecutorMapping mapping = MappingExecutorUtil.map(method1, m);
if (highest == null || mapping.same > highest.same)
{
highest = mapping;
multiple = false;
}
else if (mapping.same == highest.same)
{
multiple = true;
}
}
if (multiple)
return null;
return highest;
}
}

View File

@@ -0,0 +1,239 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Mapper
{
private static final Logger logger = LoggerFactory.getLogger(Mapper.class);
private final ClassGroup source, target;
private ParallelExecutorMapping mapping;
public Mapper(ClassGroup source, ClassGroup target)
{
this.source = source;
this.target = target;
}
public ParallelExecutorMapping getMapping()
{
return mapping;
}
public void run()
{
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
finalm.merge(mapStaticMethods());
finalm.merge(mapMethods());
finalm.reduce();
// map unexecuted methods (their mapping have not yet been merged)
while (mapUnexecutedMethods(finalm));
finalm.buildClasses();
mapMemberMethods(finalm);
new ConstructorMapper(source, target, finalm).mapConstructors();
finalm.reduce();
mapping = finalm;
}
private ParallelExecutorMapping mapMethods()
{
MethodSignatureMapper msm = new MethodSignatureMapper();
msm.map(source, target);
List<ParallelExecutorMapping> pmes = new ArrayList<>();
for (Method m : msm.getMap().keySet())
{
Collection<Method> methods = msm.getMap().get(m);
ExecutionMapper em = new ExecutionMapper(m, methods);
ParallelExecutorMapping mapping = em.run();
if (mapping == null)
{
continue;
}
mapping.map(null, mapping.m1, mapping.m2).wasExecuted = true;
logger.debug("map methods mapped {} -> {}", mapping.m1, mapping.m2);
pmes.add(mapping);
}
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
for (ParallelExecutorMapping pme : pmes)
{
finalm.merge(pme);
}
return finalm;
}
private ParallelExecutorMapping mapStaticMethods()
{
StaticMethodSignatureMapper smsm = new StaticMethodSignatureMapper();
smsm.map(source, target);
List<ParallelExecutorMapping> pmes = new ArrayList<>();
for (Method m : smsm.getMap().keySet())
{
Collection<Method> methods = smsm.getMap().get(m);
ExecutionMapper em = new ExecutionMapper(m, methods);
ParallelExecutorMapping mapping = em.run();
if (mapping == null)
{
continue;
}
Mapping map = mapping.map(null, mapping.m1, mapping.m2);
map.wasExecuted = true;
map.setWeight(mapping.same);
logger.debug("map static methods mapped {} -> {}", mapping.m1, mapping.m2);
pmes.add(mapping);
}
ParallelExecutorMapping finalm = new ParallelExecutorMapping(source, target);
for (ParallelExecutorMapping pme : pmes)
{
finalm.merge(pme);
}
return finalm;
}
private void mapMemberMethods(ParallelExecutorMapping mapping)
{
// pass #2 at method mapping, can use class file mapping learned
for (ClassFile cf : source.getClasses())
{
ClassFile other = (ClassFile) mapping.get(cf);
if (other == null)
{
continue;
}
List<Method> methods1 = cf.getMethods().stream()
.filter(m -> !m.isStatic())
.filter(m -> !m.getName().equals("<init>"))
.filter(m -> m.getCode() != null)
.collect(Collectors.toList());
List<Method> methods2 = other.getMethods().stream()
.filter(m -> !m.isStatic())
.filter(m -> !m.getName().equals("<init>"))
.filter(m -> m.getCode() != null)
.collect(Collectors.toList());
for (Method method : methods1)
{
if (mapping.get(method) != null) // already mapped
{
continue;
}
List<Method> possible = methods2.stream()
.filter(m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), method.getDescriptor()))
.collect(Collectors.toList());
// Run over execution mapper
ExecutionMapper em = new ExecutionMapper(method, possible);
ParallelExecutorMapping map = em.run();
if (map == null)
{
continue;
}
map.map(null, map.m1, map.m2);
logger.debug("Mapped {} -> {} based on exiting class mapping and method signatures", map.m1, map.m2);
mapping.merge(map);
}
}
}
/**
* execute and map already mapped methods that have not yet been "executed",
* and apply their mapping
* @param mapping
* @return
*/
private boolean mapUnexecutedMethods(ParallelExecutorMapping mapping)
{
// map has already been reduced
boolean mapped = false;
for (Object o : mapping.getMap().keySet())
{
Mapping m = mapping.getMappings(o).iterator().next();
if (m.wasExecuted || !(m.getFrom() instanceof Method))
{
continue;
}
Method m1 = (Method) m.getFrom(), m2 = (Method) m.getObject();
if (m1.getCode() == null || m2.getCode() == null)
{
continue;
}
// m was picked up as an invoke instruction when mapping
// something else, but wasn't executed itself
logger.debug("Wasn't executed {}", m);
ParallelExecutorMapping ma = MappingExecutorUtil.map(m1, m2);
m.wasExecuted = true;
mapped = true;
mapping.merge(ma);
}
return mapped;
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.attributes.code.Instruction;
public class Mapping
{
private Object from;
private Object object;
private int count;
private List<Instruction> ins = new ArrayList<>();
public boolean wasExecuted;
public int weight; // weight of mapping, based on same instruction count
public Mapping(Object from, Object object)
{
this.from = from;
this.object = object;
}
@Override
public String toString()
{
return "Mapping{" + "from=" + from + ", object=" + object + ", count=" + count + '}';
}
public Object getFrom()
{
return from;
}
public Object getObject()
{
return object;
}
public int getCount()
{
return count;
}
public void inc()
{
++count;
}
public void merge(Mapping other)
{
assert object == other.object;
count += other.count;
for (Instruction i : other.ins)
{
addInstruction(i);
}
wasExecuted |= other.wasExecuted;
weight = Math.max(weight, other.weight);
}
public void addInstruction(Instruction i)
{
if (!ins.contains(i))
{
ins.add(i);
}
}
public void setWeight(int w)
{
if (w > weight)
{
weight = w;
}
}
}

View File

@@ -0,0 +1,311 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Interfaces;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.ArrayLoad;
import net.runelite.asm.attributes.code.instruction.types.ConversionInstruction;
import net.runelite.asm.attributes.code.instruction.types.DupInstruction;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.MappableInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.Frame;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.ParallellMappingExecutor;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.execution.VariableContext;
import net.runelite.asm.execution.Variables;
import net.runelite.asm.signature.Signature;
public class MappingExecutorUtil
{
public static ParallelExecutorMapping map(Method m1, Method m2)
{
ClassGroup group1 = m1.getClassFile().getGroup();
ClassGroup group2 = m2.getClassFile().getGroup();
Execution e = new Execution(group1);
e.step = true;
Frame frame = new Frame(e, m1);
frame.initialize();
e.frames.add(frame);
Execution e2 = new Execution(group2);
e2.step = true;
Frame frame2 = new Frame(e2, m2);
frame2.initialize();
e2.frames.add(frame2);
frame.other = frame2;
frame2.other = frame;
ParallellMappingExecutor parallel = new ParallellMappingExecutor(e, e2);
ParallelExecutorMapping mappings = new ParallelExecutorMapping(m1.getClassFile().getGroup(),
m2.getClassFile().getGroup());
mappings.m1 = m1;
mappings.m2 = m2;
parallel.mappings = mappings;
int same = 0;
while (parallel.step())
{
// get what each frame is paused/exited on
InstructionContext p1 = parallel.getP1(), p2 = parallel.getP2();
assert p1.getInstruction() instanceof MappableInstruction;
assert p2.getInstruction() instanceof MappableInstruction;
MappableInstruction mi1 = (MappableInstruction) p1.getInstruction(),
mi2 = (MappableInstruction) p2.getInstruction();
boolean isSame = mi1.isSame(p1, p2);
assert isSame == mi2.isSame(p2, p1)
: "isSame fail " + p1.getInstruction() + " <> " + p2.getInstruction();
if (!isSame)
{
mappings.crashed = true;
p1.getFrame().stop();
p2.getFrame().stop();
continue;
}
++same;
mi1.map(mappings, p1, p2);
}
mappings.same = same;
return mappings;
}
public static boolean isMappable(InvokeInstruction ii)
{
String className;
net.runelite.asm.pool.Method m = ii.getMethod();
className = m.getClazz().getName();
if (className.startsWith("java/lang/reflect/") || className.startsWith("java/io/") || className.startsWith("java/util/"))
return true;
if (className.startsWith("java/") || className.startsWith("netscape/") || className.startsWith("javax/"))
return false;
return true;
}
public static boolean isInlineable(Instruction i)
{
if (!(i instanceof InvokeStatic))
return false;
ClassGroup group = i.getInstructions().getCode().getMethod().getClassFile().getGroup();
InvokeStatic is = (InvokeStatic) i;
net.runelite.asm.pool.Method m = is.getMethod();
return group.findClass(m.getClazz().getName()) != null;
}
public static InstructionContext resolve(
InstructionContext ctx,
StackContext from // pushed from ctx
)
{
if (ctx.getInstruction() instanceof SetFieldInstruction)
{
StackContext s = ctx.getPops().get(0);
return resolve(s.getPushed(), s);
}
if (ctx.getInstruction() instanceof ConversionInstruction)
{
// assume it pops one and pushes one
StackContext s = ctx.getPops().get(0);
return resolve(s.getPushed(), s);
}
if (ctx.getInstruction() instanceof DupInstruction)
{
DupInstruction d = (DupInstruction) ctx.getInstruction();
StackContext s = d.getOriginal(from);
return resolve(s.getPushed(), s);
}
if (ctx.getInstruction() instanceof ArrayLoad)
{
// might be multidimensional array
StackContext s = ctx.getPops().get(1); // the array
return resolve(s.getPushed(), s);
}
if (ctx.getInstruction() instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) ctx.getInstruction();
Variables variables = ctx.getVariables();
if (lvt.store())
{
StackContext s = ctx.getPops().get(0); // is this right?
return resolve(s.getPushed(), s);
}
else
{
VariableContext vctx = variables.get(lvt.getVariableIndex()); // variable being loaded
assert vctx != null;
InstructionContext storedCtx = vctx.getInstructionWhichStored();
if (storedCtx == null)
return ctx; // initial parameter
if (vctx.isIsParameter())
{
// Normally storedCtx being an InvokeInstruction means that this resolves to the
// return value of an invoke instruction, but if the variable is a parameter, it means
// this storedCtx is the invoke instruction which called this method.
assert storedCtx.getInstruction() instanceof InvokeInstruction;
// In PME non static functions are never stepped into/aren't inline obfuscated
assert storedCtx.getInstruction() instanceof InvokeStatic;
// Figure out parameter index from variable index.
Signature sig = ctx.getFrame().getMethod().getDescriptor(); // signature of current method
int paramIndex = 0;
for (int lvtIndex = 0 /* static */;
paramIndex < sig.size();
lvtIndex += sig.getTypeOfArg(paramIndex++).getSize())
if (lvtIndex == lvt.getVariableIndex())
break;
assert paramIndex < sig.size();
// Get stack context that was popped by the invoke
// pops[0] is the first thing popped, which is the last parameter.
StackContext sctx = storedCtx.getPops().get(sig.size() - 1 - paramIndex);
return resolve(sctx.getPushed(), sctx);
}
return resolve(storedCtx, null);
}
}
if (ctx.getInstruction() instanceof InvokeStatic)
{
if (from.returnSource != null)
{
return resolve(from.returnSource.getPushed(), from.returnSource);
}
}
return ctx;
}
public static boolean isMaybeEqual(Type t1, Type t2)
{
if (t1.getDimensions() != t2.getDimensions())
return false;
while (t1.getDimensions() > 0)
{
t1 = t1.getSubtype();
t2 = t2.getSubtype();
}
if (t1.isPrimitive() || t2.isPrimitive())
return t1.equals(t2);
return true;
}
public static boolean isMaybeEqual(Signature s1, Signature s2)
{
if (s1.size() != s2.size())
return false;
if (!isMaybeEqual(s1.getReturnValue(), s2.getReturnValue()))
return false;
for (int i = 0; i < s1.size(); ++i)
{
Type t1 = s1.getTypeOfArg(i), t2 = s2.getTypeOfArg(i);
if (!isMaybeEqual(t1, t2))
return false;
}
return true;
}
public static boolean isMaybeEqual(ClassFile cf1, ClassFile cf2)
{
if (cf1 == null && cf2 == null)
return true;
if (cf1 == null || cf2 == null)
return false;
if (cf1.getParent() != null || cf2.getParent() != null)
{
if (!isMaybeEqual(cf1.getParent(), cf2.getParent()))
return false;
}
else
{
// otherwise parents are not our classes
if (!cf1.getParentClass().equals(cf2.getParentClass()))
return false;
}
Interfaces i1 = cf1.getInterfaces(), i2 = cf2.getInterfaces();
if (i1.getInterfaces().size() != i2.getInterfaces().size())
return false;
return true;
}
public static boolean isMaybeEqual(Field f1, Field f2)
{
if (f1 == null && f2 == null)
return true;
if (f1 == null || f2 == null)
return false;
if (f1.isStatic() != f2.isStatic())
return false;
return isMaybeEqual(f1.getType(), f2.getType());
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.signature.Signature;
public class MethodSignatureMapper
{
private Multimap<Method, Method> map = LinkedHashMultimap.create();
private List<Method> getMethods(ClassGroup group)
{
List<Method> methods = new ArrayList<>();
for (ClassFile cf : group.getClasses())
for (Method m : cf.getMethods())
if (!m.isStatic() && !m.getName().equals("<init>") && m.getCode() != null)
methods.add(m);
return methods;
}
private List<Method> getMethodsOfSignature(ClassGroup group, ClassFile cf, Signature sig)
{
return getMethods(group).stream()
.filter(m -> MappingExecutorUtil.isMaybeEqual(cf, m.getClassFile()))
.filter(m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), sig))
.collect(Collectors.toList());
}
public void map(ClassGroup group1, ClassGroup group2)
{
for (Method m : getMethods(group1))
{
map.putAll(m, getMethodsOfSignature(group2, m.getClassFile(), m.getDescriptor()));
}
}
public Multimap<Method, Method> getMap()
{
return map;
}
}

View File

@@ -0,0 +1,420 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ParallelExecutorMapping
{
private static final Logger logger = LoggerFactory.getLogger(ParallelExecutorMapping.class);
private ClassGroup group, group2;
private Multimap<Object, Mapping> map = HashMultimap.create();
public Method m1, m2;
public boolean crashed;
public int same;
public ParallelExecutorMapping(ClassGroup group, ClassGroup group2)
{
this.group = group;
this.group2 = group2;
assert group != group2;
}
@Override
public String toString()
{
return "ParallelExecutorMapping{size = " + map.keySet().size() + ", crashed = " + crashed + ", same = " + same + ", m1 = " + m1 + ", m2 = " + m2 + "}";
}
private Mapping getMapping(Object from, Object to)
{
for (Mapping m : map.get(from))
{
if (m.getObject() == to)
{
return m;
}
}
Mapping m = new Mapping(from, to);
map.put(from, m);
return m;
}
private Object highest(Object from)
{
Mapping highest = null;
for (Mapping m : map.get(from))
{
if (highest == null || m.getCount() > highest.getCount())
{
highest = m;
}
else if (m.getCount() == highest.getCount() && getName(from).compareTo(getName(highest.getObject())) > 0)
{
highest = m;
}
}
return highest != null ? highest.getObject() : null;
}
public void merge(ParallelExecutorMapping other)
{
assert this != other;
for (Entry<Object, Mapping> e : other.map.entries())
{
Object o = e.getKey();
Mapping v = e.getValue();
Mapping m = this.getMapping(o, v.getObject());
m.merge(v);
}
}
public Mapping map(Instruction mapper, Object one, Object two)
{
Mapping m = getMapping(one, two);
if (mapper != null)
{
m.addInstruction(mapper);
}
belongs(one, group);
belongs(two, group2);
sanityCheck(one, two);
m.inc();
return m;
}
private void sanityCheck(Object one, Object two)
{
if (one instanceof Field && two instanceof Field)
{
Field f1 = (Field) one;
Field f2 = (Field) two;
assert f1.isStatic() == f2.isStatic() : "field map with static mismatch!";
}
}
private void mapClass(StaticInitializerIndexer staticIndexer1, StaticInitializerIndexer staticIndexer2, Object one, Object two)
{
ClassFile cf1, cf2;
if (one instanceof Field || two instanceof Field)
{
assert one instanceof Field;
assert two instanceof Field;
Field f1 = (Field) one;
Field f2 = (Field) two;
assert f1.isStatic() == f2.isStatic();
if (staticIndexer1.isStatic(f1) && staticIndexer2.isStatic(f2))
{
logger.debug("Mapping class of {} -> {} due to static initializer", f1, f2);
}
else if (f1.isStatic() || f2.isStatic())
{
return;
}
cf1 = f1.getClassFile();
cf2 = f2.getClassFile();
}
else if (one instanceof Method || two instanceof Method)
{
assert one instanceof Method;
assert two instanceof Method;
Method m1 = (Method) one;
Method m2 = (Method) two;
assert m1.isStatic() == m1.isStatic();
if (m1.isStatic() || m2.isStatic())
{
return;
}
cf1 = m1.getClassFile();
cf2 = m2.getClassFile();
}
else
{
assert false;
return;
}
belongs(cf1, group);
belongs(cf2, group2);
Mapping m = getMapping(cf1, cf2);
m.inc();
}
/**
* makes the map one to one based on the weight of each mapping. If a
* mapping is mapped from the same object multiple places, the highest
* is used, and the other mapping are not considered when deducing the
* mapping of the other objects.
*/
public void reduce()
{
List<Mapping> sorted = new ArrayList<>(map.values());
// Sort indepdent of the map's order, which is undefined
Collections.sort(sorted, (m1, m2) ->
{
// Number of times mapped
int i = Integer.compare(m1.getCount(), m2.getCount());
if (i != 0)
{
return i;
}
if (m1.weight != m2.weight)
{
// If equal, number of ins equal in the mapping
return Integer.compare(m1.weight, m2.weight);
}
logger.debug("Count and weight for {} <-> {} are the same! Sorting from name", m1, m2);
return getName(m1.getFrom()).compareTo(getName(m2.getFrom()));
});
// reverse so highest is first
Collections.reverse(sorted);
Multimap<Object, Mapping> reducedMap = HashMultimap.create();
Map<Object, Object> reverse = new HashMap<>();
for (Mapping m : sorted)
{
if (reducedMap.containsKey(m.getFrom()))
{
logger.debug("Reduced out mapping {} because of {}", m, reducedMap.get(m.getFrom()).iterator().next());
continue;
}
if (reverse.containsKey(m.getObject()))
{
logger.debug("Redudced out mapping {} because of {}", m, reducedMap.get(reverse.get(m.getObject())).iterator().next());
continue;
}
reducedMap.put(m.getFrom(), m);
reverse.put(m.getObject(), m.getFrom());
}
map = reducedMap;
// map is now one to one
}
public void buildClasses()
{
for (Object o : new HashSet<>(map.keySet()))
{
if (o instanceof ClassFile)
{
map.removeAll(o);
}
}
StaticInitializerIndexer staticIndexer1 = new StaticInitializerIndexer(group);
staticIndexer1.index();
StaticInitializerIndexer staticIndexer2 = new StaticInitializerIndexer(group2);
staticIndexer2.index();
Map<Object, Object> map = getMap();
for (Object key : map.keySet())
{
Object value = map.get(key);
mapClass(staticIndexer1, staticIndexer2, key, value);
}
map = getMap(); // rebuild map now we've inserted classes...
reduce();
/* get leftover classes, they usually contain exclusively static
* fields and methods so they're hard to pinpoint
*/
ClassGroupMapper m = new ClassGroupMapper(group, group2);
m.map();
for (ClassFile cf : group.getClasses())
{
if (!map.containsKey(cf))
{
ClassFile other = m.get(cf);
if (other == null)
{
logger.info("Unable to map class {}", cf);
}
else
{
// these are both probably very small
long nonStaticFields = cf.getFields().stream().filter(f -> !f.isStatic()).count(),
nonStaticMethods = cf.getMethods().stream().filter(m2 -> !m2.isStatic()).count();
logger.info("Build classes fallback {} -> {}, non static fields: {}, non static methods: {}",
cf, other, nonStaticFields, nonStaticMethods);
Mapping ma = getMapping(cf, other);
ma.inc();
}
}
}
}
public Object get(Object o)
{
return highest(o);
}
public Collection<Mapping> getMappings(Object o)
{
return map.get(o);
}
public Map<Object, Object> getMap()
{
Map<Object, Object> m = new HashMap<>();
for (Object o : map.keySet())
{
m.put(o, highest(o));
}
return m;
}
private void belongs(Object o, ClassGroup to)
{
if (o instanceof Field)
{
Field f = (Field) o;
assert f.getClassFile().getGroup() == to;
}
else if (o instanceof Method)
{
Method m = (Method) o;
assert m.getClassFile().getGroup() == to;
}
else if (o instanceof ClassFile)
{
ClassFile c = (ClassFile) o;
assert c.getGroup() == to;
}
else
{
assert false;
}
}
public int contradicts(ParallelExecutorMapping other)
{
int count = 0;
for (Entry<Object, Mapping> e : other.map.entries())
{
Object key = e.getKey();
Mapping value = e.getValue();
Object highest = highest(key);
if (highest != null && highest != value.getObject())
{
++count;
}
}
return count;
}
public boolean hasAnyMultiples()
{
for (Object o : map.keySet())
{
if (map.get(o).size() > 1)
{
return true;
}
}
return false;
}
private String getName(Object o)
{
if (o instanceof Field)
{
Field f = (Field) o;
return f.getClassFile().getClassName() + "." + f.getName();
}
else if (o instanceof Method)
{
Method m = (Method) o;
return m.getClassFile().getClassName() + "." + m.getName();
}
else if (o instanceof ClassFile)
{
ClassFile c = (ClassFile) o;
return c.getName();
}
else
{
assert false;
return null;
}
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2016-2018, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import java.util.HashSet;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Finds fields which are initialized in clinit
*
* @author Adam
*/
public class StaticInitializerIndexer
{
private static final Logger logger = LoggerFactory.getLogger(StaticInitializerIndexer.class);
private final ClassGroup group;
private final Set<Field> fields = new HashSet<>();
public StaticInitializerIndexer(ClassGroup group)
{
this.group = group;
}
public void index()
{
for (ClassFile cf : group.getClasses())
{
Method method = cf.findMethod("<clinit>");
if (method == null)
{
continue;
}
Instructions instructions = method.getCode().getInstructions();
for (Instruction i : instructions.getInstructions())
{
if (i.getType() != InstructionType.PUTSTATIC)
{
continue;
}
PutStatic putstatic = (PutStatic) i;
if (!putstatic.getField().getClazz().equals(cf.getPoolClass()) || putstatic.getMyField() == null)
{
continue;
}
fields.add(putstatic.getMyField());
}
}
logger.debug("Indexed {} statically initialized fields", fields.size());
}
public boolean isStatic(Field field)
{
return fields.contains(field);
}
}

View File

@@ -0,0 +1,87 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.mapping;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.signature.Signature;
public class StaticMethodSignatureMapper
{
private Multimap<Method, Method> map = LinkedHashMultimap.create();
private List<Method> getStaticMethods(ClassGroup group)
{
List<Method> methods = new ArrayList<>();
for (ClassFile cf : group.getClasses())
{
if (cf.getName().startsWith("net/runelite"))
{
// XXX net/runelite/rs/Reflection uses invokedynamic
continue;
}
for (Method m : cf.getMethods())
{
// this used to check the method wasnt <clinit>,
// but fernflower was modified to not remove code
// in clinit and place into ConstantValue attriutes
// on fields, so the execution order of clinit no longer
// depends on field order
if (m.isStatic())
{
methods.add(m);
}
}
}
return methods;
}
private List<Method> getStaticMethodsOfSignature(ClassGroup group, Signature sig)
{
return getStaticMethods(group).stream().filter(
m -> MappingExecutorUtil.isMaybeEqual(m.getDescriptor(), sig)
).collect(Collectors.toList());
}
public void map(ClassGroup group1, ClassGroup group2)
{
for (Method m : getStaticMethods(group1))
{
map.putAll(m, getStaticMethodsOfSignature(group2, m.getDescriptor()));
}
}
public Multimap<Method, Method> getMap()
{
return map;
}
}

View File

@@ -0,0 +1,83 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.menuaction;
import java.util.Objects;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
class Comparison
{
LVTInstruction lvt;
Instruction ldc;
Instruction cmp;
Label next;
Number getConstant()
{
return (Number) ((PushConstantInstruction) ldc).getConstant();
}
@Override
public int hashCode()
{
int hash = 3;
hash = 41 * hash + Objects.hashCode(this.cmp);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final Comparison other = (Comparison) obj;
if (!Objects.equals(this.cmp, other.cmp))
{
return false;
}
return true;
}
@Override
public String toString()
{
return "Comparison{" + "lvt=" + lvt + ", ldc=" + ldc + ", cmp=" + cmp + ", next=" + next + '}';
}
}

View File

@@ -0,0 +1,257 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.menuaction;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPEQ;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPGE;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPGT;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPLE;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPLT;
import static net.runelite.asm.attributes.code.InstructionType.IF_ICMPNE;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.IfICmpEq;
import net.runelite.asm.attributes.code.instructions.IfICmpNe;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.Frame;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.deob.Deobfuscator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Sort the ifs in menuAction
*
* @author Adam
*/
public class MenuActionDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(MenuActionDeobfuscator.class);
private static final int THRESHOLD_EQ = 50;
private static final int THRESHOLD_LT = 1;
@Override
public void run(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
run(m);
}
}
}
private void run(Method method)
{
if (method.getCode() == null)
{
return;
}
Execution execution = new Execution(method.getClassFile().getGroup());
execution.addMethod(method);
execution.noInvoke = true;
Multimap<Integer, Comparison> comps = HashMultimap.create();
execution.addExecutionVisitor((InstructionContext ictx) ->
{
Instruction i = ictx.getInstruction();
Frame frame = ictx.getFrame();
if (i instanceof If)
{
InstructionContext ctx1 = ictx.getPops().get(0).getPushed(); // constant
InstructionContext ctx2 = ictx.getPops().get(1).getPushed(); // lvt
if (ctx1.getInstruction() instanceof PushConstantInstruction
&& ctx2.getInstruction() instanceof LVTInstruction)
{
Comparison comparison = new Comparison();
comparison.cmp = i;
comparison.ldc = ctx1.getInstruction();
comparison.lvt = (LVTInstruction) ctx2.getInstruction();
comps.put(comparison.lvt.getVariableIndex(), comparison);
}
}
});
execution.run();
for (int i : comps.keySet())
{
Collection<Comparison> get = comps.get(i);
long l = get.stream().filter(c -> c.cmp.getType() == IF_ICMPGE
|| c.cmp.getType() == IF_ICMPGT
|| c.cmp.getType() == IF_ICMPLE
|| c.cmp.getType() == IF_ICMPLT
).count();
List<Comparison> eqcmp = get.stream()
.filter(c -> c.cmp.getType() == IF_ICMPEQ || c.cmp.getType() == IF_ICMPNE)
.collect(Collectors.toList());
if (get.size() > THRESHOLD_EQ && l <= THRESHOLD_LT)
{
logger.info("Sorting {} comparisons in {}", eqcmp.size(), method);
insert(method, eqcmp);
}
}
}
private void insert(Method method, List<Comparison> comparisons)
{
Instructions instructions = method.getCode().getInstructions();
List<Instruction> ins = instructions.getInstructions();
// replace all if(var == constant) with a jump to the false branch
// then, insert before the first jump the ifs to jump to the old
// true branch
//
// this is probably actually lookupswitch but it isn't mappable
// currently...
int min = -1;
for (Comparison comp : comparisons)
{
if (min == -1)
{
min = ins.indexOf(comp.lvt);
}
else
{
min = Math.min(min, ins.indexOf(comp.lvt));
}
if (comp.cmp.getType() == InstructionType.IF_ICMPEQ)
{
If cmp = (If) comp.cmp;
// remove
instructions.remove(comp.ldc);
instructions.remove((Instruction) comp.lvt);
instructions.remove(comp.cmp);
comp.next = cmp.getJumps().get(0);
}
else if (comp.cmp.getType() == InstructionType.IF_ICMPNE)
{
// replace with goto dest
If cmp = (If) comp.cmp;
int idx = ins.indexOf(cmp);
assert idx != -1;
comp.next = instructions.createLabelFor(ins.get(idx + 1));
instructions.remove(comp.ldc);
instructions.remove((Instruction) comp.lvt);
instructions.replace(comp.cmp, new Goto(instructions, cmp.getJumps().get(0)));
}
else
{
throw new IllegalStateException();
}
}
assert min != -1;
// sort comparisons - but if they jump to the same address, they are equal..
List<Comparison> sortedComparisons = new ArrayList<>(comparisons);
Collections.sort(sortedComparisons, (c1, c2) -> compare(comparisons, c1, c2));
// reinsert jumps
for (int i = 0; i < sortedComparisons.size(); ++i)
{
Comparison comp = sortedComparisons.get(i);
Instruction lvt = (Instruction) comp.lvt;
lvt.setInstructions(instructions);
comp.ldc.setInstructions(instructions);
instructions.addInstruction(min++, lvt);
instructions.addInstruction(min++, comp.ldc);
// use if_icmpeq if what follows also jumps to the same location
boolean multiple = i + 1 < sortedComparisons.size()
&& sortedComparisons.get(i + 1).next == comp.next;
if (multiple)
{
instructions.addInstruction(min++, new IfICmpEq(instructions, comp.next));
}
else
{
// fernflower decompiles a series of if_icmpeq as chains of not equal expressions
Label label = instructions.createLabelFor(ins.get(min));
instructions.addInstruction(min++, new IfICmpNe(instructions, label));
instructions.addInstruction(min++, new Goto(instructions, comp.next));
++min; // go past label
}
}
}
private int compare(List<Comparison> comparisons, Comparison c1, Comparison c2)
{
// Compare by constant
assert comparisons.contains(c1);
assert comparisons.contains(c2);
// The constant of a comparison is the lowest comparison that jumps to the same location,
// if all jumps to the same label are in the same location fernflower will decompile
// to if (foo || bar)
Comparison mc1 = comparisons.stream()
.filter(c -> c1.next == c.next)
.min((m1, m2) -> Integer.compare(m1.getConstant().intValue(), m2.getConstant().intValue()))
.get();
Comparison mc2 = comparisons.stream()
.filter(c -> c2.next == c.next)
.min((m1, m2) -> Integer.compare(m1.getConstant().intValue(), m2.getConstant().intValue()))
.get();
return Integer.compare(mc1.getConstant().intValue(), mc2.getConstant().intValue());
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packethandler;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IALoad;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketLengthFinder
{
private static final Logger logger = LoggerFactory.getLogger(PacketLengthFinder.class);
private final ClassGroup group;
private final PacketTypeFinder packetType;
private Field packetLength;
private GetStatic getArray;
private GetStatic getType;
private IALoad load;
private PutStatic store;
public PacketLengthFinder(ClassGroup group, PacketTypeFinder packetType)
{
this.group = group;
this.packetType = packetType;
}
public Field getPacketLength()
{
return packetLength;
}
public GetStatic getGetArray()
{
return getArray;
}
public GetStatic getGetType()
{
return getType;
}
public IALoad getLoad()
{
return load;
}
public PutStatic getStore()
{
return store;
}
public void find()
{
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
run(method.getCode());
}
}
logger.info("Found packet length: {}", packetLength);
}
// getstatic class272/field3690 [I
// getstatic Client/packetType I
// iaload
// putstatic Client/packetLength I
private void run(Code code)
{
if (code == null)
{
return;
}
Instructions instructions = code.getInstructions();
Field type = packetType.getPacketType();
for (int i = 0; i < instructions.getInstructions().size() - 3; ++i)
{
Instruction i1 = instructions.getInstructions().get(i),
i2 = instructions.getInstructions().get(i + 1),
i3 = instructions.getInstructions().get(i + 2),
i4 = instructions.getInstructions().get(i + 3);
if (!(i1 instanceof GetStatic))
{
continue;
}
if (!(i2 instanceof GetStatic))
{
continue;
}
GetStatic gs = (GetStatic) i2;
if (gs.getMyField() != type)
{
continue;
}
if (!(i3 instanceof IALoad))
{
continue;
}
if (!(i4 instanceof PutStatic))
{
continue;
}
PutStatic ps = (PutStatic) i4;
assert packetLength == null : "packetLength already found";
packetLength = ps.getMyField();
getArray = (GetStatic) i1;
getType = gs;
load = (IALoad) i3;
store = ps;
return;
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packethandler;
import java.util.Objects;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.execution.InstructionContext;
public class PacketRead
{
private final Type type; // type of read, from return of function
private final Instruction getBuffer; // getstatic
private final InstructionContext invokeCtx;
private final Instruction invoke; // invoke instruction to read data
private Instruction store; // lvt store for reorderable reads
public PacketRead(Type type, Instruction getBuffer, InstructionContext invokeCtx)
{
this.type = type;
this.getBuffer = getBuffer;
this.invokeCtx = invokeCtx;
this.invoke = invokeCtx.getInstruction();
}
public Type getType()
{
return type;
}
public Instruction getGetBuffer()
{
return getBuffer;
}
public InstructionContext getInvokeCtx()
{
return invokeCtx;
}
public Instruction getInvoke()
{
return invoke;
}
public Instruction getStore()
{
return store;
}
public void setStore(Instruction store)
{
this.store = store;
}
@Override
public int hashCode()
{
int hash = 5;
hash = 37 * hash + Objects.hashCode(this.type);
hash = 37 * hash + Objects.hashCode(this.invoke);
return hash;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final PacketRead other = (PacketRead) obj;
if (!Objects.equals(this.type, other.type))
{
return false;
}
if (!Objects.equals(this.invoke, other.invoke))
{
return false;
}
return true;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packethandler;
import com.google.common.base.Objects;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketTypeFinder
{
private static final Logger logger = LoggerFactory.getLogger(PacketTypeFinder.class);
private final ClassGroup group;
private final Map<Field, Integer> sets = new HashMap<>();
private Field packetType;
public PacketTypeFinder(ClassGroup group)
{
this.group = group;
}
public Field getPacketType()
{
return packetType;
}
public void find()
{
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
run(method.getCode());
}
}
packetType = sets.entrySet().stream()
.max((entry1, entry2) -> Integer.compare(entry1.getValue(), entry2.getValue()))
.get()
.getKey();
logger.info("Identified {} as packetType", packetType);
}
private void run(Code code)
{
if (code == null)
{
return;
}
Instructions instructions = code.getInstructions();
for (int i = 0; i < instructions.getInstructions().size() - 1; ++i)
{
Instruction i1 = instructions.getInstructions().get(i),
i2 = instructions.getInstructions().get(i + 1);
if (i1 instanceof PushConstantInstruction && i2.getType() == InstructionType.PUTSTATIC)
{
PushConstantInstruction pci = (PushConstantInstruction) i1;
SetFieldInstruction sfi = (SetFieldInstruction) i2;
Field field = sfi.getMyField();
if (Objects.equal(-1, pci.getConstant()) && field != null)
{
Integer count = sets.get(field);
if (count == null)
{
sets.put(field, 1);
}
else
{
sets.put(field, count + 1);
}
}
}
}
}
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packetwrite;
import java.util.Collection;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.PutStatic;
import static net.runelite.deob.deobfuscators.transformers.OpcodesTransformer.RUNELITE_OPCODES;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class OpcodeReplacer
{
private static final Logger logger = LoggerFactory.getLogger(OpcodeReplacer.class);
public void run(ClassGroup group, Collection<PacketWrite> writes)
{
int count = 0;
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
assert runeliteOpcodes != null : "Opcodes class must exist";
for (PacketWrite wp : writes)
{
Instructions ins = wp.getInstructions();
Instruction param = wp.getOpcodeIns();
if (!(param instanceof PushConstantInstruction))
{
continue;
}
final String fieldName = "PACKET_CLIENT_" + wp.getOpcode();
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(RUNELITE_OPCODES),
fieldName,
Type.INT
);
ins.replace(param, new GetStatic(ins, field));
if (runeliteOpcodes.findField(fieldName) == null)
{
Field opField = new Field(runeliteOpcodes, fieldName, Type.INT);
// ACC_FINAL causes javac to inline the fields, which prevents
// the mapper from doing field mapping
opField.setAccessFlags(ACC_PUBLIC | ACC_STATIC);
// setting a non-final static field value
// doesn't work with fernflower
opField.setValue(wp.getOpcode());
runeliteOpcodes.addField(opField);
// add initialization
Method clinit = runeliteOpcodes.findMethod("<clinit>");
assert clinit != null;
Instructions instructions = clinit.getCode().getInstructions();
instructions.addInstruction(0, new LDC(instructions, wp.getOpcode()));
instructions.addInstruction(1, new PutStatic(instructions, opField));
}
++count;
}
logger.info("Injected {} packet writes", count);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packetwrite;
import java.util.ArrayList;
import java.util.List;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.execution.InstructionContext;
class PacketWrite
{
InstructionContext putOpcode;
List<InstructionContext> writes = new ArrayList<>();
Instruction getOpcodeIns()
{
return putOpcode.getPops().get(0).getPushed().getInstruction();
}
public int getOpcode()
{
return ((Number) ((PushConstantInstruction) getOpcodeIns()).getConstant()).intValue();
}
Instructions getInstructions()
{
return putOpcode.getInstruction().getInstructions();
}
}

View File

@@ -0,0 +1,337 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.packetwrite;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.code.Instruction;
import static net.runelite.asm.attributes.code.InstructionType.INVOKEVIRTUAL;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.attributes.code.instruction.types.ReturnInstruction;
import net.runelite.asm.attributes.code.instruction.types.SetFieldInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.Goto;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.If0;
import net.runelite.asm.attributes.code.instructions.IfEq;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.execution.StackContext;
import net.runelite.asm.pool.Method;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Deobfuscator;
import net.runelite.deob.c2s.RWOpcodeFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketWriteDeobfuscator implements Deobfuscator
{
private static final Logger logger = LoggerFactory.getLogger(PacketWriteDeobfuscator.class);
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
private final OpcodeReplacer opcodeReplacer = new OpcodeReplacer();
private RWOpcodeFinder rw;
private PacketWrite cur;
private final Map<Instruction, PacketWrite> writes = new HashMap<>();
public void visit(InstructionContext ctx)
{
if (isEnd(ctx))
{
end();
return;
}
if (ctx.getInstruction().getType() != INVOKEVIRTUAL)
{
return;
}
InvokeInstruction ii = (InvokeInstruction) ctx.getInstruction();
if (ii.getMethods().contains(rw.getWriteOpcode()))
{
end();
PacketWrite write = start();
write.putOpcode = ctx;
return;
}
PacketWrite write = cur;
if (write == null)
{
return;
}
if (!ii.getMethod().getClazz().getName().equals(rw.getWriteOpcode().getClassFile().getSuperName()))
{
return;
}
write.writes.add(ctx);
}
private PacketWrite start()
{
end();
cur = new PacketWrite();
return cur;
}
private void end()
{
if (cur != null)
{
if (!writes.containsKey(cur.putOpcode.getInstruction()))
{
writes.put(cur.putOpcode.getInstruction(), cur);
}
cur = null;
}
}
private boolean isEnd(InstructionContext ctx)
{
// conditions where packet write ends:
// any invoke that isn't to the packet buffer
// any variable assignment
// any field assignment
// any conditional jump
// any return
Instruction i = ctx.getInstruction();
if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
Method method = ii.getMethod();
if (!method.getClazz().equals(rw.getSecretBuffer().getPoolClass())
&& !method.getClazz().equals(rw.getBuffer().getPoolClass()))
{
return true;
}
}
if (i instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) i;
if (lvt.store())
{
return true;
}
}
if (i instanceof SetFieldInstruction)
{
return true;
}
if (i instanceof If || i instanceof If0)
{
return true;
}
if (i instanceof ReturnInstruction)
{
return true;
}
return false;
}
@Override
public void run(ClassGroup group)
{
rw = new RWOpcodeFinder(group);
rw.find();
Execution e = new Execution(group);
e.addExecutionVisitor(this::visit);
e.populateInitialMethods();
e.run();
end();
opcodeReplacer.run(group, writes.values());
int count = 0;
int writesCount = 0;
for (PacketWrite write : writes.values())
{
if (write.writes.isEmpty())
{
continue;
}
insert(group, write);
++count;
writesCount += write.writes.size();
}
logger.info("Converted buffer write methods for {} opcodes ({} writes)", count, writesCount);
}
private void insert(ClassGroup group, PacketWrite write)
{
Instructions instructions = write.putOpcode.getInstruction().getInstructions();
List<Instruction> ins = instructions.getInstructions();
InstructionContext firstWrite = write.writes.get(0);
InstructionContext lastWrite = write.writes.get(write.writes.size() - 1);
int idx = ins.indexOf(lastWrite.getInstruction());
assert idx != -1;
++idx; // past write
Label afterWrites = instructions.createLabelFor(ins.get(idx));
// pops arg, getfield
InstructionContext beforeFirstWrite = firstWrite.getPops().get(1).getPushed();
Label putOpcode = instructions.createLabelFor(beforeFirstWrite.getInstruction(), true);
idx = ins.indexOf(beforeFirstWrite.getInstruction());
assert idx != -1;
--idx;
net.runelite.asm.pool.Field field = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(findClient(group).getName()),
RUNELITE_PACKET,
Type.BOOLEAN
);
instructions.addInstruction(idx++, new GetStatic(instructions, field));
instructions.addInstruction(idx++, new IfEq(instructions, putOpcode));
Instruction before = ins.get(idx);
for (InstructionContext ctx : write.writes)
{
insert(instructions, ctx, before);
}
idx = ins.indexOf(before);
instructions.addInstruction(idx++, new Goto(instructions, afterWrites));
}
private void insert(Instructions ins, InstructionContext ic, Instruction before)
{
List<StackContext> pops = new ArrayList<>(ic.getPops());
Collections.reverse(pops);
for (StackContext sc : pops)
{
insert(ins, sc.getPushed(), before);
}
Instruction i = ic.getInstruction().clone();
i = translate(i);
assert i.getInstructions() == ins;
int idx = ins.getInstructions().indexOf(before);
assert idx != -1;
ins.addInstruction(idx, i);
}
private Instruction translate(Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return i;
}
InvokeVirtual ii = (InvokeVirtual) i;
Method invoked = ii.getMethod();
assert invoked.getType().size() == 1;
Type argumentType = invoked.getType().getTypeOfArg(0);
Method invokeMethod;
if (argumentType.equals(Type.BYTE))
{
invokeMethod = new Method(
ii.getMethod().getClazz(),
"runeliteWriteByte",
new Signature("(B)V")
);
}
else if (argumentType.equals(Type.SHORT))
{
invokeMethod = new Method(
ii.getMethod().getClazz(),
"runeliteWriteShort",
new Signature("(S)V")
);
}
else if (argumentType.equals(Type.INT))
{
invokeMethod = new Method(
ii.getMethod().getClazz(),
"runeliteWriteInt",
new Signature("(I)V")
);
}
else if (argumentType.equals(Type.LONG))
{
invokeMethod = new Method(
ii.getMethod().getClazz(),
"runeliteWriteLong",
new Signature("(J)V")
);
}
else if (argumentType.equals(Type.STRING))
{
invokeMethod = new Method(
ii.getMethod().getClazz(),
"runeliteWriteString",
new Signature("(Ljava/lang/String;)V")
);
}
else
{
throw new IllegalStateException("Unknown type " + argumentType);
}
return new InvokeVirtual(i.getInstructions(), invokeMethod);
}
private ClassFile findClient(ClassGroup group)
{
// "client" in vainlla but "Client" in deob..
ClassFile cf = group.findClass("client");
return cf != null ? cf : group.findClass("Client");
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.IfNull;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.VReturn;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClientErrorTransformer implements Transformer
{
private static final Logger logger = LoggerFactory.getLogger(ClientErrorTransformer.class);
private boolean done = false;
@Override
public void transform(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
transform(m);
}
}
logger.info("Transformed: " + done);
}
private void transform(Method m)
{
if (!m.isStatic() || m.getDescriptor().size() != 2
|| !m.getDescriptor().getTypeOfArg(0).equals(Type.STRING)
|| !m.getDescriptor().getTypeOfArg(1).equals(Type.THROWABLE))
return;
Code code = m.getCode();
Instructions ins = code.getInstructions();
/*
Makes it so the old code in this method is logically dead,
letting the mapper map it but making it so it's never executed.
*/
Instruction aload0 = new ALoad(ins, 1); // load throwable
IfNull ifNull = new IfNull(ins, InstructionType.IFNULL);
ifNull.setTo(ins.createLabelFor(ins.getInstructions().get(0)));
Instruction aload1 = new ALoad(ins, 1); // load throwable
InvokeVirtual printStackTrace = new InvokeVirtual(ins,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("java/lang/Throwable"),
"printStackTrace",
new Signature("()V")
)
);
Instruction ret = new VReturn(ins);
ins.addInstruction(0, aload0);
ins.addInstruction(1, ifNull);
ins.addInstruction(2, aload1);
ins.addInstruction(3, printStackTrace);
ins.addInstruction(4, ret);
done = true;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instruction.types.InvokeInstruction;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.Pop;
import net.runelite.deob.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GetPathTransformer implements Transformer
{
private static final Logger logger = LoggerFactory.getLogger(GetPathTransformer.class);
private boolean done = false;
@Override
public void transform(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
transform(m);
}
}
logger.info("Transformed: " + done);
}
private void transform(Method m)
{
int count = 0;
if (m.getCode() == null)
{
return;
}
for (Instruction i : m.getCode().getInstructions().getInstructions())
{
if (i instanceof InvokeInstruction)
{
InvokeInstruction ii = (InvokeInstruction) i;
if (ii.getMethod().getName().equals("getPath"))
{
if (++count == 2)
{
removeInvoke(i);
done = true;
break;
}
}
}
}
}
private void removeInvoke(Instruction i)
{
Instructions ins = i.getInstructions();
int idx = ins.getInstructions().indexOf(i);
ins.remove(i);
ins.getInstructions().add(idx, new Pop(ins)); // pop File
ins.getInstructions().add(idx + 1, new LDC(ins, ""));
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.Dup;
import net.runelite.asm.attributes.code.instructions.I2L;
import net.runelite.asm.attributes.code.instructions.IAdd;
import net.runelite.asm.attributes.code.instructions.InvokeSpecial;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.attributes.code.instructions.LDC;
import net.runelite.asm.attributes.code.instructions.New;
import net.runelite.asm.attributes.code.instructions.Pop;
import net.runelite.asm.pool.Class;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MaxMemoryTransformer implements Transformer
{
private static final Logger logger = LoggerFactory.getLogger(MaxMemoryTransformer.class);
private boolean done = false;
@Override
public void transform(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
transform(m);
}
}
logger.info("Transformed: " + done);
}
private void transform(Method m)
{
Code code = m.getCode();
if (code == null)
{
return;
}
Instructions ins = code.getInstructions();
for (Instruction i : ins.getInstructions())
{
if (i instanceof InvokeVirtual)
{
/*
invokestatic java/lang/Runtime/getRuntime()Ljava/lang/Runtime;
invokevirtual java/lang/Runtime/maxMemory()J
ldc2_w 1048576
ldiv
l2i
*/
if (((InvokeVirtual) i).getMethod().getName().equals("maxMemory"))
{
insert(ins, ins.getInstructions().indexOf(i));
done = true;
break;
}
}
}
}
private void insert(Instructions ins, int idx)
{
Class randomClass = new net.runelite.asm.pool.Class("java/util/Random");
ins.getInstructions().remove(idx);
ins.getInstructions().add(idx++, new Pop(ins)); // pop runtime
ins.getInstructions().add(idx++, new New(ins, randomClass));
ins.getInstructions().add(idx++, new Dup(ins));
ins.getInstructions().add(idx++, new InvokeSpecial(ins, new net.runelite.asm.pool.Method(randomClass, "<init>", new Signature("()V")))); // new Random
ins.getInstructions().add(idx++, new LDC(ins, 31457280));
ins.getInstructions().add(idx++, new InvokeVirtual(ins, new net.runelite.asm.pool.Method(randomClass, "nextInt", new Signature("(I)I")))); // nextInt(31457280)
ins.getInstructions().add(idx++, new LDC(ins, 230686720));
ins.getInstructions().add(idx++, new IAdd(ins)); // 230686720 + nextInt(31457280)
ins.getInstructions().add(idx++, new I2L(ins));
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.VReturn;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Transformer;
import org.objectweb.asm.Opcodes;
public class OpcodesTransformer implements Transformer
{
public static final String RUNELITE_OPCODES = "net/runelite/rs/Opcodes";
@Override
public void transform(ClassGroup group)
{
ClassFile runeliteOpcodes = group.findClass(RUNELITE_OPCODES);
if (runeliteOpcodes == null)
{
runeliteOpcodes = new ClassFile(group);
runeliteOpcodes.setName(RUNELITE_OPCODES);
runeliteOpcodes.setSuperName(Type.OBJECT.getInternalName());
runeliteOpcodes.setAccess(Opcodes.ACC_PUBLIC);
group.addClass(runeliteOpcodes);
}
else
{
runeliteOpcodes.getFields().clear();
}
Method clinit = runeliteOpcodes.findMethod("<clinit>");
if (clinit == null)
{
clinit = new Method(runeliteOpcodes, "<clinit>", new Signature("()V"));
clinit.setStatic();
Code code = new Code(clinit);
code.setMaxStack(1);
clinit.setCode(code);
runeliteOpcodes.addMethod(clinit);
Instructions instructions = code.getInstructions();
instructions.addInstruction(new VReturn(instructions));
}
}
}

View File

@@ -0,0 +1,254 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import java.util.ArrayList;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.InvokeStatic;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Transformer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ReflectionTransformer implements Transformer
{
private static final Logger logger = LoggerFactory.getLogger(ReflectionTransformer.class);
@Override
public void transform(ClassGroup group)
{
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
transform(method);
}
}
}
private void transform(Method method)
{
Code code = method.getCode();
if (code == null)
{
return;
}
Instructions ins = code.getInstructions();
for (Instruction i : new ArrayList<>(ins.getInstructions()))
{
transformFindClass(i);
transformMethodName(ins, i);
transformGetParameterTypes(ins, i);
transformGetDeclaredField(ins, i);
transformSetInt(ins, i);
transformGetInt(ins, i);
transformInvokeVirtual(ins, i);
}
}
// invokestatic java/lang/Class/forName(Ljava/lang/String;)Ljava/lang/Class;
// to
// invokestatic net/runelite/rs/Reflection/findClass(Ljava/lang/String;)Ljava/lang/Class;
private void transformFindClass(Instruction i)
{
if (!(i instanceof InvokeStatic))
{
return;
}
InvokeStatic is = (InvokeStatic) i;
if (is.getMethod().getClazz().getName().equals("java/lang/Class")
&& is.getMethod().getName().equals("forName"))
{
is.setMethod(
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "findClass", new Signature("(Ljava/lang/String;)Ljava/lang/Class;")
)
);
logger.info("Transformed Class.forName call");
}
}
// invokevirtual java/lang/reflect/Method/getName()Ljava/lang/String;
// to
// invokestatic net/runelite/rs/Reflection/getMethodName(Ljava/lang/reflect/Method;)Ljava/lang/String;
private void transformMethodName(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
&& iv.getMethod().getName().equals("getName"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getMethodName", new Signature("(Ljava/lang/reflect/Method;)Ljava/lang/String;")
)
);
instructions.replace(iv, is);
logger.info("Transformed Method.getName call");
}
}
// invokevirtual java/lang/reflect/Method/getParameterTypes()[Ljava/lang/Class;
// to
// invokestatic net/runelite/rs/Reflection/getParameterTypes(Ljava/lang/reflect/Method;)[Ljava/lang/Class;
private void transformGetParameterTypes(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
&& iv.getMethod().getName().equals("getParameterTypes"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getParameterTypes", new Signature("(Ljava/lang/reflect/Method;)[Ljava/lang/Class;")
)
);
instructions.replace(iv, is);
logger.info("Transformed Method.getParameterTypes call");
}
}
// invokevirtual java/lang/Class/getDeclaredField(Ljava/lang/String;)Ljava/lang/reflect/Field;
// to
// invokestatic net/runelite/rs/Reflection/findField
private void transformGetDeclaredField(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/Class")
&& iv.getMethod().getName().equals("getDeclaredField"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "findField", new Signature("(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Field;")
)
);
instructions.replace(iv, is);
logger.info("Transformed Class.getDeclaredField call");
}
}
// invokevirtual java/lang/reflect/Field/setInt(Ljava/lang/Object;I)V
private void transformSetInt(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Field")
&& iv.getMethod().getName().equals("setInt"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "setInt", new Signature("(Ljava/lang/reflect/Field;Ljava/lang/Object;I)V")
)
);
instructions.replace(iv, is);
logger.info("Transformed Field.setInt call");
}
}
// invokevirtual java/lang/reflect/Field/getInt(Ljava/lang/Object;)I
private void transformGetInt(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Field")
&& iv.getMethod().getName().equals("getInt"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "getInt", new Signature("(Ljava/lang/reflect/Field;Ljava/lang/Object;)I")
)
);
instructions.replace(iv, is);
logger.info("Transformed Field.getInt call");
}
}
// invokevirtual java/lang/reflect/Method/invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
private void transformInvokeVirtual(Instructions instructions, Instruction i)
{
if (!(i instanceof InvokeVirtual))
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
if (iv.getMethod().getClazz().getName().equals("java/lang/reflect/Method")
&& iv.getMethod().getName().equals("invoke"))
{
InvokeStatic is = new InvokeStatic(instructions,
new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class("net/runelite/rs/Reflection"), "invoke", new Signature("(Ljava/lang/reflect/Method;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;")
)
);
instructions.replace(iv, is);
logger.info("Transformed Method.invoke call");
}
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers;
import java.io.IOException;
import java.util.List;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import static net.runelite.asm.attributes.code.InstructionType.RETURN;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instructions.ALoad;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.IfEq;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.signature.Signature;
import net.runelite.deob.Transformer;
import net.runelite.deob.c2s.RWOpcodeFinder;
import net.runelite.deob.deobfuscators.transformers.buffer.BufferFinder;
import net.runelite.deob.deobfuscators.transformers.buffer.BufferMethodInjector;
import net.runelite.deob.deobfuscators.transformers.buffer.BufferPayloadFinder;
import net.runelite.deob.deobfuscators.transformers.buffer.PacketFlushFinder;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class RuneliteBufferTransformer implements Transformer
{
private static final Logger logger = LoggerFactory.getLogger(RuneliteBufferTransformer.class);
private static final String RUNELITE_PACKET = "RUNELITE_PACKET";
private static final String RUNELITE_INIT_PACKET = "runeliteInitPacket";
private static final String RUNELITE_FINISH_PACKET = "runeliteFinishPacket";
@Override
public void transform(ClassGroup group)
{
injectRunelitePacket(group);
injectBufferMethods(group);
injectLengthHeader(group);
injectPacketFinish(group);
}
/**
* inject RUNELITE_PACKET field
*
* @param group
*/
private void injectRunelitePacket(ClassGroup group)
{
ClassFile client = findClient(group);
assert client != null : "no client class";
if (client.findField(RUNELITE_PACKET) != null)
{
return;
}
Field field = new Field(client, RUNELITE_PACKET, Type.BOOLEAN);
field.setAccessFlags(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC);
client.addField(field);
}
private ClassFile findClient(ClassGroup group)
{
// "client" in vainlla but "Client" in deob..
ClassFile cf = group.findClass("client");
return cf != null ? cf : group.findClass("Client");
}
/**
* inject runelite buffer methods
*
* @param group
*/
private void injectBufferMethods(ClassGroup group)
{
BufferFinder bf = new BufferFinder(group);
bf.find();
BufferPayloadFinder bpf = new BufferPayloadFinder(bf.getBuffer());
bpf.find();
BufferMethodInjector bmi = new BufferMethodInjector(bpf);
try
{
bmi.inject();
}
catch (IOException ex)
{
logger.warn("unable to inject buffer methods", ex);
}
}
/**
* inject the length header after the packet opcode
*
* @param group
*/
private void injectLengthHeader(ClassGroup group)
{
RWOpcodeFinder rw = new RWOpcodeFinder(group);
rw.find();
Method writeOpcode = rw.getWriteOpcode();
Code code = writeOpcode.getCode();
Instructions instructions = code.getInstructions();
List<Instruction> ins = instructions.getInstructions();
Instruction start = ins.get(0);
Instruction end = ins.stream().filter(i -> i.getType() == RETURN).findFirst().get();
Label labelForStart = instructions.createLabelFor(start);
Label labelForEnd = instructions.createLabelFor(end);
final net.runelite.asm.pool.Field runelitePacketField = new net.runelite.asm.pool.Field(
new net.runelite.asm.pool.Class(findClient(group).getName()),
RUNELITE_PACKET,
Type.BOOLEAN
);
int idx = ins.indexOf(labelForStart);
instructions.addInstruction(idx++, new GetStatic(instructions, runelitePacketField));
instructions.addInstruction(idx++, new IfEq(instructions, labelForStart));
net.runelite.asm.pool.Method method = new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(writeOpcode.getClassFile().getName()),
RUNELITE_FINISH_PACKET,
new Signature("()V")
);
instructions.addInstruction(idx++, new ALoad(instructions, 0));
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
idx = ins.indexOf(labelForEnd);
instructions.addInstruction(idx++, new GetStatic(instructions, runelitePacketField));
instructions.addInstruction(idx++, new IfEq(instructions, labelForEnd));
method = new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(writeOpcode.getClassFile().getName()),
RUNELITE_INIT_PACKET,
new Signature("()V")
);
instructions.addInstruction(idx++, new ALoad(instructions, 0));
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
logger.info("Injected finish/init packet calls into {}", writeOpcode);
}
private void injectPacketFinish(ClassGroup group)
{
PacketFlushFinder pff = new PacketFlushFinder(group);
pff.find();
for (InstructionContext queueForWriteCtx : pff.getQueueForWrite())
{
Instruction before = queueForWriteCtx.getPops().get(3) // socket
.getPushed().getInstruction();
GetStatic getBuffer;
try
{
getBuffer = (GetStatic) queueForWriteCtx.getPops().get(2) // buffer
.getPushed().getPops().get(0) // getstatic
.getPushed().getInstruction();
}
catch (ClassCastException ex)
{
continue;
}
Instructions instructions = before.getInstructions();
int idx = instructions.getInstructions().indexOf(before);
assert idx != -1;
instructions.addInstruction(idx++, getBuffer.clone());
net.runelite.asm.pool.Method method = new net.runelite.asm.pool.Method(
new net.runelite.asm.pool.Class(getBuffer.getField().getType().getInternalName()),
RUNELITE_FINISH_PACKET,
new Signature("()V")
);
instructions.addInstruction(idx++, new InvokeVirtual(instructions, method));
}
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers.buffer;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class which identifies Buffer and PacketBuffer
*
* @author Adam
*/
public class BufferFinder
{
private static final Logger logger = LoggerFactory.getLogger(BufferFinder.class);
private final ClassGroup group;
private ClassFile buffer, packetBuffer;
public BufferFinder(ClassGroup group)
{
this.group = group;
}
public ClassFile getBuffer()
{
return buffer;
}
public ClassFile getPacketBuffer()
{
return packetBuffer;
}
public void find()
{
for (ClassFile cf : group.getClasses())
{
for (Method m : cf.getMethods())
{
Code code = m.getCode();
if (findModPow(code))
{
buffer = cf;
// packetBuffer extends this
packetBuffer = group.getClasses().stream()
.filter(cl -> cl.getParent() == cf)
.findAny()
.get();
logger.info("Identified buffer {}, packetBuffer {}", buffer, packetBuffer);
}
}
}
}
// Find encryptRsa in buffer
private boolean findModPow(Code code)
{
if (code == null)
{
return false;
}
Instructions instructions = code.getInstructions();
for (Instruction i : instructions.getInstructions())
{
if (!(i instanceof InvokeVirtual))
{
continue;
}
InvokeVirtual iv = (InvokeVirtual) i;
net.runelite.asm.pool.Method method = iv.getMethod();
if (method.getName().equals("modPow"))
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers.buffer;
import java.io.IOException;
import java.io.InputStream;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.FieldInstruction;
import net.runelite.asm.visitors.ClassFileVisitor;
import org.objectweb.asm.ClassReader;
public class BufferMethodInjector
{
private final BufferPayloadFinder bp;
public BufferMethodInjector(BufferPayloadFinder bp)
{
this.bp = bp;
}
public void inject() throws IOException
{
Field buffer = bp.getBuffer();
Field offset = bp.getOffset();
assert buffer.getClassFile() == offset.getClassFile();
InputStream in = getClass().getResourceAsStream("RuneliteBuffer.class");
assert in != null : "no RuneliteBuffer";
ClassFile runeliteBuffer = loadClass(in);
ClassFile bufferClass = buffer.getClassFile();
for (Field f : runeliteBuffer.getFields())
{
if (!f.getName().startsWith("runelite"))
{
continue;
}
inject(bufferClass, f);
}
for (Method m : runeliteBuffer.getMethods())
{
if (!m.getName().startsWith("runelite"))
{
continue;
}
inject(bufferClass, m);
}
}
private void inject(ClassFile bufferClass, Method method)
{
assert method.getExceptions().getExceptions().isEmpty();
Method newMethod = new Method(bufferClass, method.getName(), method.getDescriptor());
Code code = new Code(newMethod);
newMethod.setCode(code);
method.getCode().getInstructions().getInstructions().stream()
.forEach(i ->
{
if (!(i instanceof Label))
{
i = i.clone();
}
if (i instanceof FieldInstruction)
{
FieldInstruction fi = (FieldInstruction) i;
if (fi.getField().getName().equals("offset"))
{
fi.setField(bp.getOffset().getPoolField());
}
else if (fi.getField().getName().equals("payload"))
{
fi.setField(bp.getBuffer().getPoolField());
}
else if (fi.getField().getName().equals("runeliteLengthOffset"))
{
fi.setField(bufferClass.findField("runeliteLengthOffset").getPoolField());
}
}
i.setInstructions(code.getInstructions());
code.getInstructions().addInstruction(i);
});
code.getExceptions().getExceptions().addAll(method.getCode().getExceptions().getExceptions());
bufferClass.addMethod(newMethod);
}
private void inject(ClassFile bufferClass, Field field)
{
Field newField = new Field(bufferClass, field.getName(), field.getType());
newField.setAccessFlags(field.getAccessFlags());
newField.setValue(field.getValue());
bufferClass.addField(newField);
}
static ClassFile loadClass(InputStream in) throws IOException
{
ClassReader reader = new ClassReader(in);
ClassFileVisitor cv = new ClassFileVisitor();
reader.accept(cv, ClassReader.SKIP_FRAMES);
return cv.getClassFile();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers.buffer;
import net.runelite.asm.ClassFile;
import net.runelite.asm.Field;
import net.runelite.asm.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BufferPayloadFinder
{
private static final Logger logger = LoggerFactory.getLogger(BufferPayloadFinder.class);
private final ClassFile bufferClass;
private Field offset;
private Field buffer;
public BufferPayloadFinder(ClassFile bufferClass)
{
this.bufferClass = bufferClass;
}
public void find()
{
for (Field field : bufferClass.getFields())
{
if (field.getType().equals(Type.INT))
{
offset = field;
}
else if (field.getType().equals(new Type("[B")))
{
buffer = field;
}
}
logger.info("Found offset {} buffer {}", offset, buffer);
}
public Field getOffset()
{
return offset;
}
public Field getBuffer()
{
return buffer;
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers.buffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Code;
import net.runelite.asm.attributes.code.Instruction;
import static net.runelite.asm.attributes.code.InstructionType.GETFIELD;
import static net.runelite.asm.attributes.code.InstructionType.INVOKEVIRTUAL;
import net.runelite.asm.attributes.code.instructions.InvokeVirtual;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.InstructionContext;
import net.runelite.asm.signature.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketFlushFinder
{
private static final Logger logger = LoggerFactory.getLogger(PacketFlushFinder.class);
private final ClassGroup group;
private final List<InstructionContext> queueForWrite = new ArrayList<>();
public PacketFlushFinder(ClassGroup group)
{
this.group = group;
}
public List<InstructionContext> getQueueForWrite()
{
return queueForWrite;
}
public void find()
{
ClassFile client = group.findClass("Client");
if (client == null)
{
client = group.findClass("client");
}
for (Method method : client.getMethods())
{
find(method);
}
}
private void find(Method method)
{
Code code = method.getCode();
Set<Instruction> checked = new HashSet<>();
Execution e = new Execution(group);
e.addMethod(method);
e.noInvoke = true;
e.noExceptions = true;
e.addExecutionVisitor(ic ->
{
Instruction i = ic.getInstruction();
if (checked.contains(i))
{
return;
}
checked.add(i);
if (i.getType() != INVOKEVIRTUAL)
{
return;
}
InvokeVirtual iv = (InvokeVirtual) i;
// queueForWrite
if (!iv.getMethod().getType().equals(new Signature("([BII)V")))
{
return;
}
InstructionContext lengthCtx = ic.getPops().get(0).getPushed();
if (lengthCtx.getInstruction().getType() != GETFIELD)
{
return;
}
queueForWrite.add(ic);
});
e.run();
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.deobfuscators.transformers.buffer;
import java.io.UnsupportedEncodingException;
/**
* Methods injected into runescape-client's Buffer
*
* @author Adam
*/
public class RuneliteBuffer
{
// so that it compiles
private int offset;
private byte[] payload;
private int runeliteLengthOffset;
public int getOffset()
{
return offset;
}
public void setOffset(int offset)
{
this.offset = offset;
}
public byte[] getPayload()
{
return payload;
}
public void setPayload(byte[] payload)
{
this.payload = payload;
}
public int getRuneliteLengthOffset()
{
return runeliteLengthOffset;
}
public void setRuneliteLengthOffset(int runeliteLengthOffset)
{
this.runeliteLengthOffset = runeliteLengthOffset;
}
public byte runeliteReadByte()
{
offset += 1;
return payload[offset - 1];
}
public short runeliteReadShort()
{
offset += 2;
return (short) (((payload[offset - 2] & 0xFF) << 8)
| (payload[offset - 1] & 0xFF));
}
public int runeliteReadInt()
{
offset += 4;
return ((payload[offset - 4] & 0xFF) << 24)
| ((payload[offset - 3] & 0xFF) << 16)
| ((payload[offset - 2] & 0xFF) << 8)
| (payload[offset - 1] & 0xFF);
}
public long runeliteReadLong()
{
offset += 8;
return ((payload[offset - 8] & 0xFFL) << 56)
| ((payload[offset - 7] & 0xFFL) << 48)
| ((payload[offset - 6] & 0xFFL) << 40)
| ((payload[offset - 5] & 0xFFL) << 32)
| ((payload[offset - 4] & 0xFFL) << 24)
| ((payload[offset - 3] & 0xFFL) << 16)
| ((payload[offset - 2] & 0xFFL) << 8)
| (payload[offset - 1] & 0xFFL);
}
public String runeliteReadString()
{
int length = runeliteReadShort();
if (length < 0)
{
throw new RuntimeException("length < 0");
}
offset += length;
try
{
return new String(payload, offset - length, length, "UTF-8");
}
catch (UnsupportedEncodingException ex)
{
throw new RuntimeException(ex);
}
}
public void runeliteWriteByte(byte b)
{
payload[offset++] = b;
}
public void runeliteWriteShort(short s)
{
payload[offset++] = (byte) (s >> 8);
payload[offset++] = (byte) s;
}
public void runeliteWriteInt(int i)
{
payload[offset++] = (byte) (i >> 24);
payload[offset++] = (byte) (i >> 16);
payload[offset++] = (byte) (i >> 8);
payload[offset++] = (byte) i;
}
public void runeliteWriteLong(long l)
{
payload[offset++] = (byte) (l >> 56);
payload[offset++] = (byte) (l >> 48);
payload[offset++] = (byte) (l >> 40);
payload[offset++] = (byte) (l >> 32);
payload[offset++] = (byte) (l >> 24);
payload[offset++] = (byte) (l >> 16);
payload[offset++] = (byte) (l >> 8);
payload[offset++] = (byte) l;
}
public void runeliteWriteString(String s)
{
byte[] bytes;
try
{
bytes = s.getBytes("UTF-8");
}
catch (UnsupportedEncodingException ex)
{
throw new RuntimeException(ex);
}
runeliteWriteShort((short) bytes.length);
for (byte b : bytes)
{
payload[offset++] = b;
}
}
public void runeliteInitPacket()
{
runeliteLengthOffset = offset;
runeliteWriteShort((short) 0); // flush() relies on default length of 0
}
public void runeliteFinishPacket()
{
if (runeliteLengthOffset > 0)
{
int length = offset - runeliteLengthOffset - 2;
if (length < 0)
{
// on flush() it sets offset = 0
// but runeliteLengthOffset remains >0
return;
}
payload[runeliteLengthOffset++] = (byte) (length >> 8);
payload[runeliteLengthOffset++] = (byte) length;
runeliteLengthOffset = 0;
}
}
}

View File

@@ -0,0 +1,309 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.s2c;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.InstructionType;
import net.runelite.asm.attributes.code.Instructions;
import net.runelite.asm.attributes.code.Label;
import net.runelite.asm.attributes.code.instruction.types.JumpingInstruction;
import net.runelite.asm.attributes.code.instruction.types.PushConstantInstruction;
import net.runelite.asm.attributes.code.instructions.GetStatic;
import net.runelite.asm.attributes.code.instructions.If;
import net.runelite.asm.attributes.code.instructions.VReturn;
import net.runelite.asm.execution.Execution;
import net.runelite.asm.execution.Frame;
import net.runelite.asm.execution.InstructionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HandlerFinder
{
private static final Logger logger = LoggerFactory.getLogger(HandlerFinder.class);
private final ClassGroup group;
private final Field packetType;
private final Execution execution;
public HandlerFinder(ClassGroup group, Field packetType)
{
this.group = group;
this.packetType = packetType;
this.execution = new Execution(group);
}
public ClassGroup getGroup()
{
return group;
}
public Execution getExecution()
{
return execution;
}
public PacketHandlers findHandlers()
{
// Some of the packet handlers are if (type == 1 || type == 2) func()
// and func() checks the type... so search all functions for packet handlers
List<PacketHandler> handlers = new ArrayList<>();
for (ClassFile cf : group.getClasses())
{
for (Method method : cf.getMethods())
{
if (method.getCode() == null)
{
continue;
}
List<PacketHandler> h = findHandlers(method, packetType);
handlers.addAll(h);
}
}
PacketHandlers packetHandlers = new PacketHandlers(group, packetType, handlers);
removeDuplicates(packetHandlers);
prepareFrame(execution, packetHandlers);
return packetHandlers;
}
private List<PacketHandler> findHandlers(Method process, Field packetOpcode)
{
List<PacketHandler> handlers = new ArrayList<>();
Instructions ins = process.getCode().getInstructions();
for (int j = 0; j < ins.getInstructions().size(); ++j)
{
Instruction i = ins.getInstructions().get(j);
if (i.getType() != InstructionType.GETSTATIC)
{
continue;
}
GetStatic gs = (GetStatic) i;
if (gs.getMyField() != packetOpcode)
{
continue;
}
Instruction push = ins.getInstructions().get(j + 1);
if (!(push instanceof PushConstantInstruction))
{
continue;
}
PushConstantInstruction pci = (PushConstantInstruction) push;
if (!(pci.getConstant() instanceof Number))
{
continue;
}
int opcode = ((Number) pci.getConstant()).intValue();
if (opcode == -1)
{
continue;
}
Instruction jump = ins.getInstructions().get(j + 2);
if (jump.getType() != InstructionType.IF_ICMPEQ && jump.getType() != InstructionType.IF_ICMPNE)
{
continue;
}
Instruction start, end;
if (jump.getType() == InstructionType.IF_ICMPEQ)
{
// this seems to not ever happen
start = ((If) jump).getJumps().get(0);
//end = ins.getInstructions().get(j + 3);
end = null;
}
else
{
start = ins.getInstructions().get(j + 3);
end = ((If) jump).getJumps().get(0);
}
PacketHandler handler = new PacketHandler(process, jump, start, push, opcode);
handlers.add(handler);
if (end != null)
{
// Anything else which jumps to here instead needs to return.
insertReturn(ins, jump, end);
}
logger.info("Found packet handler {} opcode {}", handler, handler.getOpcode());
}
return handlers;
}
private void removeDuplicates(PacketHandlers handlers)
{
// remove handlers which have multiple opcodes
Multimap<Instruction, PacketHandler> i2h = HashMultimap.create();
for (PacketHandler handler : handlers.getHandlers())
{
i2h.put(handler.getStart(), handler);
}
for (Instruction i : i2h.keySet())
{
int sz = i2h.get(i).size();
if (sz == 1)
{
continue;
}
// this is part of if (opcode == 1 || opcode == 2 || ...) func();
for (PacketHandler ph : i2h.get(i))
{
handlers.getHandlers().remove(ph);
logger.debug("Removed duplicate handler {}", ph);
}
}
}
private void prepareFrame(Execution e, PacketHandlers handlers)
{
List<Method> methods = handlers.getHandlers().stream()
.map(handler -> handler.getMethod())
.distinct()
.collect(Collectors.toList());
for (Method method : methods)
{
List<PacketHandler> phandlers = handlers.getHandlers().stream()
.filter(handler -> handler.getMethod() == method)
.collect(Collectors.toList());
prepareFrame(e, method, phandlers);
}
}
private void prepareFrame(Execution e, Method method, List<PacketHandler> handlers)
{
e.step = true;
Frame f = new Frame(e, method);
f.initialize();
e.addFrame(f);
while (e.frames.isEmpty() == false)
{
f = e.frames.get(0);
if (!f.isExecuting())
{
e.frames.remove(0);
continue;
}
assert f.isExecuting();
f.execute();
e.paused = false;
InstructionContext ctx = f.getInstructions().get(f.getInstructions().size() - 1);
for (PacketHandler handler : handlers)
{
if (handler.getAfterRead() == ctx.getInstruction())
{
// frame is stopped at jump prior to handler
handler.frame = f.dup();
e.frames.remove(handler.frame);
logger.info("Found frame for {}: {}", handler, handler.frame);
}
if (handler.getJump() == ctx.getInstruction())
{
handler.jumpFrame = f.dup();
e.frames.remove(handler.jumpFrame);
assert handler.jumpFrame.isExecuting();
logger.info("Found jump frame for {}: {}", handler, handler.jumpFrame);
}
}
}
}
private void insertReturn(Instructions ins, Instruction start, Instruction end)
{
assert end instanceof Label;
int idx = ins.getInstructions().indexOf(end);
assert idx != -1;
Instruction before = ins.getInstructions().get(idx - 1);
if (before.getType() == InstructionType.RETURN) //XXX check isTerminal?
{
return;
}
// insert return before end
logger.info("Inserting return before {}", end);
Instruction ret = new VReturn(ins);
ins.addInstruction(idx, ret);
Label label = ins.createLabelFor(ret);
// Change jumps which go to the next handler to instead go to return
for (Instruction i : ins.getInstructions())
{
if (i instanceof JumpingInstruction)
{
JumpingInstruction j = (JumpingInstruction) i;
if (i == start)
{
continue;
}
if (j.getJumps().size() == 1 && j.getJumps().get(0) == end)
{
j.setJumps(Collections.singletonList(label));
}
}
}
}
}

View File

@@ -0,0 +1,176 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.s2c;
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.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.code.Instruction;
import net.runelite.asm.attributes.code.instruction.types.LVTInstruction;
import net.runelite.asm.execution.Frame;
import net.runelite.deob.deobfuscators.packethandler.PacketRead;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PacketHandler implements Cloneable
{
private static final Logger logger = LoggerFactory.getLogger(PacketHandler.class);
private final Method method;
private final Instruction jump; // jump for the packet handler
private final Instruction start; // first instruction in the handler
private final Instruction push; // instruction which pushes opcode constant
private final int opcode;
public Frame frame; // after read
public Frame jumpFrame;
public List<Instruction> mappable = new ArrayList<>(); // mappable instructions
public List<PacketRead> reads = new ArrayList<>(); // instructions which read packet data
public List<PacketRead> sortedReads;
public Map<Integer, Integer> lvtOrder = new HashMap<>(); // access order of lvt, lvt index -> order
public Set<Field> fieldRead = new HashSet<>();
public Set<Field> fieldWrite = new HashSet<>();
public Set<Method> methodInvokes = new HashSet<>();
public List<Object> constants = new ArrayList<>();
public PacketHandler(Method method, Instruction jump, Instruction start, Instruction push, int opcode)
{
this.method = method;
this.jump = jump;
this.start = start;
this.push = push;
this.opcode = opcode;
}
@Override
public String toString()
{
return "PacketHandler{" + "start=" + start + ", opcode=" + opcode + '}';
}
@Override
public PacketHandler clone()
{
try
{
return (PacketHandler) super.clone();
}
catch (CloneNotSupportedException ex)
{
throw new RuntimeException(ex);
}
}
public Method getMethod()
{
return method;
}
public Instruction getJump()
{
return jump;
}
public Instruction getStart()
{
return start;
}
public Instruction getPush()
{
return push;
}
public Instruction getAfterRead()
{
if (reads.isEmpty())
{
return null;
}
PacketRead last = reads.get(reads.size() - 1);
if (last.getStore() == null)
{
return null;
}
List<Instruction> ins = method.getCode().getInstructions().getInstructions();
int idx = ins.indexOf(last.getStore());
if (idx == -1)
{
return null; // can be a read in not this function
}
return ins.get(idx + 1);
}
public int getOpcode()
{
return opcode;
}
public boolean hasPacketRead(Instruction i)
{
for (PacketRead pr : reads)
{
if (pr.getInvoke() == i)
{
return true;
}
}
return false;
}
public void findReorderableReads()
{
for (PacketRead pr : reads)
{
//InstructionContext invokeCtx = pr.getInvokeCtx();
List<Instruction> instructions = pr.getInvoke().getInstructions().getInstructions();
// look for an lvt store immediately after
int invokeIdx = instructions.indexOf(pr.getInvoke());
assert invokeIdx != -1;
Instruction next = instructions.get(invokeIdx + 1);
if (next instanceof LVTInstruction)
{
LVTInstruction lvt = (LVTInstruction) next;
if (lvt.store())
{
logger.info("Found lvt store {} for {}", next, pr.getInvoke());
pr.setStore(next);
}
}
}
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.s2c;
import java.util.List;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
public class PacketHandlers
{
private final ClassGroup group;
private final Field packetType;
private final List<PacketHandler> handlers;
public PacketHandlers(ClassGroup group, Field packetType, List<PacketHandler> handlers)
{
this.group = group;
this.packetType = packetType;
this.handlers = handlers;
}
public PacketHandler find(int opcode)
{
for (PacketHandler handler : handlers)
{
if (handler.getOpcode() == opcode)
{
return handler;
}
}
return null;
}
public ClassGroup getGroup()
{
return group;
}
public Field getPacketType()
{
return packetType;
}
public List<PacketHandler> getHandlers()
{
return handlers;
}
}

View File

@@ -0,0 +1,120 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.updater;
import java.util.Arrays;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.Type;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.asm.attributes.annotation.Element;
public class AnnotationCopier
{
private final ClassGroup group1, group2;
private final Type[] types;
public AnnotationCopier(ClassGroup group1, ClassGroup group2, Type... types)
{
this.group1 = group1;
this.group2 = group2;
this.types = types;
}
public void copy()
{
for (ClassFile cf1 : group1.getClasses())
{
ClassFile cf2 = group2.findClass(cf1.getName());
assert cf2 != null;
copy(cf1.getAnnotations(), cf2.getAnnotations());
for (Field f : cf1.getFields())
{
Field f2 = cf2.findField(f.getName(), f.getType());
assert f2 != null || f.getAnnotations() == null;
if (f2 == null)
continue;
copy(f.getAnnotations(), f2.getAnnotations());
}
for (Method m : cf1.getMethods())
{
Method m2 = cf2.findMethod(m.getName(), m.getDescriptor());
assert m2 != null || m.getAnnotations() == null;
if (m2 == null)
continue;
copy(m.getAnnotations(), m2.getAnnotations());
}
}
}
private void copy(Annotations an, Annotations an2)
{
for (Annotation a : an2.getAnnotations())
{
if (isType(a.getType()))
{
an2.removeAnnotation(a);
}
}
for (Annotation a : an.getAnnotations())
{
if (!isType(a.getType()))
continue;
Annotation a2 = new Annotation(an2);
a2.setType(a.getType());
for (Element element : a.getElements())
{
Element element2 = new Element(a2);
element2.setName(element.getName());
element2.setValue(element.getValue());
a2.addElement(element2);
}
an2.addAnnotation(a2);
}
}
private boolean isType(Type type)
{
return Arrays.asList(types).contains(type);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.updater;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Field;
import net.runelite.asm.Method;
import net.runelite.asm.attributes.Annotations;
import net.runelite.asm.attributes.annotation.Annotation;
import net.runelite.deob.DeobAnnotations;
import net.runelite.deob.deobfuscators.Renamer;
import net.runelite.deob.util.NameMappings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AnnotationRenamer
{
private static final Logger logger = LoggerFactory.getLogger(AnnotationRenamer.class);
private ClassGroup group;
public AnnotationRenamer(ClassGroup group)
{
this.group = group;
}
public void run()
{
NameMappings mappings = buildMappings();
Renamer renamer = new Renamer(mappings);
renamer.run(group);
}
private NameMappings buildMappings()
{
NameMappings mappings = new NameMappings();
for (ClassFile cf : group.getClasses())
{
String name = getImplements(cf.getAnnotations());
if (name != null)
mappings.map(cf.getPoolClass(), name);
for (Field f : cf.getFields())
{
name = DeobAnnotations.getExportedName(f.getAnnotations());
if (name != null)
mappings.map(f.getPoolField(), name);
}
for (Method m : cf.getMethods())
{
name = DeobAnnotations.getExportedName(m.getAnnotations());
if (name != null)
mappings.map(m.getPoolMethod(), name);
}
}
return mappings;
}
private String getImplements(Annotations annotations)
{
Annotation an = annotations.find(DeobAnnotations.IMPLEMENTS);
return an != null ? an.getElement().getString() : null;
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2018, Morgan Lewis <http://github.com/MESLewis>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.updater;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.Method;
import net.runelite.deob.deobfuscators.mapping.ParallelExecutorMapping;
public class ParameterRenamer
{
private final ClassGroup source;
private final ClassGroup dest;
private final ParallelExecutorMapping mapping;
public ParameterRenamer(ClassGroup source, ClassGroup dest, ParallelExecutorMapping mapping)
{
this.source = source;
this.dest = dest;
this.mapping = mapping;
}
public void run()
{
for (ClassFile sourceCF : source.getClasses())
{
for (Method sourceM : sourceCF.getMethods())
{
Method destM = (Method) mapping.get(sourceM);
if (destM != null)
{
destM.setParameters(sourceM.getParameters());
}
}
}
}
}

View File

@@ -0,0 +1,96 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.updater;
import java.io.File;
import java.io.IOException;
import net.runelite.asm.ClassGroup;
import net.runelite.deob.deobfuscators.mapping.AnnotationIntegrityChecker;
import net.runelite.deob.deobfuscators.mapping.AnnotationMapper;
import net.runelite.deob.deobfuscators.mapping.Mapper;
import net.runelite.deob.deobfuscators.mapping.ParallelExecutorMapping;
import net.runelite.deob.util.JarUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UpdateMappings
{
private static final Logger logger = LoggerFactory.getLogger(UpdateMappings.class);
private final ClassGroup group1, group2;
public UpdateMappings(ClassGroup group1, ClassGroup group2)
{
this.group1 = group1;
this.group2 = group2;
}
public void update()
{
Mapper mapper = new Mapper(group1, group2);
mapper.run();
ParallelExecutorMapping mapping = mapper.getMapping();
AnnotationMapper amapper = new AnnotationMapper(group1, group2, mapping);
amapper.run();
AnnotationIntegrityChecker aic = new AnnotationIntegrityChecker(group1, group2, mapping);
aic.run();
int errors = aic.getErrors();
if (errors > 0)
{
logger.warn("Errors in annotation checker, exiting");
System.exit(-1);
}
AnnotationRenamer an = new AnnotationRenamer(group2);
an.run();
ParameterRenamer pr = new ParameterRenamer(group1, group2, mapping);
pr.run();
}
public void save(File out) throws IOException
{
JarUtil.saveJar(group2, out);
}
public static void main(String[] args) throws IOException
{
if (args.length < 3)
{
System.exit(-1);
}
UpdateMappings u = new UpdateMappings(
JarUtil.loadJar(new File(args[0])),
JarUtil.loadJar(new File(args[1]))
);
u.update();
u.save(new File(args[2]));
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.util;
public class IdGen
{
private int cur = 1;
public synchronized int get()
{
return cur++;
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import java.util.jar.Manifest;
import net.runelite.asm.ClassFile;
import net.runelite.asm.ClassGroup;
import net.runelite.asm.objectwebasm.NonloadingClassWriter;
import net.runelite.asm.visitors.ClassFileVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.util.CheckClassAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JarUtil
{
private static final Logger logger = LoggerFactory.getLogger(JarUtil.class);
public static ClassGroup loadJar(File jarfile) throws IOException
{
ClassGroup group = new ClassGroup();
try (JarFile jar = new JarFile(jarfile))
{
for (Enumeration<JarEntry> it = jar.entries(); it.hasMoreElements();)
{
JarEntry entry = it.nextElement();
if (!entry.getName().endsWith(".class"))
{
continue;
}
InputStream is = jar.getInputStream(entry);
ClassReader reader = new ClassReader(is);
ClassFileVisitor cv = new ClassFileVisitor();
reader.accept(cv, ClassReader.SKIP_FRAMES);
group.addClass(cv.getClassFile());
}
}
group.initialize();
return group;
}
public static void saveJar(ClassGroup group, File jarfile) throws IOException
{
try (JarOutputStream jout = new JarOutputStream(new FileOutputStream(jarfile), new Manifest()))
{
for (ClassFile cf : group.getClasses())
{
JarEntry entry = new JarEntry(cf.getName() + ".class");
jout.putNextEntry(entry);
byte[] data = writeClass(group, cf);
jout.write(data);
jout.closeEntry();
}
}
}
public static byte[] writeClass(ClassGroup group, ClassFile cf)
{
ClassWriter writer = new NonloadingClassWriter(group, ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
CheckClassAdapter cca = new CheckClassAdapter(writer, false);
cf.accept(cca);
byte[] data = writer.toByteArray();
validateDataFlow(cf.getName(), data);
return data;
}
private static void validateDataFlow(String name, byte[] data)
{
try
{
ClassReader cr = new ClassReader(data);
ClassWriter cw = new ClassWriter(cr, 0);
ClassVisitor cv = new CheckClassAdapter(cw, true);
cr.accept(cv, 0);
}
catch (Exception ex)
{
logger.warn("Class {} failed validation", name, ex);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.util;
import java.util.HashMap;
import java.util.Map;
import net.runelite.asm.pool.Class;
import net.runelite.asm.pool.Field;
import net.runelite.asm.pool.Method;
public class NameMappings
{
private final Map<Object, String> map = new HashMap<>();
private final Map<Object, String[]> paramMap = new HashMap<>();
public void map(Class cf, String name)
{
map.put(cf, name);
}
public void map(Field field, String name)
{
map.put(field, name);
}
public void map(Method method, String name)
{
map.put(method, name);
}
public void map(Method method, String[] params)
{
paramMap.put(method, params);
}
public String get(Object object)
{
return map.get(object);
}
public String[] getP(Method method)
{
return paramMap.get(method);
}
public Map<Object, String> getMap()
{
return map;
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (c) 2016-2017, Adam <Adam@sigterm.info>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package net.runelite.deob.util;
public class PrimitiveUtils
{
public static Class<?> unbox(Class<?> c)
{
if (c == int.class)
return Integer.class;
else if (c == long.class)
return Long.class;
else if (c == byte.class)
return Byte.class;
else if (c == char.class)
return Character.class;
else if (c == short.class)
return Short.class;
else if (c == boolean.class)
return Boolean.class;
else if (c == float.class)
return Float.class;
else if (c == double.class)
return Double.class;
else if (c == void.class)
return Void.class;
else
return c;
}
public static Object convert(Number n, Class<?> c)
{
c = unbox(c);
if (c == Integer.class)
return n.intValue();
else if (c == Long.class)
return n.longValue();
else if (c == Byte.class)
return n.byteValue();
else if (c == Short.class)
return n.shortValue();
else if (c == Float.class)
return n.floatValue();
else if (c == Double.class)
return n.doubleValue();
throw new IllegalArgumentException();
}
}