jhs_raw_codec_rpc.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. 'use strict';
  2. var _state = {
  3. ready: false,
  4. B: null,
  5. A: null,
  6. bInst: null,
  7. aInst: null,
  8. RequestBuilder: null,
  9. ResponseBuilder: null,
  10. ResponseBody: null,
  11. MediaType: null,
  12. Protocol: null,
  13. ChainEnc: null,
  14. ChainDec: null,
  15. capturedUrl: '',
  16. jniProbeReady: false,
  17. jniAttached: {}
  18. };
  19. var _JNI_NAME_RE = /(enc|encrypt|cipher|aes|codec|raw|sign|decode|decrypt)/i;
  20. function _tryReadUtf8(p) {
  21. if (!p || p.isNull()) return '';
  22. try { return Memory.readUtf8String(p); } catch (_) {}
  23. return '';
  24. }
  25. function _attachNativeProbe(fnPtr, tag) {
  26. if (!fnPtr || fnPtr.isNull()) return false;
  27. var key = fnPtr.toString();
  28. if (_state.jniAttached[key]) return false;
  29. _state.jniAttached[key] = tag || 'native';
  30. Interceptor.attach(fnPtr, {
  31. onEnter: function (args) {
  32. var input = _tryReadUtf8(args[0]);
  33. if (!input) input = _tryReadUtf8(args[2]);
  34. console.log("input:", input);
  35. console.log("key_ptr:", args[1]);
  36. try {
  37. console.log("key:", Memory.readUtf8String(args[1]));
  38. } catch(e){}
  39. }
  40. });
  41. console.log('[jni-probe] attached:', _state.jniAttached[key], '@', key);
  42. return true;
  43. }
  44. function _hookRegisterNatives(regPtr, label) {
  45. if (!regPtr || regPtr.isNull()) return;
  46. Interceptor.attach(regPtr, {
  47. onEnter: function (args) {
  48. try {
  49. var methods = args[2];
  50. var nMethods = args[3].toInt32();
  51. if (!methods || methods.isNull()) return;
  52. if (nMethods <= 0 || nMethods > 1024) return;
  53. var step = Process.pointerSize * 3;
  54. for (var i = 0; i < nMethods; i++) {
  55. var item = methods.add(i * step);
  56. var namePtr = item.readPointer();
  57. var sigPtr = item.add(Process.pointerSize).readPointer();
  58. var fnPtr = item.add(Process.pointerSize * 2).readPointer();
  59. var name = _tryReadUtf8(namePtr);
  60. var sig = _tryReadUtf8(sigPtr);
  61. if (!_JNI_NAME_RE.test(name) && !_JNI_NAME_RE.test(sig)) continue;
  62. _attachNativeProbe(fnPtr, name + sig);
  63. }
  64. } catch (_) {}
  65. }
  66. });
  67. console.log('[jni-probe] hooked RegisterNatives:', label);
  68. }
  69. function ensureJniProbe() {
  70. if (_state.jniProbeReady) return;
  71. _state.jniProbeReady = true;
  72. try {
  73. var resolver = new ApiResolver('module');
  74. var regs = resolver.enumerateMatchesSync('exports:*!RegisterNatives*');
  75. for (var i = 0; i < regs.length; i++) {
  76. _hookRegisterNatives(regs[i].address, regs[i].name);
  77. }
  78. } catch (e) {
  79. console.log('[jni-probe] register hook failed:', e);
  80. }
  81. try {
  82. var mods = Process.enumerateModulesSync();
  83. for (var m = 0; m < mods.length; m++) {
  84. var mod = mods[m];
  85. if (mod.name.indexOf('.so') === -1) continue;
  86. var exps = [];
  87. try { exps = Module.enumerateExportsSync(mod.name); } catch (_) { continue; }
  88. for (var j = 0; j < exps.length; j++) {
  89. var exp = exps[j];
  90. if (exp.type !== 'function') continue;
  91. if (exp.name.indexOf('Java_') !== 0) continue;
  92. if (!_JNI_NAME_RE.test(exp.name)) continue;
  93. _attachNativeProbe(exp.address, exp.name + '@' + mod.name);
  94. }
  95. }
  96. } catch (e2) {
  97. console.log('[jni-probe] export scan failed:', e2);
  98. }
  99. }
  100. function initJava() {
  101. if (_state.ready) return;
  102. _state.B = Java.use('gc.b');
  103. _state.A = Java.use('gc.a');
  104. _state.RequestBuilder = Java.use('okhttp3.Request$Builder');
  105. _state.ResponseBuilder = Java.use('okhttp3.Response$Builder');
  106. _state.ResponseBody = Java.use('okhttp3.ResponseBody');
  107. _state.MediaType = Java.use('okhttp3.MediaType');
  108. _state.Protocol = Java.use('okhttp3.Protocol');
  109. _state.ChainEnc = Java.registerClass({
  110. name: 'com.jhs.RawCodecEncChain',
  111. implements: [Java.use('okhttp3.Interceptor$Chain')],
  112. methods: {
  113. request: function () { return this.req.value; },
  114. proceed: function (req) {
  115. _state.capturedUrl = req.url().toString();
  116. var body = _state.ResponseBody.create.overload('okhttp3.MediaType', 'java.lang.String')
  117. .call(_state.ResponseBody, _state.MediaType.parse('application/json; charset=utf-8'), '{}');
  118. return _state.ResponseBuilder.$new()
  119. .request(req)
  120. .protocol(_state.Protocol.valueOf('HTTP_1_1'))
  121. .code(200)
  122. .message('OK')
  123. .body(body)
  124. .build();
  125. },
  126. connection: function () { return null; },
  127. call: function () { return null; },
  128. connectTimeoutMillis: function () { return 15000; },
  129. readTimeoutMillis: function () { return 15000; },
  130. writeTimeoutMillis: function () { return 15000; },
  131. withConnectTimeout: function (_t, _u) { return this; },
  132. withReadTimeout: function (_t, _u) { return this; },
  133. withWriteTimeout: function (_t, _u) { return this; }
  134. },
  135. fields: { req: 'okhttp3.Request' }
  136. });
  137. _state.ChainDec = Java.registerClass({
  138. name: 'com.jhs.RawCodecDecChain',
  139. implements: [Java.use('okhttp3.Interceptor$Chain')],
  140. methods: {
  141. request: function () { return this.req.value; },
  142. proceed: function (req) {
  143. var json = '{"raw_data":"' + this.cipher.value + '"}';
  144. var body = _state.ResponseBody.create.overload('okhttp3.MediaType', 'java.lang.String')
  145. .call(_state.ResponseBody, _state.MediaType.parse('application/json; charset=utf-8'), json);
  146. return _state.ResponseBuilder.$new()
  147. .request(req)
  148. .protocol(_state.Protocol.valueOf('HTTP_1_1'))
  149. .code(200)
  150. .message('OK')
  151. .body(body)
  152. .build();
  153. },
  154. connection: function () { return null; },
  155. call: function () { return null; },
  156. connectTimeoutMillis: function () { return 15000; },
  157. readTimeoutMillis: function () { return 15000; },
  158. writeTimeoutMillis: function () { return 15000; },
  159. withConnectTimeout: function (_t, _u) { return this; },
  160. withReadTimeout: function (_t, _u) { return this; },
  161. withWriteTimeout: function (_t, _u) { return this; }
  162. },
  163. fields: {
  164. req: 'okhttp3.Request',
  165. cipher: 'java.lang.String'
  166. }
  167. });
  168. _state.ready = true;
  169. }
  170. function getCachedInstance(cacheKey, C, className) {
  171. if (_state[cacheKey] !== null) return _state[cacheKey];
  172. // Fast path: directly construct once and reuse.
  173. try {
  174. _state[cacheKey] = C.$new();
  175. return _state[cacheKey];
  176. } catch (_) {}
  177. // Slow fallback: heap-scan only once when constructor is unavailable.
  178. var out = null;
  179. Java.choose(className, {
  180. onMatch: function (o) {
  181. if (out === null) out = Java.retain(o);
  182. },
  183. onComplete: function () {}
  184. });
  185. if (out !== null) {
  186. _state[cacheKey] = out;
  187. return out;
  188. }
  189. throw new Error('unable to resolve instance for ' + className);
  190. }
  191. function extractRawData(url) {
  192. var m = /[?&]raw_data=([^&#]+)/.exec(url);
  193. if (!m) return '';
  194. var v = m[1];
  195. try { v = decodeURIComponent(v); } catch (_) {}
  196. return v;
  197. }
  198. function encryptInner(url) {
  199. ensureJniProbe();
  200. initJava();
  201. _state.capturedUrl = '';
  202. var req = _state.RequestBuilder.$new().url(url).get().build();
  203. var ch = _state.ChainEnc.$new();
  204. ch.req.value = req;
  205. var b = getCachedInstance('bInst', _state.B, 'gc.b');
  206. b.b(req, ch);
  207. var outUrl = _state.capturedUrl;
  208. return {
  209. ok: true,
  210. input_url: url,
  211. output_url: outUrl,
  212. raw_data: extractRawData(outUrl)
  213. };
  214. }
  215. function decryptInner(requestUrlWithRawData, responseRawData) {
  216. initJava();
  217. var req = _state.RequestBuilder.$new().url(requestUrlWithRawData).get().build();
  218. var ch = _state.ChainDec.$new();
  219. ch.req.value = req;
  220. ch.cipher.value = responseRawData;
  221. var a = getCachedInstance('aInst', _state.A, 'gc.a');
  222. var resp = a.intercept(ch);
  223. var body = resp.body();
  224. var text = body ? body.string() : '';
  225. var parsed = null;
  226. var rawDataPlain = null;
  227. try {
  228. parsed = JSON.parse(text);
  229. if (parsed && parsed.raw_data !== undefined) {
  230. rawDataPlain = (typeof parsed.raw_data === 'string') ? parsed.raw_data : JSON.stringify(parsed.raw_data);
  231. }
  232. } catch (_) {}
  233. return {
  234. ok: true,
  235. request_url: requestUrlWithRawData,
  236. response_raw_data: responseRawData,
  237. response_body: text,
  238. raw_data_plain: rawDataPlain
  239. };
  240. }
  241. rpc.exports = {
  242. ping: function () {
  243. return 'ok';
  244. },
  245. call: function (params) {
  246. return new Promise(function (resolve, reject) {
  247. Java.perform(function () {
  248. try {
  249. if (!params || typeof params !== 'object') {
  250. throw new Error('params must be an object');
  251. }
  252. var op = ('' + (params.op || '')).toLowerCase().trim();
  253. if (op === 'enc') {
  254. if (!params.url) throw new Error("enc requires params.url");
  255. resolve(encryptInner('' + params.url));
  256. return;
  257. }
  258. if (op === 'dec') {
  259. if (!params.request_url) throw new Error("dec requires params.request_url");
  260. if (!params.response_raw_data) throw new Error("dec requires params.response_raw_data");
  261. resolve(decryptInner('' + params.request_url, '' + params.response_raw_data));
  262. return;
  263. }
  264. throw new Error("params.op must be 'enc' or 'dec'");
  265. } catch (e) {
  266. reject('call failed: ' + e);
  267. }
  268. });
  269. });
  270. },
  271. encrypt: function (url) {
  272. return new Promise(function (resolve, reject) {
  273. Java.perform(function () {
  274. try {
  275. resolve(encryptInner(url));
  276. } catch (e) {
  277. reject('encrypt failed: ' + e);
  278. }
  279. });
  280. });
  281. },
  282. decrypt: function (requestUrlWithRawData, responseRawData) {
  283. return new Promise(function (resolve, reject) {
  284. Java.perform(function () {
  285. try {
  286. resolve(decryptInner(requestUrlWithRawData, responseRawData));
  287. } catch (e) {
  288. reject('decrypt failed: ' + e);
  289. }
  290. });
  291. });
  292. }
  293. };