feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区
新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
75
reverse/frida/gen_jni_targets.py
Normal file
75
reverse/frida/gen_jni_targets.py
Normal 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)
|
||||
44
reverse/frida/jni_targets.md
Normal file
44
reverse/frida/jni_targets.md
Normal 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.so(riskToken / 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 全文
|
||||
30
reverse/frida/pull_split_apk.ps1
Normal file
30
reverse/frida/pull_split_apk.ps1
Normal 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"
|
||||
3
reverse/frida/requirements.txt
Normal file
3
reverse/frida/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
# MariBank Frida trace dependencies (host PC)
|
||||
frida>=16.0.0
|
||||
frida-tools>=12.0.0
|
||||
66
reverse/frida/run-frida-trace.ps1
Normal file
66
reverse/frida/run-frida-trace.ps1
Normal 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
|
||||
132
reverse/frida/run_frida_trace.py
Normal file
132
reverse/frida/run_frida_trace.py
Normal 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()
|
||||
55
reverse/frida/run_spawn_trace.py
Normal file
55
reverse/frida/run_spawn_trace.py
Normal 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()
|
||||
52
reverse/frida/run_trace.py
Normal file
52
reverse/frida/run_trace.py
Normal 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()
|
||||
28
reverse/frida/test_attach.py
Normal file
28
reverse/frida/test_attach.py
Normal 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")
|
||||
45
reverse/frida/test_java_wait.py
Normal file
45
reverse/frida/test_java_wait.py
Normal 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)
|
||||
182
reverse/frida/trace_maribank_register.js
Normal file
182
reverse/frida/trace_maribank_register.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user