阶段四收尾:面板两行布局(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:类/方法/字段映射、推导办法、已踩的坑、验证状态
This commit is contained in:
1 parent
2e7ab7c33c
commit
8f7b72271f
8 files changed
+677
-52
No files matched your search
@@ -0,0 +1,159 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user