Files
Spine_lab/tools/obfmap/MapMethods.java
T
tianmo 8f7b72271f 阶段四收尾:面板两行布局(row/expand/fill)+ 英文输出 + 混淆名对照表
- ViewInstaller:用 Table.row()/Cell.expand()+fill()/setFillParent 排出"
  按钮一行、可滚动文字区一行"的面板;修掉把 setDebug 当成 setFillParent 的误用
- Scene2dPanel:按钮 isChecked/setChecked 与 Label.setText 改用对照表定名,
  修掉"点一次反复触发"与"文字不刷新"两个 bug
- SceneReader:面板输出改英文(编辑器缺中文字形,中文会渲染成方块)
- 新增 tools/obfmap/MapMethods.java:载荷 vs 上游 libGDX 的字节码指纹比对工具
- 新增 混淆名对照表.md:类/方法/字段映射、推导办法、已踩的坑、验证状态
2026-10-16 14:06:42 +08:00

160 lines
6.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String, List<MethodNode>> byFingerprint = new LinkedHashMap<String, List<MethodNode>>();
for (MethodNode method : upstream.methods) {
if (method.name.startsWith("<")) continue;
byFingerprint.computeIfAbsent(fingerprint(method), key -> new ArrayList<MethodNode>())
.add(method);
}
// 撞车的按声明顺序登记已用掉的下标
Map<String, Integer> used = new LinkedHashMap<String, Integer>();
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<MethodNode> 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<String, String> names = new LinkedHashMap<String, String>();
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<String, String> 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();
}
}