feat(tng): TNG eWallet Promon bypass、Zygisk 信号守卫与注册链保护

新增 TngRootBypassHook 与 tng_exit_guard 模块,修复 BAL 强拉、SIGABRT/pc==lr SEGV 崩溃,验证可进入注册页。
This commit is contained in:
mars
2026-07-31 17:55:13 +08:00
parent 5a232213f3
commit 13e407623b
16 changed files with 3960 additions and 17 deletions

View File

@@ -0,0 +1,370 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.WeakHashMap;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* 过滤 /proc/self/maps 等敏感路径,隐藏 Xposed / Magisk / Zygisk 库名。
*/
public final class ProcMapsFilterHook {
private static final String TAG = "notiMessageHook/ProcMaps";
private static final Set<String> PROC_SENSITIVE = new HashSet<>(Arrays.asList(
"/proc/self/maps",
"/proc/version",
"/proc/self/status",
"/proc/mounts",
"/proc/self/attr/current",
"/proc/self/mountinfo"
));
private static final String[] MAPS_HIDE_MARKERS = {
"xposed", "lsposed", "edxposed", "magisk", "frida", "substrate",
"libpine", "pine.so", "zygisk", "riru", "shamiko", "notimessage",
"miraclegarden", "libbytehook", "libgadget", "libfrida", "libriru",
"liblspd", "libzygisk", "libvector", "zygisk_vector", "vector",
};
private static final String FAKE_SELINUX_CTX =
"u:r:untrusted_app:s0:c512,c768";
private static final WeakHashMap<Object, String> TRACKED_INPUTS = new WeakHashMap<>();
private static volatile boolean installed = false;
private ProcMapsFilterHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
}
installed = true;
hookProcAccess(lpparam);
hookProcViaRandomAccessFile(lpparam);
hookBufferedReader(lpparam);
hookSystemProperties(lpparam);
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
}
private static void hookProcAccess(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
File.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
File file = (File) param.args[0];
if (file != null) {
String path = normalizeProcPath(file.getAbsolutePath());
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
}
);
} catch (Throwable ignored) {
}
XC_MethodHook readFilter = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || param.getResult() == null) {
return;
}
if (param.getResult() instanceof Integer) {
int read = (Integer) param.getResult();
if (read <= 0 || param.args.length == 0 || !(param.args[0] instanceof byte[])) {
return;
}
byte[] buf = (byte[]) param.args[0];
int off = param.args.length > 1 ? (Integer) param.args[1] : 0;
filterProcBytes(path, buf, off, read);
} else if (param.getResult() instanceof byte[]) {
byte[] data = (byte[]) param.getResult();
param.setResult(filterProcBytesAll(path, data));
} else if (param.getResult() instanceof String) {
param.setResult(filterProcText(path, (String) param.getResult()));
}
}
};
try {
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, readFilter);
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, int.class, int.class, readFilter);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream.read hook failed: " + t.getMessage());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
XposedHelpers.findAndHookMethod(
"java.nio.file.Files",
lpparam.classLoader,
"readAllBytes",
"java.nio.file.Path",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof byte[])) {
return;
}
String norm = normalizeProcPath(String.valueOf(param.args[0]));
if (norm != null) {
param.setResult(filterProcBytesAll(
norm, (byte[]) param.getResult()));
}
}
}
);
} catch (Throwable ignored) {
}
}
}
private static void hookProcViaRandomAccessFile(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
"java.io.RandomAccessFile",
lpparam.classLoader,
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"java.io.RandomAccessFile",
lpparam.classLoader,
"read",
byte[].class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || !(param.getResult() instanceof Integer)) {
return;
}
int read = (Integer) param.getResult();
if (read > 0 && param.args[0] instanceof byte[]) {
filterProcBytes(path, (byte[]) param.args[0], 0, read);
}
}
}
);
} catch (Throwable ignored) {
}
}
private static void hookBufferedReader(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
BufferedReader.class,
"readLine",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof String)) {
return;
}
String line = (String) param.getResult();
if (shouldHideMapsLine(line)) {
param.setResult(readNextSafeLine((BufferedReader) param.thisObject));
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " BufferedReader hook failed: " + t.getMessage());
}
}
private static String readNextSafeLine(BufferedReader reader) {
try {
String line;
while ((line = reader.readLine()) != null) {
if (!shouldHideMapsLine(line)) {
return line;
}
}
} catch (Throwable ignored) {
}
return "";
}
private static void hookSystemProperties(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> sp = XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader);
for (java.lang.reflect.Method method : sp.getDeclaredMethods()) {
if (!"get".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.args.length == 0 || !(param.args[0] instanceof String)) {
return;
}
String key = (String) param.args[0];
String spoofed = spoofProperty(key, param.getResult());
if (spoofed != null) {
param.setResult(spoofed);
}
}
});
}
} catch (Throwable t) {
XposedBridge.log(TAG + " SystemProperties hook failed: " + t.getMessage());
}
}
private static String spoofProperty(String key, Object current) {
if ("ro.debuggable".equals(key)) {
return "0";
}
if ("init.svc.adbd".equals(key) || "init.svc.adb".equals(key)) {
return "stopped";
}
if ("service.adb.root".equals(key)) {
return "0";
}
if ("ro.secure".equals(key)) {
return "1";
}
if ("ro.build.tags".equals(key)) {
if (current instanceof String && String.valueOf(current).contains("test-keys")) {
return "release-keys";
}
}
if ("ro.boot.verifiedbootstate".equals(key)) {
return "green";
}
if ("ro.boot.vbmeta.device_state".equals(key)
|| "vendor.boot.vbmeta.device_state".equals(key)) {
return "locked";
}
return null;
}
private static String normalizeProcPath(String path) {
if (path == null) {
return null;
}
String norm = path.trim();
for (String p : PROC_SENSITIVE) {
if (norm.equals(p) || norm.endsWith(p)) {
return p;
}
}
return null;
}
private static byte[] filterProcBytesAll(String path, byte[] data) {
return filterProcText(path, new String(data)).getBytes();
}
private static void filterProcBytes(String path, byte[] buf, int off, int len) {
String text = new String(buf, off, len);
String filtered = filterProcText(path, text);
if (filtered.equals(text)) {
return;
}
byte[] out = filtered.getBytes();
int copy = Math.min(len, out.length);
System.arraycopy(out, 0, buf, off, copy);
if (copy < len) {
Arrays.fill(buf, off + copy, off + len, (byte) 0);
}
}
private static String filterProcText(String path, String text) {
if ("/proc/self/maps".equals(path) || "/proc/self/mountinfo".equals(path)
|| "/proc/mounts".equals(path)) {
StringBuilder sb = new StringBuilder();
for (String line : text.split("\n")) {
if (!shouldHideMapsLine(line)) {
if (sb.length() > 0) {
sb.append('\n');
}
sb.append(line);
}
}
return sb.toString();
}
if ("/proc/self/attr/current".equals(path)) {
String lower = text.toLowerCase(Locale.US);
if (lower.contains("magisk") || lower.contains("su") || lower.contains("zygisk")
|| lower.contains("xposed")) {
return FAKE_SELINUX_CTX;
}
return text;
}
if ("/proc/version".equals(path)) {
return text.replace("dirty", "").replace("test-keys", "release-keys");
}
if ("/proc/self/status".equals(path)) {
return text.replaceAll("(?m)^TracerPid:\\s*[1-9]\\d*",
"TracerPid:\t0");
}
return text;
}
private static boolean shouldHideMapsLine(String line) {
if (line == null || line.isEmpty()) {
return false;
}
String lower = line.toLowerCase(Locale.US);
for (String marker : MAPS_HIDE_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return false;
}
}

View File

@@ -131,25 +131,26 @@ public final class RootBypassHelper {
}
public static void hookRuntimeExec(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blockExec = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String cmd = null;
if (param.args.length > 0 && param.args[0] instanceof String) {
cmd = (String) param.args[0];
} else if (param.args.length > 0 && param.args[0] instanceof String[]) {
cmd = String.join(" ", (String[]) param.args[0]);
}
if (ProbeGuard.isBlockedCommand(cmd)) {
param.setResult(ProbeGuard.fakeFailedProcess(cmd));
}
}
};
try {
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String.class, blockExec);
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String[].class, blockExec);
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String.class, String[].class, blockExec);
XposedHelpers.findAndHookMethod(
Runtime.class,
"exec",
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String cmd = (String) param.args[0];
if (cmd == null) {
return;
}
String lower = cmd.toLowerCase(Locale.US);
if (lower.contains("su") || lower.contains("magisk") || lower.contains("which su")) {
throw new SecurityException("blocked root probe");
}
}
}
);
Runtime.class, "exec", String[].class, String[].class, blockExec);
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exec hook failed: " + t.getMessage());
}

