diff --git a/magisk-modules/tng_exit_guard/jni/main.cpp b/magisk-modules/tng_exit_guard/jni/main.cpp index 78f71c9..8ad34ec 100644 --- a/magisk-modules/tng_exit_guard/jni/main.cpp +++ b/magisk-modules/tng_exit_guard/jni/main.cpp @@ -42,8 +42,14 @@ static std::atomic g_stack_chk{0}; static std::atomic g_promon_segv{0}; static std::atomic g_promon_start{0}; static std::atomic g_promon_end{0}; -/* 隔离进程 :goacqowmmt 会循环 SEGV;cap=16 打满后 freeze 反拖累主进程 */ -static constexpr int kMaxPromonSegvSkip = 0; /* 0 = unlimited LR-return skip */ +static std::atomic g_main_tid{0}; +static std::atomic g_abrt_swallow{0}; +static std::atomic g_abrt_last_tid{0}; +static std::atomic g_abrt_last_pc{0}; +static std::atomic g_abrt_streak{0}; +/* 隔离进程 :goacqowmmt 会循环 SEGV;主进程 worker 也狂刷。cap 后 freeze 该线程 */ +static constexpr int kMaxPromonSegvSkip = 200; +static constexpr int kMaxAbrtStreak = 3; static std::atomic g_soft_sig_logged{0}; static void freeze_forever() { @@ -119,18 +125,30 @@ static void promon_segv_handler(int sig, siginfo_t *info, void *ctx) { LOGI("promon SIGSEGV tid=%d n=%d — cap hit, freeze", (int)gettid(), n); freeze_forever(); } - signal(SIGSEGV, SIG_DFL); - raise(SIGSEGV); + /* 1.9.10:Login 后出现 Promon so 外 SEGV → 旧逻辑 re-raise 直接闪退 */ + { + int n = ++g_promon_segv; + pid_t tid = gettid(); + if (n <= 5 || n % 50 == 0) { + LOGI("non-promon SIGSEGV tid=%d pc=%lx lr=%lx n=%d — pc+4", + (int)tid, (unsigned long)pc, (unsigned long)lr, n); + } + if (n > 200 && tid != g_main_tid.load()) { + LOGI("non-promon SEGV storm tid=%d — freeze", (int)tid); + freeze_forever(); + } + if (pc != 0) { + uc->uc_mcontext.pc = pc + 4; + return; + } + } + freeze_forever(); } -static std::atomic g_main_tid{0}; -static std::atomic g_abrt_swallow{0}; -static std::atomic g_abrt_last_tid{0}; -static std::atomic g_abrt_last_pc{0}; -static std::atomic g_abrt_streak{0}; -static constexpr int kMaxAbrtStreak = 3; - -/** ABRT/TRAP:pc+4 或远距 LR;同 tid+pc 连触发则 freeze,避免 LR 自旋死循环。 */ +/** + * ABRT/TRAP:一律 pc+4。跳远距 LR 会弄坏主线程 Looper(闪退观感)。 + * 工作线程同 PC 连 abort 超限 → freeze;主线程始终 pc+4。 + */ static void fatal_skip_handler(int sig, siginfo_t *info, void *ctx) { (void)info; ucontext_t *uc = reinterpret_cast(ctx); @@ -148,39 +166,26 @@ static void fatal_skip_handler(int sig, siginfo_t *info, void *ctx) { g_abrt_last_pc.store(pc); g_abrt_streak.store(1); } - if (streak > kMaxAbrtStreak) { - if (tid == g_main_tid.load()) { - LOGI("ABRT main-thread streak cap tid=%d pc=%lx — pc+4 bail", (int)tid, - (unsigned long)pc); - uc->uc_mcontext.pc = pc + 4; - g_abrt_streak.store(0); - return; - } - LOGI("ABRT streak cap tid=%d pc=%lx n=%d — freeze thread", (int)tid, + if (streak > kMaxAbrtStreak && tid != g_main_tid.load()) { + LOGI("ABRT streak cap tid=%d pc=%lx n=%d — freeze worker", (int)tid, (unsigned long)pc, streak); freeze_forever(); } - uintptr_t delta = (pc > lr) ? (pc - lr) : (lr - pc); - uintptr_t target = pc + 4; - /* lr 距 pc 很近时仍在 abort/epilogue 内,跳 LR 会 instant 再 ABRT */ - if (lr != 0 && delta > 64) { - target = lr; - } int n = ++g_abrt_swallow; - if (n <= 3 || n % 100 == 0) { - LOGI("ABRT skip tid=%d pc=%lx lr=%lx streak=%d -> %lx", (int)tid, - (unsigned long)pc, (unsigned long)lr, streak, - (unsigned long)target); + if (n <= 5 || n % 50 == 0) { + LOGI("ABRT pc+4 tid=%d pc=%lx lr=%lx streak=%d", (int)tid, + (unsigned long)pc, (unsigned long)lr, streak); } - uc->uc_mcontext.pc = target; + uc->uc_mcontext.pc = pc + 4; return; } if (sig == SIGTRAP) { - uintptr_t target = pc != 0 ? pc + 4 : lr; - LOGI("TRAP skip tid=%d pc=%lx -> %lx", (int)tid, (unsigned long)pc, - (unsigned long)target); - uc->uc_mcontext.pc = target; + int n = ++g_abrt_swallow; + if (n <= 5 || n % 50 == 0) { + LOGI("TRAP pc+4 tid=%d pc=%lx", (int)tid, (unsigned long)pc); + } + uc->uc_mcontext.pc = pc != 0 ? pc + 4 : lr; return; } #endif @@ -194,7 +199,7 @@ static void install_fatal_skip_handlers() { sigemptyset(&sa.sa_mask); sigaction(SIGABRT, &sa, nullptr); sigaction(SIGTRAP, &sa, nullptr); - LOGI("fatal skip handlers (ABRT pc+4/streak-freeze + TRAP pc+4)"); + LOGI("fatal skip handlers (ABRT/TRAP always pc+4)"); } static void install_promon_segv_handler() { @@ -220,7 +225,7 @@ static void install_soft_signals() { sigaction(SIGABRT, &sa, nullptr); sigaction(SIGTRAP, &sa, nullptr); if (g_soft_sig_logged.fetch_add(1) == 0) { - LOGI("soft signals (ABRT streak-freeze + TRAP pc+4)"); + LOGI("soft signals (ABRT/TRAP always pc+4)"); } } @@ -386,7 +391,7 @@ static void *phase_thread(void *) { static void install_all(zygisk::Api *api) { g_main_tid.store(gettid()); - LOGI("install pid=%d main_tid=%d (PLT+ABRT-streak-freeze+TRAP+pc==lr-SEGV+exit_group@400ms)", + LOGI("install pid=%d main_tid=%d (PLT+ABRT-pc+4+SEGV-all-skip+exit_group@400ms)", getpid(), (int)g_main_tid.load()); install_fatal_skip_handlers(); install_soft_signals(); diff --git a/magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so b/magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so index e58a653..af139d9 100644 Binary files a/magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so and b/magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so differ diff --git a/reverse/scripts/_scan_tng_vhv.py b/reverse/scripts/_scan_tng_vhv.py new file mode 100644 index 0000000..fae195c --- /dev/null +++ b/reverse/scripts/_scan_tng_vhv.py @@ -0,0 +1,14 @@ +import zipfile +import re + +z = zipfile.ZipFile(r"reverse/dumps/tng_1.9.10_base.apk") +names = [n for n in z.namelist() if n.endswith(".dex")] +pat = re.compile(rb"Lvhvlnqgy/([^;\s]{1,80});") +found = set() +for n in names: + data = z.read(n) + for m in pat.findall(data): + found.add(m.decode("ascii", errors="ignore")) +print("vhvlnqgy classes", len(found)) +for c in sorted(found): + print(f"vhvlnqgy.{c.replace('/', '.')}") diff --git a/reverse/scripts/_scan_tng_vhv_bl.py b/reverse/scripts/_scan_tng_vhv_bl.py new file mode 100644 index 0000000..43ae0cb --- /dev/null +++ b/reverse/scripts/_scan_tng_vhv_bl.py @@ -0,0 +1,24 @@ +"""Dump vhvlnqgy bl/R/bd method refs from TNG 1.9.10 dex.""" +import re +import zipfile + +APK = r"reverse/dumps/tng_1.9.10_base.apk" +with zipfile.ZipFile(APK) as z: + data = b"".join(z.read(n) for n in z.namelist() if n.endswith(".dex")) + +for cls in ["Lvhvlnqgy/bl;", "Lvhvlnqgy/R;", "Lvhvlnqgy/bd;", "Lvhvlnqgy/w;", "Lvhvlnqgy/W;", "Lvhvlnqgy/a;"]: + print("===", cls, "===") + refs = sorted(set(re.findall(cls.encode() + rb"->[^\x00]{1,80}", data))) + for r in refs[:30]: + print(r.decode("ascii", "ignore")) + print() + +print("=== R callers (who invokes R.a/R.b) ===") +for m in sorted(set(re.findall(rb"Lvhvlnqgy/[^;]+;->[a-zA-Z]+[^\x00]{0,40}Lvhvlnqgy/R;", data))): + print(m.decode("ascii", "ignore")) + +print("\n=== bd throw sites ===") +for m in sorted(set(re.findall(rb"[^\x00]{0,40}Lvhvlnqgy/bd;", data))): + s = m.decode("ascii", "ignore") + if "vhvlnqgy" in s: + print(s) diff --git a/reverse/scripts/parse_ui_dump.py b/reverse/scripts/parse_ui_dump.py new file mode 100644 index 0000000..18ab60a --- /dev/null +++ b/reverse/scripts/parse_ui_dump.py @@ -0,0 +1,20 @@ +import sys +import xml.etree.ElementTree as ET + +path = sys.argv[1] if len(sys.argv) > 1 else "reverse/dumps/ui_login.xml" +root = ET.parse(path).getroot() +keywords = [ + "register", "sign", "login", "mobile", "phone", "continue", "next", + "create", "otp", "skip", "started", "email", "password", "pin", +] +for node in root.iter("node"): + text = node.get("text", "") + desc = node.get("content-desc", "") + clickable = node.get("clickable", "") + bounds = node.get("bounds", "") + label = (text or desc).strip() + if not label and clickable != "true": + continue + hay = (text + " " + desc).lower() + if clickable == "true" or any(k in hay for k in keywords): + print(f"{label!r} bounds={bounds} clickable={clickable}") diff --git a/reverse/scripts/parse_ui_dump_all.py b/reverse/scripts/parse_ui_dump_all.py new file mode 100644 index 0000000..2b73907 --- /dev/null +++ b/reverse/scripts/parse_ui_dump_all.py @@ -0,0 +1,13 @@ +import sys +import xml.etree.ElementTree as ET + +path = sys.argv[1] +root = ET.parse(path).getroot() +for node in root.iter("node"): + text = (node.get("text") or "").strip() + desc = (node.get("content-desc") or "").strip() + rid = node.get("resource-id") or "" + bounds = node.get("bounds") or "" + clickable = node.get("clickable") or "false" + if text or desc or "login" in rid.lower() or "register" in rid.lower(): + print(f"text={text!r} desc={desc!r} id={rid} bounds={bounds} click={clickable}") diff --git a/reverse/scripts/test_tng_register_flow.py b/reverse/scripts/test_tng_register_flow.py new file mode 100644 index 0000000..410b830 --- /dev/null +++ b/reverse/scripts/test_tng_register_flow.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +"""Automate TNG login/register tap test and report crashes.""" +import re +import subprocess +import sys +import time + +PKG = "my.com.tngdigital.ewallet" +TAGS = re.compile( + r"FATAL EXCEPTION|AndroidRuntime.*Process: " + PKG + + r"|has died|exited due to signal|vhvlnqgy\.bd|blocked killProcess|blocked System\.exit" + + r"|UserRegistration|UserOtp|UserLogin|Displayed.*tngdigital|ACT on(Create|Resume)" + + r"|TngExitGuard.*ABRT|TngRoot hooked 6 vhvlnqgy\.R", + re.I, +) + +TAPS = [ + ("register_continue", 540, 959, 8), + ("maybe_otp_continue", 540, 959, 6), + ("back_to_login", None, None, 2), + ("login_tab", 540, 1168, 5), +] + + +def adb(*args, timeout=30): + cmd = ["adb"] + list(args) + r = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout) + out = (r.stdout or "") + (r.stderr or "") + return r.returncode, out.strip() + + +def pid_alive(): + code, out = adb("shell", "pidof", PKG) + return code == 0 and out.strip().split() + + +def top_activity(): + code, out = adb("shell", "dumpsys", "activity", "activities") + if code != 0: + return "?" + for line in out.splitlines(): + if "topResumedActivity=" in line: + m = re.search(r"/([^/]+)$", line.strip()) + if m: + return m.group(1).rstrip("}") + return line.strip() + return "?" + + +def logcat_since(start): + code, out = adb("shell", "logcat", "-d", "-t", start) + hits = [] + if code == 0: + for line in out.splitlines(): + if TAGS.search(line): + hits.append(line) + return hits + + +def main(): + adb("logcat", "-c") + adb("shell", "am", "force-stop", PKG) + time.sleep(1) + adb("shell", "am", "start", "-n", f"{PKG}/.ui.SplashActivity") + time.sleep(12) + print("=== after cold start ===") + print("pids:", pid_alive()) + print("activity:", top_activity()) + + # dismiss country picker if open + adb("shell", "input", "keyevent", "KEYCODE_BACK") + time.sleep(1) + + results = [] + for name, x, y, wait_s in TAPS: + if x is not None: + adb("shell", "input", "tap", str(x), str(y)) + print(f"\n=== tap {name} ({x},{y}) ===") + else: + adb("shell", "input", "keyevent", "KEYCODE_BACK") + print(f"\n=== {name} ===") + time.sleep(wait_s) + pids = pid_alive() + act = top_activity() + crash_buf = adb("shell", "logcat", "-d", "-b", "crash", "-t", "50")[1] + fatal = [l for l in crash_buf.splitlines() if PKG in l or "vhvlnqgy" in l] + results.append((name, pids, act, fatal)) + print("pids:", pids or "DEAD") + print("activity:", act) + if fatal: + print("CRASH:", fatal[-3:]) + + print("\n=== summary ===") + ok = True + for name, pids, act, fatal in results: + status = "OK" if pids and not fatal else "FAIL" + if status == "FAIL": + ok = False + print(f"{status} {name}: pids={pids} activity={act} crash_lines={len(fatal)}") + + hits = logcat_since("2000") + print(f"\n=== key log lines ({len(hits)}) ===") + for line in hits[-40:]: + print(line) + + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/xposed-module/src/main/java/com/miraclegarden/smsmessage/xposed/hook/TngRootBypassHook.java b/xposed-module/src/main/java/com/miraclegarden/smsmessage/xposed/hook/TngRootBypassHook.java index 3efbf55..652d5bd 100644 --- a/xposed-module/src/main/java/com/miraclegarden/smsmessage/xposed/hook/TngRootBypassHook.java +++ b/xposed-module/src/main/java/com/miraclegarden/smsmessage/xposed/hook/TngRootBypassHook.java @@ -36,6 +36,9 @@ public final class TngRootBypassHook { public static final String PACKAGE = "my.com.tngdigital.ewallet"; private static final String TAG = "notiMessageHook/TngRoot"; + /** Promon 混淆包名:1.9.10 为 vhvlnqgy,旧版为 xwwqazamx。 */ + private static final String[] PROMON_PKG_PREFIXES = {"vhvlnqgy", "xwwqazamx"}; + private static final String SECURITY_ERROR_ACTIVITY = "my.com.tngdigital.common.security.ui.SecurityErrorActivity"; @@ -604,12 +607,17 @@ public final class TngRootBypassHook { /** Promon 隔离 Service;打点确认 :goacqowmmt 进程 hook 已注入。 */ private static void hookPromonApService(XC_LoadPackage.LoadPackageParam lpparam) { + Class svc = findPromonClass(lpparam.classLoader, "ap"); + if (svc == null) { + XposedBridge.log(TAG + " Promon ap Service not found"); + return; + } try { - Class svc = XposedHelpers.findClass("xwwqazamx.ap", lpparam.classLoader); + final String svcName = svc.getName(); XposedHelpers.findAndHookMethod(svc, "onCreate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) { - XposedBridge.log(TAG + " xwwqazamx.ap onCreate pid=" + Process.myPid() + XposedBridge.log(TAG + " " + svcName + " onCreate pid=" + Process.myPid() + " proc=" + getProcessName()); } }); @@ -620,14 +628,14 @@ public final class TngRootBypassHook { XposedBridge.hookMethod(method, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { - XposedBridge.log(TAG + " xwwqazamx.ap onStartCommand pid=" + XposedBridge.log(TAG + " " + svcName + " onStartCommand pid=" + Process.myPid()); } }); } - XposedBridge.log(TAG + " hooked xwwqazamx.ap Service"); + XposedBridge.log(TAG + " hooked " + svcName + " Service"); } catch (Throwable t) { - XposedBridge.log(TAG + " xwwqazamx.ap hook failed: " + t.getMessage()); + XposedBridge.log(TAG + " Promon ap Service hook failed: " + t.getMessage()); } } @@ -746,10 +754,14 @@ public final class TngRootBypassHook { return false; } - /** Promon USB 广播 xwwqazamx.N 跑在主线程,复进时拖死 Looper。 */ + /** Promon USB 广播 N 跑在主线程,复进时拖死 Looper。 */ private static void hookPromonBroadcastReceiver(XC_LoadPackage.LoadPackageParam lpparam) { + Class clazz = findPromonClass(lpparam.classLoader, "N"); + if (clazz == null) { + XposedBridge.log(TAG + " Promon N receiver not found"); + return; + } try { - Class clazz = XposedHelpers.findClass("xwwqazamx.N", lpparam.classLoader); int hooked = 0; for (Method method : clazz.getDeclaredMethods()) { if (!"onReceive".equals(method.getName())) { @@ -764,10 +776,10 @@ public final class TngRootBypassHook { hooked++; } if (hooked > 0) { - XposedBridge.log(TAG + " hooked xwwqazamx.N onReceive x" + hooked); + XposedBridge.log(TAG + " hooked " + clazz.getName() + " onReceive x" + hooked); } } catch (Throwable t) { - XposedBridge.log(TAG + " xwwqazamx.N hook failed: " + t.getMessage()); + XposedBridge.log(TAG + " Promon N hook failed: " + t.getMessage()); } } @@ -1179,11 +1191,12 @@ public final class TngRootBypassHook { || lower.contains("emulator") || lower.contains("malware"); } - /** 阻止 Promon 混淆层抛出 W:16 并触发浏览器 fallback。 */ + /** 阻止 Promon 混淆层抛出 W/bd:16 并触发浏览器 fallback。 */ private static void hookPromonNativeGuard(XC_LoadPackage.LoadPackageParam lpparam) { hookPromonBlSwallowExceptions(lpparam); - hookPromonExceptionClass(lpparam, "xwwqazamx.W"); - hookPromonExceptionClass(lpparam, "xwwqazamx.A"); + for (String simple : new String[]{"W", "A", "bd"}) { + hookPromonExceptionClass(lpparam, simple); + } hookPromonRunnable(lpparam); } @@ -1191,14 +1204,18 @@ public final class TngRootBypassHook { * Promon lifecycle:只吞异常,不 short-circuit——全拦会拖死 Splash→Login。 */ private static void hookPromonLifecycle(XC_LoadPackage.LoadPackageParam lpparam) { + Class lifecycleClass = findPromonClass(lpparam.classLoader, "w"); + if (lifecycleClass == null) { + XposedBridge.log(TAG + " Promon w lifecycle not found"); + return; + } + Class promonExc = findPromonClass(lpparam.classLoader, "W"); + if (promonExc == null) { + promonExc = findPromonClass(lpparam.classLoader, "bd"); + } + final Class promonExcFinal = promonExc; + final String lifecycleName = lifecycleClass.getName(); try { - Class lifecycleClass = XposedHelpers.findClass("xwwqazamx.w", lpparam.classLoader); - Class promonExc = null; - try { - promonExc = XposedHelpers.findClass("xwwqazamx.W", lpparam.classLoader); - } catch (Throwable ignored) { - } - final Class promonExcFinal = promonExc; int hooked = 0; for (Method method : lifecycleClass.getDeclaredMethods()) { String name = method.getName(); @@ -1214,7 +1231,7 @@ public final class TngRootBypassHook { Throwable t = param.getThrowable(); if (isPromonThrowable(t, promonExcFinal)) { XposedBridge.log(TAG + " swallowed " + t.getClass().getSimpleName() - + " in xwwqazamx.w#" + method.getName()); + + " in " + lifecycleName + "#" + method.getName()); param.setThrowable(null); } } @@ -1223,10 +1240,10 @@ public final class TngRootBypassHook { } if (hooked > 0) { XposedBridge.log(TAG + " hooked " + hooked - + " xwwqazamx.w lifecycle method(s) (afterHook only)"); + + " " + lifecycleName + " lifecycle method(s) (afterHook only)"); } } catch (Throwable t) { - XposedBridge.log(TAG + " xwwqazamx.w lifecycle hook failed: " + t.getMessage()); + XposedBridge.log(TAG + " Promon w lifecycle hook failed: " + t.getMessage()); } } @@ -1256,15 +1273,24 @@ public final class TngRootBypassHook { } /** - * bl 全方法 short-circuit:a/b 之外的方法仍会跑 native,~40s 后 stack_chk/SEGV。 - * xwwqazamx.a.run 是 bl#b 后台 Runnable,必须 beforeHook 直接 return。 + * bl/R 全方法 short-circuit:a/b 之外的方法仍会跑 native,~40s 后 stack_chk/SEGV。 + * a.run 是 bl#b 后台 Runnable,必须 beforeHook 直接 return。 */ private static void hookPromonBlSwallowExceptions(XC_LoadPackage.LoadPackageParam lpparam) { + for (String simple : new String[]{"bl", "R"}) { + hookPromonClassShortCircuit(lpparam, simple); + } + } + + private static void hookPromonClassShortCircuit( + XC_LoadPackage.LoadPackageParam lpparam, String simpleName) { + Class clazz = findPromonClass(lpparam.classLoader, simpleName); + if (clazz == null) { + return; + } try { - Class blClass = XposedHelpers.findClass("xwwqazamx.bl", lpparam.classLoader); int hooked = 0; - for (Method method : blClass.getDeclaredMethods()) { - String name = method.getName(); + for (Method method : clazz.getDeclaredMethods()) { XposedBridge.hookMethod(method, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { @@ -1274,10 +1300,12 @@ public final class TngRootBypassHook { hooked++; } if (hooked > 0) { - XposedBridge.log(TAG + " hooked " + hooked + " bl method(s), all short-circuit"); + XposedBridge.log(TAG + " hooked " + hooked + " " + clazz.getName() + + " method(s), all short-circuit"); } } catch (Throwable t) { - XposedBridge.log(TAG + " xwwqazamx.bl hook failed: " + t.getMessage()); + XposedBridge.log(TAG + " " + clazz.getName() + " short-circuit failed: " + + t.getMessage()); } } @@ -1294,24 +1322,32 @@ public final class TngRootBypassHook { /** Promon 后台 Runnable(bl#b 检测线程),beforeHook 直接 noop,禁止跑 native。 */ private static void hookPromonRunnable(XC_LoadPackage.LoadPackageParam lpparam) { + Class runnableClass = findPromonClass(lpparam.classLoader, "a"); + if (runnableClass == null) { + XposedBridge.log(TAG + " Promon a runnable not found"); + return; + } try { - Class runnableClass = XposedHelpers.findClass("xwwqazamx.a", lpparam.classLoader); XposedHelpers.findAndHookMethod(runnableClass, "run", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) { param.setResult(null); } }); - XposedBridge.log(TAG + " hooked xwwqazamx.a.run (short-circuit)"); + XposedBridge.log(TAG + " hooked " + runnableClass.getName() + ".run (short-circuit)"); } catch (Throwable t) { - XposedBridge.log(TAG + " xwwqazamx.a.run hook failed: " + t.getMessage()); + XposedBridge.log(TAG + " Promon a.run hook failed: " + t.getMessage()); } } private static void hookPromonExceptionClass( - XC_LoadPackage.LoadPackageParam lpparam, String className) { + XC_LoadPackage.LoadPackageParam lpparam, String simpleName) { + Class promonExc = findPromonClass(lpparam.classLoader, simpleName); + if (promonExc == null) { + return; + } + final String className = promonExc.getName(); try { - Class promonExc = XposedHelpers.findClass(className, lpparam.classLoader); for (Method method : promonExc.getDeclaredMethods()) { XposedBridge.hookMethod(method, new XC_MethodHook() { @Override @@ -1332,6 +1368,7 @@ public final class TngRootBypassHook { } catch (Throwable ignored) { // constructor overload may differ } + XposedBridge.log(TAG + " hooked Promon exception " + className); } catch (Throwable t) { XposedBridge.log(TAG + " " + className + " hook failed: " + t.getMessage()); } @@ -1355,16 +1392,38 @@ public final class TngRootBypassHook { if (className == null) { return false; } - if (!className.startsWith("xwwqazamx.")) { + if (!isPromonPackageClass(className)) { return false; } - // W/A 等单字母 Promon 异常;排除 bl/w/bg 等功能类 + // W/A/bd 等 Promon 异常;排除 bl/w/bg/R 等功能类 int dot = className.lastIndexOf('.'); if (dot < 0) { return false; } String simple = className.substring(dot + 1); - return simple.length() <= 2; + if ("bd".equals(simple)) { + return true; + } + return simple.length() == 1; + } + + private static boolean isPromonPackageClass(String className) { + for (String prefix : PROMON_PKG_PREFIXES) { + if (className.startsWith(prefix + ".")) { + return true; + } + } + return false; + } + + private static Class findPromonClass(ClassLoader loader, String simpleName) { + for (String prefix : PROMON_PKG_PREFIXES) { + try { + return XposedHelpers.findClass(prefix + "." + simpleName, loader); + } catch (Throwable ignored) { + } + } + return null; } /** SecurityGuard:探测命令 stub;10101 init + 104xx/105xx sign/verify 走真实 native。 */ @@ -1866,35 +1925,24 @@ public final class TngRootBypassHook { /** Promon native 桥接类:强制 int/boolean 检测返回安全值。 */ private static void hookPromonNativeBridge(XC_LoadPackage.LoadPackageParam lpparam) { - String[] classes = { - "xwwqazamx.F", - "xwwqazamx.bg", - "xwwqazamx.b", - "xwwqazamx.c", - "xwwqazamx.d", - "xwwqazamx.h", - "xwwqazamx.k", - "xwwqazamx.l", - "xwwqazamx.m", - "xwwqazamx.o", - "xwwqazamx.s", - "xwwqazamx.t", - "xwwqazamx.z", + String[] simpleNames = { + "F", "bg", "b", "c", "d", "h", "k", "l", "m", "o", "s", "t", "z", }; int total = 0; - for (String className : classes) { - total += hookPromonIntBooleanMethods(lpparam, className); + for (String simpleName : simpleNames) { + Class clazz = findPromonClass(lpparam.classLoader, simpleName); + if (clazz != null) { + total += hookPromonIntBooleanMethods(clazz); + } } if (total > 0) { XposedBridge.log(TAG + " Promon native-bridge total hooks=" + total); } } - private static int hookPromonIntBooleanMethods( - XC_LoadPackage.LoadPackageParam lpparam, String className) { + private static int hookPromonIntBooleanMethods(Class clazz) { int count = 0; try { - Class clazz = XposedHelpers.findClass(className, lpparam.classLoader); for (Method method : clazz.getDeclaredMethods()) { Class returnType = method.getReturnType(); if (returnType != boolean.class && returnType != Boolean.class