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