/* * 深度闭环抓证脚本: * * /f/a/p response -> wcfg["a_y_q_z"] 写入 -> WeaponHI.dd(21) 读取 * -> passport_account_image * * 用法: * 先把 APP 打开到登录页,再按主进程 PID 附加: * powershell -NoProfile -ExecutionPolicy Bypass ` * -File tools\frida_attach_nebula_main_wcfg.ps1 * * 说明: * deep 版会读取 /f/a/p 请求体和响应体;仍不推荐直接 -f 包名。部分设备上 spawn 可能落到 :push_v3 等子进程, * 本脚本 MAIN_ONLY=true 时会正确跳过这些子进程。 * * 设计原则:每个 hook 独立 try/catch,单点失败不影响后续 hook。 */ 'use strict'; (function () { const SCRIPT = 'frida_probe_passport_wcfg_response_safe.js'; const CFG = { // 安全模式:抓关键业务链。REQUEST_BODY 已开启,用于一次性抓取 /f/a/p 请求体明文 payload(U1); // RESPONSE_BODY_STRING 抓响应体里的 a_y_q_z(U2)。两者配合回填 fap_request.py 占位。 MAIN_ONLY: false, STACK: false, REQUEST_BODY: true, RESPONSE_BODY_STRING: true, SP_EDITOR_INTERFACE: false, PROCESS_NAME_PROBE: false }; const INSTALL_DELAY_MS = 2500; const MAIN_PROCESS = 'com.kuaishou.nebula'; let seq = 0; function now() { return new Date().toISOString(); } function shortValue(v, n) { const s = (v === null || v === undefined) ? '' : String(v); const limit = n || 160; if (s.length <= limit) return s; return s.slice(0, limit) + '...(len=' + s.length + ')'; } function valueFields(key, value, n) { const s = (value === null || value === undefined) ? '' : String(value); const out = { value: shortValue(s, n || 260) }; if (isInterestingKey(key) || isInterestingValue(s)) { out.value_full = s; out.value_len = s.length; } return out; } function namedValueFields(primary, value, n, forceFull) { const s = (value === null || value === undefined) ? '' : String(value); const out = {}; out[primary] = shortValue(s, n || 260); if (forceFull || isInterestingValue(s) || s.indexOf('a_y_q_z') >= 0 || s.indexOf('passport_account_image') >= 0) { out[primary + '_full'] = s; out[primary + '_len'] = s.length; } return out; } function urlFields(url, n) { const s = (url === null || url === undefined) ? '' : String(url); return { url: shortValue(s, n || 700), url_full: s, url_len: s.length }; } function emit(tag, obj) { try { obj = obj || {}; obj.tag = tag; obj.seq = ++seq; obj.ts = now(); obj.pid = Process.id; console.log('@@WCFG ' + JSON.stringify(obj)); } catch (e) { console.log('@@WCFG {"tag":"EMIT_ERR","err":"' + String(e) + '"}'); } } function safeHook(name, fn) { try { fn(); emit('HOOK_OK', { name: name }); } catch (e) { emit('HOOK_FAIL', { name: name, err: String(e), stack: String(e && e.stack || '') }); } } function javaStack() { if (!CFG.STACK) return ''; try { const Log = Java.use('android.util.Log'); const Exception = Java.use('java.lang.Exception'); return String(Log.getStackTraceString(Exception.$new())); } catch (e) { return '[stack_err] ' + String(e); } } function withStack(obj) { if (CFG.STACK) obj.stack = javaStack(); return obj; } function objId(o) { try { return String(o.hashCode()); } catch (e) { return '[hash_err]'; } } function isInterestingKey(k) { const s = String(k || ''); return s === 'a_y_q_z' || s.indexOf('a_y_q_z') >= 0 || s === 'passport_account_image'; } function isInterestingValue(v) { const s = String(v || ''); return s.indexOf('VIMG_') >= 0 || s.indexOf('$AI_') >= 0; } function isTrafficUrl(url) { const s = String(url || ''); return s.indexOf('/f/a/p') >= 0 || s.indexOf('mobile/checker') >= 0 || s.indexOf('requestMobileCode') >= 0 || s.indexOf('mobileVerifyCode') >= 0; } function summarizeArgs(argsLike) { const out = []; for (let i = 0; i < argsLike.length; i++) { out.push(shortValue(argsLike[i], 220)); } return out; } function r1EndpointHint(argsLike) { try { if (argsLike.length === 2) return '/f/a/p'; if (argsLike.length >= 4) return String(argsLike[2]) + String(argsLike[3]); } catch (e) {} return ''; } emit('SCRIPT_START', { script: SCRIPT, cfg: CFG }); setTimeout(function () { Java.perform(function () { const prefsByHash = {}; const requestUrlByHash = {}; const responseBodyUrlByHash = {}; let processName = ''; if (CFG.PROCESS_NAME_PROBE) { safeHook('process name probe', function () { try { const Application = Java.use('android.app.Application'); processName = String(Application.getProcessName()); } catch (e) { try { const ActivityThread = Java.use('android.app.ActivityThread'); processName = String(ActivityThread.currentProcessName()); } catch (ignored) { processName = ''; } } emit('PROCESS_NAME', { process_name: processName }); }); } else { emit('PROCESS_NAME_SKIPPED', { reason: 'safe_mode' }); } if (CFG.MAIN_ONLY && processName && processName !== MAIN_PROCESS) { emit('PROCESS_SKIP', { process_name: processName, reason: 'main_only', main_process: MAIN_PROCESS }); return; } function requestBodyToString(req) { if (!CFG.REQUEST_BODY) return ''; try { const body = req.body(); if (body === null || body === undefined) return ''; const Buffer = Java.use('okio.Buffer'); const buffer = Buffer.$new(); body.writeTo(buffer); return String(buffer.readUtf8()); } catch (e) { return '[body_err] ' + String(e); } } function formBodyToString(req) { try { const body = req.body(); if (body === null || body === undefined) return ''; const FormBody = Java.use('okhttp3.FormBody'); const fb = Java.cast(body, FormBody); const size = fb.size(); const pairs = []; for (let i = 0; i < size; i++) { pairs.push(String(fb.encodedName(i)) + '=' + String(fb.encodedValue(i))); } return pairs.join('&'); } catch (e) { return ''; } } function emitFormField(tag, name, value) { try { const key = String(name || ''); const val = String(value || ''); if (isInterestingKey(key) || isInterestingValue(val)) { emit(tag, { name: key, ...valueFields(key, val, 320), stack: javaStack() }); } } catch (e) { emit(tag + '_ERR', { err: String(e) }); } } function k1Info(k1) { const out = { url: '', body: '' }; try { if (k1 === null || k1 === undefined) return out; try { out.url = String(k1.a.value || ''); } catch (e1) { out.url = ''; } try { const bodyObj = k1.c(); out.body = bodyObj ? String(bodyObj.toString()) : ''; } catch (e2) { out.body = ''; } } catch (e) {} return out; } safeHook('ContextImpl.getSharedPreferences', function () { const ContextImpl = Java.use('android.app.ContextImpl'); const ov = ContextImpl.getSharedPreferences.overload('java.lang.String', 'int'); ov.implementation = function (name, mode) { const ret = ov.call(this, name, mode); try { prefsByHash[objId(ret)] = String(name); if (String(name) === 'wcfg') { emit('PREFS_OPEN', { name: String(name), mode: mode, prefs_hash: objId(ret) }); } } catch (e) {} return ret; }; }); safeHook('SharedPreferencesImpl.getString', function () { const SPI = Java.use('android.app.SharedPreferencesImpl'); const ov = SPI.getString.overload('java.lang.String', 'java.lang.String'); ov.implementation = function (key, defValue) { const ret = ov.call(this, key, defValue); const prefsName = prefsByHash[objId(this)] || ''; if (prefsName === 'wcfg' || isInterestingKey(key) || isInterestingValue(ret)) { emit('PREFS_GET_STRING', { prefs: prefsName, prefs_hash: objId(this), key: String(key), def: shortValue(defValue, 120), ...valueFields(key, ret, 220), stack: javaStack() }); } return ret; }; }); if (CFG.SP_EDITOR_INTERFACE) { safeHook('SharedPreferences.Editor.putString', function () { const Editor = Java.use('android.content.SharedPreferences$Editor'); const ov = Editor.putString.overload('java.lang.String', 'java.lang.String'); ov.implementation = function (key, value) { if (isInterestingKey(key) || isInterestingValue(value)) { emit('EDITOR_PUT_STRING_IFACE', { editor_class: String(this.getClass().getName()), key: String(key), ...valueFields(key, value, 260), stack: javaStack() }); } return ov.call(this, key, value); }; }); } else { emit('HOOK_SKIPPED', { name: 'SharedPreferences.Editor.putString', reason: 'interface_hook_disabled' }); } safeHook('SharedPreferencesImpl.EditorImpl.putString', function () { const EditorImpl = Java.use('android.app.SharedPreferencesImpl$EditorImpl'); const ov = EditorImpl.putString.overload('java.lang.String', 'java.lang.String'); ov.implementation = function (key, value) { if (isInterestingKey(key) || isInterestingValue(value)) { emit('EDITOR_PUT_STRING_IMPL', { key: String(key), ...valueFields(key, value, 260), stack: javaStack() }); } return ov.call(this, key, value); }; }); safeHook('com.kuaishou.weapon.ks.z0 read/write wrappers', function () { const Z0 = Java.use('com.kuaishou.weapon.ks.z0'); const read = Z0.a.overload('java.lang.String', 'java.lang.String'); read.implementation = function (key, defValue) { const ret = read.call(this, key, defValue); if (isInterestingKey(key) || isInterestingValue(ret)) { emit('Z0_READ_A_STRING_STRING', { key: String(key), def: shortValue(defValue, 120), ...valueFields(key, ret, 260), stack: javaStack() }); } return ret; }; const write3 = Z0.a.overload('boolean', 'java.lang.String', 'java.lang.String'); write3.implementation = function (applyNow, key, value) { if (isInterestingKey(key) || isInterestingValue(value)) { emit('Z0_WRITE_A_BOOL_STRING_STRING', { apply_now: !!applyNow, key: String(key), ...valueFields(key, value, 260), stack: javaStack() }); } return write3.call(this, applyNow, key, value); }; const writeB = Z0.b.overload('java.lang.String', 'java.lang.String', 'boolean'); writeB.implementation = function (key, value, encode) { if (isInterestingKey(key) || isInterestingValue(value)) { emit('Z0_WRITE_B_STRING_STRING_BOOL', { key: String(key), ...valueFields(key, value, 260), encode: !!encode, stack: javaStack() }); } return writeB.call(this, key, value, encode); }; }); safeHook('WeaponHI.dd/db/b', function () { const WeaponHI = Java.use('com.kuaishou.weapon.i.WeaponHI'); if (WeaponHI.dd) { const dd = WeaponHI.dd.overload('int'); dd.implementation = function (type) { const ret = dd.call(this, type); if (type === 21 || isInterestingValue(ret)) { emit('WEAPON_DD', { type: type, ...valueFields('a_y_q_z', ret, 260), stack: javaStack() }); } return ret; }; } if (WeaponHI.b) { const b = WeaponHI.b.overload('java.lang.String'); b.implementation = function (input) { const ret = b.call(this, input); emit('WEAPON_B_UPLOAD_ENCRYPT', { input: shortValue(input, 180), ...valueFields('weaponhi_b', ret, 220), stack: javaStack() }); return ret; }; } }); safeHook('com.kuaishou.weapon.ks.r1 upload wrappers', function () { const R1 = Java.use('com.kuaishou.weapon.ks.r1'); R1.a.overloads.forEach(function (ov, idx) { ov.implementation = function () { const endpointHint = r1EndpointHint(arguments); emit('R1_A_BEFORE', { overload: idx, argc: arguments.length, endpoint_hint: endpointHint, args: summarizeArgs(arguments), stack: javaStack() }); const ret = ov.apply(this, arguments); emit('R1_A_AFTER', { overload: idx, endpoint_hint: endpointHint, ...namedValueFields('ret', ret, 600, endpointHint.indexOf('/f/a/p') >= 0 || isInterestingValue(ret)) }); return ret; }; }); R1.b.overloads.forEach(function (ov, idx) { ov.implementation = function () { emit('R1_B_BEFORE', { overload: idx, argc: arguments.length, args: summarizeArgs(arguments), stack: javaStack() }); const ret = ov.apply(this, arguments); emit('R1_B_AFTER', { overload: idx, ...namedValueFields('ret', ret, 600, isInterestingValue(ret)) }); return ret; }; }); }); safeHook('com.kuaishou.weapon.ks.i/y0 dispatch returns', function () { const I = Java.use('com.kuaishou.weapon.ks.i'); const Y0 = Java.use('com.kuaishou.weapon.ks.y0'); const iStatic = I.a.overload('com.kuaishou.weapon.ks.k1'); iStatic.implementation = function (k1) { const info = k1Info(k1); if (isTrafficUrl(info.url) || isInterestingValue(info.body)) { emit('I_A_K1_BEFORE', { ...urlFields(info.url, 700), ...namedValueFields('body', info.body, 1000, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(info.body)), stack: javaStack() }); } const ret = iStatic.call(this, k1); if (isTrafficUrl(info.url) || isInterestingValue(ret)) { emit('I_A_K1_AFTER', { ...urlFields(info.url, 700), ...namedValueFields('ret', ret, 1200, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(ret)) }); } return ret; }; const iCtx = I.a.overload('android.content.Context', 'com.kuaishou.weapon.ks.k1'); iCtx.implementation = function (ctx, k1) { const info = k1Info(k1); if (isTrafficUrl(info.url) || isInterestingValue(info.body)) { emit('I_A_CTX_K1_BEFORE', { ...urlFields(info.url, 700), ...namedValueFields('body', info.body, 1000, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(info.body)), stack: javaStack() }); } const ret = iCtx.call(this, ctx, k1); if (isTrafficUrl(info.url) || isInterestingValue(ret)) { emit('I_A_CTX_K1_AFTER', { ...urlFields(info.url, 700), ...namedValueFields('ret', ret, 1200, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(ret)) }); } return ret; }; const y0a = Y0.a.overload('com.kuaishou.weapon.ks.k1'); y0a.implementation = function (k1) { const info = k1Info(k1); if (isTrafficUrl(info.url) || isInterestingValue(info.body)) { emit('Y0_A_K1_BEFORE', { ...urlFields(info.url, 700), ...namedValueFields('body', info.body, 1000, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(info.body)), stack: javaStack() }); } const ret = y0a.call(this, k1); if (isTrafficUrl(info.url) || isInterestingValue(ret)) { emit('Y0_A_K1_AFTER', { ...urlFields(info.url, 700), ...namedValueFields('ret', ret, 1200, info.url.indexOf('/f/a/p') >= 0 || isInterestingValue(ret)) }); } return ret; }; }); safeHook('okhttp3.Request.Builder.url', function () { const Builder = Java.use('okhttp3.Request$Builder'); Builder.url.overloads.forEach(function (ov, idx) { ov.implementation = function () { const arg0 = arguments.length > 0 ? String(arguments[0]) : ''; if (arg0.indexOf('/f/a/p') >= 0 || arg0.indexOf('mobile/checker') >= 0) { emit('OKHTTP_BUILDER_URL', { overload: idx, ...urlFields(arg0, 500), stack: javaStack() }); } return ov.apply(this, arguments); }; }); }); safeHook('okhttp3.Request.Builder.build', function () { const Builder = Java.use('okhttp3.Request$Builder'); const build = Builder.build.overload(); build.implementation = function () { const req = build.call(this); try { const url = String(req.url()); if (isTrafficUrl(url)) { const body = formBodyToString(req) || requestBodyToString(req); requestUrlByHash[objId(req)] = url; emit('OKHTTP_REQUEST_BUILD', { request_hash: objId(req), method: String(req.method()), ...urlFields(url, 700), ...namedValueFields('body', body, 700, url.indexOf('/f/a/p') >= 0 || isInterestingValue(body)), stack: javaStack() }); } } catch (e) { emit('OKHTTP_REQUEST_BUILD_ERR', { err: String(e) }); } return req; }; }); safeHook('okhttp3.OkHttpClient.newCall', function () { const Client = Java.use('okhttp3.OkHttpClient'); const ov = Client.newCall.overload('okhttp3.Request'); ov.implementation = function (req) { try { const url = String(req.url()); if (isTrafficUrl(url)) { requestUrlByHash[objId(req)] = url; emit('OKHTTP_NEWCALL', { request_hash: objId(req), method: String(req.method()), ...urlFields(url, 700) }); } } catch (e) {} return ov.call(this, req); }; }); safeHook('okhttp3.FormBody.Builder.add/addEncoded', function () { const FormBuilder = Java.use('okhttp3.FormBody$Builder'); const add = FormBuilder.add.overload('java.lang.String', 'java.lang.String'); add.implementation = function (name, value) { emitFormField('OKHTTP_FORM_ADD', name, value); return add.call(this, name, value); }; const addEncoded = FormBuilder.addEncoded.overload('java.lang.String', 'java.lang.String'); addEncoded.implementation = function (name, value) { emitFormField('OKHTTP_FORM_ADD_ENCODED', name, value); return addEncoded.call(this, name, value); }; }); safeHook('com.squareup.okhttp.FormEncodingBuilder.add/addEncoded', function () { const FormBuilder = Java.use('com.squareup.okhttp.FormEncodingBuilder'); const add = FormBuilder.add.overload('java.lang.String', 'java.lang.String'); add.implementation = function (name, value) { emitFormField('OKHTTP2_FORM_ADD', name, value); return add.call(this, name, value); }; const addEncoded = FormBuilder.addEncoded.overload('java.lang.String', 'java.lang.String'); addEncoded.implementation = function (name, value) { emitFormField('OKHTTP2_FORM_ADD_ENCODED', name, value); return addEncoded.call(this, name, value); }; }); if (CFG.RESPONSE_BODY_STRING) { safeHook('okhttp3.Response.body -> ResponseBody.string', function () { const Response = Java.use('okhttp3.Response'); const bodyOv = Response.body.overload(); bodyOv.implementation = function () { const body = bodyOv.call(this); try { const req = this.request(); const url = requestUrlByHash[objId(req)] || String(req.url()); if (body !== null && body !== undefined && isTrafficUrl(url)) { responseBodyUrlByHash[objId(body)] = url; emit('OKHTTP_RESPONSE_BODY', { body_hash: objId(body), request_hash: objId(req), code: this.code(), ...urlFields(url, 700) }); } } catch (e) {} return body; }; const ResponseBody = Java.use('okhttp3.ResponseBody'); const stringOv = ResponseBody.string.overload(); stringOv.implementation = function () { const ret = stringOv.call(this); try { const url = responseBodyUrlByHash[objId(this)] || ''; if (isTrafficUrl(url) || isInterestingValue(ret) || String(ret).indexOf('a_y_q_z') >= 0) { emit('OKHTTP_RESPONSE_BODY_STRING', { body_hash: objId(this), ...urlFields(url, 700), ...namedValueFields('body', ret, 1000, isTrafficUrl(url) || isInterestingValue(ret)) }); } } catch (e) { emit('OKHTTP_RESPONSE_BODY_STRING_ERR', { err: String(e) }); } return ret; }; }); } else { emit('HOOK_SKIPPED', { name: 'okhttp3.Response.body -> ResponseBody.string', reason: 'response_body_string_disabled' }); } }); }, INSTALL_DELAY_MS); })();