feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区
新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
19
reverse/README.md
Normal file
19
reverse/README.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# reverse/ 逆向工作区
|
||||
|
||||
| 目录 | 内容 |
|
||||
|------|------|
|
||||
| `scripts/` | DEX/APK 扫描与分析 Python 脚本 |
|
||||
| `frida/` | Frida trace 脚本与运行器 |
|
||||
| `apks/` | 从设备拉取的 APK(不入库) |
|
||||
| `extracted/` | 解压产物、dex dump、`.so`(不入库) |
|
||||
| `output/` | 脚本输出的 `.txt` 分析报告 |
|
||||
| `logs/` | 运行日志(含 `logs/frida/`) |
|
||||
| `tmp/` | 临时 `.dex` 文件(不入库) |
|
||||
|
||||
常用命令(在项目根目录执行):
|
||||
|
||||
```powershell
|
||||
python reverse/scripts/scan_dex.py reverse/extracted
|
||||
python reverse/frida/run_frida_trace.py attach
|
||||
powershell -File reverse/frida/pull_split_apk.ps1
|
||||
```
|
||||
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);
|
||||
});
|
||||
13
reverse/scripts/_paths.py
Normal file
13
reverse/scripts/_paths.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Shared paths for reverse/ scripts (scripts live in reverse/scripts/)."""
|
||||
from pathlib import Path
|
||||
|
||||
REVERSE_ROOT = Path(__file__).resolve().parent.parent
|
||||
APKS_DIR = REVERSE_ROOT / "apks"
|
||||
EXTRACTED_DIR = REVERSE_ROOT / "extracted"
|
||||
NATIVE_DIR = EXTRACTED_DIR / "native"
|
||||
TMP_DIR = REVERSE_ROOT / "tmp"
|
||||
OUTPUT_DIR = REVERSE_ROOT / "output"
|
||||
LOGS_DIR = REVERSE_ROOT / "logs"
|
||||
|
||||
DEFAULT_APK = APKS_DIR / "seabank_ph_base.apk"
|
||||
20
reverse/scripts/analyze_shps_so.py
Normal file
20
reverse/scripts/analyze_shps_so.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
SO = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libshpssdk_bank.so"
|
||||
ndk = Path(r"C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\21.4.7075529\toolchains\llvm\prebuilt\windows-x86_64\bin")
|
||||
readelf = ndk / "llvm-readelf.exe"
|
||||
out = subprocess.check_output([str(readelf), "-Ws", str(SO)], universal_newlines=True, errors="replace")
|
||||
print("=== JNI Java_* ===")
|
||||
for line in out.splitlines():
|
||||
if "Java_com_shopee" in line:
|
||||
print(line)
|
||||
print("\n=== risk/root/token strings in .dynsym FUNC ===")
|
||||
data = SO.read_bytes()
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,}", data):
|
||||
s = m.group().decode("latin1")
|
||||
if any(k in s.lower() for k in ["risk", "root", "hook", "token", "proc/", "magisk", "xposed", "emulator", "assess"]):
|
||||
if len(s) < 120:
|
||||
print(s)
|
||||
5
reverse/scripts/context_wbroot.py
Normal file
5
reverse/scripts/context_wbroot.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
idx = data.find(b"WBRootDetectionModule")
|
||||
print("offset", idx)
|
||||
print(data[idx-120:idx+200].decode("latin1", "ignore"))
|
||||
45
reverse/scripts/decode_shps_strings.py
Normal file
45
reverse/scripts/decode_shps_strings.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Decode SHPSSDK obfuscated hex strings via uvuwwwuwu."""
|
||||
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")
|
||||
|
||||
# sample pairs from SHPSSDK.wwwwvwwwu
|
||||
samples = [
|
||||
("0F580514065B111F0E1A10", "wuvuvvwvv"),
|
||||
("100F3C013C1C19113F18271F3A36371F2C253C403F323C053F353C013F1C19123F1F3C4F", "uvvvuvvvv"),
|
||||
("0E5806170758131F0D1B10", "vuuvwuuvu"),
|
||||
("120E3D023E1C1A113C1A261E3934371C2C263E413E313A053C353F033E1D1A103F1C3C4C", "wwwuwvuvu"),
|
||||
]
|
||||
|
||||
# dump uvuwwwuwu implementation
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
print("=== uvuwwwuwu implementation ===")
|
||||
for line in out.splitlines():
|
||||
if "uvuwwwuwu:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;" in line:
|
||||
cap = True
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
if cap and "locals :" in line:
|
||||
break
|
||||
|
||||
print("\n=== try XOR decode (common pattern) ===")
|
||||
for hex_str, key in samples:
|
||||
data = bytes.fromhex(hex_str) if all(c in "0123456789ABCDEFabcdef" for c in hex_str) else hex_str.encode()
|
||||
# try simple xor with key bytes cycling
|
||||
kb = key.encode()
|
||||
dec = bytes(b ^ kb[i % len(kb)] for i, b in enumerate(data))
|
||||
try:
|
||||
txt = dec.decode("utf-8")
|
||||
except Exception:
|
||||
txt = dec.decode("latin1", errors="replace")
|
||||
print(hex_str[:20], "...", "->", repr(txt[:80]))
|
||||
100
reverse/scripts/dump_crypto_jni.py
Normal file
100
reverse/scripts/dump_crypto_jni.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump JNI/native methods for sdkutils crypto + register-related classes."""
|
||||
import re
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
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.parent / "output" / "crypto_jni_targets.txt"
|
||||
|
||||
CLASS_PATTERNS = [
|
||||
rb"Lcom/shopee/bke/lib/jni/[^;]{1,80};",
|
||||
rb"Lcom/shopee/shpssdkbank/wvvvuwwu;",
|
||||
rb"Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;",
|
||||
rb"Lcom/shopee/shpssdkbank/uwuvuvvww/[^;]{1,40};",
|
||||
]
|
||||
|
||||
EXTRA_KEYWORDS = (
|
||||
b"NativeEncrypt",
|
||||
b"CharacterCrypto",
|
||||
b"SecurityMain",
|
||||
b"encrypt",
|
||||
b"decrypt",
|
||||
b"register",
|
||||
)
|
||||
|
||||
|
||||
def dump_class(out: str, target: str) -> List[str]:
|
||||
lines: list[str] = []
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
|
||||
def main() -> None:
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
|
||||
classes: set[str] = set()
|
||||
for pat in CLASS_PATTERNS:
|
||||
for m in re.finditer(pat, data):
|
||||
classes.add(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
for kw in EXTRA_KEYWORDS:
|
||||
for m in re.finditer(rb"Lcom/[^;]{0,120};", data):
|
||||
s = m.group()
|
||||
if kw in s or kw in data[data.find(s) : data.find(s) + 4000]:
|
||||
classes.add(s.decode()[1:-1].replace("/", "."))
|
||||
|
||||
# log tags -> classes from crash log
|
||||
for tag in [
|
||||
"com.shopee.bke.lib.jni.utils.f", # SoUtils
|
||||
"com.shopee.bke.lib.jni.uwuwuwuw",
|
||||
"com.shopee.bke.lib.jni.uvuvuvuv",
|
||||
"com.shopee.bke.lib.jni.uvwwwwuv",
|
||||
]:
|
||||
classes.add(tag)
|
||||
|
||||
native_entries: List[str] = []
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
hits = [c for c in classes if c.replace(".", "/") in dex.decode("latin1", errors="ignore")]
|
||||
if not hits:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_crypto_jni.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for cls in sorted(hits):
|
||||
block = dump_class(out, "L" + cls.replace(".", "/") + ";")
|
||||
if not block:
|
||||
continue
|
||||
native_entries.append(f"\n=== {dex_name} {cls} ===")
|
||||
for line in block:
|
||||
if any(
|
||||
k in line
|
||||
for k in ("NATIVE", "name :", "type :", "loadLibrary")
|
||||
):
|
||||
native_entries.append(line)
|
||||
|
||||
text = "\n".join(native_entries)
|
||||
OUT.write_text(text, encoding="utf-8")
|
||||
print(text)
|
||||
print(f"\nwritten {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
31
reverse/scripts/dump_error_handler.py
Normal file
31
reverse/scripts/dump_error_handler.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/b;",
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/b$a;",
|
||||
"Lcom/shopee/bke/biz/user/errorcodehandler/a;",
|
||||
]
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for target in targets:
|
||||
print("=" * 60, target)
|
||||
capture = False
|
||||
lines = 0
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if capture:
|
||||
if "name :" in line or "type :" in line or "Class descriptor" in line:
|
||||
print(line.strip())
|
||||
lines += 1
|
||||
if lines > 200:
|
||||
break
|
||||
20
reverse/scripts/dump_error_iface.py
Normal file
20
reverse/scripts/dump_error_iface.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
target = "Lcom/shopee/bke/biz/user/errorcodehandler/b$a;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("name :" in line or "type :" in line or "Class descriptor" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
23
reverse/scripts/dump_global_auth.py
Normal file
23
reverse/scripts/dump_global_auth.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp6.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes6.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
cls = "GlobalAuthErrorImpl"
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Lcom/shopee/bke/digitalbank/container/user/errorcodehandler/{cls};" in line and "Class descriptor" in line:
|
||||
capture = True
|
||||
if capture:
|
||||
print(line)
|
||||
if line.strip() == "" and "Method" not in line and capture:
|
||||
pass
|
||||
if capture and line.startswith(" Class descriptor") and cls not in line:
|
||||
break
|
||||
52
reverse/scripts/dump_key_methods.py
Normal file
52
reverse/scripts/dump_key_methods.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
methods = [
|
||||
("Lcom/shopee/shpssdkbank/SHPSSDK;", "getRiskToken"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vvuwuuvuu"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vuwuuwvw"),
|
||||
("Lcom/shopee/shpssdkbank/wvvvuwwu;", "vuwuuuwv"),
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
|
||||
for cls, method in methods:
|
||||
needle = f"{cls.replace('L', '').replace(';', '').split('/')[-1]}.{method}:"
|
||||
print("\n" + "=" * 70, cls, method)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if needle in line.replace("com.shopee.shpssdkbank.", ""):
|
||||
cap = True
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
if line.strip().startswith("catches") or (cap and line.strip() == "locals :"):
|
||||
pass
|
||||
if cap and line.strip() == "" and "positions" in line:
|
||||
break
|
||||
if cap and line.startswith(" name") and method not in line and cap:
|
||||
# next method
|
||||
if methods.index((cls, method)) < len(methods) - 1:
|
||||
break
|
||||
|
||||
# classes8 - sdkutils / crypto
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp8.dex"
|
||||
tmp.write_bytes(zf.read("classes8.dex"))
|
||||
out8 = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for tag in ["CharacterCrypto", "SoUtils", "SecurityMain", "sdkutils", "encrypt"]:
|
||||
print("\n--- search", tag, "in classes8 ---")
|
||||
for line in out8.splitlines():
|
||||
if tag.lower() in line.lower() and ("Class descriptor" in line or "name :" in line):
|
||||
print(line.encode("ascii", "replace").decode()[:200])
|
||||
62
reverse/scripts/dump_native_bridge.py
Normal file
62
reverse/scripts/dump_native_bridge.py
Normal file
@@ -0,0 +1,62 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/wvvvuwwu;",
|
||||
"Lcom/shopee/shpssdkbank/uwuvuvvww/wvvuuwvwu;",
|
||||
"Lcom/shopee/shpssdkbank/uwuvuvvww/uvwuuuuuw/vvvvuwwvu;",
|
||||
"Lcom/shopee/shpssdkbank/SHPSSDK;",
|
||||
"Lcom/shopee/shpssdkbank/ShpssInstall;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
hit = any(t.replace("L", "").replace(";", "") in dex.decode("latin1", errors="ignore") for t in targets)
|
||||
if not hit:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_native.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in dex.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("\n" + "=" * 70)
|
||||
print(dex_name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
safe = line.encode("ascii", "replace").decode()
|
||||
if any(k in safe for k in ["name", "type", "access", "NATIVE", "Method", "loadLibrary", "register"]):
|
||||
print(safe)
|
||||
|
||||
# find sdkutils / crypto manager classes
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
print("\n--- sdkutils / crypto / dfp classes ---")
|
||||
for pat in [
|
||||
rb"Lcom/[^;]{0,80}sdkutils[^;]{0,20};",
|
||||
rb"Lcom/[^;]{0,80}[Cc]rypto[^;]{0,40};",
|
||||
rb"Lcom/[^;]{0,80}dfp[^;]{0,30};",
|
||||
rb"Lcom/[^;]{0,80}SecurityMain[^;]{0,20};",
|
||||
]:
|
||||
found = set()
|
||||
for m in re.finditer(pat, data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if s not in found:
|
||||
found.add(s)
|
||||
print(s)
|
||||
38
reverse/scripts/dump_phone_register_vm.py
Normal file
38
reverse/scripts/dump_phone_register_vm.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/PhoneNumViewModel;",
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/RegisterViewModel;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"PhoneNumViewModel" not in data and b"RegisterViewModel" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_user.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in data.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("=" * 60, name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap and ("name :" in line or "type :" in line
|
||||
or "Method" in line or "register" in line.lower()):
|
||||
safe = line.strip().encode("ascii", "replace").decode()
|
||||
print(safe)
|
||||
35
reverse/scripts/dump_register_request.py
Normal file
35
reverse/scripts/dump_register_request.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/[^;]{0,80}RegisterRequest[^;]{0,20};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(name)
|
||||
if b"RegisterRequest" not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_reg.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in re.findall(r"Lcom/shopee/bke/[^;]*RegisterRequest[^;]*;", data.decode("latin1", errors="ignore")):
|
||||
cap = False
|
||||
print("\n" + "=" * 60, name, target)
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap:
|
||||
print(line)
|
||||
35
reverse/scripts/dump_register_vm.py
Normal file
35
reverse/scripts/dump_register_vm.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
targets = [
|
||||
"Lcom/shopee/bke/biz/user/viewmodel/RegisterViewModel;",
|
||||
"Lcom/shopee/bke/biz/user/ui/PhoneNumActivity;",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"RegisterViewModel" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_reg.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
if target.replace("L", "").replace(";", "") not in data.decode("latin1", errors="ignore"):
|
||||
continue
|
||||
print("=" * 60, name, target)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor") and target not in line:
|
||||
break
|
||||
if cap and ("name :" in line or "type :" in line):
|
||||
print(line.strip().encode("ascii", "replace").decode())
|
||||
66
reverse/scripts/dump_risk_register_chain.py
Normal file
66
reverse/scripts/dump_risk_register_chain.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Dump riskToken + requestDefense + register crypto call chain."""
|
||||
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")
|
||||
|
||||
TARGETS = [
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/SHPSSDK;", "getRiskToken"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/SHPSSDK;", "requestDefense"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;", "wwvuwuwvu"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;", "wuvwuvwwu"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/wvvvuwwu;", "wwvwvwuvv"),
|
||||
("classes11.dex", "Lcom/shopee/shpssdkbank/wvvvuwwu;", "vvuwuuvuu"),
|
||||
]
|
||||
|
||||
def dump_method(out, cls, method):
|
||||
cls_short = cls.replace("L", "").replace(";", "").replace("/", ".")
|
||||
needle = cls_short + "." + method + ":"
|
||||
print("\n" + "=" * 72)
|
||||
print(cls_short, method)
|
||||
cap = False
|
||||
lines = []
|
||||
for line in out.splitlines():
|
||||
if needle in line:
|
||||
cap = True
|
||||
if cap:
|
||||
lines.append(line)
|
||||
if len(lines) > 1 and line.strip().startswith("name :") and method not in line:
|
||||
break
|
||||
for line in lines[:80]:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name, cls, method in TARGETS:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "_tmp.dex"
|
||||
tmp.write_bytes(zf.read(dex_name))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
dump_method(out, cls, method)
|
||||
|
||||
# classes8 CharacterCrypto / SoUtils
|
||||
print("\n" + "=" * 72, "classes8 crypto classes")
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "_tmp8.dex"
|
||||
tmp.write_bytes(zf.read("classes8.dex"))
|
||||
out8 = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp8)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
for line in out8.splitlines():
|
||||
if "Class descriptor" in line and (
|
||||
"CharacterCrypto" in line or "SoUtils" in line or "sdkutils" in line.lower()
|
||||
):
|
||||
print("\n---", line.strip())
|
||||
cap = True
|
||||
continue
|
||||
if cap:
|
||||
if line.startswith(" Class descriptor") and "CharacterCrypto" not in line:
|
||||
cap = False
|
||||
continue
|
||||
if "name :" in line or "NATIVE" in line or "loadLibrary" in line:
|
||||
print(line.strip()[:180])
|
||||
54
reverse/scripts/dump_safemode_b.py
Normal file
54
reverse/scripts/dump_safemode_b.py
Normal file
@@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import sys
|
||||
import zipfile
|
||||
import tempfile
|
||||
import os
|
||||
|
||||
TARGET = "com/shopee/bke/lib/safemode/b;"
|
||||
APK = sys.argv[1] if len(sys.argv) > 1 else "reverse/apks/seabank_ph_base.apk"
|
||||
BT = None
|
||||
|
||||
|
||||
def find_build_tools():
|
||||
base = os.environ.get("ANDROID_HOME") or os.path.expanduser(
|
||||
r"~\AppData\Local\Android\Sdk"
|
||||
)
|
||||
tools = os.path.join(base, "build-tools")
|
||||
versions = sorted(os.listdir(tools), reverse=True)
|
||||
return os.path.join(tools, versions[0], "dexdump.exe")
|
||||
|
||||
|
||||
def main():
|
||||
dexdump = find_build_tools()
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if TARGET.encode() not in data and b"Lcom/shopee/bke/lib/safemode/b;" not in data:
|
||||
continue
|
||||
print("FOUND in", name)
|
||||
tmp = tempfile.NamedTemporaryFile(suffix=".dex", delete=False)
|
||||
tmp.write(data)
|
||||
tmp.close()
|
||||
try:
|
||||
out = subprocess.check_output(
|
||||
[dexdump, "-f", tmp.name], stderr=subprocess.STDOUT, text=True,
|
||||
errors="ignore"
|
||||
)
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/bke/lib/safemode/b;'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture:
|
||||
if "name :" in line or "type :" in line or "Class descriptor" in line:
|
||||
print(line)
|
||||
finally:
|
||||
os.unlink(tmp.name)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
20
reverse/scripts/dump_shps_bank.py
Normal file
20
reverse/scripts/dump_shps_bank.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
target = "Lcom/shopee/shpssdkbank/SHPSSDK;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("name :" in line or "type :" in line or "access :" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
21
reverse/scripts/dump_shps_bank_full.py
Normal file
21
reverse/scripts/dump_shps_bank_full.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
target = "Lcom/shopee/shpssdkbank/SHPSSDK;"
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
32
reverse/scripts/dump_shps_install.py
Normal file
32
reverse/scripts/dump_shps_install.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/ShpssInstall;",
|
||||
"Lcom/shopee/shpssdkbank/vuvuwwwuw;",
|
||||
"Lcom/shopee/shpssdkbank/vwuuwwvwv;",
|
||||
"Lcom/shopee/shpssdkbank/uvuwwuvwv/uvwwuuvvw;",
|
||||
]
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for target in targets:
|
||||
print("=" * 60, target)
|
||||
cap = False
|
||||
n = 0
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if cap:
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
n += 1
|
||||
if n > 80:
|
||||
print("...truncated...")
|
||||
break
|
||||
20
reverse/scripts/dump_shps_methods.py
Normal file
20
reverse/scripts/dump_shps_methods.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for line in out.splitlines():
|
||||
if "SHPSSDK;" in line and any(
|
||||
k in line for k in ("getRisk", "assessRisk", "getRiskToken", "getExtRisk")
|
||||
):
|
||||
print(line.strip())
|
||||
19
reverse/scripts/dump_shps_native_methods.py
Normal file
19
reverse/scripts/dump_shps_native_methods.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
in_shps = False
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/shpssdk" in line:
|
||||
in_shps = "SHPSSDK;" in line or "shpssdkbank" in line
|
||||
if in_shps and line.startswith(" Class descriptor") and "shpssdk" not in line:
|
||||
break
|
||||
if in_shps and ("NATIVE" in line or "name :" in line):
|
||||
print(line.strip())
|
||||
22
reverse/scripts/extract_all_so.py
Normal file
22
reverse/scripts/extract_all_so.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SPLIT = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_config.arm64_v8a.apk"
|
||||
if not SPLIT.exists():
|
||||
native_dir = SPLIT.parent
|
||||
candidates = list(native_dir.glob("split_config.arm64*.apk"))
|
||||
if not candidates:
|
||||
print("missing split APK at", SPLIT, file=sys.stderr)
|
||||
print("run: reverse/frida/pull_split_apk.ps1", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
SPLIT = candidates[0]
|
||||
|
||||
OUT = SPLIT.parent
|
||||
with zipfile.ZipFile(str(SPLIT)) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print(path.name, path.stat().st_size)
|
||||
13
reverse/scripts/extract_from_split.py
Normal file
13
reverse/scripts/extract_from_split.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
split = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_arm64.apk"
|
||||
out = split.parent
|
||||
with zipfile.ZipFile(str(split)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name:
|
||||
dest = out / Path(name).name
|
||||
dest.write_bytes(zf.read(name))
|
||||
print(dest, dest.stat().st_size)
|
||||
14
reverse/scripts/extract_native.py
Normal file
14
reverse/scripts/extract_native.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
OUT = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print("extracted", path, path.stat().st_size)
|
||||
14
reverse/scripts/extract_shps_so.py
Normal file
14
reverse/scripts/extract_shps_so.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
OUT = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
dest = OUT / Path(name).name
|
||||
dest.write_bytes(zf.read(name))
|
||||
print(dest, dest.stat().st_size)
|
||||
13
reverse/scripts/extract_split_so.py
Normal file
13
reverse/scripts/extract_split_so.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
SPLIT = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_config.arm64_v8a.apk"
|
||||
OUT = SPLIT.parent
|
||||
with zipfile.ZipFile(str(SPLIT)) as zf:
|
||||
for name in zf.namelist():
|
||||
if "libshpssdk" in name and name.endswith(".so"):
|
||||
path = OUT / Path(name).name
|
||||
path.write_bytes(zf.read(name))
|
||||
print("extracted", path, path.stat().st_size)
|
||||
20
reverse/scripts/find_blocked_msg.py
Normal file
20
reverse/scripts/find_blocked_msg.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import glob
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
root = str(Path(__file__).resolve().parent.parent / "extracted" / "apk_extract")
|
||||
needle = "this service has been temporarily blocked"
|
||||
for fp in glob.glob(os.path.join(root, "**", "en.json"), recursive=True):
|
||||
try:
|
||||
with open(fp, encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
if not isinstance(d, dict):
|
||||
continue
|
||||
for k, v in d.items():
|
||||
if isinstance(v, str) and needle in v.lower():
|
||||
print(fp)
|
||||
print(k, "->", v)
|
||||
except Exception:
|
||||
pass
|
||||
18
reverse/scripts/find_crypto_classes.py
Normal file
18
reverse/scripts/find_crypto_classes.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(dex_name)
|
||||
if b"CharacterCrypto" not in data and b"SoUtils" not in data:
|
||||
continue
|
||||
print("===", dex_name, "===")
|
||||
for m in re.finditer(rb"L[^;]{0,120};", data):
|
||||
s = m.group().decode("latin1", errors="replace")
|
||||
if "CharacterCrypto" in s or "SoUtils" in s or "SecurityMain" in s:
|
||||
print(s[1:-1].replace("/", "."))
|
||||
22
reverse/scripts/find_detection_classes.py
Normal file
22
reverse/scripts/find_detection_classes.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
found = set()
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
low = s.lower()
|
||||
if any(k in low for k in ("rootdetect", "emulatordetect", "safemode", "risk", "integrity", "xposed", "hookdetect")):
|
||||
found.add(s)
|
||||
if "WBRoot" in s or "WBEmulator" in s:
|
||||
found.add(s)
|
||||
if found:
|
||||
print("=== %s ===" % name)
|
||||
for s in sorted(found):
|
||||
print(s)
|
||||
10
reverse/scripts/find_global_auth_dex.py
Normal file
10
reverse/scripts/find_global_auth_dex.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"Lcom/shopee/bke/digitalbank/container/user/errorcodehandler/GlobalAuthErrorImpl;"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if name.endswith(".dex") and needle in zf.read(name):
|
||||
print("found in", name)
|
||||
16
reverse/scripts/find_iv_monitor.py
Normal file
16
reverse/scripts/find_iv_monitor.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [b"IV_Monitor", b"register scene", b"dfp is empty", b"CharacterCrypto"]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
ctx = data[max(0, idx - 80): idx + 120]
|
||||
import re
|
||||
for m in re.finditer(rb"Lcom/[^;\x00]{5,120};", ctx):
|
||||
print(" class", m.group().decode()[1:-1].replace("/", "."))
|
||||
30
reverse/scripts/find_load_so_lib.py
Normal file
30
reverse/scripts/find_load_so_lib.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(dex_name)
|
||||
if b"loadSoLibrary" not in data:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_loadso.dex"
|
||||
tmp.write_bytes(data)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
current_class = ""
|
||||
for line in out.splitlines():
|
||||
m = re.search(r"Class descriptor\s+:\s+'([^']+)'", line)
|
||||
if m:
|
||||
current_class = m.group(1)
|
||||
if "loadSoLibrary" not in line:
|
||||
continue
|
||||
cls = current_class.replace("L", "").replace(";", "").replace("/", ".")
|
||||
print(f"{dex_name}\t{cls}\t{line.strip()}")
|
||||
19
reverse/scripts/find_native_encrypt.py
Normal file
19
reverse/scripts/find_native_encrypt.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for kw in [b"NativeEncrypt", b"CharacterCrypto", b"IV_Monitor", b"register scene", b"dfp is empty"]:
|
||||
print(kw.decode(), data.count(kw))
|
||||
print("\n--- bke.lib.jni crypto/security ---")
|
||||
seen = set()
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/lib/jni/[^;]{1,120};", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if s in seen:
|
||||
continue
|
||||
if any(x in s.lower() for x in ("crypto", "encrypt", "security", "native", "tee")):
|
||||
seen.add(s)
|
||||
print(s)
|
||||
37
reverse/scripts/find_native_encrypt_class.py
Normal file
37
reverse/scripts/find_native_encrypt_class.py
Normal file
@@ -0,0 +1,37 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
needle = b"NativeEncrypt"
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if needle not in dex:
|
||||
continue
|
||||
print("===", dex_name, "===")
|
||||
for m in re.finditer(rb"const-string[^/]*// string@[0-9a-f]+", dex):
|
||||
pass
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_ne.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if "NativeEncrypt" in line:
|
||||
print(line.strip())
|
||||
if "Class descriptor" in line:
|
||||
current = line
|
||||
if "NativeEncrypt" in line:
|
||||
# print previous class context
|
||||
idx = out.splitlines().index(line)
|
||||
for prev in out.splitlines()[max(0, idx - 40) : idx + 5]:
|
||||
if "Class descriptor" in prev or "name :" in prev or "NATIVE" in prev:
|
||||
print(prev.strip())
|
||||
21
reverse/scripts/find_native_methods.py
Normal file
21
reverse/scripts/find_native_methods.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import subprocess
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
split_apk = Path(__file__).resolve().parent.parent / "extracted" / "native" / "split_arm64.apk"
|
||||
base_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")
|
||||
apk = base_apk if base_apk.exists() else None
|
||||
if apk is None:
|
||||
# use device base if needed - skip
|
||||
import sys
|
||||
print("no base apk")
|
||||
sys.exit(0)
|
||||
with zipfile.ZipFile(str(apk)) as zf:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp11.dex"
|
||||
tmp.write_bytes(zf.read("classes11.dex"))
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace")
|
||||
for line in out.splitlines():
|
||||
if "0x0101" in line or "NATIVE" in line:
|
||||
print(line.strip())
|
||||
26
reverse/scripts/find_shps_sig.py
Normal file
26
reverse/scripts/find_shps_sig.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"getRiskTokenAsync",
|
||||
b"getRiskToken",
|
||||
b"getRiskSync",
|
||||
b"getRiskAsync",
|
||||
b"assessRisk",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if not hits:
|
||||
continue
|
||||
print(name, hits)
|
||||
for m in re.finditer(
|
||||
rb"Lcom/shopee/shpssdk(?:bank)?/SHPSSDK;\.(\w+):\([^)]+\)[^;]+;", data
|
||||
):
|
||||
print(" ", m.group().decode())
|
||||
17
reverse/scripts/find_wb_classes.py
Normal file
17
reverse/scripts/find_wb_classes.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"WBRootDetectionModule" not in data:
|
||||
continue
|
||||
print("===", name, "===")
|
||||
for m in re.finditer(rb"Lcom/shopee/[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "Root" in s or "Detect" in s or "Emulator" in s or "Safe" in s or "Risk" in s or "WB" in s:
|
||||
print(s)
|
||||
22
reverse/scripts/find_wb_root.py
Normal file
22
reverse/scripts/find_wb_root.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"WBRootDetectionModule", b"WBEmulatorDetectionModule", b"SPSAssessRisk", b"shpssdk"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if hits:
|
||||
print(name, hits)
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]*WBRootDetectionModule;", data):
|
||||
print(" ", m.group().decode()[1:-1].replace("/", "."))
|
||||
for m in re.finditer(rb"L[a-zA-Z0-9_$/]*SPSAssessRisk[^;]*;", data):
|
||||
print(" ", m.group().decode()[1:-1].replace("/", "."))
|
||||
for m in re.finditer(rb"Lcom/shopee/shpssdk[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "shpssdk" in s.lower():
|
||||
print(" ", s)
|
||||
16
reverse/scripts/list_methods_classes6.py
Normal file
16
reverse/scripts/list_methods_classes6.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
# method names in dex are plain utf8 strings
|
||||
methods = set(re.findall(rb"[a-zA-Z][a-zA-Z0-9_]{2,60}", data))
|
||||
interesting = sorted(
|
||||
m.decode("ascii", "ignore")
|
||||
for m in methods
|
||||
if any(k in m.lower() for k in (
|
||||
b"root", b"jail", b"safe", b"xposed", b"frida", b"emulator",
|
||||
b"integrity", b"detect", b"hook", b"debug", b"tamper", b"risk"
|
||||
))
|
||||
)
|
||||
for m in interesting:
|
||||
print(m)
|
||||
17
reverse/scripts/list_safemode.py
Normal file
17
reverse/scripts/list_safemode.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
|
||||
data = open(r"C:\Users\Administrator\Desktop\notiMessage\reverse\extracted\classes6.dex", "rb").read()
|
||||
classes = set()
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/lib/safemode/[a-zA-Z0-9_$/]+;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "R$" in s:
|
||||
continue
|
||||
classes.add(s)
|
||||
for c in sorted(classes):
|
||||
print(c)
|
||||
|
||||
print("\n--- interesting strings ---")
|
||||
for pat in [b"isRoot", b"jailbroken", b"rooted", b"SafeMode", b"checkRoot", b"detect", b"xposed", b"frida", b"integrity"]:
|
||||
if pat in data:
|
||||
print(pat.decode(), "YES")
|
||||
9
reverse/scripts/list_sdkutils_jni.py
Normal file
9
reverse/scripts/list_sdkutils_jni.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
so = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libsdkutils.so"
|
||||
data = so.read_bytes()
|
||||
for name in sorted(set(m.group().decode() for m in re.finditer(rb"Java_com_shopee_bke_lib_jni_[A-Za-z0-9_]+", data))):
|
||||
if "utils" in name or "encrypt" in name.lower():
|
||||
print(name)
|
||||
10
reverse/scripts/read_alc.py
Normal file
10
reverse/scripts/read_alc.py
Normal file
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if "alc" in name.lower() and name.endswith(".json"):
|
||||
print("===", name, "===")
|
||||
print(zf.read(name).decode("utf-8", "ignore")[:2000])
|
||||
print()
|
||||
11
reverse/scripts/scan_1201.py
Normal file
11
reverse/scripts/scan_1201.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"-1201", b"1201", b"ErrorCode", b"error has occurred"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
print(n.decode(), "at", idx, "context:", data[max(0,idx-40):idx+60])
|
||||
20
reverse/scripts/scan_4067_ctx.py
Normal file
20
reverse/scripts/scan_4067_ctx.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"4067"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(needle, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 80): idx + 120]
|
||||
import re
|
||||
strings = [m.group().decode("latin1") for m in re.finditer(rb"[\x20-\x7e]{3,80}", ctx)]
|
||||
print("--- at", idx, "---")
|
||||
for s in strings:
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
24
reverse/scripts/scan_common_dialog.py
Normal file
24
reverse/scripts/scan_common_dialog.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
targets = [
|
||||
b"Lcom/shopee/bke/lib/commonui/widget/CommonDialog;",
|
||||
b"Lcom/shopee/bke/lib/commonui/widget/CommonDialog$Builder;",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for t in targets:
|
||||
print("===", t.decode()[1:-1].replace("/", "."), "===")
|
||||
idx = 0
|
||||
c = 0
|
||||
while c < 5:
|
||||
idx = data.find(t, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 100): idx + 300]
|
||||
for m in re.finditer(rb"(show|build|create|setMessage|setContent|display)[a-zA-Z0-9_$<>]*", ctx):
|
||||
print(" ", m.group().decode())
|
||||
idx += 1
|
||||
c += 1
|
||||
40
reverse/scripts/scan_crypto_flow.py
Normal file
40
reverse/scripts/scan_crypto_flow.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for needle in [b"sdkutils", b"SoUtils", b"CharacterCrypto", b"vvuwuuvuu", b"vuwuuwvw", b"wwvwvwuvv", b"dfp is empty"]:
|
||||
print(needle.decode(), data.count(needle))
|
||||
|
||||
print("\n--- classes referencing sdkutils ---")
|
||||
for m in re.finditer(rb"Lcom/[^;]{0,120};", data):
|
||||
s = m.group()
|
||||
if b"sdkutils" in s.lower() or b"SoUtils" in s or b"Crypto" in s:
|
||||
print(s.decode()[1:-1].replace("/", "."))
|
||||
|
||||
# dump vvuuuuvvv.wwvuwuwvu (getRiskToken core)
|
||||
target = "Lcom/shopee/shpssdkbank/uwuvuvvww/vvuuuuvvv;"
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if target.encode() not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_vv.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output([str(DEXDUMP), "-d", str(tmp)], text=True, errors="replace")
|
||||
print("\n===", dex_name, "vvuuuuvvv methods (native only) ===")
|
||||
cap = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
cap = True
|
||||
elif cap and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if cap and ("NATIVE" in line or "name :" in line):
|
||||
print(line.encode("ascii", "replace").decode())
|
||||
57
reverse/scripts/scan_crypto_register.py
Normal file
57
reverse/scripts/scan_crypto_register.py
Normal file
@@ -0,0 +1,57 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
|
||||
needles = [
|
||||
b"CharacterCrypto",
|
||||
b"sdkutils",
|
||||
b"SoUtils",
|
||||
b"dfp is empty",
|
||||
b"register scene",
|
||||
b"deviceToken",
|
||||
b"getRiskToken",
|
||||
b"encrypt",
|
||||
b"decrypt",
|
||||
b"/uapi/v2/register",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
|
||||
print("\n--- CharacterCrypto classes ---")
|
||||
for m in re.finditer(rb"L[^;]{0,100}CharacterCrypto[^;]{0,40};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
print("\n--- SoUtils classes ---")
|
||||
for m in re.finditer(rb"L[^;]{0,80}SoUtils[^;]{0,20};", data):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
|
||||
print("\n--- native methods in shpssdkbank ---")
|
||||
for dex_name in zf.namelist():
|
||||
if not dex_name.endswith(".dex"):
|
||||
continue
|
||||
dex = zf.read(dex_name)
|
||||
if b"shpssdkbank" not in dex:
|
||||
continue
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_shps.dex"
|
||||
tmp.write_bytes(dex)
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
cap = False
|
||||
cls = ""
|
||||
for line in out.splitlines():
|
||||
if "Class descriptor : 'Lcom/shopee/shpssdkbank/" in line:
|
||||
cap = True
|
||||
cls = line.split("'")[1]
|
||||
elif cap and line.startswith(" Class descriptor") and "shpssdkbank" not in line:
|
||||
cap = False
|
||||
if cap and ("0x0101" in line or "NATIVE" in line):
|
||||
print(cls.replace("L", "").replace(";", "").replace("/", "."), line.strip())
|
||||
24
reverse/scripts/scan_detail.py
Normal file
24
reverse/scripts/scan_detail.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, re, sys
|
||||
|
||||
def scan_dir(base, pat):
|
||||
rx = re.compile(pat, re.I)
|
||||
all_m = set()
|
||||
for dex in sorted(os.listdir(base)):
|
||||
if not dex.endswith('.dex'):
|
||||
continue
|
||||
data = open(os.path.join(base, dex), 'rb').read()
|
||||
strs = set(m.group().decode('ascii', 'ignore') for m in re.finditer(rb'[\x20-\x7e]{5,}', data))
|
||||
all_m |= {s for s in strs if rx.search(s)}
|
||||
return sorted(all_m)
|
||||
|
||||
apps = {
|
||||
'up': (r'(HandlerService|NotificationHandler|showNotification|RemoteMessage|Util\$NotificationType|processPush|handleMessage|up/money/notifications)'),
|
||||
'suncorp': (r'SuncorpMessagingService|onMessageReceived|showNotification|NotificationDetails|pushNotification|FirebaseService'),
|
||||
'ubank': (r'MoEFireBase|MessagingService|onMessageReceived|showNotification|Will try to show|bank86400|MoEngage'),
|
||||
}
|
||||
root = sys.argv[1]
|
||||
for app, pat in apps.items():
|
||||
print('\n====', app, '====')
|
||||
for s in scan_dir(os.path.join(root, app), pat)[:60]:
|
||||
print(s)
|
||||
91
reverse/scripts/scan_dex.py
Normal file
91
reverse/scripts/scan_dex.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
PATTERNS = [
|
||||
r'FirebaseMessagingService',
|
||||
r'onMessageReceived',
|
||||
r'NotificationManager',
|
||||
r'NotificationCompat',
|
||||
r'NotificationChannel',
|
||||
r'PushNotification',
|
||||
r'PushMessage',
|
||||
r'Transaction',
|
||||
r'transaction',
|
||||
r'InboxMessage',
|
||||
r'AlertMessage',
|
||||
r'showNotification',
|
||||
r'postNotification',
|
||||
r'NotificationReceiver',
|
||||
r'FCM',
|
||||
r'FirebaseMessaging',
|
||||
r'MessagingService',
|
||||
r'PaymentNotification',
|
||||
r'TransferNotification',
|
||||
r'BankNotification',
|
||||
]
|
||||
|
||||
CLASS_LIKE = re.compile(r'[A-Za-z][\w$/]{3,120}')
|
||||
|
||||
|
||||
def extract_strings(data, min_len=4):
|
||||
out = set()
|
||||
for m in re.finditer(rb'[\x20-\x7e]{' + str(min_len).encode() + rb',}', data):
|
||||
try:
|
||||
out.add(m.group().decode('ascii'))
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
def scan_file(path):
|
||||
with open(path, 'rb') as f:
|
||||
data = f.read()
|
||||
strings = extract_strings(data, 5)
|
||||
hits = {}
|
||||
for pat in PATTERNS:
|
||||
rx = re.compile(pat, re.I)
|
||||
matched = sorted({s for s in strings if rx.search(s)})
|
||||
if matched:
|
||||
hits[pat] = matched[:40]
|
||||
# interesting fully-qualified class names
|
||||
fqcn = sorted({
|
||||
s for s in strings
|
||||
if ('/' in s or s.startswith('L')) and any(k in s.lower() for k in (
|
||||
'notif', 'push', 'fcm', 'firebase', 'message', 'transaction', 'alert', 'inbox', 'payment', 'transfer'
|
||||
))
|
||||
})
|
||||
return hits, fqcn[:80]
|
||||
|
||||
|
||||
def main(root):
|
||||
for app in sorted(os.listdir(root)):
|
||||
app_dir = os.path.join(root, app)
|
||||
if not os.path.isdir(app_dir):
|
||||
continue
|
||||
print('\n' + '=' * 70)
|
||||
print('APP:', app)
|
||||
print('=' * 70)
|
||||
dex_files = [f for f in os.listdir(app_dir) if f.endswith('.dex')]
|
||||
all_hits = {}
|
||||
all_fqcn = set()
|
||||
for dex in sorted(dex_files):
|
||||
path = os.path.join(app_dir, dex)
|
||||
hits, fqcn = scan_file(path)
|
||||
for k, v in hits.items():
|
||||
all_hits.setdefault(k, set()).update(v)
|
||||
all_fqcn.update(fqcn)
|
||||
for pat in PATTERNS:
|
||||
if pat in all_hits:
|
||||
print('\n[%s]' % pat)
|
||||
for s in sorted(all_hits[pat])[:25]:
|
||||
print(' ', s)
|
||||
print('\n[interesting class-like strings]')
|
||||
for s in sorted(all_fqcn)[:60]:
|
||||
print(' ', s)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
root = sys.argv[1] if len(sys.argv) > 1 else 'extracted'
|
||||
main(root)
|
||||
11
reverse/scripts/scan_error_classes.py
Normal file
11
reverse/scripts/scan_error_classes.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for m in re.finditer(rb"Lcom/shopee/[a-zA-Z0-9_$/]*(error|Error|dialog|Dialog|Risk|risk|Otp|otp|Phone|Register)[a-zA-Z0-9_$/]*;", data):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "bke" in s or "seabank" in s or "shpssdk" in s:
|
||||
print(s)
|
||||
28
reverse/scripts/scan_error_handler.py
Normal file
28
reverse/scripts/scan_error_handler.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
import struct
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
targets = [
|
||||
b"Lcom/shopee/bke/biz/user/errorcodehandler/a;",
|
||||
b"Lcom/shopee/bke/biz/user/errorcodehandler/b;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSAssessRisk;",
|
||||
b"Lcom/shopee/bke/biz/user/rn/helper/ErrorFlowHelper;",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for t in targets:
|
||||
print("===", t.decode()[1:-1].replace("/", "."), "===")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(t, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 200): idx + 400]
|
||||
# crude string extraction nearby
|
||||
for m in __import__("re").finditer(rb"[\x20-\x7e]{4,80}", ctx):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ["1201", "error", "kill", "finish", "risk", "root", "code", "handle"]):
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
13
reverse/scripts/scan_error_strings.py
Normal file
13
reverse/scripts/scan_error_strings.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [b"An error has occurred", b"-1201", b"8424 8050", b"Error --", b"killProcess", b"finishAffinity"]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
idx = data.find(n)
|
||||
if idx >= 0:
|
||||
ctx = re.sub(rb"[^\x20-\x7e]+", b" ", data[max(0, idx - 60): idx + len(n) + 80])
|
||||
print(n.decode(), "->", ctx.decode())
|
||||
17
reverse/scripts/scan_json_lib.py
Normal file
17
reverse/scripts/scan_json_lib.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
libs = [
|
||||
b"com/google/gson/Gson",
|
||||
b"com/fasterxml/jackson",
|
||||
b"com/alibaba/fastjson",
|
||||
b"org/json/JSONObject",
|
||||
b"okhttp3/RequestBody",
|
||||
b"retrofit2/",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for lib in libs:
|
||||
print(lib.decode(), data.count(lib))
|
||||
20
reverse/scripts/scan_method_hints.py
Normal file
20
reverse/scripts/scan_method_hints.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
METHOD_HINTS = [
|
||||
b"isRoot", b"isRooted", b"jailbroken", b"checkRoot", b"detectRoot",
|
||||
b"detectXposed", b"checkXposed", b"isXposed", b"checkFrida", b"isEmulator",
|
||||
b"checkIntegrity", b"SafeMode", b"needSafeMode", b"enterSafeMode",
|
||||
b"showRoot", b"rooted", b"factory settings",
|
||||
]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [h.decode("ascii", "ignore") for h in METHOD_HINTS if h in data]
|
||||
if hits:
|
||||
print(name, ":", ", ".join(hits))
|
||||
28
reverse/scripts/scan_needle_context.py
Normal file
28
reverse/scripts/scan_needle_context.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
TARGETS = [b"isXposed", b"isRooted", b"checkRoot", b"isRoot", b"isEmulator", b"jailbroken"]
|
||||
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for dex_name in ["classes6.dex", "classes9.dex", "classes15.dex", "classes3.dex"]:
|
||||
data = zf.read(dex_name)
|
||||
print("=== %s ===" % dex_name)
|
||||
for needle in TARGETS:
|
||||
if needle not in data:
|
||||
continue
|
||||
idx = 0
|
||||
shown = 0
|
||||
while shown < 8:
|
||||
i = data.find(needle, idx)
|
||||
if i < 0:
|
||||
break
|
||||
s = max(0, i - 80)
|
||||
e = min(len(data), i + 80)
|
||||
chunk = re.sub(rb"[^\x20-\x7e]+", b" ", data[s:e]).decode("ascii", "ignore")
|
||||
if "shopee" in chunk.lower() or "seabank" in chunk.lower() or "bke" in chunk.lower() or "alc" in chunk.lower():
|
||||
print(" ", needle.decode(), "->", chunk.strip())
|
||||
shown += 1
|
||||
idx = i + 1
|
||||
print()
|
||||
28
reverse/scripts/scan_register_api.py
Normal file
28
reverse/scripts/scan_register_api.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"GlobalAuthErrorImpl",
|
||||
b"errorcodehandler",
|
||||
b"sendOtp",
|
||||
b"register",
|
||||
b"verifyMobile",
|
||||
b"mobile/register",
|
||||
b"preRegister",
|
||||
b"riskToken",
|
||||
b"risk_token",
|
||||
b"8424 8050",
|
||||
b"-1201",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
print(n.decode(), data.count(n))
|
||||
print("\n--- urls ---")
|
||||
for m in re.finditer(rb"https?://[a-zA-Z0-9._/-]{8,120}", data):
|
||||
u = m.group().decode()
|
||||
if "seabank" in u or "register" in u or "otp" in u or "mobile" in u:
|
||||
print(u)
|
||||
24
reverse/scripts/scan_register_class.py
Normal file
24
reverse/scripts/scan_register_class.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"v2/register",
|
||||
b"/register",
|
||||
b"RegisterRequest",
|
||||
b"registerPhone",
|
||||
b"signUp",
|
||||
b"preRegister",
|
||||
b"riskToken",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
c = data.count(n)
|
||||
if c:
|
||||
print(n.decode(), c)
|
||||
print("\n--- classes near register ---")
|
||||
for m in re.finditer(rb"Lcom/shopee/bke/[^;]{0,120}register[^;]{0,40};", data, re.I):
|
||||
print(m.group().decode()[1:-1].replace("/", "."))
|
||||
15
reverse/scripts/scan_register_endpoint.py
Normal file
15
reverse/scripts/scan_register_endpoint.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"uapi/v2/register"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
idx = data.find(needle)
|
||||
print("found at", idx)
|
||||
if idx >= 0:
|
||||
ctx = data[max(0, idx - 400): idx + 400]
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,120}", ctx):
|
||||
print(" ", m.group().decode("latin1"))
|
||||
29
reverse/scripts/scan_register_paths.py
Normal file
29
reverse/scripts/scan_register_paths.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"mobile",
|
||||
b"register",
|
||||
b"otp",
|
||||
b"signUp",
|
||||
b"signup",
|
||||
b"preCheck",
|
||||
b"checkMobile",
|
||||
b"sendSms",
|
||||
b"verifyPhone",
|
||||
b"4067004",
|
||||
b"4067",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for n in needles:
|
||||
if n in data:
|
||||
print("hit", n.decode())
|
||||
print("\n--- api paths ---")
|
||||
for m in re.finditer(rb"/v[0-9]/[a-zA-Z0-9_/-]{6,80}", data):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ("user", "auth", "register", "mobile", "otp", "sign", "risk", "phone")):
|
||||
print(s)
|
||||
24
reverse/scripts/scan_risk_fields.py
Normal file
24
reverse/scripts/scan_risk_fields.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for needle in [b"riskToken", b"risk_token", b"deviceToken", b"secToken", b"shpsToken", b"mobileNo", b"phoneNo"]:
|
||||
print(needle.decode(), data.count(needle))
|
||||
print("\n--- context riskToken ---")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(b"riskToken", idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 60): idx + 120]
|
||||
for m in re.finditer(rb"[\x20-\x7e]{3,60}", ctx):
|
||||
s = m.group().decode("latin1")
|
||||
if any(k in s.lower() for k in ["risk", "token", "mobile", "phone", "register", "device"]):
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
if idx > 5000000:
|
||||
break
|
||||
19
reverse/scripts/scan_risk_token_ctx.py
Normal file
19
reverse/scripts/scan_risk_token_ctx.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
needle = b"riskToken"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
idx = data.find(needle)
|
||||
if idx < 0:
|
||||
continue
|
||||
ctx = data[max(0, idx - 300): idx + 300]
|
||||
print("===", name, "===")
|
||||
import re
|
||||
for m in re.finditer(rb"[\x20-\x7e]{4,100}", ctx):
|
||||
print(" ", m.group().decode("latin1"))
|
||||
42
reverse/scripts/scan_root.py
Normal file
42
reverse/scripts/scan_root.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
KEYS = [
|
||||
b"rooted", b"jailbroken", b"RootBeer", b"isRoot", b"checkRoot", b"detectRoot",
|
||||
b"SafetyNet", b"PlayIntegrity", b"magisk", b"/su", b"tamper", b"safemode",
|
||||
b"SafeMode", b"xposed", b"lsposed", b"frida", b"emulator", b"debuggable",
|
||||
b"Integrity", b"jailbreak", b"factory settings", b"RiskDevice", b"DeviceRisk",
|
||||
b"root device", b"seabank", b"SeaBank", b"MariBank", b"alc", b"ALC",
|
||||
]
|
||||
|
||||
|
||||
def scan_apk(apk_path):
|
||||
with zipfile.ZipFile(apk_path) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
print("=== %s (%d bytes) ===" % (name, len(data)))
|
||||
hits = set()
|
||||
for key in KEYS:
|
||||
start = 0
|
||||
while True:
|
||||
idx = data.find(key, start)
|
||||
if idx < 0:
|
||||
break
|
||||
s = max(0, idx - 40)
|
||||
e = min(len(data), idx + len(key) + 60)
|
||||
chunk = re.sub(rb"[^\x20-\x7e]+", b" ", data[s:e])
|
||||
chunk = chunk.decode("ascii", "ignore").strip()
|
||||
if len(chunk) > 8:
|
||||
hits.add(chunk)
|
||||
start = idx + 1
|
||||
for hit in sorted(hits):
|
||||
print(" ", hit)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scan_apk(sys.argv[1] if len(sys.argv) > 1 else "reverse/apks/seabank_ph_base.apk")
|
||||
19
reverse/scripts/scan_root_dialog.py
Normal file
19
reverse/scripts/scan_root_dialog.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"rooted or jailbroken",
|
||||
b"factory settings",
|
||||
b"cannot be accessed",
|
||||
b"bke_toast_not_support_root",
|
||||
b"not_support_root",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not (name.endswith(".dex") or name.endswith(".xml") or name.endswith(".json")):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
for n in needles:
|
||||
if n in data:
|
||||
print(name, n.decode())
|
||||
20
reverse/scripts/scan_root_string_ctx.py
Normal file
20
reverse/scripts/scan_root_string_ctx.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
if b"bke_toast_not_support_root" not in data:
|
||||
continue
|
||||
print("===", name, "===")
|
||||
idx = data.find(b"bke_toast_not_support_root")
|
||||
ctx = re.sub(rb"[^\x20-\x7e]+", b" ", data[max(0, idx - 120): idx + 200])
|
||||
print(ctx.decode())
|
||||
for m in re.finditer(rb"Lcom/shopee/bke[^;]{0,120};", data[max(0, idx - 800): idx + 800]):
|
||||
s = m.group().decode()[1:-1].replace("/", ".")
|
||||
if "dialog" in s.lower() or "root" in s.lower() or "safemode" in s.lower() or "risk" in s.lower() or "toast" in s.lower():
|
||||
print(" ", s)
|
||||
44
reverse/scripts/scan_security_classes.py
Normal file
44
reverse/scripts/scan_security_classes.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Scan dex class descriptors for Shopee/SeaBank security SDK."""
|
||||
import re
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
TARGETS = (
|
||||
"safemode",
|
||||
"SafeMode",
|
||||
"alc/",
|
||||
"ALC",
|
||||
"integrity",
|
||||
"rooted",
|
||||
"jailbroken",
|
||||
"RootBeer",
|
||||
"xposed",
|
||||
"frida",
|
||||
"isRoot",
|
||||
"detectRoot",
|
||||
)
|
||||
|
||||
|
||||
def scan(apk_path):
|
||||
with zipfile.ZipFile(apk_path) as zf:
|
||||
for name in sorted(zf.namelist()):
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
classes = set(re.findall(rb"L[a-zA-Z0-9_$/]+;", data))
|
||||
hits = []
|
||||
for raw in classes:
|
||||
s = raw.decode("ascii", "ignore")
|
||||
low = s.lower()
|
||||
if any(t.lower() in low for t in TARGETS):
|
||||
hits.append(s[1:-1].replace("/", "."))
|
||||
if hits:
|
||||
print("=== %s ===" % name)
|
||||
for h in sorted(set(hits)):
|
||||
print(h)
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
scan(sys.argv[1])
|
||||
19
reverse/scripts/scan_shps_classes.py
Normal file
19
reverse/scripts/scan_shps_classes.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
APK = Path(__file__).resolve().parent.parent / "apks" / "seabank_ph_base.apk"
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = zf.read("classes11.dex")
|
||||
for pat in [b"loadLibrary", b"libshpssdk", b"JNI_OnLoad", b"RegisterNatives", b"native "]:
|
||||
print(pat.decode(), data.count(pat))
|
||||
print("\n--- classes with shpssdkbank ---")
|
||||
for m in re.finditer(rb"Lcom/shopee/shpssdkbank/[a-zA-Z0-9_$;/]+;", data):
|
||||
s = m.group().decode()
|
||||
if "uvu" in s or "SPS" in s or "SHPS" in s or "Native" in s:
|
||||
if s not in []:
|
||||
pass
|
||||
classes = sorted(set(m.group().decode()[1:-1].replace("/", ".") for m in re.finditer(rb"Lcom/shopee/shpssdkbank/[a-zA-Z0-9_$]+;", data)))
|
||||
for c in classes[:60]:
|
||||
print(c)
|
||||
31
reverse/scripts/scan_shps_methods.py
Normal file
31
reverse/scripts/scan_shps_methods.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
classes = [
|
||||
b"Lcom/shopee/shpssdk/SPSRiskTokenCallback;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSRiskTokenCallback;",
|
||||
b"Lcom/shopee/shpssdk/SPSResultCallback;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSResultCallback;",
|
||||
b"Lcom/shopee/shpssdk/SHPSSDK;",
|
||||
b"Lcom/shopee/shpssdkbank/SPSAssessRisk;",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
data = b"".join(zf.read(n) for n in zf.namelist() if n.endswith(".dex"))
|
||||
for c in classes:
|
||||
name = c.decode()[1:-1].replace("/", ".")
|
||||
print("===", name, "===")
|
||||
idx = 0
|
||||
while True:
|
||||
idx = data.find(c, idx)
|
||||
if idx < 0:
|
||||
break
|
||||
ctx = data[max(0, idx - 150): idx + 400]
|
||||
for m in re.finditer(rb"[a-zA-Z][a-zA-Z0-9_$]{2,40}", ctx):
|
||||
s = m.group().decode()
|
||||
if any(k in s.lower() for k in ["token", "risk", "result", "callback", "assess", "get", "on"]):
|
||||
if len(s) > 4:
|
||||
print(" ", s)
|
||||
idx += 1
|
||||
break
|
||||
41
reverse/scripts/scan_shps_native.py
Normal file
41
reverse/scripts/scan_shps_native.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
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")
|
||||
targets = [
|
||||
"Lcom/shopee/shpssdkbank/SHPSSDK;",
|
||||
"Lcom/shopee/shpssdk/SHPSSDK;",
|
||||
]
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
for dex_name in ["classes11.dex", "classes10.dex", "classes6.dex"]:
|
||||
tmp = Path(__file__).resolve().parent.parent / "tmp" / "tmp_scan.dex"
|
||||
tmp.write_bytes(zf.read(dex_name))
|
||||
out = subprocess.check_output(
|
||||
[str(DEXDUMP), "-d", str(tmp)], universal_newlines=True, errors="replace"
|
||||
)
|
||||
for target in targets:
|
||||
capture = False
|
||||
for line in out.splitlines():
|
||||
if f"Class descriptor : '{target}'" in line:
|
||||
capture = True
|
||||
print("===", dex_name, target, "===")
|
||||
elif capture and line.startswith(" Class descriptor"):
|
||||
break
|
||||
if capture and ("native" in line.lower() or "loadLibrary" in line
|
||||
or "System" in line and "load" in line):
|
||||
print(line.strip())
|
||||
if capture and "name :" in line and "type :" in line:
|
||||
pass
|
||||
if capture and "access : 0x0101" in line or (
|
||||
capture and "NATIVE" in line):
|
||||
print(line.strip())
|
||||
|
||||
# also grep dex binary for loadLibrary strings near shpssdk
|
||||
with zipfile.ZipFile(str(APK)) as zf:
|
||||
data = zf.read("classes11.dex")
|
||||
for m in re.finditer(rb"libshpssdk[^\x00]{0,40}", data):
|
||||
print("str", m.group().decode("latin1"))
|
||||
23
reverse/scripts/scan_shps_token.py
Normal file
23
reverse/scripts/scan_shps_token.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import zipfile
|
||||
|
||||
APK = r"C:\Users\Administrator\Desktop\notiMessage\reverse\apks\seabank_ph_base.apk"
|
||||
needles = [
|
||||
b"SPSRiskTokenCallback",
|
||||
b"SPSResultCallback",
|
||||
b"SPSCallback",
|
||||
b"getRiskToken",
|
||||
b"riskToken",
|
||||
b"RiskToken",
|
||||
b"assessRisk",
|
||||
b"AssessRisk",
|
||||
]
|
||||
with zipfile.ZipFile(APK) as zf:
|
||||
for name in zf.namelist():
|
||||
if not name.endswith(".dex"):
|
||||
continue
|
||||
data = zf.read(name)
|
||||
hits = [n.decode() for n in needles if n in data]
|
||||
if hits:
|
||||
print(name, hits)
|
||||
19
reverse/scripts/scan_so_jni.py
Normal file
19
reverse/scripts/scan_so_jni.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
native = Path(__file__).resolve().parent.parent / "extracted" / "native"
|
||||
needles = [b"uvwuvwuv", b"NativeEncrypt", b"encryptByRSA", b"aesEncrypt"]
|
||||
for so in sorted(native.glob("lib*.so")):
|
||||
data = so.read_bytes()
|
||||
hits = []
|
||||
for n in needles:
|
||||
if n in data:
|
||||
hits.append(n.decode())
|
||||
if not hits:
|
||||
continue
|
||||
print("\n===", so.name, hits, "===")
|
||||
for m in sorted(set(re.findall(rb"Java_com_shopee_bke_[A-Za-z0-9_]+", data))):
|
||||
s = m.decode()
|
||||
if any(k in s.lower() for k in ("encrypt", "utils", "crypto", "jni")):
|
||||
print(" ", s)
|
||||
36
reverse/scripts/scan_target.py
Normal file
36
reverse/scripts/scan_target.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os, re, sys
|
||||
|
||||
def extract_strings(data, min_len=5):
|
||||
out = set()
|
||||
for m in re.finditer(rb'[\x20-\x7e]{' + str(min_len).encode() + rb',}', data):
|
||||
out.add(m.group().decode('ascii', 'ignore'))
|
||||
return out
|
||||
|
||||
def scan_app(app_dir, filters):
|
||||
strings = set()
|
||||
for name in os.listdir(app_dir):
|
||||
if not name.endswith('.dex'):
|
||||
continue
|
||||
with open(os.path.join(app_dir, name), 'rb') as f:
|
||||
strings |= extract_strings(f.read())
|
||||
print('\n===', os.path.basename(app_dir), '===')
|
||||
for label, rx in filters:
|
||||
matched = sorted({s for s in strings if re.search(rx, s, re.I)})
|
||||
print('\n[%s] count=%d' % (label, len(matched)))
|
||||
for s in matched[:50]:
|
||||
print(' ', s)
|
||||
|
||||
filters = [
|
||||
('up notifications', r'au\.com\.up\.money\.notifications|Lau/com/up/money/notifications'),
|
||||
('suncorp messaging', r'au\.com\.suncorp\.marketplace.*(Messaging|Firebase|Notification|Push)'),
|
||||
('ubank messaging', r'au\.com\.bank86400|bank86400|86400.*(Messaging|Firebase|Notification|Push|MoEngage)'),
|
||||
('ubank onMessage', r'onMessageReceived|Will try to show push|MoEngage'),
|
||||
('custom FCM services', r'MessagingService;|HandlerService|FirebaseService'),
|
||||
]
|
||||
|
||||
root = sys.argv[1]
|
||||
for app in sorted(os.listdir(root)):
|
||||
p = os.path.join(root, app)
|
||||
if os.path.isdir(p):
|
||||
scan_app(p, filters)
|
||||
19
reverse/scripts/strings_shps_so.py
Normal file
19
reverse/scripts/strings_shps_so.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
SO = Path(__file__).resolve().parent.parent / "extracted" / "native" / "libshpssdk_bank.so"
|
||||
data = SO.read_bytes()
|
||||
seen = set()
|
||||
for m in re.finditer(rb"[\x20-\x7e]{3,}", data):
|
||||
s = m.group().decode("latin1")
|
||||
if s in seen or len(s) > 200:
|
||||
continue
|
||||
low = s.lower()
|
||||
if any(k in low for k in [
|
||||
"proc", "root", "hook", "xposed", "magisk", "frida", "emulator",
|
||||
"risk", "token", "su", "debug", "maps", "version", "selinux",
|
||||
"shpssdk", "detect", "jail", "integrity"
|
||||
]):
|
||||
seen.add(s)
|
||||
print(s)
|
||||
Reference in New Issue
Block a user