import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.IincInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.IntInsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.MultiANewArrayInsnNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; /** * 把"载荷里被混淆的类"和"上游 libGDX 的同名类"逐方法对上号。 * * 思路:混淆器只改名字,不改描述符(参数/返回值类型)和指令语义。所以给每个方法算一个 * **忽略名字的字节码指纹**(操作码序列 + 常量 + 成员的描述符),再拿两边的指纹去撞。 * 指纹唯一的就直接对上;撞车的(比如 expandX/expandY 这类只差一个字段的)按**声明顺序** * 对齐 —— 前提是载荷保留了上游的成员顺序(对 libGDX 这种"原样编译+混淆"成立)。 * * 用法: * java -cp tools/asm/asm.jar:build MapMethods <上游.class> <载荷.class> * 输出:每个载荷方法一行 obfName+desc 指纹唯一性 -> 上游名字 */ public final class MapMethods { public static void main(String[] args) throws Exception { ClassNode upstream = read(args[0]); ClassNode payload = read(args[1]); Map> byFingerprint = new LinkedHashMap>(); for (MethodNode method : upstream.methods) { if (method.name.startsWith("<")) continue; byFingerprint.computeIfAbsent(fingerprint(method), key -> new ArrayList()) .add(method); } // 撞车的按声明顺序登记已用掉的下标 Map used = new LinkedHashMap(); System.out.println("# " + upstream.name + " (" + upstream.methods.size() + " 个方法) <- " + payload.name + " (" + payload.methods.size() + " 个方法)"); int unique = 0; int ambiguous = 0; int miss = 0; for (MethodNode method : payload.methods) { if (method.name.startsWith("<")) continue; List candidates = byFingerprint.get(fingerprint(method)); String from = method.name + method.desc; if (candidates == null || candidates.isEmpty()) { System.out.println(String.format("%-28s ?? (上游没有同形状的方法)", from)); miss++; continue; } if (candidates.size() == 1) { System.out.println(String.format("%-28s = %s", from, candidates.get(0).name)); unique++; continue; } // 多个候选:按声明顺序取还没用过的那个 String key = fingerprint(method); int index = used.getOrDefault(key, 0); MethodNode chosen = candidates.get(Math.min(index, candidates.size() - 1)); used.put(key, index + 1); StringBuilder all = new StringBuilder(); for (MethodNode candidate : candidates) all.append(candidate.name).append(' '); System.out.println(String.format("%-28s ~%d/%-3d %s (候选: %s)", from, index + 1, candidates.size(), chosen.name, all.toString().trim())); ambiguous++; } System.out.println("# 唯一匹配 " + unique + ",按顺序消歧 " + ambiguous + ",未匹配 " + miss); } private static ClassNode read(String path) throws Exception { ClassNode node = new ClassNode(); new ClassReader(new java.io.FileInputStream(path)).accept(node, 0); return node; } /** * 忽略名字的指纹:只记操作码、常量、以及成员的**描述符**。 * 类名(NEW/CHECKCAST 之类指向的类)可能是混淆过的,所以那一部分只记操作码。 */ static String fingerprint(MethodNode method) { // 类名两边不一样(Cell vs dR),所以按"每个方法内部首次出现的顺序"给非 JDK 类编号, // 编号后的描述符就能直接比。JDK 类(java/*)名保留,不参与编号。 Map names = new LinkedHashMap(); StringBuilder out = new StringBuilder(); out.append(canon(method.desc, names)).append('\u0001'); for (AbstractInsnNode insn = method.instructions.getFirst(); insn != null; insn = insn.getNext()) { int opcode = insn.getOpcode(); if (opcode < 0) continue; // 标签/行号 out.append(opcode); if (insn instanceof FieldInsnNode) { out.append(':').append(canon(((FieldInsnNode) insn).desc, names)); } else if (insn instanceof MethodInsnNode) { out.append(':').append(canon(((MethodInsnNode) insn).desc, names)); } else if (insn instanceof TypeInsnNode) { out.append(':').append(canon("L" + ((TypeInsnNode) insn).desc + ";", names)); } else if (insn instanceof LdcInsnNode) { Object value = ((LdcInsnNode) insn).cst; out.append(':').append(value instanceof org.objectweb.asm.Type ? canon(((org.objectweb.asm.Type) value).getDescriptor(), names) : value); } else if (insn instanceof IntInsnNode) { out.append(':').append(((IntInsnNode) insn).operand); } else if (insn instanceof VarInsnNode) { out.append(':').append(((VarInsnNode) insn).var); } else if (insn instanceof IincInsnNode) { out.append(':').append(((IincInsnNode) insn).var).append('+') .append(((IincInsnNode) insn).incr); } else if (insn instanceof MultiANewArrayInsnNode) { out.append(':').append(((MultiANewArrayInsnNode) insn).dims); } else if (insn instanceof InsnNode) { // 无操作数 } else { out.append(":?").append(insn.getClass().getSimpleName()); } out.append('|'); } return out.toString(); } /** 把描述符里出现的类名换成"本方法内第 n 个出现的类",JDK 类原样保留。 */ private static String canon(String descriptor, Map names) { StringBuilder out = new StringBuilder(); for (int i = 0; i < descriptor.length(); i++) { char c = descriptor.charAt(i); if (c != 'L') { out.append(c); continue; } int end = descriptor.indexOf(';', i); if (end < 0) { out.append(c); continue; } String type = descriptor.substring(i + 1, end); if (type.startsWith("java/") || type.startsWith("javax/") || type.startsWith("sun/")) { out.append('L').append(type).append(';'); } else { String slot = names.get(type); if (slot == null) { slot = "#" + names.size(); names.put(type, slot); } out.append('L').append(slot).append(';'); } i = end; } return out.toString(); } }