chore(tng): 清理死代码并停止跟踪 reverse/dumps
精简 login-first 不可达 Hook,忽略调试产物目录,从仓库移除已入库的 dumps 文件。
This commit is contained in:
@@ -13,7 +13,6 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Process;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
@@ -45,20 +44,9 @@ public final class TngRootBypassHook {
|
||||
/** Promon 混淆包名:1.9.10 为 vhvlnqgy,旧版为 xwwqazamx。 */
|
||||
private static final String[] PROMON_PKG_PREFIXES = {"vhvlnqgy", "xwwqazamx"};
|
||||
|
||||
/**
|
||||
* 登录优先:少 Hook、少短路 native-bridge,避免 JNI DeleteLocalRef 损坏 → Runtime abort。
|
||||
* 能进 UserLogin 后再逐步打开诊断 Hook。
|
||||
*/
|
||||
private static final boolean LOGIN_FIRST_MINIMAL = true;
|
||||
|
||||
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",
|
||||
@@ -71,8 +59,6 @@ public final class TngRootBypassHook {
|
||||
};
|
||||
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",
|
||||
@@ -90,8 +76,6 @@ public final class TngRootBypassHook {
|
||||
"EmailOtpVerificationActivity",
|
||||
};
|
||||
|
||||
private static volatile int captchaDialogShowCount = 0;
|
||||
|
||||
private static final String[] REGISTRATION_RPC_MARKERS = {
|
||||
"phonecheck", "com.abl.wallet.phone", "com.abl.wallet.otp",
|
||||
"customer.registration", "customer.verify", "customer.login",
|
||||
@@ -106,7 +90,52 @@ public final class TngRootBypassHook {
|
||||
return PACKAGE.equals(packageName);
|
||||
}
|
||||
|
||||
private static final ThreadLocal<String> CURRENT_REQUEST_URL = new ThreadLocal<>();
|
||||
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());
|
||||
|
||||
hookEarlyAttachLog(lpparam);
|
||||
hookConscryptStatsLogGuard();
|
||||
|
||||
// 登录优先:藏 root + 拦自杀 Java 链 + bl#a/b,不碰 native-bridge 探测短路。
|
||||
hookSplashForceLogin(lpparam);
|
||||
hookLoginDismissSplash(lpparam);
|
||||
// 点区号会走 onCountryClick → i7.l loading;真 show → HWUI setName/gralloc abort
|
||||
hookLoadingDialogSkip();
|
||||
// setName 一律 noop(A16+Zygisk 下 native setName→dlopen libandroid 易 ART abort)。
|
||||
// 区号 Compose 必须 HW:软件绘制会 IllegalArgumentException(hardware bitmaps)。
|
||||
hookHardwareRendererSetNameNoop();
|
||||
hookCallingCodeAllowHwSurface(lpparam);
|
||||
|
||||
RootBypassHelper.hookFileExists(lpparam);
|
||||
RootBypassHelper.hookRuntimeExec(lpparam);
|
||||
RootBypassHelper.hookSystemGetProperty(lpparam);
|
||||
ProcMapsFilterHook.install(lpparam);
|
||||
|
||||
hookAntiSuicide();
|
||||
hookPromonSuicideUpstream(lpparam);
|
||||
hookUncaughtPromonException(lpparam);
|
||||
hookKillApplicationHandler(lpparam);
|
||||
hookBlockSecurityErrorLaunch(lpparam);
|
||||
hookPromonNativeGuard(lpparam);
|
||||
hookPromonLifecycle(lpparam);
|
||||
hookActivityThreadExit(lpparam);
|
||||
hookFinishAllActivityAndKillApp(lpparam);
|
||||
hookSecurityUrlOpeners(lpparam);
|
||||
hookJailBroken(lpparam);
|
||||
hookJailBrokenRpc(lpparam);
|
||||
hookAppSecurityManager(lpparam);
|
||||
hookAppSecurityCallbacks(lpparam);
|
||||
hookSecurityErrorActivity(lpparam);
|
||||
// seccomp 开着时必须 stub TigerTally init,否则 fork getprop 永不退出 → App.onCreate ANR
|
||||
hookTigerTally(lpparam);
|
||||
hookTigerTallyAppWrappers(lpparam);
|
||||
XposedBridge.log(TAG + " login-first hooks armed");
|
||||
}
|
||||
|
||||
/** 进程启动最早打点,便于确认 LSPosed 是否注入(注册闪退常因 hook 未生效)。 */
|
||||
private static void hookEarlyAttachLog(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
@@ -130,13 +159,6 @@ public final class TngRootBypassHook {
|
||||
/** 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) {
|
||||
@@ -152,7 +174,7 @@ public final class TngRootBypassHook {
|
||||
public void run() {
|
||||
pendingForceLoginRunnable = null;
|
||||
try {
|
||||
if (registrationFlowActive || isTopActivityRegistrationFlow(appCtx)) {
|
||||
if (isTopActivityRegistrationFlow(appCtx)) {
|
||||
XposedBridge.log(TAG + " skip force login (" + reason + ", registration flow)");
|
||||
return;
|
||||
}
|
||||
@@ -224,106 +246,6 @@ public final class TngRootBypassHook {
|
||||
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",
|
||||
"captcha", "nc_token", "nvc", "slide", "security",
|
||||
};
|
||||
|
||||
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()
|
||||
+ " minimal=" + LOGIN_FIRST_MINIMAL);
|
||||
|
||||
hookEarlyAttachLog(lpparam);
|
||||
hookConscryptStatsLogGuard();
|
||||
if (LOGIN_FIRST_MINIMAL) {
|
||||
installLoginFirstMinimal(lpparam);
|
||||
return;
|
||||
}
|
||||
hookRegistrationFlowGuard(lpparam);
|
||||
hookSplashForceLogin(lpparam);
|
||||
hookLoginDismissSplash(lpparam);
|
||||
hookHwuiBySurfacePolicy(lpparam);
|
||||
hookLoadingDialogSkip();
|
||||
hookBottomSelectDialogDiag(lpparam);
|
||||
hookCaptchaDiag(lpparam);
|
||||
hookActivityLifecycleDiag(lpparam);
|
||||
hookPromonApService(lpparam);
|
||||
hookPromonBroadcastReceiver(lpparam);
|
||||
hookAppAttachForceLogin(lpparam);
|
||||
|
||||
RootBypassHelper.hookFileExists(lpparam);
|
||||
RootBypassHelper.hookRuntimeExec(lpparam);
|
||||
RootBypassHelper.hookSystemGetProperty(lpparam);
|
||||
ProcMapsFilterHook.install(lpparam);
|
||||
|
||||
hookAntiSuicide();
|
||||
hookPromonSuicideUpstream(lpparam);
|
||||
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);
|
||||
}
|
||||
|
||||
/** 最小集:藏 root + 拦自杀 Java 链 + bl#a/b,不碰 native-bridge 探测短路。 */
|
||||
private static void installLoginFirstMinimal(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
hookSplashForceLogin(lpparam);
|
||||
hookLoginDismissSplash(lpparam);
|
||||
// 点区号会走 onCountryClick → i7.l loading;真 show → HWUI setName/gralloc abort
|
||||
hookLoadingDialogSkip();
|
||||
// setName 一律 noop(A16+Zygisk 下 native setName→dlopen libandroid 易 ART abort)。
|
||||
// 区号 Compose 必须 HW:软件绘制会 IllegalArgumentException(hardware bitmaps)。
|
||||
hookHardwareRendererSetNameNoop();
|
||||
hookCallingCodeAllowHwSurface(lpparam);
|
||||
|
||||
RootBypassHelper.hookFileExists(lpparam);
|
||||
RootBypassHelper.hookRuntimeExec(lpparam);
|
||||
RootBypassHelper.hookSystemGetProperty(lpparam);
|
||||
ProcMapsFilterHook.install(lpparam);
|
||||
|
||||
hookAntiSuicide();
|
||||
hookPromonSuicideUpstream(lpparam);
|
||||
hookUncaughtPromonException(lpparam);
|
||||
hookKillApplicationHandler(lpparam);
|
||||
hookBlockSecurityErrorLaunch(lpparam);
|
||||
hookPromonNativeGuard(lpparam);
|
||||
hookPromonLifecycle(lpparam);
|
||||
hookActivityThreadExit(lpparam);
|
||||
hookFinishAllActivityAndKillApp(lpparam);
|
||||
hookSecurityUrlOpeners(lpparam);
|
||||
hookJailBroken(lpparam);
|
||||
hookJailBrokenRpc(lpparam);
|
||||
hookAppSecurityManager(lpparam);
|
||||
hookAppSecurityCallbacks(lpparam);
|
||||
hookSecurityErrorActivity(lpparam);
|
||||
// seccomp 开着时必须 stub TigerTally init,否则 fork getprop 永不退出 → App.onCreate ANR
|
||||
hookTigerTally(lpparam);
|
||||
hookTigerTallyAppWrappers(lpparam);
|
||||
XposedBridge.log(TAG + " login-first minimal hooks armed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Zygisk/命名空间下 android.util.StatsLog native 常 UnsatisfiedLinkError,
|
||||
* Conscrypt TLS 指标线程一写就炸 → ART fatal。直接 noop 指标写入。
|
||||
@@ -365,276 +287,6 @@ public final class TngRootBypassHook {
|
||||
XposedBridge.log(TAG + " conscrypt/StatsLog guard hooked n=" + hooked);
|
||||
}
|
||||
|
||||
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("ebuckler")
|
||||
|| lower.contains("imgw")
|
||||
|| lower.contains("abl.wallet")
|
||||
|| lower.contains("alipay")
|
||||
|| lower.contains("aliyun")
|
||||
|| lower.contains("captcha")
|
||||
|| lower.contains("dun.163")
|
||||
|| lower.contains("nc_token")
|
||||
|| 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());
|
||||
}
|
||||
}
|
||||
|
||||
/** 仅记录注册/登录链 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());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Android 16 HWUI 策略(经 APK 逆向核对):
|
||||
* - i7.l:一律 skip show(真 show → gralloc ABRT)
|
||||
* - Login / Splash / 普通 View 页:清 HW flag + 拦 enableHW(软件绘制)
|
||||
* - UserSearchCallingCodeActivity:Compose CallingListScreen/LazyColumn,
|
||||
* 软件绘制 = 黑屏;必须完整 HW(与 BottomSelect 相同)
|
||||
* - BottomSelect:完整 HW(关了会黑)
|
||||
* - captcha:软件层弹窗,不放行 enableHW
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> blockSetNameForDialog = new ThreadLocal<>();
|
||||
/** BottomSelect Dialog.show 期间放行 enableHW。 */
|
||||
private static final ThreadLocal<Boolean> allowHwForDialog = new ThreadLocal<>();
|
||||
|
||||
/** 区号 BottomSelect 弹窗可见。 */
|
||||
private static volatile boolean bottomSelectDialogActive = false;
|
||||
|
||||
/**
|
||||
* 区号 Compose Activity 生命周期标记。
|
||||
* 必须在 execStart/onCreate 前设为 true,供 enableHardwareAcceleration 放行。
|
||||
@@ -669,223 +321,6 @@ public final class TngRootBypassHook {
|
||||
return action != null && action.contains("CallingCode");
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose 区号页 + BottomSelect 必须放行 enableHW。
|
||||
* 其它页面拦 enableHW 且清 FLAG,避免半开 HW 黑屏。
|
||||
*/
|
||||
private static boolean shouldAllowHwSurface() {
|
||||
return callingCodeSurfaceActive
|
||||
|| bottomSelectDialogActive
|
||||
|| Boolean.TRUE.equals(allowHwForDialog.get());
|
||||
}
|
||||
|
||||
private static boolean shouldBlockSetNameOnMain() {
|
||||
if (shouldAllowHwSurface()) {
|
||||
return false;
|
||||
}
|
||||
// 区号页绘制期也不拦 setName(软件层下 setName 较少);仅 Dialog 窗口期拦
|
||||
return Boolean.TRUE.equals(blockSetNameForDialog.get());
|
||||
}
|
||||
|
||||
/** 必须在 ViewRoot 附着前清 flag,否则拦 enableHW 会黑屏。 */
|
||||
private static void clearHardwareAcceleratedFlag(Activity activity) {
|
||||
try {
|
||||
Window window = activity.getWindow();
|
||||
if (window != null) {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " clear HW flag failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 区号 BottomSelect:须 hook 父类 DialogFragment.show/onStart(Kotlin 子类不 override show)。
|
||||
* 勿关 BottomSheet HW 加速(Android 16 黑屏);可见期间放行主线程 setName。
|
||||
*/
|
||||
private static void hookBottomSelectDialogDiag(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
int hooked = 0;
|
||||
try {
|
||||
Class<?> df = XposedHelpers.findClass(
|
||||
"androidx.fragment.app.DialogFragment", lpparam.classLoader);
|
||||
Class<?> fm = XposedHelpers.findClass(
|
||||
"androidx.fragment.app.FragmentManager", lpparam.classLoader);
|
||||
XposedHelpers.findAndHookMethod(df, "show", fm, String.class, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
String owner = param.thisObject.getClass().getName();
|
||||
if (!owner.contains("BottomSelect")) {
|
||||
return;
|
||||
}
|
||||
bottomSelectDialogActive = true;
|
||||
XposedBridge.log(TAG + " BottomSelect show " + owner);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
XposedHelpers.findAndHookMethod(df, "onStart", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
String owner = param.thisObject.getClass().getName();
|
||||
if (!owner.contains("BottomSelect")) {
|
||||
return;
|
||||
}
|
||||
bottomSelectDialogActive = true;
|
||||
XposedBridge.log(TAG + " BottomSelect onStart " + owner);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
XC_MethodHook deactivateHook = new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
String owner = param.thisObject.getClass().getName();
|
||||
if (!owner.contains("BottomSelect")) {
|
||||
return;
|
||||
}
|
||||
bottomSelectDialogActive = false;
|
||||
XposedBridge.log(TAG + " BottomSelect inactive "
|
||||
+ param.method.getName() + " " + owner);
|
||||
}
|
||||
};
|
||||
XposedHelpers.findAndHookMethod(df, "onStop", deactivateHook);
|
||||
XposedHelpers.findAndHookMethod(df, "onDestroyView", deactivateHook);
|
||||
hooked += 2;
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " DialogFragment BottomSelect hook failed: " + t.getMessage());
|
||||
}
|
||||
try {
|
||||
Class<?> sheet = XposedHelpers.findClass(
|
||||
"com.google.android.material.bottomsheet.BottomSheetDialog", lpparam.classLoader);
|
||||
XposedHelpers.findAndHookMethod(sheet, "show", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
bottomSelectDialogActive = true;
|
||||
XposedBridge.log(TAG + " BottomSheetDialog.show active");
|
||||
}
|
||||
});
|
||||
XposedHelpers.findAndHookMethod(sheet, "dismiss", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
bottomSelectDialogActive = false;
|
||||
XposedBridge.log(TAG + " BottomSheetDialog.dismiss inactive");
|
||||
}
|
||||
});
|
||||
hooked += 2;
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " BottomSheetDialog hook skip: " + t.getMessage());
|
||||
}
|
||||
XposedBridge.log(TAG + " hooked BottomSelect lifecycle n=" + hooked);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全验证诊断:只 hook 关键类 + WebView,避免全量 58 方法拖垮主线程。
|
||||
*/
|
||||
private static void hookCaptchaDiag(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
int total = 0;
|
||||
for (String className : new String[]{
|
||||
"com.aliyun.captcha.Captcha",
|
||||
"com.aliyun.captcha.CaptchaWebViewDialog",
|
||||
}) {
|
||||
total += hookAllConcreteMethods(lpparam.classLoader, className, "captcha");
|
||||
try {
|
||||
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
|
||||
for (Class<?> inner : clazz.getDeclaredClasses()) {
|
||||
String n = inner.getSimpleName();
|
||||
if (!n.contains("Callback") && !n.contains("Listener")
|
||||
&& !n.contains("Completion")) {
|
||||
continue;
|
||||
}
|
||||
total += hookAllConcreteMethodsOnClass(inner, "captcha");
|
||||
XposedBridge.log(TAG + " captcha inner " + inner.getName());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " captcha inner scan " + className + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
hookCaptchaWebView(lpparam);
|
||||
XposedBridge.log(TAG + " captcha diag hooked methods n=" + total);
|
||||
}
|
||||
|
||||
private static int hookAllConcreteMethods(
|
||||
ClassLoader loader, String className, String tag) {
|
||||
try {
|
||||
Class<?> clazz = XposedHelpers.findClass(className, loader);
|
||||
return hookAllConcreteMethodsOnClass(clazz, tag);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " " + tag + " class skip " + className + ": " + t.getMessage());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static int hookAllConcreteMethodsOnClass(Class<?> clazz, String tag) {
|
||||
int hooked = 0;
|
||||
final String simple = clazz.getName();
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (Modifier.isAbstract(method.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
// 跳过高频噪声
|
||||
String name = method.getName();
|
||||
if ("toString".equals(name) || "hashCode".equals(name)
|
||||
|| "equals".equals(name) || "clone".equals(name)) {
|
||||
continue;
|
||||
}
|
||||
final String hookLabel = simple + "#" + name;
|
||||
final Method hookedMethod = method;
|
||||
try {
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
XposedBridge.log(TAG + " " + tag + " CALL " + hookLabel
|
||||
+ " args=" + describeHookArgs(param.args));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
if (param.hasThrowable()) {
|
||||
XposedBridge.log(TAG + " " + tag + " ERR " + hookLabel
|
||||
+ " " + param.getThrowable().getClass().getSimpleName()
|
||||
+ ": " + param.getThrowable().getMessage());
|
||||
return;
|
||||
}
|
||||
Object result = param.getResult();
|
||||
if (result == null
|
||||
&& (hookedMethod.getReturnType() == void.class
|
||||
|| hookedMethod.getReturnType() == Void.class)) {
|
||||
return;
|
||||
}
|
||||
XposedBridge.log(TAG + " " + tag + " RET " + hookLabel
|
||||
+ " -> " + describeHookResult(result));
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " " + tag + " hook fail " + hookLabel
|
||||
+ ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
if (hooked > 0) {
|
||||
XposedBridge.log(TAG + " " + tag + " hooked " + hooked + " on " + simple);
|
||||
}
|
||||
return hooked;
|
||||
}
|
||||
|
||||
private static String describeHookResult(Object result) {
|
||||
if (result == null) {
|
||||
return "null";
|
||||
}
|
||||
if (result instanceof String) {
|
||||
String text = (String) result;
|
||||
return "String(" + (text.length() > 200 ? text.substring(0, 200) + "..." : text) + ")";
|
||||
}
|
||||
if (result instanceof byte[]) {
|
||||
return "byte[" + ((byte[]) result).length + "]";
|
||||
}
|
||||
if (result instanceof Boolean || result instanceof Number) {
|
||||
return String.valueOf(result);
|
||||
}
|
||||
return result.getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(result));
|
||||
}
|
||||
|
||||
private static String shortStack(int maxFrames) {
|
||||
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -910,124 +345,13 @@ public final class TngRootBypassHook {
|
||||
return sb.length() == 0 ? "(empty)" : sb.toString();
|
||||
}
|
||||
|
||||
/** Captcha WebView:页面加载/错误/JS 回调全打点。 */
|
||||
private static void hookCaptchaWebView(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
"android.webkit.WebView", lpparam.classLoader, "loadUrl", String.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
String url = (String) param.args[0];
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
String lower = url.toLowerCase(Locale.US);
|
||||
if (lower.contains("captcha") || lower.contains("aliyun")
|
||||
|| lower.contains("nvc") || lower.contains("nc_")
|
||||
|| lower.contains("slide") || lower.startsWith("javascript:")) {
|
||||
XposedBridge.log(TAG + " captcha WebView.loadUrl "
|
||||
+ (url.length() > 300 ? url.substring(0, 300) + "..." : url));
|
||||
}
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked WebView.loadUrl (captcha filter)");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " WebView.loadUrl captcha hook failed: " + t.getMessage());
|
||||
}
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
"android.webkit.WebView", lpparam.classLoader,
|
||||
"evaluateJavascript", String.class,
|
||||
"android.webkit.ValueCallback",
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
String script = (String) param.args[0];
|
||||
if (script == null) {
|
||||
return;
|
||||
}
|
||||
String lower = script.toLowerCase(Locale.US);
|
||||
if (lower.contains("captcha") || lower.contains("token")
|
||||
|| lower.contains("verify") || lower.contains("nc")
|
||||
|| lower.contains("slide") || lower.contains("callback")) {
|
||||
XposedBridge.log(TAG + " captcha WebView.evalJS "
|
||||
+ (script.length() > 240 ? script.substring(0, 240) + "..." : script));
|
||||
}
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked WebView.evaluateJavascript (captcha filter)");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " WebView.evalJS captcha hook failed: " + t.getMessage());
|
||||
}
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
"android.webkit.WebViewClient",
|
||||
lpparam.classLoader,
|
||||
"onPageFinished",
|
||||
"android.webkit.WebView",
|
||||
String.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
String url = (String) param.args[1];
|
||||
if (url == null) {
|
||||
return;
|
||||
}
|
||||
String lower = url.toLowerCase(Locale.US);
|
||||
if (lower.contains("captcha") || lower.contains("aliyun")
|
||||
|| lower.contains("nvc") || lower.contains("nc_")) {
|
||||
XposedBridge.log(TAG + " captcha WebView.onPageFinished " + url);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " WebViewClient.onPageFinished captcha hook failed: "
|
||||
+ t.getMessage());
|
||||
}
|
||||
// 已有 onReceivedError diag;再补 captcha 专用标记
|
||||
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 req = param.args[1];
|
||||
Object err = param.args[2];
|
||||
Object url = XposedHelpers.callMethod(req, "getUrl");
|
||||
Object desc = XposedHelpers.callMethod(err, "getDescription");
|
||||
String urlStr = String.valueOf(url);
|
||||
if (urlStr.toLowerCase(Locale.US).contains("captcha")
|
||||
|| urlStr.toLowerCase(Locale.US).contains("aliyun")
|
||||
|| urlStr.toLowerCase(Locale.US).contains("nvc")) {
|
||||
XposedBridge.log(TAG + " captcha WebView.error url="
|
||||
+ urlStr + " desc=" + desc);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private static int hookCaptchaCallbackClass(Class<?> clazz, String label) {
|
||||
return hookAllConcreteMethodsOnClass(clazz, "captcha");
|
||||
}
|
||||
|
||||
/** 已 skip show 的 loading Dialog,让 isShowing=true 避免业务层卡死。 */
|
||||
private static final Set<Dialog> skippedLoadingDialogs =
|
||||
Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
/**
|
||||
* i7.l 等 loading:一律 skip show + 伪装 isShowing。
|
||||
* 不在此处开 HW 白名单——区号列表由 startActivity 标记。
|
||||
* Captcha 弹窗清 HW flag,避免 Android 16 gralloc abort。
|
||||
*/
|
||||
private static void hookLoadingDialogSkip() {
|
||||
try {
|
||||
@@ -1039,42 +363,21 @@ public final class TngRootBypassHook {
|
||||
if (isLoadingDialog(owner)) {
|
||||
skippedLoadingDialogs.add(dialog);
|
||||
XposedBridge.log(TAG + " Dialog.show skip loading " + owner
|
||||
+ " callingCode=" + callingCodeSurfaceActive
|
||||
+ " regFlow=" + registrationFlowActive);
|
||||
+ " callingCode=" + callingCodeSurfaceActive);
|
||||
param.setResult(null);
|
||||
return;
|
||||
}
|
||||
if (owner.contains("BottomSelect") || owner.contains("BottomSheet")) {
|
||||
bottomSelectDialogActive = true;
|
||||
allowHwForDialog.set(Boolean.TRUE);
|
||||
} else if (isCaptchaDialog(owner)) {
|
||||
// Android 16:captcha 放行 enableHW → gralloc ABRT 黑屏闪退
|
||||
// 改为软件层弹窗,不进白名单
|
||||
if (isCaptchaDialog(owner)) {
|
||||
try {
|
||||
Dialog captchaDialog = (Dialog) param.thisObject;
|
||||
Window cw = captchaDialog.getWindow();
|
||||
Window cw = dialog.getWindow();
|
||||
if (cw != null) {
|
||||
cw.clearFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
captchaDialogShowCount++;
|
||||
XposedBridge.log(TAG + " Dialog.show CaptchaWebViewDialog #"
|
||||
+ captchaDialogShowCount
|
||||
+ " owner=" + owner
|
||||
+ " hw=software"
|
||||
+ " regFlow=" + registrationFlowActive
|
||||
+ " stack=" + shortStack(8));
|
||||
} else if (!callingCodeSurfaceActive) {
|
||||
blockSetNameForDialog.set(Boolean.TRUE);
|
||||
XposedBridge.log(TAG + " Dialog.show captcha software " + owner);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
blockSetNameForDialog.remove();
|
||||
allowHwForDialog.remove();
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked Dialog.show (skip loading only)");
|
||||
} catch (Throwable t) {
|
||||
@@ -1098,9 +401,6 @@ public final class TngRootBypassHook {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
String owner = param.thisObject.getClass().getName();
|
||||
if (owner.contains("BottomSelect") || owner.contains("BottomSheet")) {
|
||||
bottomSelectDialogActive = false;
|
||||
}
|
||||
if (isLoadingDialog(owner)) {
|
||||
skippedLoadingDialogs.remove(param.thisObject);
|
||||
}
|
||||
@@ -1229,322 +529,6 @@ public final class TngRootBypassHook {
|
||||
XposedBridge.log(TAG + " callingCode allow-HW surface armed");
|
||||
}
|
||||
|
||||
/**
|
||||
* 非区号:清 HW flag + 拦 enableHW(成对,防半开黑屏);
|
||||
* 区号 Compose / BottomSelect:完整 HW(软件绘制 Compose 必黑)。
|
||||
* Dialog.show 窗口期可拦 setName。
|
||||
*/
|
||||
private static void hookHwuiBySurfacePolicy(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
// setName 一律 noop(A16 Zygisk 下 native setName 会 abort)
|
||||
hookHardwareRendererSetNameNoop();
|
||||
|
||||
try {
|
||||
Class<?> vri = XposedHelpers.findClass("android.view.ViewRootImpl", null);
|
||||
int hooked = 0;
|
||||
for (Method method : vri.getDeclaredMethods()) {
|
||||
if (!"enableHardwareAcceleration".equals(method.getName())) {
|
||||
continue;
|
||||
}
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
if (shouldAllowHwSurface()) {
|
||||
return;
|
||||
}
|
||||
param.setResult(null);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
}
|
||||
XposedBridge.log(TAG + " hooked ViewRootImpl.enableHardwareAcceleration n=" + hooked);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " ViewRootImpl HW hook failed: " + t.getMessage());
|
||||
}
|
||||
|
||||
XC_MethodHook markCallingCodeLaunch = new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
Intent intent = extractIntent(param.args);
|
||||
if (isCallingCodeIntent(intent)) {
|
||||
callingCodeSurfaceActive = true;
|
||||
XposedBridge.log(TAG + " callingCode surface ON (launch, HW allowed for Compose)");
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Activity.class, "startActivity", Intent.class, markCallingCodeLaunch);
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Activity.class, "startActivity", Intent.class, Bundle.class, markCallingCodeLaunch);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " startActivity callingCode mark failed: " + t.getMessage());
|
||||
}
|
||||
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 (isCallingCodeIntent(intent)) {
|
||||
callingCodeSurfaceActive = true;
|
||||
XposedBridge.log(TAG
|
||||
+ " callingCode surface ON (execStart, HW allowed for Compose)");
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " execStartActivity callingCode mark failed: " + t.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Instrumentation.class,
|
||||
"callActivityOnCreate",
|
||||
Activity.class,
|
||||
Bundle.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.args[0];
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
String name = activity.getClass().getName();
|
||||
if (isCallingCodeActivity(name)) {
|
||||
callingCodeSurfaceActive = true;
|
||||
XposedBridge.log(TAG
|
||||
+ " callingCode onCreate BEFORE — keep HW for Compose");
|
||||
// Compose 禁止清 FLAG
|
||||
return;
|
||||
}
|
||||
if (!shouldAllowHwSurface()) {
|
||||
clearHardwareAcceleratedFlag(activity);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.args[0];
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
String name = activity.getClass().getName();
|
||||
if (isCallingCodeActivity(name)) {
|
||||
boolean hw = false;
|
||||
try {
|
||||
View decor = activity.getWindow() != null
|
||||
? activity.getWindow().getDecorView()
|
||||
: null;
|
||||
hw = decor != null && decor.isHardwareAccelerated();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
XposedBridge.log(TAG
|
||||
+ " callingCode onCreate AFTER hwAccelerated=" + hw
|
||||
+ " allow=" + shouldAllowHwSurface());
|
||||
return;
|
||||
}
|
||||
if (shouldAllowHwSurface()) {
|
||||
return;
|
||||
}
|
||||
clearHardwareAcceleratedFlag(activity);
|
||||
XposedBridge.log(TAG + " clear HW flag "
|
||||
+ activity.getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
// Android 16 可能走 PersistableBundle 重载
|
||||
try {
|
||||
Class<?> persistable = Class.forName("android.os.PersistableBundle");
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Instrumentation.class,
|
||||
"callActivityOnCreate",
|
||||
Activity.class,
|
||||
Bundle.class,
|
||||
persistable,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.args[0];
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
if (isCallingCodeActivity(activity.getClass().getName())) {
|
||||
callingCodeSurfaceActive = true;
|
||||
XposedBridge.log(TAG
|
||||
+ " callingCode onCreate(Persistable) — keep HW");
|
||||
return;
|
||||
}
|
||||
if (!shouldAllowHwSurface()) {
|
||||
clearHardwareAcceleratedFlag(activity);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Instrumentation.class,
|
||||
"callActivityOnDestroy",
|
||||
Activity.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.args[0];
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
if (isCallingCodeActivity(activity.getClass().getName())) {
|
||||
callingCodeSurfaceActive = false;
|
||||
XposedBridge.log(TAG + " callingCode surface OFF (onDestroy)");
|
||||
}
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked HWUI surface policy (Compose callingCode=HW)");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " HWUI surface policy failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static String describeHookArgs(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 == null) {
|
||||
sb.append("null");
|
||||
} else if (arg instanceof String) {
|
||||
String text = (String) arg;
|
||||
sb.append("String(").append(text.length() > 120
|
||||
? text.substring(0, 120) + "..." : text).append(")");
|
||||
} else if (arg instanceof byte[]) {
|
||||
sb.append("byte[").append(((byte[]) arg).length).append("]");
|
||||
} else {
|
||||
sb.append(arg.getClass().getSimpleName());
|
||||
}
|
||||
}
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** Promon 隔离 Service;打点确认 :goacqowmmt 进程 hook 已注入。 */
|
||||
private static void hookPromonApService(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
Class<?> svc = findPromonClass(lpparam.classLoader, "ap");
|
||||
if (svc == null) {
|
||||
XposedBridge.log(TAG + " Promon ap Service not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final String svcName = svc.getName();
|
||||
XposedHelpers.findAndHookMethod(svc, "onCreate", new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
XposedBridge.log(TAG + " " + svcName + " 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 + " " + svcName + " onStartCommand pid="
|
||||
+ Process.myPid());
|
||||
}
|
||||
});
|
||||
}
|
||||
XposedBridge.log(TAG + " hooked " + svcName + " Service");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " Promon ap Service 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;
|
||||
callingCodeSurfaceActive = false;
|
||||
} else if (name.endsWith(".UserLoginActivity")) {
|
||||
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;
|
||||
@@ -1590,35 +574,6 @@ public final class TngRootBypassHook {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Promon USB 广播 N 跑在主线程,复进时拖死 Looper。 */
|
||||
private static void hookPromonBroadcastReceiver(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
Class<?> clazz = findPromonClass(lpparam.classLoader, "N");
|
||||
if (clazz == null) {
|
||||
XposedBridge.log(TAG + " Promon N receiver not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
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 " + clazz.getName() + " onReceive x" + hooked);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " Promon N hook failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 强拉 Login 后系统 Splash 遮罩常挂在 UserLogin 上(windows=Splash Screen),
|
||||
* 导致复进「未响应」。onCreate/onResume 强制 dismiss。
|
||||
@@ -1754,33 +709,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 volatile long lastSuicideLogAt = 0L;
|
||||
private static volatile int suicideBlockCount = 0;
|
||||
|
||||
@@ -2128,20 +1056,14 @@ public final class TngRootBypassHook {
|
||||
* 登录优先:onActivity* / onApplication* 全部 noop,不调原 native。
|
||||
*/
|
||||
private static void hookPromonLifecycle(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
Class<?> promonExc = findPromonClass(lpparam.classLoader, "W");
|
||||
if (promonExc == null) {
|
||||
promonExc = findPromonClass(lpparam.classLoader, "bd");
|
||||
}
|
||||
final Class<?> promonExcFinal = promonExc;
|
||||
for (String simple : new String[]{"w", "u"}) {
|
||||
hookPromonLifecycleClass(lpparam, simple, promonExcFinal);
|
||||
hookPromonLifecycleClass(lpparam, simple);
|
||||
}
|
||||
}
|
||||
|
||||
private static void hookPromonLifecycleClass(
|
||||
XC_LoadPackage.LoadPackageParam lpparam,
|
||||
String simpleName,
|
||||
Class<?> promonExc) {
|
||||
String simpleName) {
|
||||
Class<?> lifecycleClass = findPromonClass(lpparam.classLoader, simpleName);
|
||||
if (lifecycleClass == null) {
|
||||
return;
|
||||
@@ -2154,41 +1076,19 @@ public final class TngRootBypassHook {
|
||||
if (!name.startsWith("onActivity") && !name.startsWith("onApplication")) {
|
||||
continue;
|
||||
}
|
||||
if (LOGIN_FIRST_MINIMAL) {
|
||||
// 不调原 native:避免 JNI DeleteLocalRef → ART abort
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
setSafeHookResult(param, method);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
if (!param.hasThrowable()) {
|
||||
return;
|
||||
}
|
||||
Throwable t = param.getThrowable();
|
||||
if (isPromonThrowable(t, promonExc)) {
|
||||
XposedBridge.log(TAG + " swallowed " + t.getClass().getSimpleName()
|
||||
+ " in " + lifecycleName + "#" + method.getName());
|
||||
param.setThrowable(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// 不调原 native:避免 JNI DeleteLocalRef → ART abort
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
setSafeHookResult(param, method);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
}
|
||||
if (hooked > 0) {
|
||||
XposedBridge.log(TAG + " hooked " + hooked + " " + lifecycleName
|
||||
+ " lifecycle ("
|
||||
+ (LOGIN_FIRST_MINIMAL ? "noop (login-first)" : "afterHook only")
|
||||
+ ")");
|
||||
}
|
||||
XposedBridge.log(TAG + " hooked " + hooked + " " + lifecycleName
|
||||
+ " lifecycle (noop)");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " " + lifecycleName + " lifecycle hook failed: "
|
||||
+ t.getMessage());
|
||||
XposedBridge.log(TAG + " skip lifecycle " + lifecycleName + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2262,17 +1162,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Class<?> runnableClass = findPromonClass(lpparam.classLoader, "a");
|
||||
@@ -2327,10 +1216,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -2398,122 +1283,6 @@ public final class TngRootBypassHook {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 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)。
|
||||
*
|
||||
@@ -2638,27 +1407,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -2826,93 +1574,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
/** 拦截把 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 {
|
||||
@@ -2936,62 +1597,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
/** Promon native 桥接类:强制 int/boolean 检测返回安全值。 */
|
||||
private static void hookPromonNativeBridge(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
String[] simpleNames = {
|
||||
"F", "bg", "b", "c", "d", "h", "k", "l", "m", "o", "s", "t", "z",
|
||||
};
|
||||
int total = 0;
|
||||
for (String simpleName : simpleNames) {
|
||||
Class<?> clazz = findPromonClass(lpparam.classLoader, simpleName);
|
||||
if (clazz != null) {
|
||||
total += hookPromonIntBooleanMethods(clazz);
|
||||
}
|
||||
}
|
||||
if (total > 0) {
|
||||
XposedBridge.log(TAG + " Promon native-bridge total hooks=" + total);
|
||||
}
|
||||
}
|
||||
|
||||
private static int hookPromonIntBooleanMethods(Class<?> clazz) {
|
||||
int count = 0;
|
||||
try {
|
||||
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",
|
||||
@@ -3051,17 +1656,6 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -3491,12 +2085,6 @@ public final class TngRootBypassHook {
|
||||
"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();
|
||||
|
||||
Reference in New Issue
Block a user