Files
notiMessage/reverse/frida/trace_maribank_sg_native.js
mars 609635aba1 chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展
TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
2026-08-03 15:23:02 +08:00

635 lines
20 KiB
JavaScript

'use strict';
/**
* MariBank SG 3.2.2 — Java + native attestation / encrypt trace
* Package: sg.com.maribankmobile.digitalbank
*
* SG 差异: 无 utils.d / com.shopee.shpssdk.*,仅 shpssdkbank + uvwuvwuv
*/
const TAG = '[MB-NATIVE]';
const MAX_STR = 4000;
const MAX_BYTES_LOG = 8192;
const HOOKED_NATIVE_PTRS = {};
const JAVA_TARGETS = [
'com.shopee.shpssdkbank.wvvvuwwu',
'com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv',
'com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu',
'com.shopee.shpssdkbank.uwuvuvvww.wvvuuwvwu',
'com.shopee.shpssdkbank.uvuwwuvwv.uvwwuuvvw',
'com.shopee.bke.lib.jni.utils.uvwuvwuv',
'com.shopee.bke.lib.jni.utils.uvwwwwuv',
'com.shopee.shpssdkbank.SHPSSDK',
];
const SO_WATCH = [
'libshpssdk_bank.so',
'libshpssdk.so',
'libsdkutils.so',
'libbkutils.so',
];
function log(msg) {
send(TAG + ' ' + msg);
}
function jniFn(envPtr, index, ret, args) {
const funcs = envPtr.readPointer();
const addr = funcs.add(index * Process.pointerSize).readPointer();
if (!addr || addr.isNull()) return null;
return new NativeFunction(addr, ret, args);
}
function jniReadByteArray(envPtr, jarrayPtr) {
if (!jarrayPtr || jarrayPtr.isNull()) return null;
try {
const GetArrayLength = jniFn(envPtr, 171, 'int', ['pointer', 'pointer']);
const GetByteArrayElements = jniFn(envPtr, 184, 'pointer', ['pointer', 'pointer', 'pointer']);
const ReleaseByteArrayElements = jniFn(envPtr, 187, 'void', ['pointer', 'pointer', 'pointer', 'int']);
if (!GetArrayLength || !GetByteArrayElements || !ReleaseByteArrayElements) {
return jniReadByteArrayArt(envPtr, jarrayPtr);
}
const len = GetArrayLength(envPtr, jarrayPtr);
if (len <= 0 || len > MAX_BYTES_LOG) return { len: len, hex: '', text: '' };
const elems = GetByteArrayElements(envPtr, jarrayPtr, ptr(0));
if (!elems || elems.isNull()) return { len: len, hex: '', text: '' };
const raw = elems.readByteArray(Math.min(len, MAX_BYTES_LOG));
ReleaseByteArrayElements(envPtr, jarrayPtr, elems, 0);
return bytesToPreview(raw, len);
} catch (e) {
return { len: -1, hex: 'err:' + e, text: '' };
}
}
function bytesToPreview(raw, len) {
const arr = new Uint8Array(raw);
let text = '';
try {
text = String.fromCharCode.apply(null, arr);
if (text.indexOf('\u0000') >= 0 || !/^[\x20-\x7e\r\n\t\u4e00-\u9fff\u0100-\u024f]+$/.test(text.substring(0, Math.min(text.length, 200)))) {
text = '';
}
} catch (e) {
text = '';
}
if (text.length > MAX_STR) text = text.substring(0, MAX_STR) + '...';
return { len: len, hex: hexPreview(arr, 64), text: text, arr: arr };
}
function jniReadByteArrayArt(envPtr, jarrayPtr) {
const art = moduleByName('libart.so');
if (!art) return { len: -1, hex: 'err:no-art', text: '' };
let sym = null;
art.enumerateSymbols().forEach(function (s) {
if (sym) return;
if (s.name.indexOf('GetByteArrayRegion') >= 0 && s.name.indexOf('JNI') >= 0) {
sym = s.address;
}
});
if (!sym) return { len: -1, hex: 'err:no-GetByteArrayRegion', text: '' };
const GetLen = jniFn(envPtr, 171, 'int', ['pointer', 'pointer']);
const len = GetLen ? GetLen(envPtr, jarrayPtr) : 0;
if (len <= 0 || len > MAX_BYTES_LOG) return { len: len, hex: '', text: '' };
const buf = Memory.alloc(len);
const GetRegion = new NativeFunction(sym, 'void', ['pointer', 'pointer', 'int', 'int', 'pointer']);
GetRegion(envPtr, jarrayPtr, 0, len, buf);
return bytesToPreview(buf.readByteArray(len), len);
}
function jniReadJstring(envPtr, jstrPtr) {
if (!jstrPtr || jstrPtr.isNull()) return '';
try {
const GetStringUTFChars = jniFn(envPtr, 169, 'pointer', ['pointer', 'pointer', 'pointer']);
const ReleaseStringUTFChars = jniFn(envPtr, 170, 'void', ['pointer', 'pointer', 'pointer']);
if (!GetStringUTFChars || !ReleaseStringUTFChars) return '';
const chars = GetStringUTFChars(envPtr, jstrPtr, ptr(0));
if (!chars || chars.isNull()) return '';
const s = chars.readCString();
ReleaseStringUTFChars(envPtr, jstrPtr, chars);
return s || '';
} catch (e) {
return '';
}
}
function dumpNativeArgs(methodName, sig, envPtr, args) {
if (methodName === 'vuwuuwvw' && sig.indexOf('[B[B') >= 0) {
const a0 = jniReadByteArray(envPtr, args[2]);
const a1 = jniReadByteArray(envPtr, args[3]);
if (a0) log(' nat in0 len=' + a0.len + ' hex=' + a0.hex);
if (a1) log(' nat in1 len=' + a1.len + ' hex=' + a1.hex);
return;
}
if (methodName === 'uvwuuww') {
const plain = jniReadByteArray(envPtr, args[2]);
const key = jniReadJstring(envPtr, args[3]);
const flag = args[4] ? args[4].toInt32() : 0;
if (plain) {
log(' nat plain len=' + plain.len + ' hex=' + plain.hex);
if (plain.text) log(' nat plain utf8=' + plain.text);
}
if (key) log(' nat key=' + key + ' flag=' + flag);
return;
}
if (methodName === 'vuwuuuwv' && sig.indexOf('[B[B') >= 0) {
const a0 = jniReadByteArray(envPtr, args[2]);
const a1 = jniReadByteArray(envPtr, args[3]);
if (a0) log(' nat defense in0 len=' + a0.len + ' hex=' + a0.hex);
if (a1) log(' nat defense in1 len=' + a1.len + ' hex=' + a1.hex);
}
}
function hexPreview(arr, limit) {
if (!arr) return '';
const n = Math.min(arr.length, limit || 64);
let hex = '';
for (let i = 0; i < n; i++) {
const b = (arr[i] & 0xff).toString(16);
hex += (b.length === 1 ? '0' : '') + b;
}
if (arr.length > n) hex += '...(' + arr.length + ')';
return hex;
}
function dumpBytes(label, jobj) {
if (jobj === null || jobj === undefined) {
log(label + ' = null');
return;
}
try {
const arr = Java.cast(jobj, Java.use('[B'));
let text = '';
try {
text = Java.use('java.lang.String').$new(arr, 'UTF-8').toString();
} catch (e) {
text = '';
}
const printable = text.length > 0 && text.indexOf('\u0000') < 0;
if (printable && (text.indexOf('rdVerifyInfo') >= 0 || text.indexOf('REGISTRATION') >= 0
|| text.indexOf('deviceFingerprint') >= 0 || text.length < MAX_STR)) {
const show = text.length > MAX_STR ? text.substring(0, MAX_STR) + '...' : text;
log(label + ' byte[' + arr.length + '] utf8=' + show);
} else {
log(label + ' byte[' + arr.length + '] hex=' + hexPreview(arr, 48));
}
} catch (e) {
log(label + ' dump err=' + e);
}
}
function dumpJava(label, obj) {
if (obj === null || obj === undefined) {
log(label + ' = null');
return;
}
try {
const cls = obj.getClass().getName();
if (cls === '[B') {
dumpBytes(label, obj);
return;
}
if (cls === 'java.lang.String') {
const s = Java.cast(obj, Java.use('java.lang.String')).toString();
const show = s.length > MAX_STR ? s.substring(0, MAX_STR) + '...' : s;
log(label + ' String(' + s.length + ') ' + show);
return;
}
if (cls === '[Ljava.lang.String;') {
const arr = Java.cast(obj, Java.use('[Ljava.lang.String;'));
log(label + ' String[' + arr.length + ']');
for (let i = 0; i < arr.length; i++) dumpJava(label + '[' + i + ']', arr[i]);
return;
}
if (cls === '[[B') {
const outer = Java.cast(obj, Java.use('[[B'));
log(label + ' byte[][] len=' + outer.length);
for (let i = 0; i < outer.length; i++) dumpBytes(label + '[' + i + ']', outer[i]);
return;
}
log(label + ' ' + cls + ' = ' + obj.toString());
} catch (e) {
log(label + ' err=' + e);
}
}
function shouldLogRegisterText(s) {
if (!s) return false;
const low = s.toLowerCase();
return low.indexOf('register') >= 0 || low.indexOf('rdverifyinfo') >= 0
|| low.indexOf('datakey') >= 0 || low.indexOf('fingerprint') >= 0
|| low.indexOf('3100012') >= 0 || s.indexOf('|') >= 0;
}
/* ---------- native: dlopen + RegisterNatives ---------- */
function moduleExport(moduleName, symbol) {
if (typeof Module.getExportByName === 'function') {
try {
return Module.getExportByName(moduleName, symbol);
} catch (e) {
return null;
}
}
if (typeof Module.findExportByName === 'function') {
return Module.findExportByName(moduleName, symbol);
}
return null;
}
function moduleByName(name) {
if (typeof Process.getModuleByName === 'function') {
try {
return Process.getModuleByName(name);
} catch (e) {
return null;
}
}
if (typeof Process.findModuleByName === 'function') {
return Process.findModuleByName(name);
}
return null;
}
function moduleByAddress(addr) {
if (typeof Process.getModuleByAddress === 'function') {
try {
return Process.getModuleByAddress(addr);
} catch (e) {
return null;
}
}
if (typeof Process.findModuleByAddress === 'function') {
return Process.findModuleByAddress(addr);
}
return null;
}
function hookDlopen() {
const names = ['android_dlopen_ext', '__loader_android_dlopen_ext', 'dlopen'];
names.forEach(function (sym) {
const addr = moduleExport(null, sym);
if (!addr) return;
Interceptor.attach(addr, {
onEnter(args) {
try {
this.path = args[0].readCString();
} catch (e) {
this.path = '';
}
},
onLeave() {
if (!this.path) return;
SO_WATCH.forEach(function (so) {
if (this.path.indexOf(so) >= 0) log('dlopen ' + this.path);
}, this);
},
});
log('hooked ' + sym);
});
}
function findRegisterNatives() {
const art = moduleByName('libart.so');
if (!art) return null;
let found = null;
art.enumerateSymbols().forEach(function (sym) {
if (found) return;
const n = sym.name;
if (n.indexOf('RegisterNatives') >= 0
&& n.indexOf('CheckJNI') < 0
&& n.indexOf('art') >= 0) {
found = sym.address;
}
});
return found;
}
function hookNativePtr(className, methodName, sig, fnPtr) {
const key = fnPtr.toString();
if (HOOKED_NATIVE_PTRS[key]) return;
HOOKED_NATIVE_PTRS[key] = true;
const mod = moduleByAddress(fnPtr);
const modName = mod ? mod.name : '?';
const off = mod ? fnPtr.sub(mod.base) : fnPtr;
log('RegisterNatives HOOK ' + className + '.' + methodName + sig
+ ' @ ' + modName + '+0x' + off.toString(16));
try {
Interceptor.attach(fnPtr, {
onEnter(args) {
this.mname = methodName;
this.msig = sig;
this.env = args[0];
log('native>> ' + className + '.' + methodName + sig);
dumpNativeArgs(methodName, sig, this.env, args);
},
onLeave(retval) {
log('native<< ' + className + '.' + methodName + ' ret=' + retval);
},
});
} catch (e) {
log('Interceptor.attach fail ' + methodName + ': ' + e);
}
}
function resolveJClassName(jclassPtr) {
if (typeof Java === 'undefined' || !Java.available) {
return '';
}
let className = '';
const run = (typeof Java.performNow === 'function') ? Java.performNow : Java.perform;
try {
run(function () {
className = Java.cast(jclassPtr, Java.use('java.lang.Class')).getName();
});
} catch (e) {
className = '';
}
return className;
}
function isInterestingSo(modName) {
if (!modName) return false;
return modName.indexOf('shpssdk') >= 0
|| modName.indexOf('sdkutils') >= 0
|| modName.indexOf('bkutils') >= 0;
}
function hookRegisterNatives() {
const addr = findRegisterNatives();
if (!addr) {
log('RegisterNatives symbol not found');
return;
}
Interceptor.attach(addr, {
onEnter(args) {
const count = args[3].toInt32();
const methods = args[2];
const clazz = args[1];
const className = resolveJClassName(clazz) || '<unknown>';
const classHit = className.indexOf('shpssdk') >= 0
|| className.indexOf('jni.utils') >= 0
|| className.indexOf('bke.lib.jni') >= 0;
const ptrSize = Process.pointerSize;
let loggedClass = false;
for (let i = 0; i < count; i++) {
const base = methods.add(i * ptrSize * 3);
const name = base.readPointer().readCString();
const sig = base.add(ptrSize).readPointer().readCString();
const fnPtr = base.add(ptrSize * 2).readPointer();
const mod = moduleByAddress(fnPtr);
const modName = mod ? mod.name : '';
if (!classHit && !isInterestingSo(modName)) continue;
if (!loggedClass) {
log('RegisterNatives class=' + className + ' count=' + count);
loggedClass = true;
}
log(' JNI ' + name + sig + ' -> ' + fnPtr + ' (' + modName + ')');
hookNativePtr(className, name, sig, fnPtr);
}
},
});
log('hooked RegisterNatives @ ' + addr);
}
/* ---------- Java: hook static native + key methods ---------- */
function hookJavaMethod(className, clazz, methodName, isNative, retName, isStatic) {
try {
const overloads = clazz[methodName].overloads;
overloads.forEach(function (ovl) {
ovl.implementation = function () {
const args = [].slice.call(arguments);
log('Java>> ' + className + '.' + methodName
+ (isStatic ? ' static' : '')
+ (isNative ? ' native' : '') + ' args=' + args.length);
args.forEach(function (a, idx) { dumpJava(' in' + idx, a); });
const ret = ovl.apply(this, args);
if (retName === 'void') {
log('Java<< ' + methodName + ' void');
} else if (retName === '[B') {
dumpBytes(' out', ret);
} else if (retName === 'java.lang.String') {
dumpJava(' out', ret);
} else if (retName === '[[B') {
dumpJava(' out', ret);
} else if (retName === 'boolean' || retName === 'int' || retName === 'long') {
log(' out=' + ret);
} else {
dumpJava(' out', ret);
}
return ret;
};
});
log('hooked ' + className + '.' + methodName + ' overloads=' + overloads.length
+ (isNative ? ' native' : '') + (isStatic ? ' static' : ''));
return 1;
} catch (e) {
return 0;
}
}
function hookClassMethods(className, staticOnly, instanceOnly) {
let clazz;
try {
clazz = Java.use(className);
} catch (e) {
log('skip Java class ' + className + ': ' + e);
return 0;
}
const Modifier = Java.use('java.lang.reflect.Modifier');
const declared = clazz.class.getDeclaredMethods();
let hooked = 0;
for (let i = 0; i < declared.length; i++) {
const m = declared[i];
const isStatic = Modifier.isStatic(m.getModifiers());
if (staticOnly && !isStatic) continue;
if (instanceOnly && isStatic) continue;
const methodName = m.getName();
const isNative = Modifier.isNative(m.getModifiers());
const retName = m.getReturnType().getName();
hooked += hookJavaMethod(className, clazz, methodName, isNative, retName, isStatic);
}
return hooked;
}
function hookShpsSdkFacade() {
try {
const SHPSSDK = Java.use('com.shopee.shpssdkbank.SHPSSDK');
['getRiskToken', 'getRiskSync', 'requestDefense', 'assessRisk'].forEach(function (name) {
if (!SHPSSDK[name]) return;
SHPSSDK[name].overloads.forEach(function (ovl) {
ovl.implementation = function () {
const args = [].slice.call(arguments);
log('Java>> SHPSSDK.' + name);
args.forEach(function (a, idx) { dumpJava(' in' + idx, a); });
const ret = ovl.apply(this, args);
dumpJava(' out', ret);
return ret;
};
});
log('hooked SHPSSDK.' + name);
});
} catch (e) {
log('SHPSSDK facade skip: ' + e);
}
}
function hookOkHttp() {
try {
const RealCall = Java.use('okhttp3.RealCall');
RealCall.execute.implementation = function () {
const req = this.request();
const url = req.url().toString();
if (url.indexOf('register') >= 0 || url.indexOf('dfp') >= 0 || url.indexOf('uapi') >= 0) {
log('HTTP>> ' + req.method() + ' ' + url);
}
const resp = this.execute.call(this);
if (url.indexOf('register') >= 0) {
try {
const peek = resp.peekBody(Java.use('java.lang.Long').parseLong('1048576'));
log('HTTP<< register ' + peek.string());
} catch (e) {
log('HTTP<< register peek err=' + e);
}
}
return resp;
};
log('hooked OkHttp RealCall.execute');
} catch (e) {
log('OkHttp skip: ' + e);
}
}
function hookGsonRegister() {
try {
const Gson = Java.use('com.google.gson.Gson');
Gson.toJson.overload('java.lang.Object').implementation = function (obj) {
const ret = this.toJson(obj);
if (shouldLogRegisterText(ret)) {
const show = ret.length > MAX_STR ? ret.substring(0, MAX_STR) + '...' : ret;
log('Gson.toJson REGISTRATION len=' + ret.length + ' ' + show);
}
return ret;
};
log('hooked Gson.toJson');
} catch (e) {
log('Gson skip: ' + e);
}
}
function hookRiskTokenEntry() {
try {
const V = Java.use('com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv');
hookJavaMethod(
'com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv',
V, 'wwvuwuwvu', false, 'java.lang.String', true);
} catch (e) {
log('vvuuuuvvv skip: ' + e);
}
}
function hookEncryptHelper() {
hookClassMethods('com.shopee.bke.lib.jni.utils.uvwwwwuv', false, true);
}
function isAdbSettingKey(key) {
if (!key) return false;
const lower = key.toLowerCase();
return lower.indexOf('adb') >= 0
|| lower === 'development_settings_enabled'
|| lower.indexOf('wireless_debug') >= 0;
}
function hookAdbBypassJava() {
try {
const fakeInt = function (key) {
if (isAdbSettingKey(key)) {
log('fake Settings int ' + key + ' -> 0');
return 0;
}
return null;
};
const fakeStr = function (key) {
if (isAdbSettingKey(key)) {
log('fake Settings str ' + key + ' -> 0');
return '0';
}
return null;
};
['Global', 'Secure', 'System'].forEach(function (bucket) {
const Cls = Java.use('android.provider.Settings$' + bucket);
Cls.getInt.overloads.forEach(function (ovl) {
ovl.implementation = function () {
const key = arguments[1];
const f = fakeInt(String(key));
if (f !== null) return f;
return ovl.apply(this, arguments);
};
});
if (Cls.getString) {
Cls.getString.overloads.forEach(function (ovl) {
ovl.implementation = function () {
const key = arguments[1];
const f = fakeStr(String(key));
if (f !== null) return f;
return ovl.apply(this, arguments);
};
});
}
});
const SysProp = Java.use('android.os.SystemProperties');
SysProp.get.overload('java.lang.String').implementation = function (key) {
if (key === 'init.svc.adbd' || key === 'init.svc.adb_wifi') {
log('fake SystemProperties ' + key + ' -> stopped');
return 'stopped';
}
return this.get(key);
};
log('hooked ADB Settings/SystemProperties bypass');
} catch (e) {
log('ADB Java bypass skip: ' + e);
}
}
function installJavaHooks() {
hookAdbBypassJava();
let total = 0;
JAVA_TARGETS.forEach(function (cn) {
total += hookClassMethods(cn, true, false);
});
hookRiskTokenEntry();
hookEncryptHelper();
hookShpsSdkFacade();
hookOkHttp();
hookGsonRegister();
log('Java hooks installed methods=' + total + ' pid=' + Process.id);
log('READY SG — Sign up -> +65 -> Next (watch native>> / Gson / HTTP)');
}
function waitForJava(n) {
n = n || 0;
if (typeof Java === 'undefined' || !Java.available) {
if (n % 10 === 0) log('waiting Java.available attempt=' + n);
setTimeout(function () { waitForJava(n + 1); }, 500);
return;
}
Java.perform(function () {
installJavaHooks();
});
}
setImmediate(function () {
log('SG native trace loaded pid=' + Process.id);
hookDlopen();
hookRegisterNatives();
waitForJava(0);
});