feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区

新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
2026-07-03 17:15:16 +08:00
parent 125dfe583b
commit 59970a84a8
121 changed files with 7606 additions and 37 deletions

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
"""Quick reference: register crypto JNI targets for Frida."""
import re
import subprocess
import zipfile
from pathlib import Path
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
DEXDUMP = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\37.0.0\dexdump.exe")
OUT = Path(__file__).resolve().parent / "frida" / "jni_targets.md"
TARGETS = [
"Lcom/shopee/bke/lib/jni/utils/d;", # NativeEncryptUtilsWrapper
"Lcom/shopee/bke/lib/jni/utils/uvwuvwuv;", # NativeEncryptUtils (sdkutils JNI)
"Lcom/shopee/bke/lib/jni/utils/f;", # SoUtils.loadSoLibrary
"Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;",
"Lcom/shopee/shpssdkbank/wvvvuwwu;",
]
lines = [
"# MariBank v3.22 register / crypto JNI targets",
"",
"## sdkutils (注册 body 加密)",
"- `com.shopee.bke.lib.jni.utils.d` — NativeEncryptUtilsWrapper",
"- `com.shopee.bke.lib.jni.utils.uvwuvwuv` — NativeEncryptUtils (native)",
"- `com.shopee.bke.lib.jni.utils.f` — SoUtils → loads `libsdkutils.so`",
"",
"## shpssdk_bank (riskToken / DFP)",
"- `vvuuuuvvv.wwvuwuwvu(Context)` — getRiskToken 真实入口",
"- `wvvvuwwu` — native bridge (`vvuwuuvuu` → `wwvwvwuvv`)",
"",
"## dexdump natives",
"",
]
with zipfile.ZipFile(str(APK)) as zf:
dex = zf.read("classes8.dex")
tmp = Path(__file__).resolve().parent / "tmp_frida_ref.dex"
tmp.write_bytes(dex)
out = subprocess.check_output(
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
)
for target in TARGETS:
lines.append("### " + target)
cap = False
for line in out.splitlines():
if ("Class descriptor : '" + target + "'") in line:
cap = True
elif cap and line.startswith(" Class descriptor") and target not in line:
break
if cap and ("NATIVE" in line or ("name :" in line and "type :" not in line)):
safe = line.encode("ascii", "replace").decode()
if "name :" in safe:
lines.append("- " + safe.strip())
lines.append("")
dex11 = zf.read("classes11.dex")
tmp.write_bytes(dex11)
out11 = subprocess.check_output(
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
)
for target in TARGETS[3:]:
lines.append("### " + target)
cap = False
for line in out11.splitlines():
if ("Class descriptor : '" + target + "'") in line:
cap = True
elif cap and line.startswith(" Class descriptor") and target not in line:
break
if cap and "NATIVE" in line:
lines.append("- " + line.encode("ascii", "replace").decode().strip())
lines.append("")
OUT.write_text("\n".join(lines), encoding="utf-8")
print("written", OUT)

View File

@@ -0,0 +1,44 @@
# MariBank v3.22 — Frida trace 目标(注册加密)
## sdkutils注册 body 很可能经此加密)
| 类 | 说明 |
|----|------|
| `com.shopee.bke.lib.jni.utils.f` | SoUtils`loadSoLibrary("sdkutils")` |
| `com.shopee.bke.lib.jni.utils.uvwuvwuv` | NativeEncryptUtils**PUBLIC STATIC NATIVE** |
| `com.shopee.bke.lib.jni.utils.d` | NativeEncryptUtilsWrapper调用 `uvwuvwuv.uvwuuww([B,String,Z,J)[[B` |
logcat 标签:`NativeEncrypt: loading JNI``CharacterCryptoManager`
## libshpssdk_bank.soriskToken / DFP
| 类 / 方法 | 说明 |
|-----------|------|
| `vvuuuuvvv.wwvuwuwvu(Context)` | getRiskToken 真实入口 |
| `wvvvuwwu.vvuwuuvuu(String,ZZ)` | → native `wwvwvwuvv(int,String)` |
| `wvvvuwwu.vuwuuuwv([B,[B)` | requestDefense 解密 |
| `SHPSSDK.requestDefense` | 出站 HTTP 头 `x-sap-fixme` |
## 运行
```powershell
# 1. 手机启动 frida-server (root)
adb push frida-server /data/local/tmp/
adb shell su -c 'chmod 755 /data/local/tmp/frida-server && /data/local/tmp/frida-server -D &'
# 2. PC 安装 frida-tools 后
cd reverse\frida
.\run-frida-trace.ps1 -Mode spawn
# 3. App 内 Sign up → 输入号码 → Next
# 关注 [MB-TRACE] NativeEncryptWrapper / NativeEncryptUtils / HTTP .../register
```
建议测试时**暂时关闭 LSPosed 对 MariBank 的作用域**,避免与 Frida 冲突。
## 预期输出
- `RegisterNatives libsdkutils.so ...` — JNI 符号
- `NativeEncryptWrapper.*` — 加密前明文(若走 Java 包装)
- `HTTP POST .../uapi/v2/register` — 请求/响应 body
- `vvuuuuvvv.wwvuwuwvu ret` — riskToken 全文

