'use strict'; var _state = { ready: false, B: null, A: null, bInst: null, aInst: null, RequestBuilder: null, ResponseBuilder: null, ResponseBody: null, MediaType: null, Protocol: null, ChainEnc: null, ChainDec: null, capturedUrl: '', jniProbeReady: false, jniAttached: {} }; var _JNI_NAME_RE = /(enc|encrypt|cipher|aes|codec|raw|sign|decode|decrypt)/i; function _tryReadUtf8(p) { if (!p || p.isNull()) return ''; try { return Memory.readUtf8String(p); } catch (_) {} return ''; } function _attachNativeProbe(fnPtr, tag) { if (!fnPtr || fnPtr.isNull()) return false; var key = fnPtr.toString(); if (_state.jniAttached[key]) return false; _state.jniAttached[key] = tag || 'native'; Interceptor.attach(fnPtr, { onEnter: function (args) { var input = _tryReadUtf8(args[0]); if (!input) input = _tryReadUtf8(args[2]); console.log("input:", input); console.log("key_ptr:", args[1]); try { console.log("key:", Memory.readUtf8String(args[1])); } catch(e){} } }); console.log('[jni-probe] attached:', _state.jniAttached[key], '@', key); return true; } function _hookRegisterNatives(regPtr, label) { if (!regPtr || regPtr.isNull()) return; Interceptor.attach(regPtr, { onEnter: function (args) { try { var methods = args[2]; var nMethods = args[3].toInt32(); if (!methods || methods.isNull()) return; if (nMethods <= 0 || nMethods > 1024) return; var step = Process.pointerSize * 3; for (var i = 0; i < nMethods; i++) { var item = methods.add(i * step); var namePtr = item.readPointer(); var sigPtr = item.add(Process.pointerSize).readPointer(); var fnPtr = item.add(Process.pointerSize * 2).readPointer(); var name = _tryReadUtf8(namePtr); var sig = _tryReadUtf8(sigPtr); if (!_JNI_NAME_RE.test(name) && !_JNI_NAME_RE.test(sig)) continue; _attachNativeProbe(fnPtr, name + sig); } } catch (_) {} } }); console.log('[jni-probe] hooked RegisterNatives:', label); } function ensureJniProbe() { if (_state.jniProbeReady) return; _state.jniProbeReady = true; try { var resolver = new ApiResolver('module'); var regs = resolver.enumerateMatchesSync('exports:*!RegisterNatives*'); for (var i = 0; i < regs.length; i++) { _hookRegisterNatives(regs[i].address, regs[i].name); } } catch (e) { console.log('[jni-probe] register hook failed:', e); } try { var mods = Process.enumerateModulesSync(); for (var m = 0; m < mods.length; m++) { var mod = mods[m]; if (mod.name.indexOf('.so') === -1) continue; var exps = []; try { exps = Module.enumerateExportsSync(mod.name); } catch (_) { continue; } for (var j = 0; j < exps.length; j++) { var exp = exps[j]; if (exp.type !== 'function') continue; if (exp.name.indexOf('Java_') !== 0) continue; if (!_JNI_NAME_RE.test(exp.name)) continue; _attachNativeProbe(exp.address, exp.name + '@' + mod.name); } } } catch (e2) { console.log('[jni-probe] export scan failed:', e2); } } function initJava() { if (_state.ready) return; _state.B = Java.use('gc.b'); _state.A = Java.use('gc.a'); _state.RequestBuilder = Java.use('okhttp3.Request$Builder'); _state.ResponseBuilder = Java.use('okhttp3.Response$Builder'); _state.ResponseBody = Java.use('okhttp3.ResponseBody'); _state.MediaType = Java.use('okhttp3.MediaType'); _state.Protocol = Java.use('okhttp3.Protocol'); _state.ChainEnc = Java.registerClass({ name: 'com.jhs.RawCodecEncChain', implements: [Java.use('okhttp3.Interceptor$Chain')], methods: { request: function () { return this.req.value; }, proceed: function (req) { _state.capturedUrl = req.url().toString(); var body = _state.ResponseBody.create.overload('okhttp3.MediaType', 'java.lang.String') .call(_state.ResponseBody, _state.MediaType.parse('application/json; charset=utf-8'), '{}'); return _state.ResponseBuilder.$new() .request(req) .protocol(_state.Protocol.valueOf('HTTP_1_1')) .code(200) .message('OK') .body(body) .build(); }, connection: function () { return null; }, call: function () { return null; }, connectTimeoutMillis: function () { return 15000; }, readTimeoutMillis: function () { return 15000; }, writeTimeoutMillis: function () { return 15000; }, withConnectTimeout: function (_t, _u) { return this; }, withReadTimeout: function (_t, _u) { return this; }, withWriteTimeout: function (_t, _u) { return this; } }, fields: { req: 'okhttp3.Request' } }); _state.ChainDec = Java.registerClass({ name: 'com.jhs.RawCodecDecChain', implements: [Java.use('okhttp3.Interceptor$Chain')], methods: { request: function () { return this.req.value; }, proceed: function (req) { var json = '{"raw_data":"' + this.cipher.value + '"}'; var body = _state.ResponseBody.create.overload('okhttp3.MediaType', 'java.lang.String') .call(_state.ResponseBody, _state.MediaType.parse('application/json; charset=utf-8'), json); return _state.ResponseBuilder.$new() .request(req) .protocol(_state.Protocol.valueOf('HTTP_1_1')) .code(200) .message('OK') .body(body) .build(); }, connection: function () { return null; }, call: function () { return null; }, connectTimeoutMillis: function () { return 15000; }, readTimeoutMillis: function () { return 15000; }, writeTimeoutMillis: function () { return 15000; }, withConnectTimeout: function (_t, _u) { return this; }, withReadTimeout: function (_t, _u) { return this; }, withWriteTimeout: function (_t, _u) { return this; } }, fields: { req: 'okhttp3.Request', cipher: 'java.lang.String' } }); _state.ready = true; } function getCachedInstance(cacheKey, C, className) { if (_state[cacheKey] !== null) return _state[cacheKey]; // Fast path: directly construct once and reuse. try { _state[cacheKey] = C.$new(); return _state[cacheKey]; } catch (_) {} // Slow fallback: heap-scan only once when constructor is unavailable. var out = null; Java.choose(className, { onMatch: function (o) { if (out === null) out = Java.retain(o); }, onComplete: function () {} }); if (out !== null) { _state[cacheKey] = out; return out; } throw new Error('unable to resolve instance for ' + className); } function extractRawData(url) { var m = /[?&]raw_data=([^&#]+)/.exec(url); if (!m) return ''; var v = m[1]; try { v = decodeURIComponent(v); } catch (_) {} return v; } function encryptInner(url) { ensureJniProbe(); initJava(); _state.capturedUrl = ''; var req = _state.RequestBuilder.$new().url(url).get().build(); var ch = _state.ChainEnc.$new(); ch.req.value = req; var b = getCachedInstance('bInst', _state.B, 'gc.b'); b.b(req, ch); var outUrl = _state.capturedUrl; return { ok: true, input_url: url, output_url: outUrl, raw_data: extractRawData(outUrl) }; } function decryptInner(requestUrlWithRawData, responseRawData) { initJava(); var req = _state.RequestBuilder.$new().url(requestUrlWithRawData).get().build(); var ch = _state.ChainDec.$new(); ch.req.value = req; ch.cipher.value = responseRawData; var a = getCachedInstance('aInst', _state.A, 'gc.a'); var resp = a.intercept(ch); var body = resp.body(); var text = body ? body.string() : ''; var parsed = null; var rawDataPlain = null; try { parsed = JSON.parse(text); if (parsed && parsed.raw_data !== undefined) { rawDataPlain = (typeof parsed.raw_data === 'string') ? parsed.raw_data : JSON.stringify(parsed.raw_data); } } catch (_) {} return { ok: true, request_url: requestUrlWithRawData, response_raw_data: responseRawData, response_body: text, raw_data_plain: rawDataPlain }; } rpc.exports = { ping: function () { return 'ok'; }, call: function (params) { return new Promise(function (resolve, reject) { Java.perform(function () { try { if (!params || typeof params !== 'object') { throw new Error('params must be an object'); } var op = ('' + (params.op || '')).toLowerCase().trim(); if (op === 'enc') { if (!params.url) throw new Error("enc requires params.url"); resolve(encryptInner('' + params.url)); return; } if (op === 'dec') { if (!params.request_url) throw new Error("dec requires params.request_url"); if (!params.response_raw_data) throw new Error("dec requires params.response_raw_data"); resolve(decryptInner('' + params.request_url, '' + params.response_raw_data)); return; } throw new Error("params.op must be 'enc' or 'dec'"); } catch (e) { reject('call failed: ' + e); } }); }); }, encrypt: function (url) { return new Promise(function (resolve, reject) { Java.perform(function () { try { resolve(encryptInner(url)); } catch (e) { reject('encrypt failed: ' + e); } }); }); }, decrypt: function (requestUrlWithRawData, responseRawData) { return new Promise(function (resolve, reject) { Java.perform(function () { try { resolve(decryptInner(requestUrlWithRawData, responseRawData)); } catch (e) { reject('decrypt failed: ' + e); } }); }); } };