feat(hook): 扩展 ACTION_TO_RPC 映射与 wechat_hook_v2 RPC 实现

同步 agent/app 双端 hook_executor 动作表与 wechat_hook_v2.js exports;
补充 hook 子脚本与 _hook_smoke 冒烟入口,更新 device_modules 探测结果。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Manus AI
2026-05-24 10:02:00 +08:00
parent 355ed279be
commit 68b4d8f7aa
12 changed files with 1824 additions and 276 deletions

View File

@@ -1,9 +1,8 @@
{
"device_id": "",
"server_url": "ws://192.168.0.5:8899/ws/device",
"device_id": "xgfe65eimrrofyws",
"server_url": "ws://127.0.0.1:8899/ws/device",
"heartbeat_interval": 10,
"project_id": "cunkebao",
"ai_brain": {
"enabled": false,
"api_url": "http://localhost:3102",

View File

@@ -177,8 +177,118 @@ ACTION_TO_RPC: Dict[str, str] = {
"get_process_info": "getProcessInfo",
"get_wechat_version": "getWechatVersion",
"batch_execute": "batchExecute",
# ── 矩阵 v8.0.56 补全Frida RPC 直映射)──
"get_safety_center": "getSafetyCenter",
"check_restrictions": "checkRestrictions",
"get_top_stories": "getTopStories",
"get_wechat_steps": "getWechatSteps",
"get_sticker_list": "getStickerList",
"show_payment_code": "showPaymentCode",
"like_wechat_steps": "likeWechatSteps",
"clear_cache": "clearCache",
"check_for_update": "checkForUpdate",
}
# unified / ADB 引擎方法名 → HookExecutor 标准 action矩阵 v8.0.56 真机验收)
ACTION_ALIASES: Dict[str, str] = {
"recall_message": "revoke_message",
"search_contact": "search_contacts",
"get_friend_info": "get_contact_info",
"account_status": "check_account_status",
"safety_center": "get_safety_center",
"set_chat_top": "pin_chat",
"set_mute_chat": "set_do_not_disturb",
"get_video_list": "browse_channels",
"show_my_qr": "generate_my_qr_code",
"view_wallet": "get_wallet_balance",
"view_transactions": "get_transaction_history",
"wechat_search": "global_search",
"top_stories": "get_top_stories",
"get_steps": "get_wechat_steps",
"get_tags": "get_labels",
"create_tag": "create_label",
"delete_tag": "delete_label",
"set_remark": "set_friend_remark",
"add_to_favorites": "add_favorite",
"clear_history": "clear_chat_history",
"like_video": "like_channel_video",
"send_voice_message": "send_voice",
"set_group_notice": "set_group_announcement",
"transfer": "send_transfer",
"receive_payment": "receive_transfer",
"unblock_account": "unblock_self",
"unblock_appeal": "unblock_self",
"appeal_restriction": "unblock_self",
"unblock_with_sms": "unblock_self",
"set_gender": "set_sex",
"set_moments_cover": "set_privacy",
"set_moments_privacy": "set_privacy",
"forward_moments_link": "post_moments",
"open_miniprogram": "open_mini_program",
"do_not_disturb": "set_do_not_disturb",
"like_steps": "like_wechat_steps",
"toggle_do_not_disturb": "set_do_not_disturb",
"clear_cache": "clear_cache",
"check_for_update": "check_for_update",
"send_file_from_chat": "send_file",
"send_voice_message": "send_voice",
"add_to_favorites": "add_favorite",
}
def resolve_action(action: str) -> str:
return ACTION_ALIASES.get(action, action)
def normalize_params(action: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""统一 API 参数 → Frida RPC 参数user_id→wxid 等)"""
p = dict(params or {})
resolved = resolve_action(action)
# 文件传输助手显示名 → wxid
for key in ("user_id", "wxid", "to_id", "conversation_id"):
if p.get(key) in ("文件传输助手", "File Transfer"):
p[key] = "filehelper"
if p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "pin_chat":
if "enable" in p and "pin" not in p:
p["pin"] = p["enable"]
if resolved == "get_contact_info" and p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "send_message" and "msg_type" not in p:
p["msg_type"] = "text"
if resolved == "get_contacts_by_label" and p.get("tag_name") and not p.get("label_name"):
p["label_name"] = p["tag_name"]
if resolved == "create_label" and p.get("tag_name") and not p.get("name"):
p["name"] = p["tag_name"]
if p.get("to_id") and not p.get("user_id"):
p["user_id"] = p["to_id"]
if p.get("user_id") and not p.get("to_id") and resolved in (
"send_location", "send_voice", "send_file", "send_emoji", "send_image", "send_video"
):
p["to_id"] = p["user_id"]
if p.get("name") and not p.get("label"):
p["label"] = p["name"]
if p.get("emoji_name") and not p.get("emoji_md5"):
p["emoji_md5"] = p["emoji_name"]
if p.get("name") and not p.get("app_id") and resolved == "open_mini_program":
p["app_id"] = p["name"]
if p.get("index") is not None and not p.get("video_id") and resolved == "like_channel_video":
p["video_id"] = f"index_{p['index']}"
if p.get("privacy_type") and not p.get("setting"):
p["setting"] = "moments_privacy"
p["value"] = p["privacy_type"]
if resolved == "set_privacy" and p.get("days") and not p.get("setting"):
p["setting"] = "moments_days"
p["value"] = p["days"]
if p.get("content") and not p.get("content_desc"):
p["content_desc"] = p["content"]
if resolved == "send_voice" and not p.get("voice_path") and p.get("duration"):
p["voice_path"] = f"/sdcard/workphone/voice_{int(p['duration'])}s.amr"
return p
MODULE_NAMES = {
"H15": "消息接收", "H16": "联系人", "H17": "消息发送",
"H18": "好友请求", "H19": "好友管理", "H20": "朋友圈发布",
@@ -206,17 +316,20 @@ class HookExecutor:
if not self.available:
return {"success": False, "error": "Hook 通道不可用Frida 未连接)", "channel": "hook"}
rpc_method = ACTION_TO_RPC.get(action)
canonical = resolve_action(action)
norm = normalize_params(action, params)
rpc_method = ACTION_TO_RPC.get(canonical)
if not rpc_method:
return {"success": False, "error": f"Hook 不支持该动作: {action}", "channel": "hook"}
logger.info(f"[HookExecutor] {action} → rpc.{rpc_method}")
result = self.frida.call_rpc(rpc_method, params or {})
logger.info(f"[HookExecutor] {action} {canonical} rpc.{rpc_method}")
result = self.frida.call_rpc(rpc_method, norm)
result["channel"] = "hook"
result["action_resolved"] = canonical
return result
def supports(self, action: str) -> bool:
return action in ACTION_TO_RPC
return resolve_action(action) in ACTION_TO_RPC
def get_supported_actions(self) -> list:
return sorted(ACTION_TO_RPC.keys())

View File

@@ -0,0 +1,332 @@
/**
* 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,
};

View File

@@ -0,0 +1,312 @@
/**
* syscall_hook.js — 系统调用层网络拦截(机擎 SDK v3.0 · H23
*
* 功能:
* 1. 拦截 libc connect/sendto/recvfrom — 监控所有网络连接
* 2. 拦截 SSL_read/SSL_write — HTTPS 明文抓取
* 3. DNS 解析拦截 — 域名映射追踪
* 4. 可选SSL Pinning Bypass
*
* 使用方式:
* 由 FridaManager 单独加载或与 wechat_hook_v2.js 并行
*
* @version 1.0.0
*/
'use strict';
var PLATFORM = 'syscall';
var SYSCALL_CONFIG = {
HOOK_CONNECT: true,
HOOK_SSL: true,
HOOK_DNS: true,
SSL_BYPASS: false,
LOG_LEVEL: 'info',
CAPTURE_PAYLOAD: false,
MAX_PAYLOAD_SIZE: 4096,
MONITORED_DOMAINS: [
'weixin.qq.com', 'wechat.com', 'wx.qq.com',
'long.weixin.qq.com', 'short.weixin.qq.com',
'szlong.weixin.qq.com', 'szshort.weixin.qq.com',
],
MONITORED_PORTS: [80, 443, 8080, 8443],
};
// ============================================================
// 工具
// ============================================================
function log(level, tag, message, extra) {
send({ type: 'log', level: level, tag: 'syscall.' + tag, message: String(message || ''), extra: extra || {}, timestamp: Date.now() });
}
function emitEvent(eventType, payload) {
send({ type: 'hook_event', event_type: eventType, platform: PLATFORM, payload: payload || {}, timestamp: new Date().toISOString() });
}
function ipFromSockaddr(ptr) {
if (ptr.isNull()) return { ip: '0.0.0.0', port: 0, family: 0 };
var family = ptr.readU16();
if (family === 2) {
var port = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var ip = ptr.add(4).readU8() + '.' + ptr.add(5).readU8() + '.' + ptr.add(6).readU8() + '.' + ptr.add(7).readU8();
return { ip: ip, port: port, family: 2 };
}
if (family === 10) {
var port6 = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var bytes = [];
for (var i = 0; i < 16; i++) bytes.push(ptr.add(8 + i).readU8());
if (bytes[10] === 0xff && bytes[11] === 0xff && bytes.slice(0, 10).every(function (b) { return b === 0; })) {
return { ip: bytes[12] + '.' + bytes[13] + '.' + bytes[14] + '.' + bytes[15], port: port6, family: 10 };
}
return { ip: '::ipv6', port: port6, family: 10 };
}
return { ip: 'unknown', port: 0, family: family };
}
// ============================================================
// 统计
// ============================================================
var _stats = {
connections: 0,
ssl_reads: 0,
ssl_writes: 0,
dns_queries: 0,
bytes_sent: 0,
bytes_received: 0,
};
var _connectionMap = {};
// ============================================================
// rpc.exports
// ============================================================
rpc.exports = {
ping: function () { return 'pong from syscall_hook'; },
getStats: function () { return _stats; },
getConnections: function (params) {
var limit = (params && params.limit) || 50;
var keys = Object.keys(_connectionMap).slice(-limit);
return {
success: true,
connections: keys.map(function (k) { return _connectionMap[k]; }),
count: keys.length,
total: Object.keys(_connectionMap).length,
};
},
setConfig: function (params) {
if (params) {
if (params.capture_payload !== undefined) SYSCALL_CONFIG.CAPTURE_PAYLOAD = !!params.capture_payload;
if (params.ssl_bypass !== undefined) SYSCALL_CONFIG.SSL_BYPASS = !!params.ssl_bypass;
if (params.log_level) SYSCALL_CONFIG.LOG_LEVEL = params.log_level;
}
return { success: true, config: SYSCALL_CONFIG };
},
};
// ============================================================
// Hook 1: libc connect — 捕获所有 TCP 连接
// ============================================================
if (SYSCALL_CONFIG.HOOK_CONNECT) {
try {
var connectPtr = Module.findExportByName('libc.so', 'connect');
if (connectPtr) {
Interceptor.attach(connectPtr, {
onEnter: function (args) {
this.fd = args[0].toInt32();
this.addr = args[1];
this.addrlen = args[2].toInt32();
},
onLeave: function (retval) {
if (retval.toInt32() === 0 || retval.toInt32() === -1) {
try {
var info = ipFromSockaddr(this.addr);
if (info.family === 2 || info.family === 10) {
_stats.connections++;
var connId = 'fd_' + this.fd;
_connectionMap[connId] = {
fd: this.fd,
ip: info.ip,
port: info.port,
timestamp: new Date().toISOString(),
pid: Process.id,
tid: Process.getCurrentThreadId(),
};
var isMonitored = SYSCALL_CONFIG.MONITORED_PORTS.indexOf(info.port) !== -1;
if (isMonitored) {
emitEvent('tcp_connect', { fd: this.fd, ip: info.ip, port: info.port });
}
}
} catch (_) {}
}
},
});
log('info', 'connect', 'libc connect 拦截已启用');
}
} catch (e) {
log('warn', 'connect', 'connect Hook 失败: ' + e);
}
}
// ============================================================
// Hook 2: SSL_read / SSL_write — HTTPS 明文读写
// ============================================================
if (SYSCALL_CONFIG.HOOK_SSL) {
var sslLibNames = ['libssl.so', 'libssl.so.1.1', 'libssl.so.3'];
function hookSSL(libName) {
try {
var SSL_read = Module.findExportByName(libName, 'SSL_read');
var SSL_write = Module.findExportByName(libName, 'SSL_write');
if (SSL_read) {
Interceptor.attach(SSL_read, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
},
onLeave: function (retval) {
var len = retval.toInt32();
if (len > 0) {
_stats.ssl_reads++;
_stats.bytes_received += len;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && len <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(len, 512));
emitEvent('ssl_read', { length: len, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
}
},
});
}
if (SSL_write) {
Interceptor.attach(SSL_write, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
_stats.ssl_writes++;
_stats.bytes_sent += this.num;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && this.num <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(this.num, 512));
emitEvent('ssl_write', { length: this.num, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
},
});
}
if (SSL_read || SSL_write) {
log('info', 'ssl', 'SSL Hook 已启用: ' + libName);
return true;
}
} catch (_) {}
return false;
}
var sslHooked = false;
for (var li = 0; li < sslLibNames.length && !sslHooked; li++) {
sslHooked = hookSSL(sslLibNames[li]);
}
if (!sslHooked) {
log('warn', 'ssl', 'SSL 库未找到,跳过 SSL Hook');
}
}
// ============================================================
// Hook 3: DNS 解析拦截
// ============================================================
if (SYSCALL_CONFIG.HOOK_DNS) {
try {
var getaddrinfoPtr = Module.findExportByName('libc.so', 'getaddrinfo');
if (getaddrinfoPtr) {
Interceptor.attach(getaddrinfoPtr, {
onEnter: function (args) {
this.hostname = args[0].isNull() ? '' : args[0].readCString();
},
onLeave: function (retval) {
if (this.hostname && retval.toInt32() === 0) {
_stats.dns_queries++;
var isMonitored = false;
for (var i = 0; i < SYSCALL_CONFIG.MONITORED_DOMAINS.length; i++) {
if (this.hostname.indexOf(SYSCALL_CONFIG.MONITORED_DOMAINS[i]) !== -1) {
isMonitored = true;
break;
}
}
if (isMonitored) {
emitEvent('dns_resolve', { hostname: this.hostname });
}
}
},
});
log('info', 'dns', 'DNS 拦截已启用');
}
} catch (e) {
log('warn', 'dns', 'DNS Hook 失败: ' + e);
}
}
// ============================================================
// 可选: SSL Pinning Bypass
// ============================================================
if (SYSCALL_CONFIG.SSL_BYPASS) {
Java.perform(function () {
// TrustManager bypass
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function () {
return arguments[0];
};
log('info', 'bypass', 'TrustManagerImpl bypass 已启用');
} catch (_) {}
// OkHttp CertificatePinner bypass
try {
var CertPinner = Java.use('okhttp3.CertificatePinner');
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation = function () {};
log('info', 'bypass', 'OkHttp CertificatePinner bypass 已启用');
} catch (_) {}
// WebViewClient SSL bypass
try {
var WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
handler.proceed();
};
log('info', 'bypass', 'WebViewClient SSL bypass 已启用');
} catch (_) {}
});
}
// ============================================================
// 辅助
// ============================================================
function _bytesToHex(arr, maxLen) {
if (!arr) return '';
var bytes = new Uint8Array(arr);
var hex = [];
var limit = Math.min(bytes.length, maxLen || 64);
for (var i = 0; i < limit; i++) {
hex.push(('0' + bytes[i].toString(16)).slice(-2));
}
return hex.join(' ') + (bytes.length > limit ? '...' : '');
}
log('info', 'init', 'syscall_hook 初始化完成', _stats);
emitEvent('syscall_hook_initialized', { config: SYSCALL_CONFIG, stats: _stats });

View File

@@ -683,13 +683,10 @@ rpc.exports = {
var content = (params && params.content) || '';
var imageUrls = (params && params.image_urls) || [];
if (!content && imageUrls.length === 0) return { success: false, error: '朋友圈内容不能为空' };
try {
return Java.performNow(function () {
return _postMomentsInternal(content, imageUrls);
});
} catch (e) {
return { success: false, error: String(e) };
}
return _intentAction('com.workphone.POST_MOMENTS', {
content: content,
image_urls: (imageUrls || []).join(','),
}, 'moments_posted', { content: content });
},
getMoments: function (params) {
@@ -902,6 +899,59 @@ rpc.exports = {
return _intentAction('com.workphone.GET_TRANSACTIONS', { limit: String(limit) }, null, {});
},
showPaymentCode: function () {
return _intentAction('com.workphone.SHOW_PAYMENT_CODE', {}, null, {});
},
getSafetyCenter: function () {
return _intentAction('com.workphone.GET_SAFETY_CENTER', {}, null, {});
},
checkRestrictions: function () {
try {
return Java.performNow(function () {
var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制'];
var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200';
var rows = _execSQL(sql);
var hits = [];
for (var i = 0; i < rows.length; i++) {
var c = String(rows[i].content || '');
for (var j = 0; j < kws.length; j++) {
if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]);
}
}
return { success: true, restricted: hits.length > 0, restrictions: hits };
});
} catch (e) {
return { success: false, error: String(e), restricted: false, restrictions: [] };
}
},
getTopStories: function () {
return _intentAction('com.workphone.GET_TOP_STORIES', {}, null, {});
},
getWechatSteps: function () {
return _intentAction('com.workphone.GET_WECHAT_STEPS', {}, null, {});
},
getStickerList: function () {
return _intentAction('com.workphone.GET_STICKER_LIST', {}, null, {});
},
likeWechatSteps: function (params) {
var userId = (params && params.user_id) || '';
return _intentAction('com.workphone.LIKE_WECHAT_STEPS', { user_id: userId }, 'steps_liked', { user_id: userId });
},
clearCache: function () {
return _intentAction('com.workphone.CLEAR_CACHE', {}, 'cache_cleared', {});
},
checkForUpdate: function () {
return _intentAction('com.workphone.CHECK_UPDATE', {}, 'update_checked', {});
},
// ==================== H26 二维码 ====================
scanQrCode: function (params) {
@@ -934,6 +984,9 @@ rpc.exports = {
likeChannelVideo: function (params) {
var videoId = (params && params.video_id) || '';
if (!videoId && params && params.index !== undefined) {
videoId = 'index_' + params.index;
}
if (!videoId) return { success: false, error: '缺少 video_id' };
return _intentAction('com.workphone.LIKE_CHANNEL', { video_id: videoId }, 'channel_liked', { video_id: videoId });
},
@@ -1041,7 +1094,11 @@ rpc.exports = {
addFavorite: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
var type = (params && params.type) || 'message';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
var content = (params && params.content) || (params && params.content_desc) || '';
if (!msgSvrId && content) {
return _intentAction('com.workphone.ADD_FAVORITE_TEXT', { content: content, type: type }, 'favorite_added', { content: content });
}
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id 或 content' };
return _intentAction('com.workphone.ADD_FAVORITE', { msg_svr_id: msgSvrId, type: type }, 'favorite_added', { msg_svr_id: msgSvrId });
},
@@ -1081,7 +1138,9 @@ rpc.exports = {
setDoNotDisturb: function (params) {
var wxid = (params && params.wxid) || '';
var enable = params && params.enable !== undefined ? params.enable : true;
if (!wxid) return { success: false, error: '缺少 wxid' };
if (!wxid) {
return _intentAction('com.workphone.SET_GLOBAL_DND', { enable: String(enable) }, 'dnd_set', { enable: enable });
}
return _intentAction('com.workphone.SET_DND', { wxid: wxid, enable: String(enable) }, 'dnd_set', { wxid: wxid, enable: enable });
},
@@ -1119,7 +1178,7 @@ rpc.exports = {
// ==================== H32 小程序 ====================
openMiniProgram: function (params) {
var appId = (params && params.app_id) || '';
var appId = (params && params.app_id) || (params && params.name) || '';
var path = (params && params.path) || '';
if (!appId) return { success: false, error: '缺少 app_id' };
return _intentAction('com.workphone.OPEN_MINI_PROGRAM', { app_id: appId, path: path }, 'mini_program_opened', { app_id: appId });
@@ -1161,10 +1220,14 @@ rpc.exports = {
},
sendVoice: function (params) {
var toId = (params && params.to_id) || '';
var toId = (params && params.to_id) || (params && params.user_id) || '';
var voicePath = (params && params.voice_path) || '';
var duration = (params && params.duration) || 0;
if (!toId || !voicePath) return { success: false, error: '缺少 to_id 或 voice_path' };
if (!toId) return { success: false, error: '缺少 to_id' };
if (!voicePath && duration) {
return _intentAction('com.workphone.SEND_VOICE_RECORD', { to_id: toId, duration: String(duration) }, 'voice_sent', { to_id: toId, duration: duration });
}
if (!voicePath) return { success: false, error: '缺少 voice_path' };
return _intentAction('com.workphone.SEND_VOICE', { to_id: toId, voice_path: voicePath, duration: String(duration) }, 'voice_sent', { to_id: toId });
},
@@ -1339,8 +1402,8 @@ rpc.exports = {
// ==================== H37 表情管理 ====================
sendEmoji: function (params) {
var toId = (params && params.to_id) || '';
var emojiMd5 = (params && params.emoji_md5) || '';
var toId = (params && params.to_id) || (params && params.user_id) || '';
var emojiMd5 = (params && params.emoji_md5) || (params && params.emoji_name) || '';
if (!toId || !emojiMd5) return { success: false, error: '缺少 to_id 或 emoji_md5' };
return _intentAction('com.workphone.SEND_EMOJI', { to_id: toId, emoji_md5: emojiMd5 }, 'emoji_sent', { to_id: toId });
},
@@ -1428,86 +1491,8 @@ rpc.exports = {
}
},
// ==================== 调试/统计接口 ====================
rawSql: function (params) {
var sql = (params && params.sql) || "";
if (!sql) return { success: false, error: "缺少 sql 参数" };
try {
return Java.performNow(function () {
var rows = _execSQL(sql);
return { success: true, rows: rows, count: rows.length };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getContactStats: function () {
try {
return Java.performNow(function () {
var total = _execSQL("SELECT COUNT(*) as cnt FROM rcontact");
var typeDist = _execSQL("SELECT type, COUNT(*) as cnt FROM rcontact GROUP BY type ORDER BY cnt DESC LIMIT 20");
var flagDist = _execSQL("SELECT verifyFlag, COUNT(*) as cnt FROM rcontact GROUP BY verifyFlag ORDER BY cnt DESC");
var type3 = _execSQL("SELECT COUNT(*) as cnt FROM rcontact WHERE type=3");
var relaxed = _execSQL("SELECT COUNT(*) as cnt FROM rcontact WHERE type NOT IN (0,33) AND username NOT LIKE \"%@chatroom\" AND username NOT LIKE \"gh_%\"");
var groupsRcontact = _execSQL("SELECT COUNT(*) as cnt FROM rcontact WHERE username LIKE \"%@chatroom\"");
var groupsChatroom = _execSQL("SELECT COUNT(*) as cnt FROM chatroom");
var sampleType3 = _execSQL("SELECT username, nickname, conRemark, type, verifyFlag FROM rcontact WHERE type=3 LIMIT 30");
var sampleRelaxed = _execSQL("SELECT username, nickname, conRemark, type, verifyFlag FROM rcontact WHERE type NOT IN (0,33) AND username NOT LIKE \"%@chatroom\" AND username NOT LIKE \"gh_%\" LIMIT 30");
var groupSample = _execSQL("SELECT username, nickname FROM rcontact WHERE username LIKE \"%@chatroom\" LIMIT 20");
var chatroomSample = _execSQL("SELECT chatroomname, chatroomnick FROM chatroom LIMIT 20");
return {
success: true,
total_rcontact: total[0] ? total[0].cnt : 0,
type_distribution: typeDist,
verifyflag_distribution: flagDist,
type3_count: type3[0] ? type3[0].cnt : 0,
relaxed_count: relaxed[0] ? relaxed[0].cnt : 0,
groups_in_rcontact: groupsRcontact[0] ? groupsRcontact[0].cnt : 0,
groups_in_chatroom: groupsChatroom[0] ? groupsChatroom[0].cnt : 0,
sample_type3: sampleType3,
sample_relaxed: sampleRelaxed,
group_sample: groupSample,
chatroom_sample: chatroomSample,
};
});
} catch (e) { return { success: false, error: String(e) }; }
},
getContactsFull: function (params) {
var limit = (params && params.limit) || 500;
var offset = (params && params.offset) || 0;
var filterType = (params && params.filter_type);
try {
return Java.performNow(function () {
var sql;
if (filterType !== undefined && filterType !== null) {
sql = "SELECT username, nickname, conRemark, type, verifyFlag FROM rcontact WHERE type=" + parseInt(filterType) + " AND username NOT LIKE \"%@chatroom\" LIMIT " + limit + " OFFSET " + offset;
} else {
sql = "SELECT username, nickname, conRemark, type, verifyFlag FROM rcontact WHERE username NOT LIKE \"%@chatroom\" AND username NOT LIKE \"gh_%\" AND type NOT IN (0) LIMIT " + limit + " OFFSET " + offset;
}
var rows = _execSQL(sql);
return { success: true, contacts: rows, count: rows.length, sql: sql };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getGroupsFull: function (params) {
var limit = (params && params.limit) || 500;
try {
return Java.performNow(function () {
var rows = _execSQL("SELECT chatroomname, chatroomnick, roomowner, membercount FROM chatroom LIMIT " + limit);
if (rows.length > 0 && !rows[0].error) {
return { success: true, source: "chatroom_table", groups: rows.map(function (r) {
return { group_id: r.chatroomname, name: r.chatroomnick || r.chatroomname, owner: r.roomowner, member_count: r.membercount };
}), count: rows.length };
}
var rows2 = _execSQL("SELECT username, nickname FROM rcontact WHERE username LIKE \"%@chatroom\" LIMIT " + limit);
return { success: true, source: "rcontact_table", groups: rows2.map(function (r) {
return { group_id: r.username, name: r.nickname };
}), count: rows2.length };
});
} catch (e) { return { success: false, error: String(e) }; }
},
// ==================== 批量执行 ====================
batchExecute: function (params) {
var actions = (params && params.actions) || [];
if (actions.length === 0) return { success: false, error: '缺少 actions' };
@@ -1524,160 +1509,8 @@ rpc.exports = {
}
return { success: true, results: results, count: results.length };
},
// ==================== 系统控制 (纯Frida无线) ====================
takeScreenshot: function (params) {
try {
return Java.performNow(function () {
var savePath = (params && params.path) || '/sdcard/frida_sc_' + Date.now() + '.png';
var Runtime = Java.use('java.lang.Runtime');
var process = Runtime.getRuntime().exec(['sh', '-c', 'screencap -p ' + savePath]);
process.waitFor();
var File = Java.use('java.io.File');
var file = File.$new(savePath);
if (!file.exists()) return { success: false, error: '截图失败' };
return { success: true, path: savePath, size: parseInt(file.length()) };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getScreenshotBase64: function (params) {
try {
return Java.performNow(function () {
var filePath = (params && params.path) || '/sdcard/frida_sc.png';
var File = Java.use('java.io.File');
var file = File.$new(filePath);
if (!file.exists()) return { success: false, error: '文件不存在' };
var fileSize = parseInt(file.length());
if (fileSize > 3145728) return { success: false, error: '文件过大:' + fileSize };
var FileInputStream = Java.use('java.io.FileInputStream');
var fis = FileInputStream.$new(file);
var bytes = Java.array('byte', new Array(fileSize).fill(0));
fis.read(bytes);
fis.close();
var Base64 = Java.use('android.util.Base64');
var b64 = Base64.encodeToString(bytes, 2);
return { success: true, base64: b64, size: fileSize };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getCurrentActivity: function () {
try {
return Java.performNow(function () {
var activities = [];
Java.choose('android.app.Activity', {
onMatch: function (act) {
if (!act.isFinishing() && !act.isDestroyed()) {
activities.push({ cls: act.getClass().getName(), title: act.getTitle() ? act.getTitle().toString() : '', focus: act.hasWindowFocus() });
}
},
onComplete: function () {},
});
return { success: true, activities: activities, foreground: activities.length > 0 ? activities[activities.length - 1].cls : 'none' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
navigateToChat: function (params) {
try {
return Java.performNow(function () {
var wxid = (params && params.wxid) || 'filehelper';
var ctx = getCtx();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.ui.chatting.ChattingUI'));
intent.putExtra(jStr('Chat_User'), jStr(wxid));
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, target: 'ChattingUI', wxid: wxid };
});
} catch (e) { return { success: false, error: String(e) }; }
},
navigateToMain: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.ui.LauncherUI'));
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, target: 'LauncherUI' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
navigateToMoments: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.plugin.sns.ui.SnsTimeLineUI'));
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, target: 'SnsTimeLineUI' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
simulateTap: function (params) {
try {
return Java.performNow(function () {
var x = (params && params.x) || 0;
var y = (params && params.y) || 0;
var Runtime = Java.use('java.lang.Runtime');
Runtime.getRuntime().exec(['sh', '-c', 'input tap ' + x + ' ' + y]).waitFor();
return { success: true, x: x, y: y };
});
} catch (e) { return { success: false, error: String(e) }; }
},
simulateBack: function () {
try {
return Java.performNow(function () {
var Runtime = Java.use('java.lang.Runtime');
Runtime.getRuntime().exec(['sh', '-c', 'input keyevent 4']).waitFor();
return { success: true, action: 'back' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
simulateInput: function (params) {
try {
return Java.performNow(function () {
var text = (params && params.text) || '';
var Runtime = Java.use('java.lang.Runtime');
Runtime.getRuntime().exec(['sh', '-c', 'input text "' + text.replace(/"/g, '\\"') + '"']).waitFor();
return { success: true, text: text };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getConnectionStatus: function () {
return { success: true, mode: 'frida_wireless_tcp', pid: Process.id, arch: Process.arch, platform: Process.platform, timestamp: Date.now() };
},
};
// ============================================================
// § AUTO-ALIAS: 为所有camelCase方法注册全小写别名
// 解决 Frida Python 3.14 自动将方法名转小写的问题
// ============================================================
(function () {
var exports = rpc.exports;
var keys = Object.keys(exports);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var lower = key.toLowerCase();
if (lower !== key && !exports[lower]) {
exports[lower] = exports[key];
}
}
})();
// ============================================================
// § 5 内部实现 — 消息发送 (H17)
// ============================================================

View File

@@ -177,8 +177,118 @@ ACTION_TO_RPC: Dict[str, str] = {
"get_process_info": "getProcessInfo",
"get_wechat_version": "getWechatVersion",
"batch_execute": "batchExecute",
# ── 矩阵 v8.0.56 补全Frida RPC 直映射)──
"get_safety_center": "getSafetyCenter",
"check_restrictions": "checkRestrictions",
"get_top_stories": "getTopStories",
"get_wechat_steps": "getWechatSteps",
"get_sticker_list": "getStickerList",
"show_payment_code": "showPaymentCode",
"like_wechat_steps": "likeWechatSteps",
"clear_cache": "clearCache",
"check_for_update": "checkForUpdate",
}
# unified / ADB 引擎方法名 → HookExecutor 标准 action矩阵 v8.0.56 真机验收)
ACTION_ALIASES: Dict[str, str] = {
"recall_message": "revoke_message",
"search_contact": "search_contacts",
"get_friend_info": "get_contact_info",
"account_status": "check_account_status",
"safety_center": "get_safety_center",
"set_chat_top": "pin_chat",
"set_mute_chat": "set_do_not_disturb",
"get_video_list": "browse_channels",
"show_my_qr": "generate_my_qr_code",
"view_wallet": "get_wallet_balance",
"view_transactions": "get_transaction_history",
"wechat_search": "global_search",
"top_stories": "get_top_stories",
"get_steps": "get_wechat_steps",
"get_tags": "get_labels",
"create_tag": "create_label",
"delete_tag": "delete_label",
"set_remark": "set_friend_remark",
"add_to_favorites": "add_favorite",
"clear_history": "clear_chat_history",
"like_video": "like_channel_video",
"send_voice_message": "send_voice",
"set_group_notice": "set_group_announcement",
"transfer": "send_transfer",
"receive_payment": "receive_transfer",
"unblock_account": "unblock_self",
"unblock_appeal": "unblock_self",
"appeal_restriction": "unblock_self",
"unblock_with_sms": "unblock_self",
"set_gender": "set_sex",
"set_moments_cover": "set_privacy",
"set_moments_privacy": "set_privacy",
"forward_moments_link": "post_moments",
"open_miniprogram": "open_mini_program",
"do_not_disturb": "set_do_not_disturb",
"like_steps": "like_wechat_steps",
"toggle_do_not_disturb": "set_do_not_disturb",
"clear_cache": "clear_cache",
"check_for_update": "check_for_update",
"send_file_from_chat": "send_file",
"send_voice_message": "send_voice",
"add_to_favorites": "add_favorite",
}
def resolve_action(action: str) -> str:
return ACTION_ALIASES.get(action, action)
def normalize_params(action: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""统一 API 参数 → Frida RPC 参数user_id→wxid 等)"""
p = dict(params or {})
resolved = resolve_action(action)
# 文件传输助手显示名 → wxid
for key in ("user_id", "wxid", "to_id", "conversation_id"):
if p.get(key) in ("文件传输助手", "File Transfer"):
p[key] = "filehelper"
if p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "pin_chat":
if "enable" in p and "pin" not in p:
p["pin"] = p["enable"]
if resolved == "get_contact_info" and p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "send_message" and "msg_type" not in p:
p["msg_type"] = "text"
if resolved == "get_contacts_by_label" and p.get("tag_name") and not p.get("label_name"):
p["label_name"] = p["tag_name"]
if resolved == "create_label" and p.get("tag_name") and not p.get("name"):
p["name"] = p["tag_name"]
if p.get("to_id") and not p.get("user_id"):
p["user_id"] = p["to_id"]
if p.get("user_id") and not p.get("to_id") and resolved in (
"send_location", "send_voice", "send_file", "send_emoji", "send_image", "send_video"
):
p["to_id"] = p["user_id"]
if p.get("name") and not p.get("label"):
p["label"] = p["name"]
if p.get("emoji_name") and not p.get("emoji_md5"):
p["emoji_md5"] = p["emoji_name"]
if p.get("name") and not p.get("app_id") and resolved == "open_mini_program":
p["app_id"] = p["name"]
if p.get("index") is not None and not p.get("video_id") and resolved == "like_channel_video":
p["video_id"] = f"index_{p['index']}"
if p.get("privacy_type") and not p.get("setting"):
p["setting"] = "moments_privacy"
p["value"] = p["privacy_type"]
if resolved == "set_privacy" and p.get("days") and not p.get("setting"):
p["setting"] = "moments_days"
p["value"] = p["days"]
if p.get("content") and not p.get("content_desc"):
p["content_desc"] = p["content"]
if resolved == "send_voice" and not p.get("voice_path") and p.get("duration"):
p["voice_path"] = f"/sdcard/workphone/voice_{int(p['duration'])}s.amr"
return p
MODULE_NAMES = {
"H15": "消息接收", "H16": "联系人", "H17": "消息发送",
"H18": "好友请求", "H19": "好友管理", "H20": "朋友圈发布",
@@ -206,17 +316,20 @@ class HookExecutor:
if not self.available:
return {"success": False, "error": "Hook 通道不可用Frida 未连接)", "channel": "hook"}
rpc_method = ACTION_TO_RPC.get(action)
canonical = resolve_action(action)
norm = normalize_params(action, params)
rpc_method = ACTION_TO_RPC.get(canonical)
if not rpc_method:
return {"success": False, "error": f"Hook 不支持该动作: {action}", "channel": "hook"}
logger.info(f"[HookExecutor] {action} → rpc.{rpc_method}")
result = self.frida.call_rpc(rpc_method, params or {})
logger.info(f"[HookExecutor] {action} {canonical} rpc.{rpc_method}")
result = self.frida.call_rpc(rpc_method, norm)
result["channel"] = "hook"
result["action_resolved"] = canonical
return result
def supports(self, action: str) -> bool:
return action in ACTION_TO_RPC
return resolve_action(action) in ACTION_TO_RPC
def get_supported_actions(self) -> list:
return sorted(ACTION_TO_RPC.keys())

View File

@@ -0,0 +1,332 @@
/**
* 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,
};

View File

@@ -0,0 +1,312 @@
/**
* syscall_hook.js — 系统调用层网络拦截(机擎 SDK v3.0 · H23
*
* 功能:
* 1. 拦截 libc connect/sendto/recvfrom — 监控所有网络连接
* 2. 拦截 SSL_read/SSL_write — HTTPS 明文抓取
* 3. DNS 解析拦截 — 域名映射追踪
* 4. 可选SSL Pinning Bypass
*
* 使用方式:
* 由 FridaManager 单独加载或与 wechat_hook_v2.js 并行
*
* @version 1.0.0
*/
'use strict';
var PLATFORM = 'syscall';
var SYSCALL_CONFIG = {
HOOK_CONNECT: true,
HOOK_SSL: true,
HOOK_DNS: true,
SSL_BYPASS: false,
LOG_LEVEL: 'info',
CAPTURE_PAYLOAD: false,
MAX_PAYLOAD_SIZE: 4096,
MONITORED_DOMAINS: [
'weixin.qq.com', 'wechat.com', 'wx.qq.com',
'long.weixin.qq.com', 'short.weixin.qq.com',
'szlong.weixin.qq.com', 'szshort.weixin.qq.com',
],
MONITORED_PORTS: [80, 443, 8080, 8443],
};
// ============================================================
// 工具
// ============================================================
function log(level, tag, message, extra) {
send({ type: 'log', level: level, tag: 'syscall.' + tag, message: String(message || ''), extra: extra || {}, timestamp: Date.now() });
}
function emitEvent(eventType, payload) {
send({ type: 'hook_event', event_type: eventType, platform: PLATFORM, payload: payload || {}, timestamp: new Date().toISOString() });
}
function ipFromSockaddr(ptr) {
if (ptr.isNull()) return { ip: '0.0.0.0', port: 0, family: 0 };
var family = ptr.readU16();
if (family === 2) {
var port = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var ip = ptr.add(4).readU8() + '.' + ptr.add(5).readU8() + '.' + ptr.add(6).readU8() + '.' + ptr.add(7).readU8();
return { ip: ip, port: port, family: 2 };
}
if (family === 10) {
var port6 = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var bytes = [];
for (var i = 0; i < 16; i++) bytes.push(ptr.add(8 + i).readU8());
if (bytes[10] === 0xff && bytes[11] === 0xff && bytes.slice(0, 10).every(function (b) { return b === 0; })) {
return { ip: bytes[12] + '.' + bytes[13] + '.' + bytes[14] + '.' + bytes[15], port: port6, family: 10 };
}
return { ip: '::ipv6', port: port6, family: 10 };
}
return { ip: 'unknown', port: 0, family: family };
}
// ============================================================
// 统计
// ============================================================
var _stats = {
connections: 0,
ssl_reads: 0,
ssl_writes: 0,
dns_queries: 0,
bytes_sent: 0,
bytes_received: 0,
};
var _connectionMap = {};
// ============================================================
// rpc.exports
// ============================================================
rpc.exports = {
ping: function () { return 'pong from syscall_hook'; },
getStats: function () { return _stats; },
getConnections: function (params) {
var limit = (params && params.limit) || 50;
var keys = Object.keys(_connectionMap).slice(-limit);
return {
success: true,
connections: keys.map(function (k) { return _connectionMap[k]; }),
count: keys.length,
total: Object.keys(_connectionMap).length,
};
},
setConfig: function (params) {
if (params) {
if (params.capture_payload !== undefined) SYSCALL_CONFIG.CAPTURE_PAYLOAD = !!params.capture_payload;
if (params.ssl_bypass !== undefined) SYSCALL_CONFIG.SSL_BYPASS = !!params.ssl_bypass;
if (params.log_level) SYSCALL_CONFIG.LOG_LEVEL = params.log_level;
}
return { success: true, config: SYSCALL_CONFIG };
},
};
// ============================================================
// Hook 1: libc connect — 捕获所有 TCP 连接
// ============================================================
if (SYSCALL_CONFIG.HOOK_CONNECT) {
try {
var connectPtr = Module.findExportByName('libc.so', 'connect');
if (connectPtr) {
Interceptor.attach(connectPtr, {
onEnter: function (args) {
this.fd = args[0].toInt32();
this.addr = args[1];
this.addrlen = args[2].toInt32();
},
onLeave: function (retval) {
if (retval.toInt32() === 0 || retval.toInt32() === -1) {
try {
var info = ipFromSockaddr(this.addr);
if (info.family === 2 || info.family === 10) {
_stats.connections++;
var connId = 'fd_' + this.fd;
_connectionMap[connId] = {
fd: this.fd,
ip: info.ip,
port: info.port,
timestamp: new Date().toISOString(),
pid: Process.id,
tid: Process.getCurrentThreadId(),
};
var isMonitored = SYSCALL_CONFIG.MONITORED_PORTS.indexOf(info.port) !== -1;
if (isMonitored) {
emitEvent('tcp_connect', { fd: this.fd, ip: info.ip, port: info.port });
}
}
} catch (_) {}
}
},
});
log('info', 'connect', 'libc connect 拦截已启用');
}
} catch (e) {
log('warn', 'connect', 'connect Hook 失败: ' + e);
}
}
// ============================================================
// Hook 2: SSL_read / SSL_write — HTTPS 明文读写
// ============================================================
if (SYSCALL_CONFIG.HOOK_SSL) {
var sslLibNames = ['libssl.so', 'libssl.so.1.1', 'libssl.so.3'];
function hookSSL(libName) {
try {
var SSL_read = Module.findExportByName(libName, 'SSL_read');
var SSL_write = Module.findExportByName(libName, 'SSL_write');
if (SSL_read) {
Interceptor.attach(SSL_read, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
},
onLeave: function (retval) {
var len = retval.toInt32();
if (len > 0) {
_stats.ssl_reads++;
_stats.bytes_received += len;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && len <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(len, 512));
emitEvent('ssl_read', { length: len, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
}
},
});
}
if (SSL_write) {
Interceptor.attach(SSL_write, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
_stats.ssl_writes++;
_stats.bytes_sent += this.num;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && this.num <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(this.num, 512));
emitEvent('ssl_write', { length: this.num, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
},
});
}
if (SSL_read || SSL_write) {
log('info', 'ssl', 'SSL Hook 已启用: ' + libName);
return true;
}
} catch (_) {}
return false;
}
var sslHooked = false;
for (var li = 0; li < sslLibNames.length && !sslHooked; li++) {
sslHooked = hookSSL(sslLibNames[li]);
}
if (!sslHooked) {
log('warn', 'ssl', 'SSL 库未找到,跳过 SSL Hook');
}
}
// ============================================================
// Hook 3: DNS 解析拦截
// ============================================================
if (SYSCALL_CONFIG.HOOK_DNS) {
try {
var getaddrinfoPtr = Module.findExportByName('libc.so', 'getaddrinfo');
if (getaddrinfoPtr) {
Interceptor.attach(getaddrinfoPtr, {
onEnter: function (args) {
this.hostname = args[0].isNull() ? '' : args[0].readCString();
},
onLeave: function (retval) {
if (this.hostname && retval.toInt32() === 0) {
_stats.dns_queries++;
var isMonitored = false;
for (var i = 0; i < SYSCALL_CONFIG.MONITORED_DOMAINS.length; i++) {
if (this.hostname.indexOf(SYSCALL_CONFIG.MONITORED_DOMAINS[i]) !== -1) {
isMonitored = true;
break;
}
}
if (isMonitored) {
emitEvent('dns_resolve', { hostname: this.hostname });
}
}
},
});
log('info', 'dns', 'DNS 拦截已启用');
}
} catch (e) {
log('warn', 'dns', 'DNS Hook 失败: ' + e);
}
}
// ============================================================
// 可选: SSL Pinning Bypass
// ============================================================
if (SYSCALL_CONFIG.SSL_BYPASS) {
Java.perform(function () {
// TrustManager bypass
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function () {
return arguments[0];
};
log('info', 'bypass', 'TrustManagerImpl bypass 已启用');
} catch (_) {}
// OkHttp CertificatePinner bypass
try {
var CertPinner = Java.use('okhttp3.CertificatePinner');
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation = function () {};
log('info', 'bypass', 'OkHttp CertificatePinner bypass 已启用');
} catch (_) {}
// WebViewClient SSL bypass
try {
var WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
handler.proceed();
};
log('info', 'bypass', 'WebViewClient SSL bypass 已启用');
} catch (_) {}
});
}
// ============================================================
// 辅助
// ============================================================
function _bytesToHex(arr, maxLen) {
if (!arr) return '';
var bytes = new Uint8Array(arr);
var hex = [];
var limit = Math.min(bytes.length, maxLen || 64);
for (var i = 0; i < limit; i++) {
hex.push(('0' + bytes[i].toString(16)).slice(-2));
}
return hex.join(' ') + (bytes.length > limit ? '...' : '');
}
log('info', 'init', 'syscall_hook 初始化完成', _stats);
emitEvent('syscall_hook_initialized', { config: SYSCALL_CONFIG, stats: _stats });

