// mine_evidence.js - 从混淆代码里挖掘"原始命名证据" // 用法: node mine_evidence.js [src目录] [输出json] // // 依据(编译产物里保留的线索): // 1) 懒加载组件 getter : var x = t.<字段>; if (null != x) return x; vj("<组件名>") // 2) 服务定位器 : t.<字段>.e2b(t, ct("<服务名>", 1, ...)) / x$("<服务名>", ...) // 3) 闭包原名 : fn.callableName = "<原名>" // 4) 数据类 toString : Q(类).toString = function () { return "<类名>(" + ... } // 5) 枚举构造 : E = new 枚举类("<成员名>", 0) // 6) 字符串常量频次(辅助命名) // // 结构说明:**类名/局部名都是模块内作用域**,因此按模块分域存放; // 属性名(字段)是全局共享字符串,故全局聚合。 const fs = require('fs'); const path = require('path'); const PROJECT = path.resolve(__dirname, '..'); const argv = process.argv.slice(2); const SRC_DIR = path.resolve(argv[0] || path.join(PROJECT, 'src')); const OUT = path.resolve(argv[1] || path.join(PROJECT, 'names', 'evidence.json')); function Push(Target, Key, Value) { if (Target instanceof globalThis.Map) { if (!Target.has(Key)) Target.set(Key, []); const List = Target.get(Key); if (List.indexOf(Value) < 0) List.push(Value); return; } // 普通对象:Key → [Value, ...] if (!Target[Key]) Target[Key] = []; if (Target[Key].indexOf(Value) < 0) Target[Key].push(Value); } function Run() { const Files = fs.readdirSync(SRC_DIR).filter((F) => /^module_\d+\.js$/.test(F)).sort(); const ByModule = {}; // 模块ID → 各类证据 const FieldsGlobal = new Map(); // 字段 → 组件/服务名(全局) const Services = new Set(); const Strings = new Map(); const Stats = { modules: 0, fields: 0, classNames: 0, enumMembers: 0, closures: 0, exports: 0 }; for (const F of Files) { const Text = fs.readFileSync(path.join(SRC_DIR, F), 'utf8'); const Id = (F.match(/module_(\d+)/) || [])[1]; const Mod = { classNames: {}, closures: {}, enums: {}, exports: {}, componentGetters: {} }; let M; // 1) 懒加载组件 getter(容忍换行/空格) const GetterRe = /var\s+([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)\.([A-Za-z_$][\w$]*)\s*;[\s\S]{0,40}?if\s*\(\s*null\s*!=\s*\1\s*\)\s*return\s+\1\s*;[\s\S]{0,40}?(?:vj|Ys|rq)\s*\(\s*"([^"]+)"\s*\)/g; while ((M = GetterRe.exec(Text)) !== null) { Mod.componentGetters[M[3]] = M[4]; Push(FieldsGlobal, M[3], M[4]); } // 2) 服务定位器 ct("name", 1, ...) / x$("name", ...) for (M of Text.matchAll(/\b(?:ct|x\$|E\$|ct)\s*\(\s*"([^"]+)"\s*,\s*\d+/g)) Services.add(M[1]); for (M of Text.matchAll(/\.e2b\s*\([^,]+,\s*(?:ct|x\$)\s*\(\s*"([^"]+)"/g)) Services.add(M[1]); // 3) callableName(模块内作用域) for (M of Text.matchAll(/([A-Za-z_$][\w$]*)\.callableName\s*=\s*"([^"]+)"/g)) Push(Mod.closures, M[1], M[2]); for (M of Text.matchAll(/([A-Za-z_$][\w$]*)\(\s*([A-Za-z_$][\w$]*)\s*\)\.toString\s*=\s*function\s*\(\s*\)\s*\{\s*return\s+"([A-Za-z_$][\w$]*)\(/g)) { Mod.classNames[M[2]] = M[3]; } // 5) 枚举: X = new Cls("Member", 数字) for (M of Text.matchAll(/([A-Za-z_$][\w$]*)\s*=\s*new\s+([A-Za-z_$][\w$]*)\(\s*"([A-Za-z_$][\w$ -]*)"\s*,\s*(-?\d+)\s*\)/g)) { if (!Mod.enums[M[2]]) Mod.enums[M[2]] = []; Mod.enums[M[2]].push({ field: M[1], member: M[3], value: Number(M[4]) }); } // 6) 字符串常量 for (M of Text.matchAll(/"([^"\\\n]{2,60})"/g)) { const V = M[1]; if (/^[0-9a-f]{8,}$/i.test(V) || /^[A-Za-z0-9+/=]{40,}$/.test(V)) continue; Strings.set(V, (Strings.get(V) || 0) + 1); } // 7) 导出表 (t.$_$.xx = LocalName) for (M of Text.matchAll(/t\.\$_\$\.([A-Za-z_$][\w$]*)\s*=\s*([A-Za-z_$][\w$]*)/g)) Mod.exports[M[1]] = M[2]; if (Object.keys(Mod.classNames).length) Stats.classNames += Object.keys(Mod.classNames).length; for (const K of Object.keys(Mod.enums)) Stats.enumMembers += Mod.enums[K].length; for (const K of Object.keys(Mod.closures)) Stats.closures += Mod.closures[K].length; Stats.exports += Object.keys(Mod.exports).length; Stats.fields += Object.keys(Mod.componentGetters).length; Stats.modules++; // 只保留有内容的模块,减小体积 if ( Object.keys(Mod.classNames).length || Object.keys(Mod.enums).length || Object.keys(Mod.closures).length || Object.keys(Mod.exports).length || Object.keys(Mod.componentGetters).length ) { ByModule[Id] = Mod; } } const Result = { bundle: 'main.6469b966.js', generatedAt: new Date().toISOString(), stats: Stats, byModule: ByModule, fieldsGlobal: Object.fromEntries([...FieldsGlobal].sort()), services: [...Services].sort(), strings: Object.fromEntries([...Strings].sort((A, B) => B[1] - A[1])), }; fs.mkdirSync(path.dirname(OUT), { recursive: true }); fs.writeFileSync(OUT, JSON.stringify(Result, null, 1), 'utf8'); console.log('=== 证据统计 ==='); for (const [K, V] of Object.entries(Stats)) console.log(' ' + K + ': ' + V); console.log(' services: ' + Services.size + ' strings: ' + Strings.size); console.log('--- 字段 → 组件名(前 15)---'); let C = 0; for (const [K, V] of [...FieldsGlobal].sort()) { if (C++ >= 15) break; console.log(' ' + K + ' -> ' + V.join('/')); } console.log('--- 数据类名示例(模块 87072)---'); const M87072 = ByModule['87072'] || {}; C = 0; for (const [K, V] of Object.entries(M87072.classNames || {})) { if (C++ >= 10) break; console.log(' ' + K + ' = ' + V); } console.log('--- 服务名(前 12)---'); console.log(' ' + [...Services].sort().slice(0, 12).join(', ')); console.log('输出: ' + OUT); } Run();