chore: 备份 TNG 注册/captcha 逆向与 MariBank SG bypass 进展

TngRootBypassHook 增强 captcha 诊断、TigerTally/JNIC 分层与 HWUI 策略;新增逆向脚本、Frida 工具与 UI dump;同步 MariBank SG hook 与 tng_exit_guard 更新。
This commit is contained in:
mars
2026-08-03 15:23:02 +08:00
parent 193c04a24b
commit 609635aba1
185 changed files with 60843 additions and 231 deletions

View File

@@ -22,16 +22,15 @@ logcat 标签:`NativeEncrypt: loading JNI`、`CharacterCryptoManager`
## 运行
```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 &'
# SG 专用 native trace推荐
.\scripts\run-frida-sg-native.ps1
# 2. PC 安装 frida-tools 后
#
cd reverse\frida
.\run-frida-trace.ps1 -Mode spawn
C:\Users\Administrator\AppData\Local\Programs\Python\Python312\python.exe run_frida_sg_native.py attach
# 3. App 内 Sign up → 输入号码 → Next
# 关注 [MB-TRACE] NativeEncryptWrapper / NativeEncryptUtils / HTTP .../register
# PH 旧脚本(勿用于 SG
.\run-frida-trace.ps1 -Mode spawn
```
建议测试时**暂时关闭 LSPosed 对 MariBank 的作用域**,避免与 Frida 冲突。

25
reverse/frida/mini_run.py Normal file
View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
import subprocess, time
import frida
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
def adb(*a): return subprocess.run([ADB, *a], capture_output=True, text=True)
def on_msg(m, d):
line = m.get("payload") if m.get("type") in ("send", "log") else str(m)
print(line, flush=True)
d = frida.get_usb_device(10)
adb("shell", "am", "force-stop", PKG); time.sleep(1)
pid = d.spawn([PKG])
s = d.attach(pid)
sc = s.create_script(open("mini_timer.js", encoding="utf-8").read())
sc.on("message", on_msg)
sc.load()
print("resume", pid, flush=True)
d.resume(pid)
for i in range(20):
time.sleep(1)
p = adb("shell", "pidof", PKG).stdout.strip()
print("t=%d pid=%s" % (i+1, p or "DEAD"), flush=True)
if not p: break
try: s.detach()
except: pass

View File

@@ -0,0 +1,9 @@
"use strict";
console.log("[MINI] start");
let i = 0;
const t = setInterval(() => {
i++;
const m = Process.findModuleByName("libtiger_tally.so");
console.log(`[MINI] tick=${i} tiger_loaded=${!!m}`);
if (i > 15) clearInterval(t);
}, 1000);

View File