View File

@@ -0,0 +1,30 @@
# Pull native libs from connected device (Pixel 6 with MariBank installed)
$ErrorActionPreference = "Stop"
$Root = Split-Path -Parent $PSScriptRoot
$OutDir = Join-Path $Root "extracted\native"
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
$AdbCandidates = @(
(Join-Path $Root "..\platform-tools\adb.exe"),
"$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe",
"adb"
)
$Adb = $AdbCandidates | Where-Object { $_ -eq "adb" -or (Test-Path $_) } | Select-Object -First 1
if (-not $Adb) { throw "adb not found" }
$Pkg = "ph.seabank.seabank"
$Base = & $Adb shell pm path $Pkg 2>$null
if (-not $Base) { throw "package $Pkg not installed on device" }
$Paths = ($Base -split "`n" | ForEach-Object { $_.Trim() -replace "^package:", "" })
foreach ($ApkPath in $Paths) {
$Name = Split-Path $ApkPath -Leaf
$LocalApk = Join-Path $OutDir $Name
Write-Host "pull $ApkPath -> $LocalApk"
& $Adb pull $ApkPath $LocalApk | Out-Null
if ($Name -like "split_config.arm64*") {
python (Join-Path $Root "scripts\extract_all_so.py")
}
}
Write-Host "done. SO files in $OutDir"

View File

@@ -0,0 +1,3 @@
# MariBank Frida trace dependencies (host PC)
frida>=16.0.0
frida-tools>=12.0.0

View File

@@ -0,0 +1,66 @@
# Run MariBank register Frida trace on connected device
param(
[ValidateSet("spawn", "attach")]
[string]$Mode = "attach",
[string]$Package = "ph.seabank.seabank"
)
$ErrorActionPreference = "Stop"
$Here = $PSScriptRoot
$LogsDir = Join-Path (Split-Path $Here -Parent) "logs\frida"
New-Item -ItemType Directory -Force -Path $LogsDir | Out-Null
$Script = Join-Path $Here "trace_maribank_register.js"
$LogFile = Join-Path $LogsDir "trace_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
if (-not (Test-Path $Script)) { throw "missing $Script" }
# adb
$AdbCandidates = @(
(Join-Path (Split-Path $Here -Parent) "..\platform-tools\adb.exe"),
"$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe",
"adb"
)
$Adb = $AdbCandidates | Where-Object { $_ -eq "adb" -or (Test-Path $_) } | Select-Object -First 1
if (-not $Adb) { Write-Warning "adb not in PATH — ensure device connected" }
# frida / python module
$FridaCmd = Get-Command frida -ErrorAction SilentlyContinue
if (-not $FridaCmd) {
Write-Host "Installing frida-tools..."
python -m pip install -r (Join-Path $Here "requirements.txt")
}
Write-Host @"
=== MariBank Frida Register Trace ===
Package : $Package
Mode : $Mode
Script : $Script
Log : $LogFile
(Pixel 6):
1. adb devices
2. push frida-server root :
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell su -c '/data/local/tmp/frida-server -D &'
3. LSPosed App Frida
4. : Sign up -> -> Next
"@
$Py312 = "C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe"
$Runner = Join-Path $Here "run_frida_trace.py"
if (Test-Path $Py312) -and (Test-Path $Runner) {
Write-Host "Using persistent Python runner: $Runner $Mode"
& $Py312 $Runner $Mode
exit $LASTEXITCODE
}
$FridaArgs = @("-U", "-f", $Package, "-l", $Script, "-o", $LogFile)
if ($Mode -eq "attach") {
$FridaArgs = @("-U", $Package, "-l", $Script, "-o", $LogFile)
}
Write-Host "frida $($FridaArgs -join ' ')"
& frida @FridaArgs

