Skip to content

Instantly share code, notes, and snippets.

@t81lal
Last active August 29, 2015 14:21
Show Gist options
  • Save t81lal/5e1d9e20c877b5ea6fcb to your computer and use it in GitHub Desktop.
Save t81lal/5e1d9e20c877b5ea6fcb to your computer and use it in GitHub Desktop.
package org.nullbool.api.obfuscation.refactor;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.nullbool.api.obfuscation.refactor.test.RefactorTestClass;
import org.nullbool.api.util.map.NullPermeableMap;
import org.nullbool.api.util.map.ValueCreator;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
public class BytecodeRefactorer implements Opcodes{
private final Collection<ClassNode> classes;
private final IRemapper remapper;
private final ClassTree classTree;
private final InheritedMethodMap methodChain;
private final Map<String, String> classMappings;
private final Map<String, String> fieldMappings;
private final Map<String, String> descMappings;
private final Map<String, String> methodMappings;
public BytecodeRefactorer(Collection<ClassNode> classes, IRemapper remapper) {
this.classes = classes;
this.remapper = remapper;
classTree = new ClassTree(classes);
methodChain = new InheritedMethodMap(classTree);
classMappings = new HashMap<String, String>();
fieldMappings = new HashMap<String, String>();
descMappings = new HashMap<String, String>();
methodMappings = new HashMap<String, String>();
}
public void start() {
startImpl();
}
private void startImpl() {
int fieldNodes = 0;
int methodNodes = 0;
int fieldCalls = 0;
int methodCalls = 0;
//TypeInsnNode
int newCalls = 0;
int newArray = 0;
int checkcasts = 0;
int instances = 0;
int mArrray = 0;
int classc = 0;
int iface = 0;
for (ClassNode cn : classes) {
for(FieldNode fn : cn.fields) {
fieldNodes++;
fn.name = getMappedFieldName(fn);
fn.desc = transformFieldDesc(fn.desc);
}
for(MethodNode mn : cn.methods) {
methodNodes++;
mn.name = getMappedMethodName(mn);
mn.desc = transformMethodDesc(mn.desc);
for(AbstractInsnNode ain : mn.instructions.toArray()) {
if(ain instanceof FieldInsnNode) {
fieldCalls++;
FieldInsnNode fin = (FieldInsnNode) ain;
String newOwner = getMappedClassName(fin.owner);
String newName = getMappedFieldName(fin.owner, fin.name, fin.desc);
String newDesc = transformFieldDesc(fin.desc);
fin.owner = newOwner;
fin.name = newName;
fin.desc = newDesc;
} else if(ain instanceof MethodInsnNode) {
methodCalls++;
MethodInsnNode min = (MethodInsnNode) ain;
String newOwner = getMappedClassName(min.owner);
String newName = getMappedMethodName(min.owner, min.name, min.desc);
String newDesc = transformMethodDesc(min.desc);
min.owner = newOwner;
min.name = newName;
min.desc = newDesc;
} else if(ain instanceof TypeInsnNode) {
TypeInsnNode tin = (TypeInsnNode) ain;
// if(tin.opcode() == ANEWARRAY && tin.desc.contains("fb")) {
// System.out.printf("opcode=%d, desc=%s.%n", tin.opcode(), tin.desc);
// }
if(tin.opcode() == NEW || tin.opcode() == ANEWARRAY) {
if(tin.opcode() == NEW)
newCalls++;
else
newArray++;
String desc = tin.desc;
if(desc.startsWith("[") || desc.endsWith(";")) {
tin.desc = transformFieldDesc(desc);
} else {
tin.desc = getMappedClassName(desc);
}
} else if(tin.opcode() == CHECKCAST || tin.opcode() == INSTANCEOF) {
//ALOAD 1
//CHECKCAST java/lang/Character
//INVOKEVIRTUAL java/lang/Character.charValue ()C
//Checkcasts are always object casts
if(tin.opcode() == CHECKCAST)
checkcasts++;
else
instances++;
String desc = tin.desc;
if(desc.startsWith("[") || desc.endsWith(";")) {
tin.desc = transformFieldDesc(desc);
} else {
tin.desc = getMappedClassName(desc);
}
}
} else if(ain instanceof MultiANewArrayInsnNode) {
mArrray++;
MultiANewArrayInsnNode main = (MultiANewArrayInsnNode) ain;
main.desc = transformFieldDesc(main.desc);
}
}
}
classc++;
cn.superName = getMappedClassName(cn.superName);
cn.name = getMappedClassName(cn.name);
List<String> oldInterfaces = cn.interfaces;
List<String> newInterfaces = new ArrayList<String>();
for(String oldIface : oldInterfaces) {
iface++;
newInterfaces.add(getMappedClassName(oldIface));
}
cn.interfaces = newInterfaces;
}
System.out.printf("Changed: %n");
System.out.printf(" %d classes and %d interfaces. %n", classc, iface);
System.out.printf(" %d fields and %d field calls. %n", fieldNodes, fieldCalls);
System.out.printf(" %d methods and %d method calls. %n", methodNodes, methodCalls);
System.out.printf(" %d news, %d anewarrays, %d checkcasts, %d instancofs, %d mnewarrays. %n", newCalls, newArray, checkcasts, instances, mArrray);
}
public static void main1(String[] args) throws Exception {
ClassReader cr = new ClassReader(RefactorTestClass.class.getCanonicalName());
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
List<ClassNode> classes = new ArrayList<ClassNode>();
classes.add(cn);
IRemapper remapper = new IRemapper() {
@Override
public String resolveMethodName(String owner, String name, String desc) {
return name;
}
@Override
public String resolveFieldName(String owner, String name, String desc) {
return name;
}
@Override
public String resolveClassName(String oldName) {
return oldName;
}
};
BytecodeRefactorer refactorer = new BytecodeRefactorer(classes, remapper);
refactorer.start();
System.out.println();
System.out.println(ClassPrinter.print(cn));
//
// @SuppressWarnings("deprecation")
// ClassLoader cl = new ClassLoader() {
// {
// ClassWriter cw = new ClassWriter(0);
// cn.accept(cw);
// byte[] b = cw.toByteArray();
// defineClass(b, 0, b.length);
// }
// };
//
// Class<?> c = cl.loadClass(cn.name.replace("/", "."));
//
// Object o = c.newInstance();
// call(c, o, "voidMethod", null);
// call(c, o, "primitiveMethod", null);
// call(c, o, "voidWithPrimitive", 1L);
// call(c, o, "doubleParam", 1F);
// call(c, o, "stringMethod", null);
// call(c, o, "stringss", 1F);
// call(c, o, "stirngsss", null);
// call(c, o, "objjssss", null);
// call(c, o, "intsss", null);
// call(c, o, "castTest", new String("taslkdasd"));
// call(c, o, "arrCast", new String[]{"asd", "fsdf"});
// call(c, o, "intCastTest", 1);
// call(c, o, "intsCastTest", new String[1][1]);
}
// private static void call(Class<?> c, Object o, String name, Object... args){
// for(Method m : c.getDeclaredMethods()){
// if(m.getName().equals(name)){
// try {
// System.out.println("Calling " + name);
// m.invoke(o, args);
// } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
// System.out.println("err calling " + name +" " + e.getMessage());
// }
// }
// }
// }
public String transformMethodDesc(String desc){
if(descMappings.containsKey(desc))
return descMappings.get(desc);
Type[] args = Type.getArgumentTypes(desc);
Type ret = Type.getReturnType(desc);
StringBuilder sb = new StringBuilder("(");
for(Type arg : args){
sb.append(transformFieldDesc(arg.getDescriptor()));
}
sb.append(")");
String retD = ret.getDescriptor();
if(retD.equals("V"))
sb.append("V");
else
sb.append(transformFieldDesc(ret.getDescriptor()));
String newDesc = sb.toString();
descMappings.put(desc, newDesc);
return newDesc;
}
public String transformFieldDesc(String desc){
String nonArrayDesc = desc.replace("[", "");
if(isPrimitive(nonArrayDesc))
return desc;
if(descMappings.containsKey(desc))
return descMappings.get(desc);
//Type type = Type.getType(desc);
//String oldClassName = type.getInternalName();
//Remove the L and ; on the front and back of the desc
int arraySize = desc.length() - nonArrayDesc.length();
nonArrayDesc = nonArrayDesc.substring(1, nonArrayDesc.length() - 1);
String newBaseDesc = String.format("L%s;", getMappedClassName(nonArrayDesc));
String newDesc = createArrayDescriptor(arraySize) + newBaseDesc;
descMappings.put(desc, newDesc);
return newDesc;
}
public static boolean isPrimitive(String desc) {
switch(desc){
case "I":
case "B":
case "S":
case "J":
case "D":
case "F":
case "Z":
case "C":
return true;
default:
return false;
}
}
public static String createArrayDescriptor(int size) {
StringBuilder sb = new StringBuilder();
for(int i=0; i < size; i++){
sb.append("[");
}
return sb.toString();
}
public String getMappedClassName(String oldName) {
if (classMappings.containsKey(oldName))
return classMappings.get(oldName);
String newName = remapper.resolveClassName(oldName);
if(newName == null)
newName = oldName;
else
newName = newName.replace(".", "/");
classMappings.put(oldName, newName);
return newName;
}
public String getMappedFieldName(FieldNode f){
return getMappedFieldName(f.owner.name, f.name, f.desc);
}
public String getMappedFieldName(String owner, String name, String desc) {
String fullKey = String.format("%s.%s %s", owner, name, desc);
if(fieldMappings.containsKey(fullKey)){
//if(fullKey.equals("gm.ec J")){
// System.out.println("BytecodeRefactorer.getMappedFieldName()");
//}
return fieldMappings.get(fullKey);
}
String newName = remapper.resolveFieldName(owner, name, desc);
/* If the newName is null, it means that the remapper may not be doing deep mapping,
* ie. if we have a class gm which has a field ec and another class fe which extends
* gm, then accessing fe.ec may not be mapped, even though it is referring (albeit
* indirectly) to gm.ec.
* If this is the case, we should work downwards through the class hierarchy tree and
* poll the remapper for a new name.
* eg. if we have the class gm with a field ec and we have another class fe which
* extends gm and another fu which extends fe, we look at fu.ec then fe.ec and
* then gm.ec for a new name.
*/
if(newName == null){
ClassNode topKlass = classTree.getClass(owner);
if(topKlass != null){
Set<ClassNode> tree = classTree.getSupers(topKlass);
if(tree != null && tree.size() > 0){
Iterator<ClassNode> it = tree.iterator();
while(it.hasNext()){
ClassNode next = it.next();
if(next == null)
break;
newName = remapper.resolveFieldName(next.name, name, desc);
if(newName != null)
break;
}
}
}
}
//If everything fails, we don't change the name.
if(newName == null)
newName = name;
fieldMappings.put(fullKey, newName);
return newName;
}
public String getMappedMethodName(String owner, String name, String desc){
MethodNode m = findMethod(owner, name, desc);
if(m == null)
return name;
return getMappedMethodName(m);
}
private MethodNode findMethod(String owner, String name, String desc){
ClassNode cn = classTree.getClasses().get(owner);
if(cn == null)
return null;
//throw new IllegalStateException(String.format("Class %s is not present in the cache. (%s.%s %s)", owner, owner, name, desc));
String halfKey = name + "." + desc;
for(MethodNode m : cn.methods){
if(m.halfKey().equals(halfKey))
return m;
}
return null;
}
public String getMappedMethodName(MethodNode m) {
/*step 1. check already mapped ones*/
String fullKey = m.key();
if(methodMappings.containsKey(fullKey))
return methodMappings.get(fullKey);
/*step 2. check the tree to see if any of the ones in the
* chain have already been changed (essentially
* looking it up */
/*If the method is static it won't be part of a chain
* since static methods can't be overridden.*/
if(!Modifier.isStatic(m.access)) {
ChainData cd = methodChain.getData(m);
if(cd == null){
System.err.println(m.key() +" is null " + Modifier.isStatic(m.access));
System.exit(1);
}
for(MethodNode mn : cd.getAggregates()){
if(!mn.name.equals(m.name)) {
String newName = mn.name;
methodMappings.put(fullKey, newName);
return newName;
}
}
}
/*step 3. ask the remapper*/
String newName = remapper.resolveMethodName(m.owner.name, m.name, m.desc);
methodMappings.put(fullKey, newName);
return newName;
}
public static class ClassTree {
private static final SetCreator<ClassNode> SET_CREATOR = new SetCreator<ClassNode>();
private final Map<String, ClassNode> classes;
private final NullPermeableMap<ClassNode, Set<ClassNode>> supers;
private final NullPermeableMap<ClassNode, Set<ClassNode>> delgates;
public ClassTree(Collection<ClassNode> classes) {
this(convertToMap(classes));
}
public ClassTree(Map<String, ClassNode> classes_) {
classes = classes_;
supers = new NullPermeableMap<ClassNode, Set<ClassNode>>(SET_CREATOR);
delgates = new NullPermeableMap<ClassNode, Set<ClassNode>>(SET_CREATOR);
build(classes);
}
private static Map<String, ClassNode> convertToMap(Collection<ClassNode> classes) {
Map<String, ClassNode> map = new HashMap<String, ClassNode>();
for (ClassNode cn : classes) {
map.put(cn.name, cn);
}
return map;
}
// TODO: optimise
private void build(Map<String, ClassNode> classes) {
for (ClassNode node : classes.values()) {
for (String iface : node.interfaces) {
ClassNode ifacecs = classes.get(iface);
if (ifacecs == null)
continue;
getDelegates0(ifacecs).add(node);
Set<ClassNode> superinterfaces = new HashSet<ClassNode>();
buildSubTree(classes, superinterfaces, ifacecs);
getSupers0(node).addAll(superinterfaces);
}
ClassNode currentSuper = classes.get(node.superName);
while (currentSuper != null) {
getDelegates0(currentSuper).add(node);
getSupers0(node).add(currentSuper);
for (String iface : currentSuper.interfaces) {
ClassNode ifacecs = classes.get(iface);
if (ifacecs == null)
continue;
getDelegates0(ifacecs).add(currentSuper);
Set<ClassNode> superinterfaces = new HashSet<ClassNode>();
buildSubTree(classes, superinterfaces, ifacecs);
getSupers0(currentSuper).addAll(superinterfaces);
getSupers0(node).addAll(superinterfaces);
}
currentSuper = classes.get(currentSuper.superName);
}
getSupers0(node);
getDelegates0(node);
}
if (classes.size() == delgates.size() && classes.size() == supers.size() && delgates.size() == supers.size()) {
System.out.println(String.format("Built tree for %d classes (%d del, %d sup).", classes.size(), delgates.size(), supers.size()));
} else {
System.out.println(String.format("WARNING: Built tree for %d classes (%d del, %d sup), may be erroneous.", classes.size(), delgates.size(),
supers.size()));
}
}
private void buildSubTree(Map<String, ClassNode> classes, Collection<ClassNode> superinterfaces, ClassNode current) {
superinterfaces.add(current);
for (String iface : current.interfaces) {
ClassNode cs = classes.get(iface);
if(cs != null){
getDelegates0(cs).add(current);
buildSubTree(classes, superinterfaces, cs);
}else{
System.out.println("Null interface -> " + iface);
}
}
}
public Set<MethodNode> getMethodsFromSuper(MethodNode m) {
return getMethodsFromSuper(m.owner, m.name, m.desc);
}
public Set<MethodNode> getMethodsFromSuper(ClassNode node, String name, String desc) {
Set<MethodNode> methods = new HashSet<MethodNode>();
for (ClassNode super_ : getSupers(node)) {
for (MethodNode mn : super_.methods) {
if (mn.name.equals(name) && mn.desc.equals(desc)) {
methods.add(mn);
}
}
}
return methods;
}
public Set<MethodNode> getMethodsFromDelegates(MethodNode m) {
return getMethodsFromDelegates(m.owner, m.name, m.desc);
}
public Set<MethodNode> getMethodsFromDelegates(ClassNode node, String name, String desc) {
Set<MethodNode> methods = new HashSet<MethodNode>();
for (ClassNode delegate : getDelegates(node)) {
for (MethodNode mn : delegate.methods) {
if (mn.name.equals(name) && mn.desc.equals(desc)) {
methods.add(mn);
}
}
}
return methods;
}
public MethodNode getFirstMethodFromSuper(ClassNode node, String name, String desc) {
for (ClassNode super_ : getSupers(node)) {
for (MethodNode mn : super_.methods) {
if (mn.name.equals(name) && mn.desc.equals(desc)) {
return mn;
}
}
}
return null;
}
public ClassNode getClass(String name) {
return classes.get(name);
}
public boolean isInherited(ClassNode cn, String name, String desc) {
return getFirstMethodFromSuper(cn, name, desc) != null;
}
public boolean isInherited(ClassNode first, MethodNode mn) {
return mn.owner.name.equals(first.name) && isInherited(mn.owner, mn.name, mn.desc);
}
private Set<ClassNode> getSupers0(ClassNode cn) {
return supers.getNotNull(cn);
}
private Set<ClassNode> getDelegates0(ClassNode cn) {
return delgates.getNotNull(cn);
}
public Map<String, ClassNode> getClasses() {
return classes;
}
public Set<ClassNode> getSupers(ClassNode cn) {
return Collections.unmodifiableSet(supers.get(cn));
// return supers.get(cn);
}
public Set<ClassNode> getDelegates(ClassNode cn) {
return Collections.unmodifiableSet(delgates.get(cn));
// return delgates.get(cn);
}
}
public static class InheritedMethodMap {
private final Map<MethodNode, ChainData> methods;
public InheritedMethodMap(ClassTree tree) {
methods = new HashMap<MethodNode, ChainData>();
build(tree);
}
private void build(ClassTree tree) {
int mCount = 0;
int aCount = 0;
for (ClassNode node : tree.getClasses().values()) {
for (MethodNode m : node.methods) {
if (!Modifier.isStatic(m.access)) {
Set<MethodNode> supers = tree.getMethodsFromSuper(node, m.name, m.desc);
Set<MethodNode> delegates = tree.getMethodsFromDelegates(node, m.name, m.desc);
ChainData data = new ChainData(m, supers, delegates);
this.methods.put(m, data);
mCount ++;
aCount += data.getAggregates().size();
}
}
}
System.out.println(String.format("Built map with %d methods connected with %d others.", mCount, aCount));
//for(ChainData data : methods.values()){
// System.out.println(data);
//}
}
public ChainData getData(MethodNode m) {
return methods.get(m);
}
}
public static class ChainData {
private final MethodNode centre;
private final Set<MethodNode> supers;
private final Set<MethodNode> delegates;
private final Set<MethodNode> aggregates;
public ChainData(MethodNode m, Set<MethodNode> supers, Set<MethodNode> delegates) {
this.centre = m;
this.supers = supers;
this.delegates = delegates;
this.supers.remove(m);
this.delegates.remove(m);
aggregates = new HashSet<MethodNode>();
aggregates.addAll(supers);
aggregates.addAll(delegates);
}
public Set<MethodNode> getSupers() {
return supers;
}
public Set<MethodNode> getDelegates() {
return delegates;
}
public Set<MethodNode> getAggregates() {
return aggregates;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append("Centre: ").append(centre.key()).append(" (").append(supers.size()).append(", ").append(delegates.size()).append(")");
boolean sups = supers.size() > 0;
boolean dels = delegates.size() > 0;
if(sups || dels){
sb.append("\n");
}
if(sups){
sb.append(" >S>U>P>E>R>S>\n");
Iterator<MethodNode> it = supers.iterator();
while(it.hasNext()){
MethodNode sup = it.next();
sb.append(" ").append(sup.key());
if(it.hasNext() || dels)
sb.append("\n");
}
}
if(dels){
sb.append(" >D>E>L>E>G>A>T>E>S>\n");
Iterator<MethodNode> it = delegates.iterator();
while(it.hasNext()){
MethodNode del = it.next();
sb.append(" ").append(del.key());
if(it.hasNext())
sb.append("\n");
}
}
return sb.toString();
}
}
public static class SetCreator<T> implements ValueCreator<Set<T>> {
@Override
public Set<T> create() {
return new HashSet<T>();
}
}
}
package org.nullbool.api.obfuscation.refactor;
public abstract interface IRemapper {
public abstract String resolveClassName(String oldName);
public abstract String resolveFieldName(String owner, String name, String desc);
public abstract String resolveMethodName(String owner, String name, String desc);
}
package org.nullbool.api.util.map;
import java.util.HashMap;
public class NullPermeableMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
private final ValueCreator<V> creator;
public NullPermeableMap(ValueCreator<V> creator) {
this.creator = creator;
}
public NullPermeableMap() {
this(new NullCreator<V>());
}
public V getNotNull(K k) {
V val = get(k);
if (val == null) {
val = creator.create();
put(k, val);
}
return val;
}
private static class NullCreator<V> implements ValueCreator<V> {
@Override
public V create() {
return null;
}
}
}
private void dumpJar(HookMap hookMap) {
Map<String, ClassHook> classes = new HashMap<String, ClassHook>();
Map<String, FieldHook> fields = new HashMap<String, FieldHook>();
for(ClassHook h : hookMap.getClasses()){
classes.put(h.getObfuscated(), h);
for(FieldHook f : h.getFields()){
fields.put(f.getOwner().getObfuscated() + "." + f.getName().getObfuscated() + " " + f.getDesc().getObfuscated(), f);
}
}
IRemapper remapper = new IRemapper() {
@Override
public String resolveMethodName(String owner, String name, String desc) {
return name;
}
@Override
public String resolveFieldName(String owner, String name, String desc) {
String key = owner + "." + name + " " + desc;
if(fields.containsKey(key)){
return fields.get(key).getName().getRefactored();
}
return null;
}
@Override
public String resolveClassName(String owner) {
ClassHook ref = classes.get(owner);
if(ref != null)
return ref.getRefactored();
return owner;
}
};
BytecodeRefactorer refactorer = new BytecodeRefactorer((Collection<ClassNode>) contents.getClassContents(), remapper);
refactorer.start();
CompleteJarDumper dumper = new CompleteJarDumper(contents);
String name = getRevision().getName();
File file = new File("out/" + name + "/refactor" + name + ".jar");
if (file.exists())
file.delete();
file.mkdirs();
try {
dumper.dump(file);
} catch (IOException e) {
e.printStackTrace();
}
}
package org.nullbool.api.util.map;
public abstract interface ValueCreator<V> {
public abstract V create();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment