feat: publish workphone SDK deployment and API docs
This commit is contained in:
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