同步 agent/app 双端 hook_executor 动作表与 wechat_hook_v2.js exports; 补充 hook 子脚本与 _hook_smoke 冒烟入口,更新 device_modules 探测结果。 Co-authored-by: Cursor <cursoragent@cursor.com>
333 lines
10 KiB
JavaScript
333 lines
10 KiB
JavaScript
/**
|
||
* 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,
|
||
};
|