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

519 lines
17 KiB
JavaScript
Raw 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
*
* 说明:
* 不推荐直接 -f 包名。部分设备上 spawn 可能落到 :push_v3 等子进程,
* 本脚本 MAIN_ONLY=true 时会正确跳过这些子进程。
*
* 设计原则:每个 hook 独立 try/catch单点失败不影响后续 hook。
*/
'use strict';
(function () {
const SCRIPT = 'frida_probe_passport_wcfg.js';
const CFG = {
// 默认安全模式:只抓关键业务链,不碰一次性 body不在热路径打堆栈。
MAIN_ONLY: true,
STACK: false,
REQUEST_BODY: false,
RESPONSE_BODY_STRING: false,
SP_EDITOR_INTERFACE: false
};
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 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 });
Java.perform(function () {
const prefsByHash = {};
const requestUrlByHash = {};
const responseBodyUrlByHash = {};
let processName = '';
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 });
});
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 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) });
}
}
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('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, url: shortValue(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 = requestBodyToString(req);
requestUrlByHash[objId(req)] = url;
emit('OKHTTP_REQUEST_BUILD', {
request_hash: objId(req),
method: String(req.method()),
url: shortValue(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()),
url: shortValue(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(),
url: shortValue(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),
url: shortValue(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' });
}
});
})();