工具链(3DTank/tools/):extract(模块提取)、mine_evidence/apply_names(命名证据与别名回填)、extract_codec_fields(字段表 + 全库槽位类型 + PrimClassWire/ResourceClasses)、mine_downlink(下行字段表)、extract_space_init/rebase_space_init/audit_space_init(空间模板与记录切分审计)、extract_space_session(把真实会话切成按上行触发的回放模板)、pair_flow(请求→应答配对)、probe_containers、ingest/decrypt_frames(抓包入库与解密)、serve/fetch_assets(本地站点镜像)、import_db(抓包对象仓库导入 MySQL)。 服务器(3DTank/tools/server/):link/entry/codec/container/dispatch/world/space/semantic(协议层与 lt/pt/zt 三个容器布局)、session(会话回放,顺序推进)、db + schema.sql(MySQL 连接层与对象仓库表结构)、store/responder(查库组帧与定向应答;接管范围默认只开 ba.lcr)。 同时修 pair_flow 的时间轴偏移:timeline_c2s 与 timeline_s2c 的 0 点相差 144~4453ms,改前配对整体偏约 2 秒(实测 space-100073070 偏 2084ms),改后 22.4% 的触发者被纠正。 src/、src_named/、packets/、3DTank/local/(含数据库口令)不进库,可用上述工具重新生成。
133 lines
5.7 KiB
JavaScript
133 lines
5.7 KiB
JavaScript
// 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();
|