Files
workphone-sdk/sdk/agent/hook/scripts/common.js

333 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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,
};