feat: publish workphone SDK deployment and API docs
This commit is contained in:
332
sdk/agent/hook/scripts/common.js
Normal file
332
sdk/agent/hook/scripts/common.js
Normal 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,
|
||||
};
|
||||
312
sdk/agent/hook/scripts/syscall_hook.js
Normal file
312
sdk/agent/hook/scripts/syscall_hook.js
Normal 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 });
|
||||
Reference in New Issue
Block a user