View File

@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
"""Persistent Frida trace session (avoids CLI exit on piped stdin)."""
import sys
import time
from datetime import datetime
from pathlib import Path
import frida
PKG = "ph.seabank.seabank"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_maribank_register.js"
LOG = LOGS_DIR / ("trace_%s.log" % datetime.now().strftime("%Y%m%d_%H%M%S"))
def on_message(message, data):
if message.get("type") == "send":
line = message.get("payload")
else:
line = str(message)
text = line if isinstance(line, str) else repr(line)
print(text, flush=True)
with open(LOG, "a", encoding="utf-8") as f:
f.write(text + "\n")
if message.get("type") == "error":
err_log = str(LOG) + ".err"
with open(err_log, "a", encoding="utf-8") as f:
f.write(text + "\n")
def wait_for_process(device, pkg, timeout_sec=30):
deadline = time.time() + timeout_sec
while time.time() < deadline:
for app in device.enumerate_applications():
if app.identifier == pkg and app.pid and app.pid > 0:
return app.pid
for proc in device.enumerate_processes():
if proc.name == pkg:
return proc.pid
params = getattr(proc, "parameters", None) or {}
if params.get("identifier") == pkg:
return proc.pid
time.sleep(0.5)
return None
def launch_app(pkg):
import subprocess
adb = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
subprocess.run(
[adb, "shell", "am", "force-stop", pkg],
check=False,
capture_output=True,
)
time.sleep(1)
subprocess.run(
[adb, "shell", "monkey", "-p", pkg, "-c", "android.intent.category.LAUNCHER", "1"],
check=False,
capture_output=True,
)
def ensure_frida_server():
import subprocess
adb = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
out = subprocess.run(
[adb, "shell", "su", "-c", "pgrep frida-server"],
capture_output=True,
text=True,
)
if out.stdout.strip():
return
subprocess.run(
[adb, "shell", "su", "-c", "/data/local/tmp/frida-server -D &"],
check=False,
capture_output=True,
)
time.sleep(2)
def main():
mode = "attach"
if len(sys.argv) > 1:
mode = sys.argv[1]
ensure_frida_server()
device = frida.get_usb_device(timeout=10)
source = SCRIPT.read_text(encoding="utf-8")
pid = None
if mode == "spawn":
print("Spawning %s ..." % PKG)
pid = device.spawn([PKG])
session = device.attach(pid)
else:
print("Attaching %s ..." % PKG)
pid = wait_for_process(device, PKG, 3)
if pid is None:
print("Launching MariBank ...")
launch_app(PKG)
pid = wait_for_process(device, PKG, 60)
if pid is None:
raise SystemExit("MariBank not running after 60s — open app manually and re-run attach")
print("Found pid=%s" % pid)
session = device.attach(pid)
script = session.create_script(source)
script.on("message", on_message)
script.load()
if mode == "spawn":
device.resume(pid)
print("Resumed pid=%s, waiting for JVM..." % pid)
time.sleep(10)
else:
print("Attached pid=%s" % pid)
time.sleep(3)
print("Trace running. Log: %s" % LOG)
print("操作: Sign up -> 输入号码 -> Next (Ctrl+C 结束)")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopping...")
session.detach()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
"""Spawn MariBank, resume after script load, wait for Java."""
import frida
import sys
import time
from datetime import datetime
from pathlib import Path
PKG = "ph.seabank.seabank"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_maribank_register.js"
LOG = LOGS_DIR / ("trace_%s.log" % datetime.now().strftime("%Y%m%d_%H%M%S"))
def on_message(message, data):
if message.get("type") == "send":
line = message.get("payload")
else:
line = str(message)
text = line if isinstance(line, str) else repr(line)
print(text, flush=True)
with open(LOG, "a", encoding="utf-8") as f:
f.write(text + "\n")
def main():
import subprocess
adb = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
subprocess.run([adb, "shell", "am", "force-stop", PKG], capture_output=True)
time.sleep(1)
d = frida.get_usb_device(10)
source = SCRIPT.read_text(encoding="utf-8")
print("Spawning %s ..." % PKG)
pid = d.spawn([PKG])
session = d.attach(pid)
script = session.create_script(source)
script.on("message", on_message)
script.load()
time.sleep(2)
d.resume(pid)
print("Resumed pid=%s, log=%s" % (pid, LOG))
print("等待 90s 让 Java Hook 就绪,然后 Sign up -> Next")
try:
for _ in range(120):
time.sleep(1)
except KeyboardInterrupt:
pass
session.detach()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""Spawn MariBank with Frida trace and keep session alive."""
import sys
import time
from pathlib import Path
import frida
PKG = "ph.seabank.seabank"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_maribank_register.js"
LOG = LOGS_DIR / "trace_live.log"
def on_message(message, data):
line = ""
if message.get("type") == "send":
line = str(message.get("payload", ""))
elif message.get("type") == "error":
line = "ERROR: " + str(message.get("stack", message))
else:
line = str(message)
print(line, flush=True)
with open(str(LOG), "a", encoding="utf-8", errors="replace") as f:
f.write(line + "\n")
def main():
LOG.write_text("", encoding="utf-8")
source = SCRIPT.read_text(encoding="utf-8")
device = frida.get_usb_device(timeout=10)
print("device:", device.name, flush=True)
pid = device.spawn([PKG])
print("spawned pid", pid, flush=True)
session = device.attach(pid)
script = session.create_script(source)
script.on("message", on_message)
script.load()
device.resume(pid)
print("resumed — 请在手机: Sign up -> 输入号码 -> Next", flush=True)
print("log:", LOG, flush=True)
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("detached", flush=True)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
import frida
import sys
import time
PKG = "ph.seabank.seabank"
d = frida.get_usb_device(10)
pid = None
for app in d.enumerate_applications():
if app.identifier == PKG and app.pid and app.pid > 0:
print("found", app.name, app.pid)
pid = app.pid
break
if not pid:
sys.exit("MariBank not running")
s = d.attach(pid)
src = open(__file__.replace("test_attach.py", "trace_maribank_register.js"), encoding="utf-8").read()
def on_m(msg, data):
print(msg)
sc = s.create_script(src)
sc.on("message", on_m)
sc.load()
print("loaded, waiting 15s for hooks...")
time.sleep(15)
print("done test")

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import frida
import subprocess
import time
PKG = "ph.seabank.seabank"
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
subprocess.run([ADB, "shell", "monkey", "-p", PKG, "-c", "android.intent.category.LAUNCHER", "1"], capture_output=True)
d = frida.get_usb_device(10)
pid = None
for a in d.enumerate_applications():
if a.identifier == PKG and a.pid > 0:
pid = a.pid
print("pid", pid, a.name)
break
if not pid:
raise SystemExit("no pid")
s = d.attach(pid)
JS = r"""
var n = 0;
function waitJava() {
if (typeof Java !== 'undefined' && Java.available) {
send({event: 'java_ready', n: n});
Java.perform(function () {
send({event: 'perform_ok'});
});
return;
}
n++;
if (n % 10 === 0) send({event: 'waiting', n: n});
if (n < 120) setTimeout(waitJava, 500);
else send({event: 'timeout', n: n});
}
setImmediate(waitJava);
"""
def on_m(msg, data):
print(msg)
sc = s.create_script(JS)
sc.on("message", on_m)
sc.load()
time.sleep(70)

View File

@@ -0,0 +1,182 @@
'use strict';
/**
* MariBank 注册 trace — attach 模式优先,聚焦 Java 层OkHttp / Gson / 加密包装)
*/
const TAG = '[MB-TRACE]';
const MAX_STR = 2000;
function log(msg) {
console.log(TAG + ' ' + msg);
}
function shouldLogUrl(url) {
if (!url) return false;
const u = String(url).toLowerCase();
return u.indexOf('register') >= 0 || u.indexOf('dfp') >= 0
|| u.indexOf('risk') >= 0 || u.indexOf('uapi') >= 0;
}
function hexPreview(arr, limit) {
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 += '...';
return hex;
}
function dumpJava(tag, obj) {
if (obj === null || obj === undefined) {
log(tag + ' = null');
return;
}
try {
const cls = obj.getClass().getName();
if (cls === '[B') {
const arr = Java.cast(obj, Java.use('[B'));
let text = '';
try {
text = Java.use('java.lang.String').$new(arr, 'UTF-8').toString();
} catch (e) {
text = '<bin>';
}
const show = text.length > MAX_STR ? text.substring(0, MAX_STR) + '...' : text;
log(tag + ' byte[' + arr.length + '] hex=' + hexPreview(arr, 48) + ' text=' + show);
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(tag + ' String(' + s.length + ') ' + show);
return;
}
log(tag + ' ' + cls + ' = ' + obj.toString());
} catch (e) {
log(tag + ' err=' + e);
}
}
function hookOkHttp() {
const RealCall = Java.use('okhttp3.RealCall');
const orig = RealCall.execute;
RealCall.execute.implementation = function () {
const req = this.request();
const url = req.url().toString();
const method = req.method();
if (shouldLogUrl(url)) {
log('HTTP >> ' + method + ' ' + url);
try {
const body = req.body();
if (body) {
const Buffer = Java.use('okio.Buffer');
const buf = Buffer.$new();
body.writeTo(buf);
const bytes = buf.readByteArray();
if (bytes) dumpJava(' reqBody', Java.array('byte', bytes));
}
} catch (e) {
log(' reqBody err: ' + e);
}
}
const resp = orig.call(this);
if (shouldLogUrl(url)) {
try {
const peek = resp.peekBody(Java.use('java.lang.Long').parseLong('1048576'));
const s = peek.string();
const show = s.length > MAX_STR ? s.substring(0, MAX_STR) + '...' : s;
log('HTTP << ' + resp.code() + ' ' + show);
} catch (e) {
log('HTTP resp err: ' + e);
}
}
return resp;
};
log('hooked RealCall.execute');
}
function hookGson() {
const Gson = Java.use('com.google.gson.Gson');
Gson.toJson.overload('java.lang.Object').implementation = function (obj) {
const ret = this.toJson(obj);
if (ret) {
const low = ret.toLowerCase();
if (low.indexOf('mobile') >= 0 || low.indexOf('phone') >= 0
|| low.indexOf('risktoken') >= 0 || low.indexOf('register') >= 0
|| low.indexOf('4067') >= 0) {
const show = ret.length > MAX_STR ? ret.substring(0, MAX_STR) + '...' : ret;
log('Gson.toJson ' + show);
}
}
return ret;
};
log('hooked Gson.toJson');
}
function hookRisk() {
const vv = Java.use('com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv');
vv.wwvuwuwvu.overload('android.content.Context').implementation = function (ctx) {
const ret = this.wwvuwuwvu(ctx);
dumpJava('riskToken', ret);
return ret;
};
log('hooked vvuuuuvvv.wwvuwuwvu');
}
function hookEncryptWrapper() {
const D = Java.use('com.shopee.bke.lib.jni.utils.d');
const methods = D.class.getDeclaredMethods();
for (let i = 0; i < methods.length; i++) {
const m = methods[i];
const name = m.getName();
if (m.getModifiers() & 0x0100) continue;
try {
D[name].overloads.forEach(function (ovl) {
ovl.implementation = function () {
const args = [].slice.call(arguments);
log('>> EncryptWrapper.' + name);
args.forEach(function (a, idx) { dumpJava(' in' + idx, a); });
const ret = ovl.apply(this, args);
if (ret && ret.getClass) {
const cn = ret.getClass().getName();
if (cn === '[Ljava.lang.String;') {
const arr = Java.cast(ret, Java.use('[Ljava.lang.String;'));
for (let j = 0; j < arr.length; j++) dumpJava(' out' + j, arr[j]);
} else {
dumpJava(' ret', ret);
}
}
return ret;
};
});
} catch (e) {}
}
log('hooked NativeEncryptUtilsWrapper (utils.d)');
}
function installAll() {
Java.perform(function () {
log('Java.perform OK pid=' + Process.id);
try { hookOkHttp(); } catch (e) { log('okhttp fail: ' + e); }
try { hookGson(); } catch (e) { log('gson fail: ' + e); }
try { hookRisk(); } catch (e) { log('risk fail: ' + e); }
try { hookEncryptWrapper(); } catch (e) { log('encrypt fail: ' + e); }
log('READY — 请在 App 输入号码点 Next');
});
}
function waitForJava(n) {
n = n || 0;
if (typeof Java === 'undefined' || !Java.available) {
if (n % 5 === 0) log('waiting Java.available attempt=' + n);
setTimeout(function () { waitForJava(n + 1); }, 500);
return;
}
installAll();
}
setImmediate(function () {
log('script loaded pid=' + Process.id);
waitForJava(0);
});