feat(tng): TNG eWallet Promon bypass、Zygisk 信号守卫与注册链保护
新增 TngRootBypassHook 与 tng_exit_guard 模块,修复 BAL 强拉、SIGABRT/pc==lr SEGV 崩溃,验证可进入注册页。
This commit is contained in:
@@ -3,6 +3,7 @@ package com.miraclegarden.smsmessage.xposed;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.MariBankRootBypassHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.MariBankShpsNativeHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.SuncorpBankMessageHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.TngRootBypassHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.SqliteMessageHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.TelegramMessageHook;
|
||||
import com.miraclegarden.smsmessage.xposed.hook.UpBankMessageHook;
|
||||
@@ -63,6 +64,11 @@ public class MainHook implements IXposedHookLoadPackage {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TngRootBypassHook.isTargetPackage(lpparam.packageName)) {
|
||||
TngRootBypassHook.install(lpparam);
|
||||
return;
|
||||
}
|
||||
|
||||
SqliteMessageHook.install(lpparam);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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.ewallet(v1.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 会取消旧 Runnable(Application 兜底 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-circuit:a/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 后台 Runnable(bl#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:探测命令 stub;10101 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 并行的设备指纹/风控 SDK(libtiger_tally.so)。
|
||||
*
|
||||
* ANR 根因(2026-07-31):init 链(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user