// 在类定义入口抓字节:判断编辑器的类是在哪个环节被解密的。 // // 用法(attach 到正在跑的编辑器,或 spawn): // rea instrument-with-frida attach --script-file frida-dump-classes.js --duration-ms 60000 // // 输出: // /tmp/spine-classes/plain/*.class —— 在入口就已经是 cafebabe 的类(直接可用) // /tmp/spine-classes/meta.txt —— 每次调用的名字 + 前 8 字节 + 长度 const OUT = "/tmp/spine-classes"; function ensureDirs() { try { new File(OUT, "r"); } catch (e) { new File(OUT, "w").close(); } const plain = OUT + "/plain"; try { new File(plain, "r"); } catch (e) { new File(plain, "w").close(); } } let metaFile = null; function meta(line) { try { if (metaFile === null) metaFile = new File(OUT + "/meta.txt", "a"); metaFile.write(line + "\n"); metaFile.flush(); } catch (e) { } } let dumped = 0; let seen = 0; function hex(bytes, count) { let s = ""; for (let i = 0; i < count && i < bytes.length; i++) { s += ("0" + bytes[i].toString(16)).slice(-2); } return s; } function handle(namePtr, bufPtr, len, where) { seen++; if (bufPtr.isNull() || len <= 8 || len > 8 * 1024 * 1024) return; let name = ""; try { name = namePtr.isNull() ? "(null)" : namePtr.readUtf8String(); } catch (e) { name = "(?)"; } let head; try { head = Memory.readByteArray(bufPtr, Math.min(len, 8)); } catch (e) { return; } const bytes = new Uint8Array(head); const tag = hex(bytes, 4); if (seen < 40) send({ where: where, name: name, len: len, head: hex(bytes, 8) }); if (tag === "cafebabe") { dumped++; try { const data = Memory.readByteArray(bufPtr, len); const file = new File(OUT + "/plain/" + (name ? name.replace(/[\\/]/g, "_") : ("anon" + dumped)) + ".class", "wb"); file.write(data); file.close(); } catch (e) { } } else if (seen < 40) { meta(where + " " + name + " len=" + len + " head=" + hex(bytes, 8)); } } // Frida 17 去掉了 Module.findExportByName,这里把新旧几套 API 都试一遍。 function exportOf(module, name) { try { if (typeof module.findExportByName === "function") return module.findExportByName(name); } catch (e) { } try { if (typeof module.getExportByName === "function") return module.getExportByName(name); } catch (e) { } try { if (typeof Module.findExportByName === "function") return Module.findExportByName(module.name, name); } catch (e) { } try { if (typeof Module.getExportByName === "function") return Module.getExportByName(module.name, name); } catch (e) { } return null; } function hookModule() { const libjvm = Process.findModuleByName("libjvm.dylib"); if (!libjvm) { send({ error: "libjvm.dylib 未加载" }); return false; } send({ module: libjvm.name, base: libjvm.base.toString(), size: libjvm.size }); const targets = [ "JVM_DefineClassWithSource", "JVM_DefineClass", "JVM_LookupDefineClass", "_JVM_DefineClassWithSource", "_JVM_DefineClass", "_JVM_LookupDefineClass", ]; let hooked = 0; for (const name of targets) { const addr = exportOf(libjvm, name); if (!addr) continue; hooked++; send({ hooked: name, addr: addr.toString() }); Interceptor.attach(addr, { onEnter(args) { // (JNIEnv*, const char* name, jobject loader, const jbyte* buf, jsize len, jobject pd[, const char* source]) try { handle(args[1], args[3], args[4].toInt32(), name); } catch (e) { } }, }); } send({ hookedCount: hooked }); return hooked > 0; } ensureDirs(); if (!hookModule()) { // libjvm 还没加载:用模块加载通知(拿不到就退回到高频轮询), // 因为类定义紧接着 libjvm 加载就开始了,晚了就什么都抓不到。 let done = false; try { Process.attachModuleObserver({ onAdded(module) { if (done || module.name !== "libjvm.dylib") return; done = true; hookModule(); }, }); } catch (e) { } const waiter = setInterval(function () { if (done) { clearInterval(waiter); return; } if (Process.findModuleByName("libjvm.dylib")) { done = true; clearInterval(waiter); hookModule(); } }, 20); } setInterval(function () { send({ seen: seen, dumped: dumped }); }, 10000);