View File

@@ -0,0 +1,2569 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.app.Application;
import android.app.Dialog;
import android.app.ActivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.Message;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Locale;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* TNG eWallet Root / Promon / JailBroken 检测绕过。
* 包名my.com.tngdigital.ewalletv1.9.9+
*/
public final class TngRootBypassHook {
public static final String PACKAGE = "my.com.tngdigital.ewallet";
private static final String TAG = "notiMessageHook/TngRoot";
private static final String SECURITY_ERROR_ACTIVITY =
"my.com.tngdigital.common.security.ui.SecurityErrorActivity";
private static final String[] BOOLEAN_HOOK_CLASSES = {
"my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
};
private static final String[] BLOCKED_SUPPORT_MARKERS = {
"36616543382169-rooting",
"36616508159769-emulator",
"36616480108697-malicious",
"36616444815257-multiboxing",
"45485757513113-private-space",
"48493749288857-malware",
"/articles/",
"support.tngdigital.com.my/hc/",
};
private static volatile long lastBlockedSuicideAt = 0L;
private static final long SOFT_CRASH_GUARD_MS = 10000L;
/** 用户已进入注册/登录后续页时,禁止 Splash 强拉回 Login。 */
private static volatile boolean registrationFlowActive = false;
private static final String[] REGISTRATION_FLOW_MARKERS = {
"GuideActivity",
"UserRegistrationMobileActivity",
"UserOtpVerificationActivity",
"UserRegistrationIdentityActivity",
"UserRegistrationSixPinActivity",
"UserRegistrationStrengthenPinActivity",
"UserRegistrationSecurityQuestionActivity",
"UserRegistrationSuccessActivity",
"UserSearchCallingCodeActivity",
"UserPinActivity",
"UserSecurePinActivity",
"UserPinVerifyActivity",
"EmailOtpVerificationActivity",
};
private static final String[] REGISTRATION_RPC_MARKERS = {
"phonecheck", "com.abl.wallet.phone", "com.abl.wallet.otp",
"customer.registration", "customer.verify", "customer.login",
"login.options", "callingcode", "pin.token", "module.whitelist",
};
private TngRootBypassHook() {
}
public static boolean isTargetPackage(String packageName) {
return PACKAGE.equals(packageName);
}
private static final ThreadLocal<String> CURRENT_REQUEST_URL = new ThreadLocal<>();
/** 进程启动最早打点,便于确认 LSPosed 是否注入(注册闪退常因 hook 未生效)。 */
private static void hookEarlyAttachLog(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(Application.class, "attachBaseContext", Context.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context ctx = (Context) param.args[0];
if (ctx != null && PACKAGE.equals(ctx.getPackageName())) {
XposedBridge.log(TAG + " attachBaseContext pid="
+ Process.myPid());
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " early attach log failed: " + t.getMessage());
}
}
/** Splash 优先生效;新 schedule 会取消旧 RunnableApplication 兜底 vs Splash 2000ms。 */
private static final Handler FORCE_LOGIN_HANDLER = new Handler(Looper.getMainLooper());
private static Runnable pendingForceLoginRunnable;
/** Promon native-bridge short-circuit 重入保护,避免 __cxa_guard_acquire 递归 abort。 */
private static final ThreadLocal<Integer> PROMON_BRIDGE_DEPTH = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static void scheduleForceLoginToUserLogin(
final Context appCtx, final Activity splashAct, final String reason, final long delayMs) {
if (appCtx == null) {
return;
}
if (pendingForceLoginRunnable != null) {
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
}
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
pendingForceLoginRunnable = new Runnable() {
@Override
public void run() {
pendingForceLoginRunnable = null;
try {
if (registrationFlowActive || isTopActivityRegistrationFlow(appCtx)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", registration flow)");
return;
}
ActivityManager am =
(ActivityManager) appCtx.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
for (ActivityManager.AppTask task : am.getAppTasks()) {
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info == null || info.topActivity == null) {
continue;
}
String top = info.topActivity.getClassName();
if (top.endsWith(".UserLoginActivity")
|| top.endsWith(".UserPinActivity")
|| isRegistrationFlowActivity(top)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", on " + top + ")");
return;
}
}
}
if (!isUiVisibleForForceLogin(appCtx)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", no visible UI / BAL)");
return;
}
Intent intent = new Intent();
intent.setClassName(PACKAGE, login);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (splashAct != null && !splashAct.isFinishing()) {
splashAct.startActivity(intent);
try {
splashAct.finish();
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from Splash)");
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appCtx.startActivity(intent);
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from AppCtx)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " force login failed (" + reason + "): " + t.getMessage());
}
}
};
FORCE_LOGIN_HANDLER.postDelayed(pendingForceLoginRunnable, delayMs);
}
/** 后台 Service 重启(如 Firebase SessionLifecycle无可见 Activity强拉会被 BAL 拦截。 */
private static boolean isUiVisibleForForceLogin(Context ctx) {
ActivityManager.RunningAppProcessInfo state = new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(state);
if (state.importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
return false;
}
try {
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
for (ActivityManager.AppTask task : am.getAppTasks()) {
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info != null && info.topActivity != null
&& PACKAGE.equals(info.topActivity.getPackageName())) {
return true;
}
}
}
} catch (Throwable ignored) {
}
return state.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
}
private static final String[] HTTP_LOG_MARKERS = {
"otp", "verify", "pin", "register", "auth", "login", "sms", "mobile",
"risk", "token", "error", "code", "unexpected", "reference",
};
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!isTargetPackage(lpparam.packageName)) {
return;
}
XposedBridge.log(TAG + " install for " + lpparam.packageName
+ " pid=" + Process.myPid()
+ " proc=" + getProcessName());
// Splash 常卡死Instrumentation 强拉即可。双通道会抢跑导致 Login 重载/卡死。
hookEarlyAttachLog(lpparam);
hookRegistrationFlowGuard(lpparam);
hookSplashForceLogin(lpparam);
hookLoginDismissSplash(lpparam);
hookDialogNoHwAccel();
hookActivityLifecycleDiag(lpparam);
hookHardwareRendererSetName();
hookBottomSelectDialogSafe(lpparam);
hookPromonApService(lpparam);
hookPromonBroadcastReceiver(lpparam);
hookAppAttachForceLogin(lpparam);
RootBypassHelper.hookFileExists(lpparam);
RootBypassHelper.hookRuntimeExec(lpparam);
RootBypassHelper.hookSystemGetProperty(lpparam);
ProcMapsFilterHook.install(lpparam);
hookAntiSuicide();
hookUncaughtPromonException(lpparam);
hookKillApplicationHandler(lpparam);
hookBlockSecurityErrorLaunch(lpparam);
hookPromonNativeGuard(lpparam);
hookPromonLifecycle(lpparam);
hookJnicLibrary(lpparam);
hookTigerTally(lpparam);
hookActivityThreadExit(lpparam);
hookForceExitFlow(lpparam);
hookFinishAllActivityAndKillApp(lpparam);
hookShowSecurityScreenForState(lpparam);
hookSecurityUrlOpeners(lpparam);
hookJailBroken(lpparam);
hookJailBrokenRpc(lpparam);
hookAppSecurityManager(lpparam);
hookAppSecurityCallbacks(lpparam);
hookPromonNativeBridge(lpparam);
hookSecurityBooleanChecks(lpparam);
hookSecurityErrorActivity(lpparam);
hookNetworkDiag(lpparam);
hookWebViewErrorDiag(lpparam);
}
private static void logActivityDiag(String phase, Activity activity) {
if (activity == null) {
return;
}
String name = activity.getClass().getName();
String lower = name.toLowerCase(Locale.US);
if (lower.contains("userpin")
|| lower.contains("userlogin")
|| lower.contains("registration")
|| lower.contains("otp")
|| lower.contains("verify")
|| lower.contains("sms")
|| lower.contains("webview")
|| lower.contains("issue")
|| lower.contains("guide")
|| lower.contains("error")
|| lower.contains("dialog")) {
XposedBridge.log(TAG + " ACT " + phase + " " + name);
}
}
/** 记录 OTP/登录相关 HTTP 请求与响应体,定位验证码提交失败原因。 */
private static void hookNetworkDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Request$Builder",
lpparam.classLoader,
"build",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object url = XposedHelpers.callMethod(param.getResult(), "url");
if (url != null) {
CURRENT_REQUEST_URL.set(String.valueOf(url));
}
} catch (Throwable ignored) {
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " diag Request.Builder.build failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
"okhttp3.ResponseBody",
lpparam.classLoader,
"string",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String body = (String) param.getResult();
String url = CURRENT_REQUEST_URL.get();
CURRENT_REQUEST_URL.remove();
if (body == null) {
return;
}
if (!shouldLogHttp(url, body)) {
return;
}
String snippet = body.length() > 800
? body.substring(0, 800) + "..." : body;
XposedBridge.log(TAG + " HTTP rsp"
+ (url != null ? " " + url : "")
+ " body=" + snippet);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " diag ResponseBody.string failed: " + t.getMessage());
}
XC_MethodHook callRequestHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object req = XposedHelpers.callMethod(param.thisObject, "request");
logHttpRequest(lpparam.classLoader, req);
} catch (Throwable ignored) {
}
}
};
XC_MethodHook enqueueHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object req = XposedHelpers.callMethod(param.thisObject, "request");
logHttpRequest(lpparam.classLoader, req);
} catch (Throwable ignored) {
}
}
};
for (String className : new String[]{
"okhttp3.RealCall", "okhttp3.internal.connection.RealCall"}) {
try {
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "execute", callRequestHook);
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "enqueue",
"okhttp3.Callback", enqueueHook);
} catch (Throwable ignored) {
}
}
XposedBridge.log(TAG + " hooked network diag (okhttp)");
}
private static void logHttpRequest(ClassLoader loader, Object req) {
if (req == null) {
return;
}
Object url = XposedHelpers.callMethod(req, "url");
if (url == null) {
return;
}
String urlStr = String.valueOf(url);
if (!shouldLogHttpUrl(urlStr)) {
return;
}
XposedBridge.log(TAG + " HTTP req " + urlStr);
Object body = XposedHelpers.callMethod(req, "body");
if (body != null) {
logRequestBodySnippet(loader, body);
}
}
private static boolean shouldLogHttpUrl(String url) {
if (url == null) {
return false;
}
String lower = url.toLowerCase(Locale.US);
return lower.contains("tngdigital")
|| lower.contains("alipay")
|| lower.contains("aliyun")
|| lower.contains("otp")
|| lower.contains("verify")
|| lower.contains("register")
|| lower.contains("auth")
|| lower.contains("login")
|| lower.contains("pin")
|| lower.contains("sms");
}
private static boolean shouldLogHttp(String url, String body) {
if (shouldLogHttpUrl(url)) {
return true;
}
String lower = body.toLowerCase(Locale.US);
for (String marker : HTTP_LOG_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return lower.contains("\"code\"") || lower.contains("reference");
}
private static void logRequestBodySnippet(ClassLoader loader, Object body) {
try {
Class<?> bufferClass = XposedHelpers.findClass("okio.Buffer", loader);
Object buffer = XposedHelpers.newInstance(bufferClass);
XposedHelpers.callMethod(body, "writeTo", buffer);
String text = (String) XposedHelpers.callMethod(buffer, "readUtf8");
if (text == null || text.isEmpty()) {
return;
}
String snippet = text.length() > 500 ? text.substring(0, 500) + "..." : text;
XposedBridge.log(TAG + " HTTP req body=" + snippet);
} catch (Throwable ignored) {
}
}
private static void hookWebViewErrorDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"android.webkit.WebViewClient",
lpparam.classLoader,
"onReceivedError",
"android.webkit.WebView",
"android.webkit.WebResourceRequest",
"android.webkit.WebResourceError",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object error = param.args[2];
Object code = XposedHelpers.callMethod(error, "getDescription");
Object url = XposedHelpers.callMethod(param.args[1], "getUrl");
XposedBridge.log(TAG + " WebView error url=" + url + " desc=" + code);
} catch (Throwable t) {
XposedBridge.log(TAG + " WebView error: " + t.getMessage());
}
}
});
XposedBridge.log(TAG + " hooked WebViewClient.onReceivedError");
} catch (Throwable t) {
XposedBridge.log(TAG + " WebView error hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Dialog.class,
"show",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String owner = param.thisObject.getClass().getName();
if (owner.contains("TNG") || owner.contains("Dialog")
|| owner.contains("Error") || owner.contains("i7.")) {
XposedBridge.log(TAG + " Dialog.show " + owner);
}
}
});
} catch (Throwable ignored) {
}
}
/** 仅记录注册/登录链 Activity 生命周期,便于 logcat 定位卡点。 */
private static void hookActivityLifecycleDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(Application.class, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context ctx = (Context) param.thisObject;
if (ctx == null || !PACKAGE.equals(ctx.getPackageName())) {
return;
}
Application app = (Application) param.thisObject;
app.registerActivityLifecycleCallbacks(
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
logActivityDiag("onCreate", activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
logActivityDiag("onResume", activity);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(
Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
XposedBridge.log(TAG + " registered activity lifecycle diag");
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " activity lifecycle diag failed: " + t.getMessage());
}
}
/**
* ANR 栈Dialog.show → enableHardwareAcceleration → HardwareRenderer.setName → future.get 卡死。
* 兜底拦截 setName避免 RenderThread 未就绪时主线程永久阻塞。
*/
private static void hookHardwareRendererSetName() {
try {
XposedHelpers.findAndHookMethod(
"android.graphics.HardwareRenderer", null, "setName", String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked HardwareRenderer.setName (blocked)");
} catch (Throwable t) {
XposedBridge.log(TAG + " HardwareRenderer.setName hook failed: " + t.getMessage());
}
}
/** Dialog.show 走 HW 加速会等 RenderThread复进时 RenderThread 易 abort → 主线程 ANR。 */
private static void hookDialogNoHwAccel() {
try {
XposedHelpers.findAndHookMethod(Dialog.class, "show", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Dialog dialog = (Dialog) param.thisObject;
android.view.Window window = dialog.getWindow();
if (window != null) {
window.setFlags(
0,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
} catch (Throwable ignored) {
}
}
});
XposedBridge.log(TAG + " hooked Dialog.show (no HW accel)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.show hook failed: " + t.getMessage());
}
}
/**
* 注册/登录页点「下一步」会弹国家区号 BottomSelectDialogFragment。
* HardwareRenderer.setName 已拦截 ANR此处仅关 Dialog HW 加速,保留区号选择。
*/
private static void hookBottomSelectDialogSafe(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.widget.BottomSelectDialogFragment",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(clazz, "onStart", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object dialog = XposedHelpers.callMethod(param.thisObject, "getDialog");
if (dialog instanceof Dialog) {
Dialog d = (Dialog) dialog;
if (d.getWindow() != null) {
d.getWindow().setFlags(
0,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
}
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " BottomSelectDialogFragment.onStart (safe, no HW)");
}
});
XposedBridge.log(TAG + " hooked BottomSelectDialogFragment.onStart (safe)");
} catch (Throwable t) {
XposedBridge.log(TAG + " BottomSelectDialog hook failed: " + t.getMessage());
}
}
/** Promon 隔离 Service打点确认 :goacqowmmt 进程 hook 已注入。 */
private static void hookPromonApService(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> svc = XposedHelpers.findClass("xwwqazamx.ap", lpparam.classLoader);
XposedHelpers.findAndHookMethod(svc, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " xwwqazamx.ap onCreate pid=" + Process.myPid()
+ " proc=" + getProcessName());
}
});
for (Method method : svc.getDeclaredMethods()) {
if (!"onStartCommand".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " xwwqazamx.ap onStartCommand pid="
+ Process.myPid());
}
});
}
XposedBridge.log(TAG + " hooked xwwqazamx.ap Service");
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.ap hook failed: " + t.getMessage());
}
}
/** 标记注册链 Activity 活跃,防止 Splash 强拉 Login 清栈。 */
private static void hookRegistrationFlowGuard(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XC_MethodHook flowGuardHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (isRegistrationFlowActivity(name)) {
registrationFlowActive = true;
if (pendingForceLoginRunnable != null) {
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
pendingForceLoginRunnable = null;
}
XposedBridge.log(TAG + " registration flow active: " + name);
} else if (name.endsWith(".SplashActivity")) {
registrationFlowActive = false;
}
}
};
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnCreate",
Activity.class,
Bundle.class,
flowGuardHook);
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnResume",
Activity.class,
flowGuardHook);
hookLoginOptionsDiag(lpparam);
XposedBridge.log(TAG + " hooked registration flow guard");
} catch (Throwable t) {
XposedBridge.log(TAG + " registration flow guard failed: " + t.getMessage());
}
}
/** Login 页 startLoginOptions 是进入注册/登录选项的网关 RPC 入口。 */
private static void hookLoginOptionsDiag(XC_LoadPackage.LoadPackageParam lpparam) {
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
try {
Class<?> clazz = XposedHelpers.findClass(login, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.contains("LoginOptions") && !name.contains("loginOptions")
&& !name.contains("startLogin")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " UserLoginActivity#" + name + " enter");
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " UserLoginActivity#" + name + " done");
}
});
}
XposedBridge.log(TAG + " hooked UserLoginActivity login-options diag");
} catch (Throwable t) {
XposedBridge.log(TAG + " login-options diag failed: " + t.getMessage());
}
}
private static boolean isRegistrationFlowActivity(String className) {
if (className == null) {
return false;
}
for (String marker : REGISTRATION_FLOW_MARKERS) {
if (className.endsWith("." + marker) || className.contains(marker)) {
return true;
}
}
return false;
}
private static String getProcessName() {
try {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Method current = activityThread.getDeclaredMethod("currentProcessName");
current.setAccessible(true);
Object name = current.invoke(null);
return name != null ? String.valueOf(name) : "?";
} catch (Throwable t) {
return "?";
}
}
private static boolean isTopActivityRegistrationFlow(Context ctx) {
try {
android.app.ActivityManager am =
(android.app.ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) {
return false;
}
for (android.app.ActivityManager.AppTask task : am.getAppTasks()) {
android.app.ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info == null || info.topActivity == null) {
continue;
}
if (isRegistrationFlowActivity(info.topActivity.getClassName())) {
return true;
}
}
} catch (Throwable ignored) {
}
return false;
}
/** Promon USB 广播 xwwqazamx.N 跑在主线程,复进时拖死 Looper。 */
private static void hookPromonBroadcastReceiver(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass("xwwqazamx.N", lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"onReceive".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked xwwqazamx.N onReceive x" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.N hook failed: " + t.getMessage());
}
}
/**
* 强拉 Login 后系统 Splash 遮罩常挂在 UserLogin 上windows=Splash Screen
* 导致复进「未响应」。onCreate/onResume 强制 dismiss。
*/
private static void hookLoginDismissSplash(XC_LoadPackage.LoadPackageParam lpparam) {
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
try {
XC_MethodHook dismissHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!login.equals(name) && !name.endsWith(".UserLoginActivity")) {
return;
}
dismissSplashScreen(activity);
}
};
XposedHelpers.findAndHookMethod(
Instrumentation.class, "callActivityOnCreate",
Activity.class, Bundle.class, dismissHook);
XposedHelpers.findAndHookMethod(
Instrumentation.class, "callActivityOnResume",
Activity.class, dismissHook);
XposedBridge.log(TAG + " hooked UserLogin splash dismiss (onCreate+onResume)");
} catch (Throwable t) {
XposedBridge.log(TAG + " login splash dismiss hook failed: " + t.getMessage());
}
}
/** 移除 Android 12+ / androidx 启动页遮罩,避免挡在 Login 前导致 ANR。 */
private static void dismissSplashScreen(Activity activity) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
activity.reportFullyDrawn();
} catch (Throwable ignored) {
}
if (Build.VERSION.SDK_INT >= 31) {
try {
android.window.SplashScreen ss = activity.getSplashScreen();
if (ss != null) {
ss.setOnExitAnimationListener(
splashScreenView -> splashScreenView.remove());
}
} catch (Throwable ignored) {
}
}
try {
ClassLoader cl = activity.getClassLoader();
Class<?> splashCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen", cl);
Object splash = XposedHelpers.callStaticMethod(
splashCl, "installSplashScreen", activity);
Class<?> condCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen$KeepOnScreenCondition", cl);
Object keepOff = Proxy.newProxyInstance(
cl, new Class<?>[] { condCl },
(proxy, method, args) -> false);
XposedHelpers.callMethod(splash, "setKeepOnScreenCondition", keepOff);
Class<?> exitCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen$OnExitAnimationListener", cl);
Object exitListener = Proxy.newProxyInstance(
cl, new Class<?>[] { exitCl },
(proxy, method, args) -> {
if (args != null && args.length > 0 && args[0] != null) {
XposedHelpers.callMethod(args[0], "remove");
}
return null;
});
XposedHelpers.callMethod(splash, "setOnExitAnimationListener", exitListener);
} catch (Throwable t) {
XposedBridge.log(TAG + " dismissSplashScreen compat: " + t.getMessage());
}
}
});
}
/**
* Splash.onCreate 常被 Promon 堵死永远不返回;必须在 onCreate 入口before就调度强拉。
*/
private static void hookSplashForceLogin(XC_LoadPackage.LoadPackageParam lpparam) {
final String splash = "my.com.tngdigital.ewallet.ui.SplashActivity";
try {
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnCreate",
Activity.class,
Bundle.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
final Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
return;
}
XposedBridge.log(TAG + " Splash.onCreate enter — schedule force login");
scheduleForceLoginToUserLogin(
activity.getApplicationContext(),
activity,
"Splash/beforeOnCreate",
2000L);
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
final Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
return;
}
try {
activity.reportFullyDrawn();
} catch (Throwable ignored) {
}
dismissSplashScreen(activity);
}
});
XposedBridge.log(TAG + " hooked Instrumentation Splash force→UserLogin (before+after)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Splash force hook failed: " + t.getMessage());
}
}
/** Application.onCreate 兜底Splash beforeHook 未触发时仍强拉 Login跳过纯 Service 进程)。 */
private static void hookAppAttachForceLogin(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
android.app.Application.class,
"onCreate",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
final Context appCtx = (Context) param.thisObject;
if (appCtx == null || !PACKAGE.equals(appCtx.getPackageName())) {
return;
}
String proc = getProcessName();
if (proc != null && proc.contains(":")) {
return;
}
XposedBridge.log(TAG + " Application.onCreate — schedule force login fallback");
scheduleForceLoginToUserLogin(appCtx, null, "Application/onCreate", 3500L);
}
});
XposedBridge.log(TAG + " hooked Application.onCreate force→UserLogin fallback");
} catch (Throwable t) {
XposedBridge.log(TAG + " Application force hook failed: " + t.getMessage());
}
}
private static void hookAntiSuicide() {
try {
XposedHelpers.findAndHookMethod(
Process.class,
"killProcess",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (((Integer) param.args[0]) == Process.myPid()) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked killProcess(self)");
param.setResult(null);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " killProcess hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
System.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked System.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " System.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked Runtime.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"halt",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked Runtime.halt(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.halt hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Process.class,
"sendSignal",
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int pid = (Integer) param.args[0];
int signal = (Integer) param.args[1];
if (pid == Process.myPid() && (signal == 9 || signal == 15)) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked sendSignal(self, " + signal + ")");
param.setResult(null);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " sendSignal hook failed: " + t.getMessage());
}
XC_MethodHook blockFinish = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (!isSecurityRelatedActivity(activity)) {
return;
}
if (System.currentTimeMillis() - lastBlockedSuicideAt > SOFT_CRASH_GUARD_MS) {
return;
}
XposedBridge.log(TAG + " blocked " + param.method.getName()
+ " on " + activity.getClass().getSimpleName());
param.setResult(null);
}
};
try {
XposedHelpers.findAndHookMethod(Activity.class, "finish", blockFinish);
XposedHelpers.findAndHookMethod(Activity.class, "finishAffinity", blockFinish);
XposedHelpers.findAndHookMethod(Activity.class, "finishAndRemoveTask", blockFinish);
} catch (Throwable t) {
XposedBridge.log(TAG + " finish hooks failed: " + t.getMessage());
}
}
private static boolean isSecurityRelatedActivity(Activity activity) {
String name = activity.getClass().getName();
return name.contains("SecurityError")
|| name.contains("security.ui")
|| name.contains("Promon");
}
private static void hookBlockSecurityErrorLaunch(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blockLaunch = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Intent intent = extractIntent(param.args);
if (intent != null && shouldBlockIntent(intent)) {
XposedBridge.log(TAG + " blocked intent via "
+ param.method.getDeclaringClass().getSimpleName()
+ "#" + param.method.getName()
+ " data=" + intent.getDataString());
param.setResult(null);
}
}
};
hookStartActivityOverloads("android.app.Activity", null, blockLaunch);
hookStartActivityOverloads("android.content.ContextWrapper", null, blockLaunch);
hookStartActivityOverloads("android.app.ContextImpl", null, blockLaunch);
try {
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"execStartActivity",
Context.class,
android.os.IBinder.class,
android.os.IBinder.class,
Activity.class,
Intent.class,
int.class,
Bundle.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Intent intent = (Intent) param.args[4];
if (shouldBlockIntent(intent)) {
XposedBridge.log(TAG + " blocked execStartActivity "
+ intent.getDataString());
param.setResult(-1);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " execStartActivity hook failed: " + t.getMessage());
}
}
/** Promon Shield 安全回调由 handle*Callback / bl short-circuit 处理lifecycle 回调勿拦。 */
private static void hookStartActivityOverloads(
String className, ClassLoader classLoader, XC_MethodHook blockLaunch) {
try {
if ("android.app.Activity".equals(className)) {
XposedHelpers.findAndHookMethod(
Activity.class, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
Activity.class, "startActivity", Intent.class, Bundle.class, blockLaunch);
return;
}
if (classLoader == null) {
XposedHelpers.findAndHookMethod(
className, null, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
className, null, "startActivity", Intent.class, Bundle.class, blockLaunch);
return;
}
XposedHelpers.findAndHookMethod(
className, classLoader, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
className, classLoader, "startActivity", Intent.class, Bundle.class, blockLaunch);
} catch (Throwable t) {
XposedBridge.log(TAG + " startActivity hooks failed for " + className + ": "
+ t.getMessage());
}
}
private static Intent extractIntent(Object[] args) {
if (args == null) {
return null;
}
for (Object arg : args) {
if (arg instanceof Intent) {
return (Intent) arg;
}
}
return null;
}
private static boolean shouldBlockIntent(Intent intent) {
if (intent == null) {
return false;
}
if (isSecurityErrorIntent(intent)) {
return true;
}
Uri data = intent.getData();
if (data != null && isBlockedSupportUrl(data.toString())) {
return true;
}
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action) && data != null) {
return isBlockedSupportUrl(data.toString());
}
return false;
}
private static boolean isBlockedSupportUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
String lower = url.toLowerCase(Locale.US);
if (!lower.contains("support.tngdigital.com.my")) {
return false;
}
for (String marker : BLOCKED_SUPPORT_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return lower.contains("rooting") || lower.contains("jailbroken")
|| lower.contains("emulator") || lower.contains("malware");
}
/** 阻止 Promon 混淆层抛出 W:16 并触发浏览器 fallback。 */
private static void hookPromonNativeGuard(XC_LoadPackage.LoadPackageParam lpparam) {
hookPromonBlSwallowExceptions(lpparam);
hookPromonExceptionClass(lpparam, "xwwqazamx.W");
hookPromonExceptionClass(lpparam, "xwwqazamx.A");
hookPromonRunnable(lpparam);
}
/**
* Promon lifecycle只吞异常不 short-circuit——全拦会拖死 Splash→Login。
*/
private static void hookPromonLifecycle(XC_LoadPackage.LoadPackageParam lpparam) {
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();
if (!name.startsWith("onActivity") && !name.startsWith("onApplication")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!param.hasThrowable()) {
return;
}
Throwable t = param.getThrowable();
if (isPromonThrowable(t, promonExcFinal)) {
XposedBridge.log(TAG + " swallowed " + t.getClass().getSimpleName()
+ " in xwwqazamx.w#" + method.getName());
param.setThrowable(null);
}
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked
+ " xwwqazamx.w lifecycle method(s) (afterHook only)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.w lifecycle hook failed: " + t.getMessage());
}
}
/** 吞掉 :tools 进程里 Promon 抛出的未捕获 W:16。 */
private static void hookUncaughtPromonException(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
Thread.class,
"dispatchUncaughtException",
Throwable.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Throwable t = (Throwable) param.args[0];
if (t == null || !isPromonThrowableName(t.getClass().getName())) {
return;
}
XposedBridge.log(TAG + " swallowed uncaught " + t.getClass().getSimpleName()
+ " in " + lpparam.processName);
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked Thread.dispatchUncaughtException");
} catch (Throwable t) {
XposedBridge.log(TAG + " dispatchUncaughtException hook failed: " + t.getMessage());
}
}
/**
* bl 全方法 short-circuita/b 之外的方法仍会跑 native~40s 后 stack_chk/SEGV。
* xwwqazamx.a.run 是 bl#b 后台 Runnable必须 beforeHook 直接 return。
*/
private static void hookPromonBlSwallowExceptions(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> blClass = XposedHelpers.findClass("xwwqazamx.bl", lpparam.classLoader);
int hooked = 0;
for (Method method : blClass.getDeclaredMethods()) {
String name = method.getName();
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " bl method(s), all short-circuit");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.bl hook failed: " + t.getMessage());
}
}
private static void fixNullPromonResult(XC_MethodHook.MethodHookParam param, Method method) {
if (param.getResult() != null) {
return;
}
Class<?> returnType = method.getReturnType();
if (returnType == Integer.class || returnType == int.class) {
XposedBridge.log(TAG + " fixed null bl#" + method.getName() + " -> 0");
setSafeHookResult(param, method);
}
}
/** Promon 后台 Runnablebl#b 检测线程beforeHook 直接 noop禁止跑 native。 */
private static void hookPromonRunnable(XC_LoadPackage.LoadPackageParam lpparam) {
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)");
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.a.run hook failed: " + t.getMessage());
}
}
private static void hookPromonExceptionClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
try {
Class<?> promonExc = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : promonExc.getDeclaredMethods()) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " swallowed " + className + "#" + method.getName());
setSafeHookResult(param, method);
}
});
}
try {
XposedHelpers.findAndHookConstructor(promonExc, int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " swallowed " + className + "<init>(int)");
param.setResult(null);
}
});
} catch (Throwable ignored) {
// constructor overload may differ
}
} catch (Throwable t) {
XposedBridge.log(TAG + " " + className + " hook failed: " + t.getMessage());
}
}
private static boolean isPromonException(Throwable t, Class<?> promonExc) {
return isPromonThrowable(t, promonExc);
}
private static boolean isPromonThrowable(Throwable t, Class<?> promonExc) {
if (t == null) {
return false;
}
if (promonExc != null && promonExc.isInstance(t)) {
return true;
}
return isPromonThrowableName(t.getClass().getName());
}
private static boolean isPromonThrowableName(String className) {
if (className == null) {
return false;
}
if (!className.startsWith("xwwqazamx.")) {
return false;
}
// W/A 等单字母 Promon 异常;排除 bl/w/bg 等功能类
int dot = className.lastIndexOf('.');
if (dot < 0) {
return false;
}
String simple = className.substring(dot + 1);
return simple.length() <= 2;
}
/** SecurityGuard探测命令 stub10101 init + 104xx/105xx sign/verify 走真实 native。 */
private static void hookJnicLibrary(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"com.hzchengdun.securityguard.adapter.JNICLibrary",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(
clazz,
"doCommand",
int.class,
Object[].class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int cmd = (Integer) param.args[0];
if (shouldStubJnicCmd(cmd)) {
Object[] payload = (Object[]) param.args[1];
Object stub = safeJnicReturn(cmd, payload);
XposedBridge.log(TAG + " stub JNICLibrary.doCommand cmd=" + cmd
+ " -> " + describeJnicResult(stub));
param.setResult(stub);
return;
}
if (shouldLogJnicCmd(cmd)) {
XposedBridge.log(TAG + " JNIC passthrough call cmd=" + cmd
+ " args=" + describeJnicArgs((Object[]) param.args[1]));
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
int cmd = (Integer) param.args[0];
if (shouldStubJnicCmd(cmd)) {
return;
}
if (!shouldLogJnicCmd(cmd)) {
return;
}
if (param.hasThrowable()) {
logJnicThrowable(cmd, param.getThrowable());
return;
}
XposedBridge.log(TAG + " JNIC passthrough cmd=" + cmd
+ " -> " + describeJnicResult(param.getResult()));
}
});
XposedBridge.log(TAG + " hooked JNICLibrary.doCommand (probe stub + init/verify passthrough)");
} catch (Throwable t) {
XposedBridge.log(TAG + " JNICLibrary hook failed: " + t.getMessage());
}
}
/** 仅 stub 低号 env/root 探测10101 init 与 104xx/105xx 必须 passthrough。 */
private static boolean shouldStubJnicCmd(int cmd) {
if (cmd == 10101 || cmd == 10102 || cmd == 10103 || cmd == 10104) {
return false;
}
if (cmd >= 10000) {
return false;
}
return true;
}
private static boolean shouldLogJnicCmd(int cmd) {
return cmd == 10101 || cmd == 10102 || cmd == 10103 || cmd == 10104
|| cmd == 10401 || cmd == 10501 || cmd == 10603
|| (cmd >= 10400 && cmd < 10700);
}
private static void logJnicThrowable(int cmd, Throwable t) {
StringBuilder sb = new StringBuilder();
sb.append(TAG).append(" JNIC passthrough cmd=").append(cmd)
.append(" err ").append(t.getClass().getName());
String msg = t.getMessage();
if (msg != null && !msg.isEmpty()) {
sb.append(" msg=").append(msg);
}
try {
Object code = XposedHelpers.callMethod(t, "getErrorCode");
if (code != null) {
sb.append(" errorCode=").append(code);
}
} catch (Throwable ignored) {
}
StackTraceElement[] stack = t.getStackTrace();
if (stack != null && stack.length > 0) {
sb.append(" at ").append(stack[0]);
}
XposedBridge.log(sb.toString());
}
private static String describeJnicArgs(Object[] args) {
if (args == null) {
return "null";
}
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(", ");
}
Object arg = args[i];
if (arg == null) {
sb.append("null");
} else if (arg instanceof byte[]) {
sb.append("byte[").append(((byte[]) arg).length).append("]");
} else if (arg instanceof String) {
String s = (String) arg;
sb.append("String(").append(s.length() > 40 ? s.substring(0, 40) + "..." : s).append(")");
} else {
sb.append(arg.getClass().getSimpleName()).append("=").append(arg);
}
}
sb.append("]");
return sb.toString();
}
/**
* Aliyun TigerTally与 Promon 并行的设备指纹/风控 SDKlibtiger_tally.so
*
* ANR 根因2026-07-31init 链TigerTallyAPI.init → initCommon → t.B.genericNt1
* native会 fork 子进程跑 `getprop ro.build.version.sdk` 并用 pipe 等其 stdout。
* Zygisk tng_exit_guard 的 exit_group seccomp 被 fork 继承 → getprop 永不退出 →
* fread 永久阻塞 → StartupManager latch 卡死 → Application.onCreate ANR。
*
* 对策:对 init 链做 beforeHook 短路(跳过 native启动不再 fork 等待。
* 其它方法保留 afterHook 清异常软化。
*/
private static void hookTigerTally(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"com.aliyun.TigerTally.TigerTallyAPI",
"com.aliyun.TigerTally.t.B",
"com.aliyun.TigerTally.t.C",
"com.aliyun.TigerTally.s.A",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
int shorted = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isAbstract(method.getModifiers())) {
continue;
}
// 不拦 Object 基础方法
String name = method.getName();
if ("equals".equals(name) || "hashCode".equals(name)
|| "toString".equals(name) || "getClass".equals(name)) {
continue;
}
final boolean sc = isTigerShortCircuit(className, name);
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (sc) {
XposedBridge.log(TAG + " TigerTally SC "
+ className + "#" + method.getName());
setSafeHookResult(param, method);
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (sc) {
return;
}
if (param.hasThrowable()) {
XposedBridge.log(TAG + " TigerTally err "
+ className + "#" + method.getName()
+ " " + param.getThrowable().getClass().getSimpleName()
+ ": " + param.getThrowable().getMessage());
param.setThrowable(null);
setSafeHookResult(param, method);
}
}
});
if (sc) {
shorted++;
}
hooked++;
}
XposedBridge.log(TAG + " hooked TigerTally " + className
+ " methods=" + hooked + " sc=" + shorted);
} catch (Throwable t) {
XposedBridge.log(TAG + " TigerTally " + className
+ " hook skipped: " + t.getMessage());
}
}
}
/** 短路 TigerTally 启动初始化native 会 fork getprop 等待 → seccomp 卡死 ANR。 */
private static boolean isTigerShortCircuit(String className, String methodName) {
if ("com.aliyun.TigerTally.TigerTallyAPI".equals(className)
&& ("init".equals(methodName) || "initCommon".equals(methodName))) {
return true;
}
return "com.aliyun.TigerTally.t.B".equals(className)
&& "genericNt1".equals(methodName);
}
private static Object safeJnicReturn(int cmd, Object[] args) {
if (args != null) {
for (Object arg : args) {
if (arg instanceof byte[]) {
return new byte[0];
}
}
}
return Integer.valueOf(0);
}
private static String describeJnicResult(Object result) {
if (result == null) {
return "null";
}
if (result instanceof byte[]) {
return "byte[" + ((byte[]) result).length + "]";
}
return result.getClass().getSimpleName() + "=" + result;
}
/** 拦截 ActivityThread / Handler 触发的应用退出Promon 常走 native→H.exit。 */
private static void hookActivityThreadExit(XC_LoadPackage.LoadPackageParam lpparam) {
hookActivityThreadExitMethods(null);
hookActivityThreadExitMethods(lpparam.classLoader);
hookActivityThreadHandlerExit(lpparam);
hookShutdownExit();
}
private static void hookActivityThreadExitMethods(ClassLoader classLoader) {
try {
Class<?> atClass = XposedHelpers.findClass("android.app.ActivityThread", classLoader);
int hooked = 0;
for (Method method : atClass.getDeclaredMethods()) {
String name = method.getName();
if (!isActivityThreadExitMethod(name)) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked ActivityThread#" + name);
setSafeHookResult(param, method);
}
});
hooked++;
XposedBridge.log(TAG + " registered ActivityThread exit hook: " + name);
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " ActivityThread exit method(s)"
+ (classLoader == null ? " [boot]" : " [app]"));
}
} catch (Throwable t) {
XposedBridge.log(TAG + " ActivityThread exit hook failed: " + t.getMessage());
}
}
/** 仅拦截真正执行退出的方法,勿匹配 isHandleSplashScreenExit 等查询方法。 */
private static boolean isActivityThreadExitMethod(String name) {
if (name.startsWith("is") || name.startsWith("get") || name.startsWith("has")) {
return false;
}
String lower = name.toLowerCase(Locale.US);
return lower.contains("exitapplication")
|| lower.equals("exit")
|| lower.contains("handleexit")
|| lower.contains("appexit");
}
/** 拦截 EXIT_APPLICATION 等 Handler 消息Android 16 上 handleExitApplication 签名已变)。 */
private static void hookActivityThreadHandlerExit(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> handlerClass = XposedHelpers.findClass("android.app.ActivityThread$H", lpparam.classLoader);
XposedHelpers.findAndHookMethod(
handlerClass,
"handleMessage",
Message.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Message msg = (Message) param.args[0];
if (msg == null) {
return;
}
int what = msg.what;
// AOSP: KILL_APPLICATION=109, EXIT_APPLICATION=111
if (what == 109 || what == 111) {
XposedBridge.log(TAG + " blocked ActivityThread$H msg.what=" + what);
param.setResult(null);
}
}
});
XposedBridge.log(TAG + " hooked ActivityThread$H.handleMessage");
} catch (Throwable t) {
XposedBridge.log(TAG + " ActivityThread$H hook failed: " + t.getMessage());
}
}
private static void hookShutdownExit() {
try {
Class<?> shutdownClass = Class.forName("java.lang.Shutdown");
XposedHelpers.findAndHookMethod(
shutdownClass,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked Shutdown.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked Shutdown.exit");
} catch (Throwable t) {
XposedBridge.log(TAG + " Shutdown.exit hook failed: " + t.getMessage());
}
}
/** 阻止 RuntimeInit 默认 handler 因 Promon W 异常杀进程。 */
private static void hookKillApplicationHandler(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> handlerClass = XposedHelpers.findClass(
"com.android.internal.os.RuntimeInit$KillApplicationHandler",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(
handlerClass,
"uncaughtException",
Thread.class,
Throwable.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Throwable t = (Throwable) param.args[1];
if (t == null || !isPromonThrowableName(t.getClass().getName())) {
return;
}
XposedBridge.log(TAG + " blocked KillApplicationHandler for "
+ t.getClass().getSimpleName()
+ " proc=" + lpparam.processName);
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked RuntimeInit$KillApplicationHandler");
} catch (Throwable t) {
XposedBridge.log(TAG + " KillApplicationHandler hook failed: " + t.getMessage());
}
}
/** Promon / SecurityError 强制退出倒计时。 */
/**
* dexdump 定位UnhandledEvent 走
* AppSecurityManager.showSecurityScreenForState → _ContextKt.finishAllActivityAndKillApp。
* 这是 UserLogin 之后 Java 层杀进程的主路径之一。
*/
private static void hookFinishAllActivityAndKillApp(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.internal._ContextKt",
"my.com.tngdigital.common.internal.ContextKt",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.toLowerCase(Locale.US).contains("finishall")
&& !name.toLowerCase(Locale.US).contains("killapp")
&& !name.equals("finishAllActivityAndKillApp")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + name);
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " kill-app method(s) in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
}
/** 拦截把 UnhandledEvent 导航成杀进程的入口。 */
private static void hookShowSecurityScreenForState(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
lpparam.classLoader);
Class<?> unhandled = null;
try {
unhandled = XposedHelpers.findClass(
"my.com.tngdigital.common.security.model.UnhandledEvent",
lpparam.classLoader);
} catch (Throwable ignored) {
// optional
}
final Class<?> unhandledFinal = unhandled;
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"showSecurityScreenForState".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Object state = param.args != null && param.args.length > 1
? param.args[1] : null;
if (state != null && unhandledFinal != null) {
try {
Object eventInfo = XposedHelpers.callMethod(state, "getEventInfo");
if (unhandledFinal.isInstance(eventInfo)) {
XposedBridge.log(TAG + " blocked showSecurityScreenForState UnhandledEvent");
param.setResult(null);
return;
}
} catch (Throwable ignored) {
// fall through to blanket block
}
}
XposedBridge.log(TAG + " blocked showSecurityScreenForState");
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " showSecurityScreenForState");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " showSecurityScreenForState hook failed: " + t.getMessage());
}
}
private static void hookForceExitFlow(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
"my.com.tngdigital.common.security.ui.SecurityErrorActivity",
"my.com.tngdigital.common.security.SecurityForceExitCountdownPolicyKt",
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
String lower = name.toLowerCase(Locale.US);
if (!lower.contains("forceexit")
&& !lower.contains("exitcountdown")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + name);
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " force-exit/queue method(s) in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
}
/** 消费 Promon Java 层安全回调,避免检测后走 SecurityError / native fallback 退出链。 */
private static void hookAppSecurityCallbacks(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.startsWith("handle") || !name.endsWith("Callback")) {
continue;
}
hookMethodNoopSilent(method);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " AppSecurityManager callback(s) (silent)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " AppSecurityManager callbacks hook failed: " + t.getMessage());
}
}
/** 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",
};
int total = 0;
for (String className : classes) {
total += hookPromonIntBooleanMethods(lpparam, className);
}
if (total > 0) {
XposedBridge.log(TAG + " Promon native-bridge total hooks=" + total);
}
}
private static int hookPromonIntBooleanMethods(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
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
&& returnType != int.class && returnType != Integer.class) {
continue;
}
if (method.getParameterTypes().length > 6) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int depth = PROMON_BRIDGE_DEPTH.get();
if (depth > 0) {
return;
}
PROMON_BRIDGE_DEPTH.set(depth + 1);
try {
if (returnType == boolean.class || returnType == Boolean.class) {
param.setResult(false);
} else {
param.setResult(0);
}
} finally {
PROMON_BRIDGE_DEPTH.set(depth);
}
}
});
count++;
}
} catch (Throwable ignored) {
// class may be absent in this process
}
return count;
}
private static void hookSecurityUrlOpeners(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
"my.com.tngdigital.common.security.ui.SecurityErrorActivity",
"my.com.tngdigital.common.security.shielding.utils.HelperKt",
"my.com.tngdigital.app.launcher.initializer.AppSecurityInitializer",
};
String[] methods = {
"openSecurityUrl", "openUrlByBrowser", "openBrowser", "openUrl",
"openWebUrl",
};
for (String className : classes) {
for (String methodName : methods) {
hookUrlOrCallbackMethod(lpparam, className, methodName);
}
}
}
private static void hookUrlOrCallbackMethod(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!methodName.equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args != null && param.args.length > 0
&& param.args[0] instanceof String) {
String url = (String) param.args[0];
if (isBlockedSupportUrl(url)) {
XposedBridge.log(TAG + " blocked " + className
+ "#" + methodName + " url=" + url);
setSafeHookResult(param, method);
return;
}
}
if ("openSecurityUrl".equals(methodName)
|| "openUrlByBrowser".equals(methodName)
|| "openBrowser".equals(methodName)) {
XposedBridge.log(TAG + " blocked " + className + "#" + methodName);
setSafeHookResult(param, method);
}
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " " + className + "#" + methodName);
}
} catch (Throwable ignored) {
// class/method may not exist in this APK split
}
}
private static void hookMethodNoop(Method method, String label) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + label);
setSafeHookResult(param, method);
}
});
XposedBridge.log(TAG + " hooked " + label);
}
/** 高频回调(如 handleTapjackingCallback禁止逐次打 log避免复进主线程 ANR。 */
private static void hookMethodNoopSilent(Method method) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
setSafeHookResult(param, method);
}
});
}
/** methodName 为 null 时 hook 类内全部方法(用于 Promon 混淆类)。 */
private static void hookAllMethodsNoop(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (methodName != null && !methodName.equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + method.getName());
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " method(s) in " + className
+ (methodName != null ? "#" + methodName : ""));
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
private static boolean isSecurityErrorIntent(Intent intent) {
if (intent == null) {
return false;
}
if (intent.getComponent() != null) {
String cls = intent.getComponent().getClassName();
if (cls != null && cls.contains("SecurityError")) {
return true;
}
}
String target = intent.getStringExtra("targetActivity");
return target != null && target.contains("SecurityError");
}
private static void hookJailBroken(XC_LoadPackage.LoadPackageParam lpparam) {
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"showJailBrokenAlert");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"showJailBrokenAlert$lambda$15");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"isJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"detectJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"isJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"detectJailBroken");
// 以下为 void 回调(非 boolean触发时弹窗/杀进程,必须 noop。
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onBlockStaticCheck");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onShowPopupDisable");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onEmptyToken");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onRpcCheckValid");
hookJailBrokenResult(lpparam);
}
/** 拦截 jail.broken.detect RPC本地直接回调「未越狱」结果不发网关。 */
private static void hookJailBrokenRpc(XC_LoadPackage.LoadPackageParam lpparam) {
String implClass = "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl";
String[] shortCircuitMethods = {
"rpcCheckJailBroken",
"jailBrokenDetect",
"rpcCheck",
};
try {
Class<?> clazz = XposedHelpers.findClass(implClass, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
boolean match = false;
for (String target : shortCircuitMethods) {
if (target.equals(name)) {
match = true;
break;
}
}
if (!match) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " skip jail RPC " + implClass + "#" + name);
Object clean = buildCleanJailBrokenResult(lpparam.classLoader);
invokeJailBrokenCallbacks(param.args, clean);
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked jail RPC short-circuit methods=" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " jail RPC short-circuit failed: " + t.getMessage());
}
hookRpcInvocationJailBypass(lpparam);
}
private static void hookRpcInvocationJailBypass(XC_LoadPackage.LoadPackageParam lpparam) {
String[] rpcClasses = {
"my.com.tngdigital.common.aliservice.quake.TngdRpcInvocationHandlerHost",
"com.alipay.imobile.network.quake.rpc.RpcInvocationHandler",
};
XC_MethodHook rpcHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String op = extractRpcOperation(param.args);
if (op != null && isRegistrationRpc(op)) {
XposedBridge.log(TAG + " RPC reg/login op=" + op + " "
+ describeRpcInvocation(param.args));
}
if (!isJailBrokenRpcInvocation(param.args)) {
return;
}
XposedBridge.log(TAG + " block jail.broken RPC invoke "
+ describeRpcInvocation(param.args));
Object clean = buildCleanJailBrokenResult(lpparam.classLoader);
if (clean != null) {
param.setResult(clean);
return;
}
param.setResult(null);
}
};
for (String className : rpcClasses) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"invoke".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, rpcHook);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked RPC guard+log " + className
+ " invoke=" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip RPC guard " + className
+ ": " + t.getMessage());
}
}
}
private static String extractRpcOperation(Object[] args) {
if (args == null) {
return null;
}
for (Object arg : args) {
if (!(arg instanceof String)) {
continue;
}
String text = (String) arg;
if (text.startsWith("com.abl.")
|| text.startsWith("ap.tngd")
|| text.startsWith("alipayplus.")) {
return text;
}
}
return null;
}
private static boolean isRegistrationRpc(String op) {
if (op == null) {
return false;
}
String lower = op.toLowerCase(Locale.US);
for (String marker : REGISTRATION_RPC_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return false;
}
private static boolean isJailBrokenRpcInvocation(Object[] args) {
if (args == null) {
return false;
}
for (Object arg : args) {
if (arg == null) {
continue;
}
if (arg instanceof Method) {
String name = ((Method) arg).getName().toLowerCase(Locale.US);
if (name.contains("jail")) {
return true;
}
continue;
}
if (arg instanceof String) {
String text = ((String) arg).toLowerCase(Locale.US);
if (text.contains("jail.broken")
|| text.contains("jailbroken")
|| text.contains("jail_broken")) {
return true;
}
continue;
}
// 勿对 RPC 动态代理 toString会再次进入 invoke 导致栈溢出。
if (Proxy.isProxyClass(arg.getClass())) {
continue;
}
Class<?> clazz = arg.getClass();
if (clazz.isArray()) {
continue;
}
String simple = clazz.getSimpleName().toLowerCase(Locale.US);
if (simple.contains("jailbroken") || simple.contains("jailbrokenrequest")) {
return true;
}
}
return false;
}
private static String describeRpcInvocation(Object[] args) {
if (args == null || args.length == 0) {
return "[]";
}
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(", ");
}
Object arg = args[i];
if (arg instanceof Method) {
sb.append("Method=").append(((Method) arg).getName());
} else if (arg instanceof String) {
String s = (String) arg;
sb.append(s.length() > 120 ? s.substring(0, 120) + "..." : s);
} else if (Proxy.isProxyClass(arg.getClass())) {
sb.append("Proxy=").append(arg.getClass().getInterfaces()[0].getSimpleName());
} else {
sb.append(arg.getClass().getSimpleName());
}
}
sb.append("]");
return sb.toString();
}
private static Object buildCleanJailBrokenResult(ClassLoader loader) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.jailbrkendetect.JailBrokenResult", loader);
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
Object[] args = new Object[types.length];
boolean ok = true;
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
if (type == boolean.class || type == Boolean.class) {
args[i] = Boolean.FALSE;
} else if (type == String.class) {
args[i] = "";
} else if (type == int.class) {
args[i] = 0;
} else if (type == Integer.class) {
args[i] = Integer.valueOf(0);
} else if (!type.isPrimitive()) {
args[i] = null;
} else {
ok = false;
break;
}
}
if (!ok) {
continue;
}
try {
constructor.setAccessible(true);
Object result = constructor.newInstance(args);
sanitizeJailBrokenResult(result);
return result;
} catch (Throwable ignored) {
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " buildCleanJailBrokenResult failed: " + t.getMessage());
}
return null;
}
private static void sanitizeJailBrokenResult(Object result) {
if (result == null) {
return;
}
try {
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
field.setAccessible(true);
String name = field.getName().toLowerCase(Locale.US);
Class<?> type = field.getType();
if ((type == boolean.class || type == Boolean.class)
&& (name.contains("jail") || name.contains("root"))) {
field.set(result, Boolean.FALSE);
} else if (type == String.class && name.contains("msg")) {
field.set(result, "");
}
}
} catch (Throwable ignored) {
}
}
private static void invokeJailBrokenCallbacks(Object[] args, Object cleanResult) {
if (args == null || cleanResult == null) {
return;
}
for (Object arg : args) {
if (arg == null) {
continue;
}
try {
for (Method method : arg.getClass().getDeclaredMethods()) {
if (method.getParameterTypes().length != 1) {
continue;
}
Class<?> paramType = method.getParameterTypes()[0];
if (!paramType.getName().contains("JailBrokenResult")) {
continue;
}
method.setAccessible(true);
method.invoke(arg, cleanResult);
XposedBridge.log(TAG + " delivered clean JailBrokenResult via "
+ arg.getClass().getSimpleName() + "#" + method.getName());
}
} catch (Throwable ignored) {
}
try {
XposedHelpers.callMethod(arg, "invoke", cleanResult);
} catch (Throwable ignored) {
}
}
}
private static void hookJailBrokenResult(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> resultClass = XposedHelpers.findClass(
"my.com.tngdigital.common.jailbrkendetect.JailBrokenResult",
lpparam.classLoader);
for (Method method : resultClass.getDeclaredMethods()) {
String name = method.getName();
Class<?> returnType = method.getReturnType();
if (returnType == boolean.class || returnType == Boolean.class) {
if (name.toLowerCase(Locale.US).contains("jail")
|| name.toLowerCase(Locale.US).contains("root")) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
XposedBridge.log(TAG + " hooked JailBrokenResult#" + name);
}
} else if (returnType == String.class
&& name.toLowerCase(Locale.US).contains("msg")) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult("");
}
});
XposedBridge.log(TAG + " hooked JailBrokenResult#" + name);
}
}
XposedHelpers.findAndHookConstructor(resultClass, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
sanitizeJailBrokenResult(param.getResult());
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " JailBrokenResult hooks failed: " + t.getMessage());
}
}
private static void hookAppSecurityManager(XC_LoadPackage.LoadPackageParam lpparam) {
// addIntoQueue 勿拦:拦截后 Promon 会走 native _exit fallback
hookReturnFalse(lpparam,
"my.com.tngdigital.common.security.malwarescan.MalwareScanUtils",
"isShowErrorScreen");
}
private static void hookSecurityBooleanChecks(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : BOOLEAN_HOOK_CLASSES) {
RootBypassHelper.hookSecurityClass(lpparam, className);
}
}
private static void hookSecurityErrorActivity(XC_LoadPackage.LoadPackageParam lpparam) {
hookSecurityErrorLaunch(lpparam);
hookGenericSecurityErrorFinish();
XC_MethodHook closeSecurityScreen = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
XposedBridge.log(TAG + " closing " + activity.getClass().getSimpleName());
activity.finish();
}
};
for (String activityClass : new String[]{
SECURITY_ERROR_ACTIVITY,
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
}) {
hookAllOnCreateMethods(lpparam, activityClass, closeSecurityScreen);
}
}
private static void hookAllOnCreateMethods(
XC_LoadPackage.LoadPackageParam lpparam,
String className,
XC_MethodHook hook) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"onCreate".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, hook);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " onCreate overload(s) in " + className);
} else {
XposedBridge.log(TAG + " no onCreate in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " " + className + " onCreate hook failed: " + t.getMessage());
}
}
/** SecurityErrorActivity 为 Compose无独立 onCreate在 Activity 基类统一 finish。 */
private static void hookGenericSecurityErrorFinish() {
try {
XposedHelpers.findAndHookMethod(
Activity.class,
"onCreate",
Bundle.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
String name = activity.getClass().getName();
if (name.contains("SecurityError")) {
XposedBridge.log(TAG + " finishing " + name);
activity.finish();
}
}
});
XposedBridge.log(TAG + " hooked Activity.onCreate SecurityError finish");
} catch (Throwable t) {
XposedBridge.log(TAG + " generic SecurityError finish failed: " + t.getMessage());
}
}
/** 拦截 Security 状态机启动 SecurityErrorActivity。 */
private static void hookSecurityErrorLaunch(XC_LoadPackage.LoadPackageParam lpparam) {
// launchProcessNextSecurityStateIfIdle / addIntoQueue 勿拦:
// 拦截后 Promon 会立刻走 native exit_group(1)。
// 杀进程改由 finishAllActivityAndKillApp / showSecurityScreenForState 兜底。
XposedBridge.log(TAG + " skip launchProcessNext noop (avoid native exit fallback)");
}
private static void hookReturnFalse(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!methodName.equals(method.getName())) {
continue;
}
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class && returnType != Boolean.class) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " overload(s) "
+ className + "#" + methodName + " -> false");
} else {
XposedBridge.log(TAG + " no boolean method " + className + "#" + methodName);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + "#" + methodName + ": " + t.getMessage());
}
}
private static void hookNoArgVoid(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
hookAllMethodsNoop(lpparam, className, methodName);
}
private static void setSafeHookResult(XC_MethodHook.MethodHookParam param, Method method) {
Class<?> returnType = method.getReturnType();
if (returnType == void.class) {
param.setResult(null);
} else if (returnType == boolean.class) {
param.setResult(false);
} else if (returnType == int.class) {
param.setResult(0);
} else if (returnType == long.class) {
param.setResult(0L);
} else if (returnType == float.class) {
param.setResult(0f);
} else if (returnType == double.class) {
param.setResult(0d);
} else if (returnType == byte.class) {
param.setResult((byte) 0);
} else if (returnType == short.class) {
param.setResult((short) 0);
} else if (returnType == char.class) {
param.setResult((char) 0);
} else if (returnType == Boolean.class) {
param.setResult(Boolean.FALSE);
} else if (returnType == Integer.class) {
param.setResult(Integer.valueOf(0));
} else if (returnType == Long.class) {
param.setResult(Long.valueOf(0L));
} else if (returnType == Float.class) {
param.setResult(Float.valueOf(0f));
} else if (returnType == Double.class) {
param.setResult(Double.valueOf(0d));
} else if (returnType == String.class) {
param.setResult("");
} else if (returnType == byte[].class) {
param.setResult(new byte[0]);
} else {
param.setResult(null);
}
}
}