ksjsb/tools/frida_probe_passport_wcfg_response_safe.js
2026-07-30 20:25:56 +08:00

638 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 深度闭环抓证脚本:
*
* /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 请求体明文 payloadU1
// RESPONSE_BODY_STRING 抓响应体里的 a_y_q_zU2。两者配合回填 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);
})();