'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) || ''; 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); });