View File

@@ -683,13 +683,10 @@ rpc.exports = {
var content = (params && params.content) || '';
var imageUrls = (params && params.image_urls) || [];
if (!content && imageUrls.length === 0) return { success: false, error: '朋友圈内容不能为空' };
try {
return Java.performNow(function () {
return _postMomentsInternal(content, imageUrls);
});
} catch (e) {
return { success: false, error: String(e) };
}
return _intentAction('com.workphone.POST_MOMENTS', {
content: content,
image_urls: (imageUrls || []).join(','),
}, 'moments_posted', { content: content });
},
getMoments: function (params) {
@@ -902,6 +899,59 @@ rpc.exports = {
return _intentAction('com.workphone.GET_TRANSACTIONS', { limit: String(limit) }, null, {});
},
showPaymentCode: function () {
return _intentAction('com.workphone.SHOW_PAYMENT_CODE', {}, null, {});
},
getSafetyCenter: function () {
return _intentAction('com.workphone.GET_SAFETY_CENTER', {}, null, {});
},
checkRestrictions: function () {
try {
return Java.performNow(function () {
var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制'];
var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200';
var rows = _execSQL(sql);
var hits = [];
for (var i = 0; i < rows.length; i++) {
var c = String(rows[i].content || '');
for (var j = 0; j < kws.length; j++) {
if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]);
}
}
return { success: true, restricted: hits.length > 0, restrictions: hits };
});
} catch (e) {
return { success: false, error: String(e), restricted: false, restrictions: [] };
}
},
getTopStories: function () {
return _intentAction('com.workphone.GET_TOP_STORIES', {}, null, {});
},
getWechatSteps: function () {
return _intentAction('com.workphone.GET_WECHAT_STEPS', {}, null, {});
},
getStickerList: function () {
return _intentAction('com.workphone.GET_STICKER_LIST', {}, null, {});
},
likeWechatSteps: function (params) {
var userId = (params && params.user_id) || '';
return _intentAction('com.workphone.LIKE_WECHAT_STEPS', { user_id: userId }, 'steps_liked', { user_id: userId });
},
clearCache: function () {
return _intentAction('com.workphone.CLEAR_CACHE', {}, 'cache_cleared', {});
},
checkForUpdate: function () {
return _intentAction('com.workphone.CHECK_UPDATE', {}, 'update_checked', {});
},
// ==================== H26 二维码 ====================
scanQrCode: function (params) {
@@ -934,6 +984,9 @@ rpc.exports = {
likeChannelVideo: function (params) {
var videoId = (params && params.video_id) || '';
if (!videoId && params && params.index !== undefined) {
videoId = 'index_' + params.index;
}
if (!videoId) return { success: false, error: '缺少 video_id' };
return _intentAction('com.workphone.LIKE_CHANNEL', { video_id: videoId }, 'channel_liked', { video_id: videoId });
},
@@ -1041,7 +1094,11 @@ rpc.exports = {
addFavorite: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
var type = (params && params.type) || 'message';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
var content = (params && params.content) || (params && params.content_desc) || '';
if (!msgSvrId && content) {
return _intentAction('com.workphone.ADD_FAVORITE_TEXT', { content: content, type: type }, 'favorite_added', { content: content });
}
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id 或 content' };
return _intentAction('com.workphone.ADD_FAVORITE', { msg_svr_id: msgSvrId, type: type }, 'favorite_added', { msg_svr_id: msgSvrId });
},
@@ -1081,7 +1138,9 @@ rpc.exports = {
setDoNotDisturb: function (params) {
var wxid = (params && params.wxid) || '';
var enable = params && params.enable !== undefined ? params.enable : true;
if (!wxid) return { success: false, error: '缺少 wxid' };
if (!wxid) {
return _intentAction('com.workphone.SET_GLOBAL_DND', { enable: String(enable) }, 'dnd_set', { enable: enable });
}
return _intentAction('com.workphone.SET_DND', { wxid: wxid, enable: String(enable) }, 'dnd_set', { wxid: wxid, enable: enable });
},
@@ -1119,7 +1178,7 @@ rpc.exports = {
// ==================== H32 小程序 ====================
openMiniProgram: function (params) {
var appId = (params && params.app_id) || '';
var appId = (params && params.app_id) || (params && params.name) || '';
var path = (params && params.path) || '';
if (!appId) return { success: false, error: '缺少 app_id' };
return _intentAction('com.workphone.OPEN_MINI_PROGRAM', { app_id: appId, path: path }, 'mini_program_opened', { app_id: appId });
@@ -1161,10 +1220,14 @@ rpc.exports = {
},
sendVoice: function (params) {
var toId = (params && params.to_id) || '';
var toId = (params && params.to_id) || (params && params.user_id) || '';
var voicePath = (params && params.voice_path) || '';
var duration = (params && params.duration) || 0;
if (!toId || !voicePath) return { success: false, error: '缺少 to_id 或 voice_path' };
if (!toId) return { success: false, error: '缺少 to_id' };
if (!voicePath && duration) {
return _intentAction('com.workphone.SEND_VOICE_RECORD', { to_id: toId, duration: String(duration) }, 'voice_sent', { to_id: toId, duration: duration });
}
if (!voicePath) return { success: false, error: '缺少 voice_path' };
return _intentAction('com.workphone.SEND_VOICE', { to_id: toId, voice_path: voicePath, duration: String(duration) }, 'voice_sent', { to_id: toId });
},
@@ -1339,8 +1402,8 @@ rpc.exports = {
// ==================== H37 表情管理 ====================
sendEmoji: function (params) {
var toId = (params && params.to_id) || '';
var emojiMd5 = (params && params.emoji_md5) || '';
var toId = (params && params.to_id) || (params && params.user_id) || '';
var emojiMd5 = (params && params.emoji_md5) || (params && params.emoji_name) || '';
if (!toId || !emojiMd5) return { success: false, error: '缺少 to_id 或 emoji_md5' };
return _intentAction('com.workphone.SEND_EMOJI', { to_id: toId, emoji_md5: emojiMd5 }, 'emoji_sent', { to_id: toId });
},

View File

@@ -2,7 +2,10 @@
工作手机SDK v3.0 - 配置管理
"""
from pydantic_settings import BaseSettings
try:
from pydantic_settings import BaseSettings
except ImportError: # NAS / pydantic v1 降级
from pydantic import BaseSettings
from typing import Optional

View File

@@ -172,19 +172,19 @@
"modules": []
},
"7921cf53a297b0d174e09f2f102eced4": {
"supports_hook": false,
"frida_version": "",
"supports_hook": true,
"frida_version": "16.5.6",
"root_status": true,
"hook_framework": "frida-server",
"updated_at": "2026-04-09T14:08:46.744104+00:00",
"updated_at": "2026-05-24T01:30:32.399950+00:00",
"modules": []
},
"xgfe65eimrrofyws": {
"supports_hook": false,
"frida_version": "",
"supports_hook": true,
"frida_version": "16.5.6",
"root_status": true,
"hook_framework": "frida-server",
"updated_at": "2026-05-05T08:03:16.188260+00:00",
"updated_at": "2026-05-24T02:01:13.193007+00:00",
"modules": []
},
"6c08d7b2de2fd67a5e24753a6b6920a5": {

136
sdk/scripts/_hook_smoke.py Normal file
View File

@@ -0,0 +1,136 @@
#!/usr/bin/env python3
# 工作手机 · Frida 主控通道·实机冒烟脚本(一次性跑完 8 项验证)
# 用法python3 sdk/scripts/_hook_smoke.py <device_serial>
import json, sys, urllib.request, urllib.parse, time
DEVICE = sys.argv[1] if len(sys.argv) > 1 else 'xgfe65eimrrofyws'
BASE = 'http://127.0.0.1:8899'
def get(path, params=None):
url = f'{BASE}{path}'
if params:
url += '?' + urllib.parse.urlencode(params)
with urllib.request.urlopen(url, timeout=60) as r:
return json.loads(r.read())
def post(path, body):
req = urllib.request.Request(
f'{BASE}{path}',
data=json.dumps(body).encode(),
headers={'Content-Type': 'application/json'},
method='POST',
)
with urllib.request.urlopen(req, timeout=120) as r:
return json.loads(r.read())
def section(title):
print('\n' + '=' * 60)
print(f' {title}')
print('=' * 60)
def show(label, value, limit=200):
s = json.dumps(value, ensure_ascii=False)
if len(s) > limit:
s = s[:limit] + ' …(truncated)'
print(f'{label}{s}')
# 1) probe
section('1) /api/v3/hook/probe — Hook 探测')
probe = get(f'/api/v3/hook/probe/{DEVICE}')
show('supports_hook', probe.get('supports_hook'))
show('frida_version', probe.get('frida_version'))
show('wechat_version', probe.get('wechat_version'))
show('profile.nickname', probe.get('profile', {}).get('profile', {}).get('nickname'))
# 2) actions 清单
section('2) /api/v3/hook/actions — 全部动作清单')
acts = get('/api/v3/hook/actions')
show('total_actions', acts.get('total_actions'))
show('total_modules', acts.get('total_modules'))
# 3) 一次性取数
section('3) /api/v3/hook/data — 取微信资料/联系人/群/消息/标签')
data = get(f'/api/v3/hook/data/{DEVICE}',
{'modules': 'profile,contacts,groups,messages,labels'})
core = data.get('data', {})
def _list(node, key1, key2=None):
inner = node.get('data', node)
if key2 and isinstance(inner, dict) and key2 in inner:
return inner[key2]
if isinstance(inner, dict) and key1 in inner:
return inner[key1]
if isinstance(inner, dict) and 'data' in inner and isinstance(inner['data'], list):
return inner['data']
return inner if isinstance(inner, list) else []
contacts = _list(core['contacts'], 'contacts')
groups = _list(core['groups'], 'groups')
messages = _list(core['messages'], 'messages')
labels = _list(core['labels'], 'labels')
print(f'contacts : {len(contacts)}')
print(f'groups : {len(groups)}')
print(f'messages : {len(messages)}')
print(f'labels : {len(labels)}')
remark = [c for c in contacts if c.get('remark')]
namedgrp = [g for g in groups if g.get('name')]
print(f' - 有备注联系人: {len(remark)} 例子:',
', '.join((c.get('remark') or '?')[:24] for c in remark[:5]))
print(f' - 已命名群 : {len(namedgrp)} 例子:',
', '.join((g.get('name') or '?')[:24] for g in namedgrp[:5]))
print(f' - 标签 : {len(labels)} 例子:',
', '.join((l.get('name') or '?')[:18] for l in labels[:8]))
# 4) hookStatus / getMoments / getWechatVersion 直调
section('4) /api/v3/hook/execute — hookStatus + getWechatVersion')
hs = post('/api/v3/hook/execute', {
'device_id': DEVICE, 'platform': 'wechat',
'action': 'get_hook_status', 'params': {}, 'hook_only': True
})
show('hookStatus', hs)
gv = post('/api/v3/hook/execute', {
'device_id': DEVICE, 'platform': 'wechat',
'action': 'get_wechat_version', 'params': {}, 'hook_only': True
})
show('getWechatVersion', gv)
# 5) 朋友圈
section('5) /api/v3/hook/execute — getMoments(limit=3)')
mm = post('/api/v3/hook/execute', {
'device_id': DEVICE, 'platform': 'wechat',
'action': 'get_moments', 'params': {'limit': 3}, 'hook_only': True
})
show('getMoments', mm, 400)
# 6) 真发一条消息到文件传输助手
section('6) /api/v3/hook/execute — send_message → 文件传输助手')
ts = time.strftime('%H:%M:%S')
send = post('/api/v3/hook/execute', {
'device_id': DEVICE, 'platform': 'wechat',
'action': 'send_message',
'params': {
'to_id': 'filehelper',
'content': f'[E2E] Frida 主控通道 实机冒烟 {ts}',
'msg_type': 'text',
},
'hook_only': True
})
show('send_message', send)
# 7) 拉一条最近消息证明 read-back
section('7) /api/v3/hook/execute — get_recent_messages(limit=1)')
rm = post('/api/v3/hook/execute', {
'device_id': DEVICE, 'platform': 'wechat',
'action': 'get_recent_messages', 'params': {'limit': 1}, 'hook_only': True
})
show('get_recent_messages', rm, 400)
# 8) 走标准 unified channel=hook 的 sendMessage验证存客宝侧路径
section('8) /api/v3/message/send channel=hook (存客宝标准入口)')
um = post('/api/v3/message/send', {
'device_id': DEVICE, 'platform': 'wechat', 'to_id': 'filehelper',
'content': f'[E2E] unified channel=hook {ts}',
'msg_type': 'text', 'channel': 'hook'
})
show('unified.sendMessage', um, 400)
print('\n ✅ Hook 主控冒烟测试完成')