@@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
"""Frida SG native trace — attach to sg.com.maribankmobile.digitalbank."""
import sys
import time
from datetime import datetime
from pathlib import Path
import frida
PKG = "sg.com.maribankmobile.digitalbank"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_maribank_sg_native.js"
LOG = LOGS_DIR / ("sg_native_%s.log" % datetime.now().strftime("%Y%m%d_%H%M%S"))
def on_message(message, data):
mtype = message.get("type")
if mtype == "send":
line = message.get("payload")
elif mtype == "log":
line = message.get("payload", message.get("description", ""))
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 mtype == "error":
with open(str(LOG) + ".err", "a", encoding="utf-8") as f:
f.write(text + "\n")
def wait_for_process(device, pkg, timeout_sec=60):
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
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",
"am",
"start",
"-n",
pkg + "/com.shopee.bke.digitalbank.ui.MainActivity",
],
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]
if not SCRIPT.is_file():
raise SystemExit("missing script: %s" % SCRIPT)
ensure_frida_server()
device = frida.get_usb_device(timeout=10)
source = SCRIPT.read_text(encoding="utf-8")
print("Package: %s" % PKG)
print("Script: %s" % SCRIPT)
print("Log: %s" % LOG)
print("")
print("IMPORTANT: keep LSPosed scope ENABLED for SG (bypasses ADB page while tracing)")
print("")
if mode == "spawn":
pid = device.spawn([PKG])
session = device.attach(pid)
else:
pid = wait_for_process(device, PKG, 3)
if pid is None:
print("Launching MariBank SG ...")
launch_app(PKG)
pid = wait_for_process(device, PKG, 90)
if pid is None:
raise SystemExit(
"SG MariBank not running — open app to Sign up page, then re-run"
)
print("Attach 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("Spawn resumed, wait JVM 15s ...")
time.sleep(15)
else:
time.sleep(3)
print("Trace running. Sign up -> +65 -> Next. Ctrl+C to stop.")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("Stopping...")
session.detach()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,113 @@
# -*- coding: utf-8 -*-
"""Spawn TNG eWallet with Frida native exit blockers."""
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
import frida
PKG = "my.com.tngdigital.ewallet"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_tng_native_exit.js"
LOG = LOGS_DIR / ("tng_native_%s.log" % datetime.now().strftime("%Y%m%d_%H%M%S"))
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
HOLD_SEC = 45
def on_message(message, data):
mtype = message.get("type")
if mtype == "send":
line = message.get("payload")
elif mtype == "log":
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 adb(*args):
return subprocess.run([ADB, *args], capture_output=True, text=True)
def ensure_frida_server():
out = adb("shell", "su", "-c", "pgrep -x frida-server")
if out.stdout.strip():
print("frida-server already running pid=%s" % out.stdout.strip())
return
print("starting frida-server ...")
adb("shell", "su", "-c", "pkill -9 frida-server; true")
# run in background via nohup-like
subprocess.Popen(
[ADB, "shell", "su", "-c", "/data/local/tmp/frida-server -D"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
time.sleep(2)
out = adb("shell", "su", "-c", "pgrep -x frida-server")
if not out.stdout.strip():
raise RuntimeError("frida-server failed to start")
print("frida-server pid=%s" % out.stdout.strip())
def main():
ensure_frida_server()
adb("shell", "am", "force-stop", PKG)
time.sleep(1)
device = frida.get_usb_device(10)
source = SCRIPT.read_text(encoding="utf-8")
print("Spawning %s ..." % PKG)
print("log=%s" % LOG)
pid = device.spawn([PKG])
session = device.attach(pid)
script = session.create_script(source)
script.on("message", on_message)
script.load()
print("script loaded, resume pid=%s" % pid)
device.resume(pid)
alive = 0
for i in range(HOLD_SEC):
time.sleep(1)
# check process still alive
out = adb("shell", "pidof", PKG)
pids = out.stdout.strip()
if not pids:
print("DEAD after %ss" % (i + 1))
break
alive = i + 1
if (i + 1) % 5 == 0:
print("alive %ss pid=%s" % (alive, pids))
else:
print("STABLE %ss pid=%s" % (HOLD_SEC, adb("shell", "pidof", PKG).stdout.strip()))
# dump activity focus
focus = adb("shell", "dumpsys", "activity", "activities")
for line in focus.stdout.splitlines():
if "tngdigital" in line.lower() and (
"mResumedActivity" in line
or "topResumedActivity" in line
or "UserLogin" in line
or "SecurityError" in line
or "Splash" in line
):
print("ACT: " + line.strip())
try:
session.detach()
except Exception:
pass
print("done alive=%ss log=%s" % (alive, LOG))
return 0 if alive >= 15 else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,178 @@
# -*- coding: utf-8 -*-
"""Compare: Xposed-only vs Frida-stealth-spawn."""
import subprocess
import time
import frida
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
STEALTH = r"""
"use strict";
function log(m){ send("[stealth] "+m); }
function findExport(mod, name) {
try {
var m = Process.getModuleByName(mod);
var a = m.findExportByName(name);
if (a) return a;
} catch (e) {}
try { return Module.getGlobalExportByName(name); } catch (e2) { return null; }
}
// Rename frida threads
try {
var pthread_setname = findExport("libc.so", "pthread_setname_np");
// also patch existing: best-effort via Java later
} catch (e) {}
// Hide maps
var markers = ["frida","gadget","linjector","gum-js","gmain","pool-frida","hluda"];
var tracked = {};
function hideLine(line) {
var l = (line||"").toLowerCase();
for (var i=0;i<markers.length;i++) if (l.indexOf(markers[i])>=0) return true;
return false;
}
function filterBuf(buf, len) {
try {
var t = buf.readUtf8String(len);
if (!t) return len;
var out = t.split("\n").filter(function(x){return !hideLine(x);}).join("\n");
var b = Memory.allocUtf8String(out);
var n = Math.min(len, out.length);
Memory.copy(buf, b, n);
return n;
} catch (e) { return len; }
}
var openat = findExport("libc.so","openat");
var readFn = findExport("libc.so","read");
if (openat) {
Interceptor.attach(openat, {
onEnter: function(args){ this.path = args[1].isNull()?null:args[1].readCString(); },
onLeave: function(retval){
var fd=retval.toInt32();
if (fd>=0 && this.path && (this.path.indexOf("maps")>=0 || this.path.indexOf("status")>=0 || this.path.indexOf("task")>=0))
tracked[fd]=this.path;
}
});
}
if (readFn) {
Interceptor.attach(readFn, {
onEnter: function(args){ this.fd=args[0].toInt32(); this.buf=args[1]; },
onLeave: function(retval){
var n=retval.toInt32();
if (n>0 && tracked[this.fd]) retval.replace(ptr(filterBuf(this.buf, n)));
}
});
}
// Patch libc exit_group SVC
var libc = Process.getModuleByName("libc.so");
libc.enumerateRanges("r-x").forEach(function(r){
for (var off=0; off+8<r.size; off+=4) {
var p=r.base.add(off);
var w;
try { w=p.readU32(); } catch(e){ return; }
if (w!==0xd2800bc8 && w!==0xd2800ba8 && w!==0x52800bc8 && w!==0x52800ba8) continue;
for (var j=4;j<=24;j+=4) {
var s=p.add(j);
try {
if (s.readU32()===0xd4000001) {
Memory.protect(s,4,"rwx");
s.writeU32(0xd65f03c0);
log("SVC->RET "+s);
}
} catch(e2){}
}
}
});
// Replace exit APIs
["_exit","exit","abort"].forEach(function(n){
var a=findExport("libc.so",n);
if(!a) return;
try {
Interceptor.replace(a, new NativeCallback(function(c){ log("block "+n+"("+c+")"); }, "void", ["int"]));
} catch(e){}
});
log("stealth ready");
"""
def adb(*a):
return subprocess.run([ADB, *a], capture_output=True, text=True)
def on_msg(m, _d):
print(m.get("payload", m), flush=True)
def wait_alive(sec, label):
for i in range(sec):
time.sleep(1)
p = adb("shell", "pidof", PKG).stdout.strip()
act = ""
dump = adb("shell", "dumpsys", "activity", "activities").stdout
for line in dump.splitlines():
if "tngdigital" in line.lower() and any(
x in line for x in ("UserLogin", "SecurityError", "Splash", "topResumedActivity")
):
act = line.strip()[:120]
break
print("%s t=%ds pid=%s act=%s" % (label, i + 1, p or "DEAD", act), flush=True)
if not p:
return i + 1
return sec
def test_xposed_only():
print("=== Xposed-only (monkey) ===", flush=True)
adb("shell", "am", "force-stop", PKG)
adb("logcat", "-c")
time.sleep(0.3)
adb(
"shell",
"monkey",
"-p",
PKG,
"-c",
"android.intent.category.LAUNCHER",
"1",
)
alive = wait_alive(15, "XPOSED")
print("xposed_alive_sec", alive, flush=True)
for line in adb("logcat", "-d").stdout.splitlines():
if "TngRoot" in line and any(
x in line for x in ("install", "UserLogin", "short-circuit", "finishing", "blocked intent")
):
print(line[line.find("TngRoot") :][:180], flush=True)
if "Displayed" in line and "tngdigital" in line:
print(line.strip()[:200], flush=True)
if "exited cleanly" in line:
print(line.strip()[:160], flush=True)
def test_frida_stealth():
print("=== Frida stealth spawn ===", flush=True)
adb("shell", "am", "force-stop", PKG)
time.sleep(0.3)
d = frida.get_usb_device(10)
pid = d.spawn([PKG])
s = d.attach(pid)
sc = s.create_script(STEALTH)
sc.on("message", on_msg)
sc.load()
d.resume(pid)
alive = wait_alive(15, "FRIDA")
print("frida_alive_sec", alive, flush=True)
def main():
test_xposed_only()
test_frida_stealth()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
import subprocess
import time
from pathlib import Path
import frida
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
SCRIPT = Path(__file__).with_name("trace_tng_diag_exit.js").read_text(encoding="utf-8")
def adb(*a):
return subprocess.run([ADB, *a], capture_output=True, text=True)
def on_msg(m, d):
if m.get("type") == "send":
print(m["payload"], flush=True)
else:
print(m, flush=True)
def main():
adb("shell", "am", "force-stop", PKG)
time.sleep(0.5)
d = frida.get_usb_device(10)
pid = d.spawn([PKG])
s = d.attach(pid)
sc = s.create_script(SCRIPT)
sc.on("message", on_msg)
sc.load()
print("resume", pid, flush=True)
d.resume(pid)
for i in range(15):
time.sleep(1)
p = adb("shell", "pidof", PKG).stdout.strip()
print("t=%ds pid=%s" % (i + 1, p or "DEAD"), flush=True)
if not p:
break
try:
s.detach()
except Exception:
pass
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
import subprocess
import time
import frida
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
def adb(*a):
return subprocess.run([ADB, *a], capture_output=True, text=True)
def main():
adb("shell", "am", "force-stop", PKG)
adb("logcat", "-c")
time.sleep(0.3)
d = frida.get_usb_device(10)
pid = d.spawn([PKG])
s = d.attach(pid)
sc = s.create_script('send("empty ok " + String(Process.id));')
sc.on("message", lambda m, _d: print(m, flush=True))
sc.load()
print("resume", pid, flush=True)
d.resume(pid)
for i in range(10):
time.sleep(1)
p = adb("shell", "pidof", PKG).stdout.strip()
print("t=%d %s" % (i + 1, p or "DEAD"), flush=True)
if not p:
break
print("--- death lines ---", flush=True)
for line in adb("logcat", "-d").stdout.splitlines():
if "exited cleanly" in line or "has died" in line and "tngdigital" in line:
print(line[:240], flush=True)
if "TngRoot" in line and ("install" in line or "short-circuit" in line or "blocked" in line):
print(line[:240], flush=True)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
"""Resolve libc _exit real target and patch its SVC."""
import subprocess
import time
import frida
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
SCRIPT = r"""
"use strict";
function log(m){ send("[TNG-native] "+m); }
function findExport(mod, name) {
try {
var m = Process.getModuleByName(mod);
var a = m.findExportByName(name);
if (a) return a;
} catch (e) {}
try { return Module.getGlobalExportByName(name); } catch (e2) { return null; }
}
function resolveTrampoline(addr, name) {
// Follow simple ADRP+ADD+BR / LDR+BR patterns a few times
var cur = addr;
for (var depth = 0; depth < 5; depth++) {
var w0 = cur.readU32();
var w1 = cur.add(4).readU32();
// BR Xn: 0xD61F0000 | (Rn<<5)
if ((w1 & 0xfffffc1f) === 0xd61f0000) {
var rn = (w1 >> 5) & 0x1f;
// LDR Xn, [PC, #imm] : 0x58000000
if ((w0 & 0xff000000) === 0x58000000) {
var imm19 = (w0 >> 5) & 0x7ffff;
if (imm19 & 0x40000) imm19 -= 0x80000;
var targetPtr = cur.add(imm19 * 4);
var target = targetPtr.readPointer();
log(name + " trampoline LDR+BR -> " + target);
cur = target;
continue;
}
// ADRP Xn, page
if ((w0 & 0x9f000000) === 0x90000000) {
var rd = w0 & 0x1f;
var immhi = (w0 >> 5) & 0x7ffff;
var immlo = (w0 >> 29) & 0x3;
var imm = ((immhi << 2) | immlo) << 12;
if (imm & 0x100000000) imm = imm - 0x200000000;
var page = cur.and(ptr("0xfffffffffffff000")).add(imm);
// next might be LDR/ADD
var w2 = cur.add(8).readU32();
log(name + " ADRP page="+page+" rn="+rn+" rd="+rd+" w2="+w2.toString(16));
}
log(name + " BR X" + rn + " at " + cur + " (stop follow)");
break;
}
break;
}
return cur;
}
function patchSvcNear(addr, name, windowSize) {
var nop = [0x1f, 0x20, 0x03, 0xd5];
var n = 0;
for (var i = 0; i < windowSize; i += 4) {
try {
var p = addr.add(i);
if (p.readU32() !== 0xd4000001) continue;
Memory.protect(p, 4, "rwx");
// replace svc with: mov x0, x0; ret — or just nop and hope
// Better: movz x0, #0; ret so "exit" becomes return 0
// movz x0,#0 = 0xD2800000; ret = 0xD65F03C0
p.writeU32(0xd2800000); // movz x0, #0
if (i + 4 < windowSize) {
var p2 = addr.add(i + 4);
// only overwrite next if also svc/brk or nop pad — safer: write ret at svc place only via branch
}
// Just NOP the svc — caller may hang; use ret instead by overwriting svc with ret
p.writeU32(0xd65f03c0); // RET
n++;
log("patched SVC->RET @ " + p + " (" + name + "+" + i + ")");
} catch (e) {
log("patch fail: " + e);
}
}
return n;
}
function hookByPatchingLibcExit() {
var libc = Process.getModuleByName("libc.so");
// Scan entire libc for the classic exit_group sequence:
// mov x8, #94; mov x0, ...; svc #0 OR svc inside _exit impl
var count = 0;
var svcAddrs = [];
// Focused: exports that must lead to exit
["_exit", "exit"].forEach(function (n) {
var a = libc.findExportByName(n);
if (!a) return;
log(n + " export " + a);
// DebugSymbol / Instruction parse: find first BL/B to real impl
});
// Brute: scan libc executable for movz x8,#94 followed within 16 bytes by svc
var ranges = libc.enumerateRanges("r-x");
ranges.forEach(function (r) {
for (var off = 0; off + 8 < r.size; off += 4) {
var p = r.base.add(off);
var w;
try { w = p.readU32(); } catch (e) { return; }
// movz x8, #94 = 0xD2800BC8 ; movz w8,#94 = 0x52800BC8
// movz x8, #93 = 0xD2800BA8
if (w !== 0xd2800bc8 && w !== 0x52800bc8 && w !== 0xd2800ba8 && w !== 0x52800ba8) continue;
// look ahead for svc
for (var j = 4; j <= 24; j += 4) {
try {
if (p.add(j).readU32() === 0xd4000001) {
svcAddrs.push(p.add(j));
log("exit-seq movz@ " + p + " svc@ " + p.add(j));
}
} catch (e2) {}
}
}
});
svcAddrs.forEach(function (svc) {
try {
Memory.protect(svc, 4, "rwx");
// Replace svc with ret — turns exit into function return
svc.writeU32(0xd65f03c0);
count++;
log("SVC->RET " + svc);
} catch (e) {
log("SVC patch fail " + svc + ": " + e);
}
});
log("libc exit SVC patches=" + count);
}
function installEntryLog() {
["_exit", "exit", "abort"].forEach(function (n) {
var a = findExport("libc.so", n);
if (!a) return;
try {
Interceptor.attach(a, {
onEnter: function (args) {
log("ENTER " + n + "(" + args[0] + ")");
}
});
} catch (e) {}
});
}
// Also Interceptor.replace as backup
function installReplace() {
["_exit", "exit", "abort"].forEach(function (n) {
var a = findExport("libc.so", n);
if (!a) return;
try {
Interceptor.replace(a, new NativeCallback(function (code) {
log("REPLACED-HIT " + n + "(" + (code|0) + ")");
}, "void", ["int"]));
log("replaced " + n);
} catch (e) {
log("replace " + n + " fail: " + e);
}
});
}
log("pid=" + Process.id);
hookByPatchingLibcExit();
installReplace();
installEntryLog();
log("ready");
"""
def adb(*a):
return subprocess.run([ADB, *a], capture_output=True, text=True)
def on_msg(m, d):
print(m.get("payload", m), flush=True)
def main():
adb("shell", "am", "force-stop", PKG)
time.sleep(0.4)
device = frida.get_usb_device(10)
pid = device.spawn([PKG])
session = device.attach(pid)
script = session.create_script(SCRIPT)
script.on("message", on_msg)
script.load()
print("resume", pid, flush=True)
device.resume(pid)
for i in range(25):
time.sleep(1)
p = adb("shell", "pidof", PKG).stdout.strip()
print("t=%ds %s" % (i + 1, p or "DEAD"), flush=True)
if not p:
break
else:
print("STABLE", flush=True)
focus = adb("shell", "dumpsys", "activity", "activities").stdout
for line in focus.splitlines():
if "tngdigital" in line.lower() and any(
x in line for x in ("UserLogin", "SecurityError", "Splash", "mResumed", "topResumed")
):
print("ACT", line.strip()[:200], flush=True)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,107 @@
# -*- coding: utf-8 -*-
"""Spawn TNG, observe TigerTally fread blocking (observe-only, no behavior change)."""
import subprocess
import sys
import time
from datetime import datetime
from pathlib import Path
import frida
PKG = "my.com.tngdigital.ewallet"
HERE = Path(__file__).resolve().parent
LOGS_DIR = HERE.parent / "logs" / "frida"
LOGS_DIR.mkdir(parents=True, exist_ok=True)
SCRIPT = HERE / "trace_tng_tiger_fread.js"
LOG = LOGS_DIR / ("tng_tiger_fread_%s.log" % datetime.now().strftime("%Y%m%d_%H%M%S"))
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
HOLD_SEC = 45
def on_message(message, data):
mtype = message.get("type")
if mtype == "send":
line = message.get("payload")
elif mtype == "log":
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 adb(*args):
return subprocess.run([ADB, *args], capture_output=True, text=True)
def ensure_frida_server():
out = adb("shell", "su", "-c", "pgrep -x frida-server")
if out.stdout.strip():
print("frida-server running pid=%s" % out.stdout.strip())
return
adb("shell", "su", "-c", "pkill -9 frida-server; true")
subprocess.Popen(
[ADB, "shell", "su", "-c", "/data/local/tmp/frida-server -D"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
time.sleep(2)
out = adb("shell", "su", "-c", "pgrep -x frida-server")
if not out.stdout.strip():
raise RuntimeError("frida-server failed to start")
print("frida-server pid=%s" % out.stdout.strip())
def main():
ensure_frida_server()
adb("shell", "am", "force-stop", PKG)
time.sleep(1)
device = frida.get_usb_device(10)
source = SCRIPT.read_text(encoding="utf-8")
print("spawn %s ..." % PKG)
print("log=%s" % LOG)
pid = device.spawn([PKG])
session = device.attach(pid)
script = session.create_script(source)
script.on("message", on_message)
script.load()
print("script loaded, resume pid=%s" % pid)
device.resume(pid)
alive = 0
for i in range(HOLD_SEC):
time.sleep(1)
out = adb("shell", "pidof", PKG)
pids = out.stdout.strip()
if not pids:
print("DEAD after %ss" % (i + 1))
break
alive = i + 1
if (i + 1) % 5 == 0:
print("alive %ss pid=%s" % (alive, pids))
else:
print("STABLE %ss pid=%s" % (HOLD_SEC, adb("shell", "pidof", PKG).stdout.strip()))
# 焦点 Activity
focus = adb("shell", "dumpsys", "activity", "activities")
for line in focus.stdout.splitlines():
if "tngdigital" in line.lower() and (
"mResumedActivity" in line or "topResumedActivity" in line
or "UserLogin" in line or "SecurityError" in line or "Splash" in line
):
print("ACT: " + line.strip())
try:
session.detach()
except Exception:
pass
print("done alive=%ss log=%s" % (alive, LOG))
return 0 if alive >= 15 else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""Spawn TNG and strace for exit syscalls (needs root)."""
import subprocess
import time
import sys
ADB = r"C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
PKG = "my.com.tngdigital.ewallet"
def adb(*args, timeout=30):
return subprocess.run([ADB, *args], capture_output=True, text=True, timeout=timeout)
def main():
adb("shell", "am", "force-stop", PKG)
time.sleep(0.5)
adb("logcat", "-c")
# start app
adb(
"shell",
"monkey",
"-p",
PKG,
"-c",
"android.intent.category.LAUNCHER",
"1",
)
time.sleep(0.4)
out = adb("shell", "pidof", PKG)
pid = out.stdout.strip().split()[0] if out.stdout.strip() else ""
if not pid:
print("no pid")
return 1
print("pid", pid)
# strace briefly
p = subprocess.Popen(
[
ADB,
"shell",
"su",
"-c",
f"timeout 8 strace -f -e trace=exit,exit_group,kill,tkill,tgkill,write -p {pid} 2>&1 | head -80",
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
try:
stdout, _ = p.communicate(timeout=15)
print(stdout)
except subprocess.TimeoutExpired:
p.kill()
print(p.stdout.read() if p.stdout else "timeout")
print("alive?", adb("shell", "pidof", PKG).stdout.strip())
return 0
if __name__ == "__main__":
sys.exit(main())

View File

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

View File

@@ -0,0 +1,74 @@
/**
* Diagnostic: only LOG exit-related calls, do not block.
*/
"use strict";
function log(msg) { send("[TNG-diag] " + msg); }
function findExport(mod, name) {
try {
var m = Process.findModuleByName(mod);
if (m) { var a = m.findExportByName(name); if (a) return a; }
} catch (e) {}
try { return Module.getGlobalExportByName(name); } catch (e2) { return null; }
}
function bt(ctx) {
try {
return Thread.backtrace(ctx, Backtracer.FUZZY).map(DebugSymbol.fromAddress).slice(0, 8).join(" <- ");
} catch (e) { return "?"; }
}
["_exit", "exit", "abort", "quick_exit"].forEach(function (n) {
var a = findExport("libc.so", n);
if (!a) return;
Interceptor.attach(a, {
onEnter: function (args) {
log("CALL " + n + "(" + args[0] + ") " + bt(this.context));
}
});
log("watch " + n + " @ " + a);
});
["kill", "tgkill", "raise"].forEach(function (n) {
var a = findExport("libc.so", n);
if (!a) return;
Interceptor.attach(a, {
onEnter: function (args) {
log("CALL " + n + "(" + args[0] + "," + args[1] + ") " + bt(this.context));
}
});
});
var sys = findExport("libc.so", "syscall");
if (sys) {
Interceptor.attach(sys, {
onEnter: function (args) {
var nr = args[0].toInt32();
if (nr === 93 || nr === 94 || nr === 129 || nr === 131) {
log("CALL syscall(" + nr + ") " + bt(this.context));
}
}
});
}
// count mprotect EXEC
var mp = findExport("libc.so", "mprotect");
if (mp) {
Interceptor.attach(mp, {
onEnter: function (args) {
if (args[2].toInt32() & 4) {
log("mprotect EXEC " + args[0] + " len=" + args[1]);
}
}
});
}
log("diag ready pid=" + Process.id);
setTimeout(function () {
var n = 0;
Process.enumerateRanges("r-x").forEach(function (r) {
var file = r.file ? r.file.path : "anon";
if (file.indexOf("/system") === 0 || file.indexOf("/apex") === 0) return;
n++;
log("RX " + file + " " + r.base + " +" + r.size);
});
log("app RX ranges=" + n);
}, 800);

View File

@@ -0,0 +1,282 @@
/**
* TNG — catch Promon exit after runtime code decrypt (mmap/mprotect RX).
* Frida 17 compatible.
*/
"use strict";
function log(msg) {
send("[TNG-native] " + msg);
}
function findExport(moduleName, name) {
try {
if (moduleName) {
var m = Process.findModuleByName(moduleName);
if (m) {
var a = m.findExportByName(name);
if (a) return a;
}
}
} catch (e) {}
try {
return Module.getGlobalExportByName(name);
} catch (e2) {
return null;
}
}
var patched = {};
function looksLikeExitSetup(addr) {
for (var i = 1; i <= 12; i++) {
try {
var w = addr.sub(i * 4).readU32();
var opc = w & 0xff800000;
if (opc === 0x52800000 || opc === 0xd2800000) {
var rd = w & 0x1f;
var imm = (w >> 5) & 0xffff;
if (rd === 8 && (imm === 93 || imm === 94)) return imm;
}
// mov x8, xN then earlier load — also catch svc after mov x0, #imm (exit code)
if ((w & 0xffe0ffff) === 0xaa0003e8) return 8; // mov x8, x0.. pattern loose
} catch (e) {}
}
return 0;
}
function patchRegion(base, size, tag) {
if (size <= 0 || size > 64 * 1024 * 1024) return;
var key = base + ":" + size;
if (patched[key]) return;
patched[key] = true;
var nop = [0x1f, 0x20, 0x03, 0xd5];
var n = 0;
var totalSvc = 0;
try {
// only scan 4-byte aligned by walking manually for reliability
var end = base.add(size - 4);
for (var p = base; p.compare(end) <= 0; p = p.add(4)) {
var w;
try {
w = p.readU32();
} catch (e) {
break;
}
if (w !== 0xd4000001) continue; // svc #0
totalSvc++;
var kind = looksLikeExitSetup(p);
if (!kind) continue;
try {
Memory.protect(p, 4, "rwx");
p.writeByteArray(nop);
n++;
log("patched exit SVC#" + kind + " @ " + p + " [" + tag + "]");
} catch (e2) {
log("patch err " + p + ": " + e2);
}
}
if (totalSvc > 0) {
log("region " + tag + " svc#0=" + totalSvc + " patched=" + n + " size=" + size);
}
} catch (e) {
log("scan err " + tag + ": " + e);
}
}
function scanAllExecutable(tag) {
Process.enumerateRanges("r-x").forEach(function (r) {
var file = r.file ? r.file.path : "anon";
// skip system libs except if anonymous / app
if (file.indexOf("/system/") === 0 || file.indexOf("/apex/") === 0) return;
if (file.indexOf("frida") >= 0) return;
patchRegion(r.base, r.size, tag + ":" + file);
});
}
function installLibcExitHooks() {
function blockExit(name, address) {
try {
Interceptor.replace(
address,
new NativeCallback(
function (code) {
log("BLOCKED " + name + "(" + (code | 0) + ")");
},
"void",
["int"]
)
);
log("replaced " + name);
} catch (e) {
Interceptor.attach(address, {
onEnter: function (args) {
log("BLOCKED(attach) " + name + "(" + args[0].toInt32() + ")");
while (true) Thread.sleep(60);
},
});
log("attached " + name);
}
}
["_exit", "exit", "abort", "quick_exit"].forEach(function (n) {
var a = findExport("libc.so", n);
if (a) blockExit(n, a);
});
["kill", "tgkill", "pthread_kill", "raise"].forEach(function (n) {
var a = findExport("libc.so", n);
if (!a) return;
Interceptor.attach(a, {
onEnter: function (args) {
var pid = args[0].toInt32();
var sig = args[1].toInt32();
if ((pid === Process.id || pid === 0 || pid === -1) &&
(sig === 9 || sig === 15 || sig === 6 || sig === 5)) {
log("BLOCKED " + n + " sig=" + sig);
args[1] = ptr(0);
}
},
});
});
var sys = findExport("libc.so", "syscall");
if (sys) {
Interceptor.attach(sys, {
onEnter: function (args) {
var nr = args[0].toInt32();
if (nr === 93 || nr === 94) {
log("BLOCKED syscall exit " + nr);
args[0] = ptr(-1);
}
},
});
}
log("libc hooks OK");
}
function installMprotectWatcher() {
var mprotect = findExport("libc.so", "mprotect");
var mmap = findExport("libc.so", "mmap");
if (mprotect) {
Interceptor.attach(mprotect, {
onEnter: function (args) {
this.addr = args[0];
this.len = args[1].toInt32();
this.prot = args[2].toInt32();
},
onLeave: function () {
// PROT_EXEC = 4
if (this.prot & 4) {
log("mprotect+EXEC " + this.addr + " len=" + this.len);
patchRegion(this.addr, this.len, "mprotect");
}
},
});
}
if (mmap) {
Interceptor.attach(mmap, {
onEnter: function (args) {
this.len = args[1].toInt32();
this.prot = args[2].toInt32();
},
onLeave: function (retval) {
if ((this.prot & 4) && !retval.isNull()) {
log("mmap+EXEC " + retval + " len=" + this.len);
patchRegion(retval, this.len, "mmap");
}
},
});
}
log("mprotect/mmap watchers OK");
}
function installMapsHide() {
var markers = ["frida", "gadget", "xposed", "lsposed", "vector", "zygisk", "magisk", "liblspd"];
var tracked = {};
function hide(line) {
var l = (line || "").toLowerCase();
for (var i = 0; i < markers.length; i++) if (l.indexOf(markers[i]) >= 0) return true;
return false;
}
function filter(buf, len) {
try {
var text = buf.readUtf8String(len);
if (!text) return len;
var out = text.split("\n").filter(function (x) { return !hide(x); }).join("\n");
var bytes = Memory.allocUtf8String(out);
var n = Math.min(len, out.length);
Memory.copy(buf, bytes, n);
return n;
} catch (e) {
return len;
}
}
var openat = findExport("libc.so", "openat");
var readFn = findExport("libc.so", "read");
if (openat) {
Interceptor.attach(openat, {
onEnter: function (args) {
this.path = args[1].isNull() ? null : args[1].readCString();
},
onLeave: function (retval) {
var fd = retval.toInt32();
if (fd >= 0 && this.path && this.path.indexOf("maps") >= 0) tracked[fd] = 1;
},
});
}
if (readFn) {
Interceptor.attach(readFn, {
onEnter: function (args) {
this.fd = args[0].toInt32();
this.buf = args[1];
},
onLeave: function (retval) {
var n = retval.toInt32();
if (n > 0 && tracked[this.fd]) retval.replace(ptr(filter(this.buf, n)));
},
});
}
log("maps hide OK");
}
function installJavaGuards() {
if (typeof Java === "undefined") {
setTimeout(installJavaGuards, 500);
return;
}
Java.perform(function () {
try {
Java.use("java.lang.System").exit.implementation = function (c) {
log("Java System.exit(" + c + ") blocked");
};
} catch (e) {}
try {
var R = Java.use("java.lang.Runtime");
R.exit.overload("int").implementation = function (c) {
log("Java Runtime.exit(" + c + ") blocked");
};
} catch (e) {}
try {
var P = Java.use("android.os.Process");
P.killProcess.implementation = function (pid) {
if (pid === P.myPid()) {
log("Java killProcess(self) blocked");
return;
}
return this.killProcess(pid);
};
} catch (e) {}
log("Java guards OK");
});
}
log("load pid=" + Process.id);
installLibcExitHooks();
installMapsHide();
installMprotectWatcher();
scanAllExecutable("boot");
setInterval(function () {
scanAllExecutable("tick");
}, 1000);
installJavaGuards();
log("ready");

View File

@@ -0,0 +1,147 @@
"use strict";
/*
* TNG eWallet — 定位 TigerTally(libtiger_tally.so) 启动期 fread 阻塞。观察不改行为。
*
* 背景(ANR 栈): CaptchaInitializer → TigerTallyAPI.init → t.B.genericNt1(native)
* → libtiger_tally.so (mNYjyzyN23) → fread → __sread → read 永远读不到数据
*/
const TIGER_SO = "libtiger_tally.so";
const LIBC = Process.getModuleByName("libc.so");
const readlink = new NativeFunction(
LIBC.findExportByName("readlink"), "long", ["pointer", "pointer", "ulong"]);
/* ---- Tiger 模块范围(热路径缓存,每 2s 刷新一次) ---- */
let tigerMod = null;
function refreshTiger() {
const m = Process.findModuleByName(TIGER_SO);
if (m) tigerMod = m;
return !!tigerMod;
}
function inTiger(addr) {
if (!addr) return false;
if (!tigerMod) return false;
return addr.compare(tigerMod.base) >= 0 && addr.compare(tigerMod.base.add(tigerMod.size)) < 0;
}
setInterval(() => { refreshTiger(); }, 2000);
function resolveFd(fd) {
try {
const link = Memory.allocUtf8String(`/proc/self/fd/${fd}`);
const out = Memory.alloc(256);
const n = readlink(link, out, 256);
if (n > 0) return out.readUtf8String(Math.min(n, 255));
} catch (e) { /* ignore */ }
return "?";
}
function fdKind(fd) {
const p = resolveFd(fd);
if (p.indexOf("socket:") === 0) return "SOCKET " + p;
if (p.indexOf("pipe:") === 0) return "PIPE " + p;
if (p.indexOf("anon_inode:") === 0) return "ANON " + p;
return p;
}
function threadName() {
try { return Process.getCurrentThreadName(); } catch (e) { return "?"; }
}
function fmtAddr(a) { return a ? a.toString(16) : "?"; }
const stats = {}; // tid -> info
function bump(fd, kind, ret) {
const tid = Process.getCurrentThreadId();
let s = stats[tid];
if (!s) { s = { name: threadName(), reads: 0, lastFd: fd, lastFdKind: kind, lastRet: ret }; stats[tid] = s; }
s.name = threadName();
s.reads++;
s.lastFd = fd;
s.lastFdKind = kind;
s.lastRet = ret;
}
/* ---- fread: FILE* 第4参数; bionic __sFILE._file 偏移约 18 ---- */
Interceptor.attach(LIBC.findExportByName("fread"), {
onEnter(args) {
const caller = this.returnAddress;
if (!inTiger(caller)) return;
const fp = args[3];
let fd = -1;
for (const off of [18, 16, 24, 20]) {
try { const v = fp.add(off).readU16(); if (v > 0 && v < 4096) { fd = v; break; } }
catch (e) { /* try next */ }
}
const kind = fd > 0 ? fdKind(fd) : "?";
bump(fd, kind, "pending");
console.log(`[FREAD] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
},
onLeave(ret) {
if (!inTiger(this.returnAddress)) return;
bump(-1, "", ret.toInt32());
console.log(`[FREAD-LEAVE] tid=${Process.getCurrentThreadId()} ret=${ret.toInt32()}`);
}
});
/* ---- read: 只观察调用者位于 libtiger_tally.so 的 ---- */
Interceptor.attach(LIBC.findExportByName("read"), {
onEnter(args) {
const caller = this.returnAddress;
if (!inTiger(caller)) return;
const fd = args[0].toInt32();
const kind = fdKind(fd);
bump(fd, kind, "?");
console.log(`[READ] tid=${Process.getCurrentThreadId()} "${threadName()}" caller=${fmtAddr(caller)} fd=${fd} kind=${kind}`);
},
onLeave(ret) {
if (!inTiger(this.returnAddress)) return;
const tid = Process.getCurrentThreadId();
const s = stats[tid];
const fd = s ? s.lastFd : -1;
const r = ret.toInt32();
if (s) s.lastRet = r;
if (r < 0) console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r} (blocked/error)`);
else if (r === 0) console.log(`[READ-EOF] tid=${tid} fd=${fd} ret=0 (EOF/closed)`);
else console.log(`[READ-RET] tid=${tid} fd=${fd} ret=${r}`);
}
});
/* ---- 周期 dump ---- */
function dumpThreads() {
try {
const dir = new File("/proc/self/task", "r");
const entries = dir.list();
dir.close();
let relevant = [];
for (const e of entries) {
let nm = "?";
try { const nf = new File(`/proc/self/task/${e}/comm`, "r"); nm = nf.readString().trim(); nf.close(); } catch (err) {}
const s = stats[e] || null;
const lower = nm.toLowerCase();
if (lower.indexOf("location") >= 0 || lower.indexOf("tally") >= 0 || s) {
let info = `tid=${e} "${nm}"`;
if (s) info += ` tigerReads=${s.reads} lastFd=${s.lastFd} kind=${s.lastFdKind} lastRet=${s.lastRet}`;
relevant.push(info);
}
}
console.log(`[DUMP] tiger-fread threads: ${relevant.length ? relevant.join(" | ") : "(none)"}`);
} catch (e) {
console.log(`[DUMP] failed: ${e}`);
}
}
setInterval(() => {
dumpThreads();
try {
const dir = new File("/proc/self/fd", "r");
const fds = dir.list();
dir.close();
let pipes = [], socks = [];
for (const f of fds) {
const kind = fdKind(parseInt(f, 10));
if (kind.indexOf("PIPE") === 0) pipes.push(f + ":" + kind.split(" ").slice(1).join(" "));
if (kind.indexOf("SOCKET") === 0) socks.push(f + ":" + kind.split(" ").slice(1).join(" "));
}
if (pipes.length) console.log(`[DUMP-FD] pipes: ${pipes.join(" | ")}`);
if (socks.length) console.log(`[DUMP-FD] sockets: ${socks.join(" | ")}`);
} catch (e) { /* ignore */ }
}, 3000);
console.log("[TIGER-FREAD] armed (observe-only)");