/** * common.js — 机擎 SDK Hook 脚本公共框架 * * 所有平台 Hook 脚本(微信/抖音/小红书等)共用的工具函数和基础设施。 * 由各平台脚本通过 Frida evaluate 或内联引用使用。 * * @version 1.0.0 */ 'use strict'; // ============================================================ // § 1 日志与事件 // ============================================================ var LOG_LEVEL_MAP = { debug: 0, info: 1, warn: 2, error: 3 }; var _currentLogLevel = 'info'; function setLogLevel(level) { if (LOG_LEVEL_MAP[level] !== undefined) _currentLogLevel = level; } function log(level, tag, message, extra) { if ((LOG_LEVEL_MAP[level] || 0) < (LOG_LEVEL_MAP[_currentLogLevel] || 0)) return; send({ type: 'log', level: level, tag: String(tag || 'hook'), message: String(message || ''), extra: extra || {}, timestamp: Date.now(), }); } function emitEvent(eventType, payload) { send({ type: 'hook_event', event_type: String(eventType), platform: (typeof PLATFORM !== 'undefined') ? PLATFORM : 'unknown', payload: payload || {}, timestamp: new Date().toISOString(), }); } // ============================================================ // § 2 安全字符串/类型工具 // ============================================================ function safeStr(v) { if (v === null || v === undefined) return ''; try { return v.toString(); } catch (_) { return ''; } } function safeInt(v, fallback) { if (v === null || v === undefined) return fallback || 0; try { var n = parseInt(v, 10); return isNaN(n) ? (fallback || 0) : n; } catch (_) { return fallback || 0; } } function safeJSON(v) { try { return JSON.parse(v); } catch (_) { return null; } } function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } function generateId(prefix) { return (prefix || 'id') + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6); } // ============================================================ // § 3 Java / Android 辅助 // ============================================================ function javaString(str) { return Java.use('java.lang.String').$new(String(str)); } function getContext() { try { return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); } catch (e) { log('warn', 'ctx', '获取 Context 失败: ' + e); return null; } } function getPackageVersion(packageName) { try { var ctx = getContext(); if (!ctx) return ''; var pm = ctx.getPackageManager(); var info = pm.getPackageInfo(javaString(packageName), 0); return safeStr(info.versionName.value); } catch (_) { return ''; } } function isServiceRunning(serviceClass) { try { var ctx = getContext(); if (!ctx) return false; var am = Java.cast( ctx.getSystemService(javaString('activity')), Java.use('android.app.ActivityManager') ); var services = am.getRunningServices(100); for (var i = 0; i < services.size(); i++) { if (safeStr(services.get(i).service.value.getClassName()) === serviceClass) return true; } } catch (_) {} return false; } // ============================================================ // § 4 SQLite 通用查询 // ============================================================ function execSQL(dbPath, sql, limit) { limit = limit || 500; var results = []; try { var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase'); var db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY.value); var cursor = db.rawQuery(sql, null); var count = 0; while (cursor.moveToNext() && count < limit) { var row = {}; for (var c = 0; c < cursor.getColumnCount(); c++) { row[cursor.getColumnName(c)] = safeStr(cursor.getString(c)); } results.push(row); count++; } cursor.close(); db.close(); } catch (e) { log('warn', 'db', 'execSQL 失败: ' + e + ' | sql=' + sql); } return results; } function findDatabase(basePath, dbName) { try { var File = Java.use('java.io.File'); var dir = File.$new(basePath); if (!dir.exists()) return null; var subs = dir.listFiles(); if (!subs) return null; for (var i = 0; i < subs.length; i++) { var candidate = subs[i].getAbsolutePath() + '/' + dbName; var f = File.$new(candidate); if (f.exists()) return candidate; } } catch (_) {} return null; } // ============================================================ // § 5 Intent 辅助 // ============================================================ function sendBroadcast(action, extras) { try { var Intent = Java.use('android.content.Intent'); var ctx = getContext(); if (!ctx) return false; var intent = Intent.$new(action); if (extras) { var keys = Object.keys(extras); for (var i = 0; i < keys.length; i++) { intent.putExtra(keys[i], String(extras[keys[i]])); } } ctx.sendBroadcast(intent); return true; } catch (e) { log('error', 'intent', 'sendBroadcast 失败: ' + e); return false; } } function startActivity(componentPkg, componentCls, extras) { try { var Intent = Java.use('android.content.Intent'); var ComponentName = Java.use('android.content.ComponentName'); var ctx = getContext(); if (!ctx) return false; var intent = Intent.$new(); intent.setComponent(ComponentName.$new(componentPkg, componentCls)); intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK if (extras) { var keys = Object.keys(extras); for (var i = 0; i < keys.length; i++) { intent.putExtra(keys[i], String(extras[keys[i]])); } } ctx.startActivity(intent); return true; } catch (e) { log('error', 'intent', 'startActivity 失败: ' + e); return false; } } // ============================================================ // § 6 Hook 辅助(安全包装 Java.use / overload) // ============================================================ function tryJavaUse(className) { try { return Java.use(className); } catch (_) { return null; } } function hookMethod(className, methodName, overloads, callbacks) { try { var Cls = Java.use(className); var method = overloads ? Cls[methodName].overload.apply(Cls[methodName], overloads) : Cls[methodName]; method.implementation = callbacks.replace || function () { if (callbacks.before) callbacks.before.apply(this, arguments); var ret = method.apply(this, arguments); if (callbacks.after) ret = callbacks.after.call(this, ret, arguments) || ret; return ret; }; log('info', 'hook', '已 Hook: ' + className + '.' + methodName); return true; } catch (e) { log('warn', 'hook', 'Hook 失败: ' + className + '.' + methodName + ' → ' + e); return false; } } function hookConstructor(className, overloads, callback) { try { var Cls = Java.use(className); var ctor = overloads ? Cls.$init.overload.apply(Cls.$init, overloads) : Cls.$init; ctor.implementation = function () { ctor.apply(this, arguments); if (callback) callback.apply(this, arguments); }; log('info', 'hook', '已 Hook 构造: ' + className); return true; } catch (e) { log('warn', 'hook', 'Hook 构造失败: ' + className + ' → ' + e); return false; } } // ============================================================ // § 7 文件系统辅助 // ============================================================ function readFile(path) { try { var File = Java.use('java.io.File'); var FileInputStream = Java.use('java.io.FileInputStream'); var BufferedReader = Java.use('java.io.BufferedReader'); var InputStreamReader = Java.use('java.io.InputStreamReader'); var f = File.$new(path); if (!f.exists()) return null; var reader = BufferedReader.$new(InputStreamReader.$new(FileInputStream.$new(f))); var sb = []; var line; while ((line = reader.readLine()) !== null) sb.push(safeStr(line)); reader.close(); return sb.join('\n'); } catch (_) { return null; } } function fileExists(path) { try { return Java.use('java.io.File').$new(path).exists(); } catch (_) { return false; } } // ============================================================ // § 8 缓冲与节流 // ============================================================ function createThrottle(minIntervalMs) { var lastFire = 0; return function (fn) { var now = Date.now(); if (now - lastFire >= minIntervalMs) { lastFire = now; fn(); } }; } function createRingBuffer(capacity) { var buf = []; return { push: function (item) { buf.push(item); if (buf.length > capacity) buf = buf.slice(-Math.floor(capacity * 0.6)); }, slice: function (start, end) { return buf.slice(start, end); }, get length() { return buf.length; }, clear: function () { buf = []; }, }; } // ============================================================ // § 9 导出(供其它脚本引用) // ============================================================ var CommonUtils = { log: log, setLogLevel: setLogLevel, emitEvent: emitEvent, safeStr: safeStr, safeInt: safeInt, safeJSON: safeJSON, clamp: clamp, generateId: generateId, javaString: javaString, getContext: getContext, getPackageVersion: getPackageVersion, isServiceRunning: isServiceRunning, execSQL: execSQL, findDatabase: findDatabase, sendBroadcast: sendBroadcast, startActivity: startActivity, tryJavaUse: tryJavaUse, hookMethod: hookMethod, hookConstructor: hookConstructor, readFile: readFile, fileExists: fileExists, createThrottle: createThrottle, createRingBuffer: createRingBuffer, };