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

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

View File

@@ -3,6 +3,8 @@ package com.miraclegarden.smsmessage.xposed.hook;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;
import java.util.Set;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
@@ -17,25 +19,70 @@ public final class MariBankAttestationHook {
private static final String TAG = "notiMessageHook/MariBankAttest";
private static volatile boolean installed = false;
private static volatile boolean probesInstalled = false;
private static volatile boolean loadClassWatcherInstalled = false;
private static final Set<String> HOOKED_ATTESTATION_CLASSES = new HashSet<>();
private MariBankAttestationHook() {
}
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
public static int installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (!probesInstalled) {
hookEnvironmentProbes(lpparam);
probesInstalled = true;
}
installed = true;
installLoadClassWatcher(lpparam);
RootBypassHelper.hookSecurityClass(lpparam, "com.shopee.bke.biz.base.risk.a");
int n = 0;
n += hookAttestationClass(lpparam, "com.shopee.shpssdkbank.wvvvuwwu");
n += hookAttestationClass(lpparam, "com.shopee.shpssdk.wvvvuwwu");
n += hookAttestationClass(lpparam,
"com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu");
for (String className : MariBankRegionProfile.shpsAttestationCoreClasses(lpparam.packageName)) {
n += hookAttestationClass(lpparam, className);
}
n += hookKnownAttestationMethods(lpparam);
hookEnvironmentProbes(lpparam);
XposedBridge.log(TAG + " attestation hooks=" + n);
if (n > 0) {
XposedBridge.log(TAG + " attestation hooks=" + n
+ " region=" + MariBankRegionProfile.label(lpparam.packageName)
+ " classes=" + HOOKED_ATTESTATION_CLASSES.size());
}
return n;
}
private static void installLoadClassWatcher(XC_LoadPackage.LoadPackageParam lpparam) {
if (loadClassWatcherInstalled) {
return;
}
loadClassWatcherInstalled = true;
try {
XposedHelpers.findAndHookMethod(
ClassLoader.class,
"loadClass",
String.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() != null) {
return;
}
String name = (String) param.args[0];
if (name == null) {
return;
}
if (name.contains("shpssdk")
|| name.contains("CharacterCrypto")
|| name.contains("bke.biz.base.risk")) {
if (MariBankRegionProfile.isSingapore(lpparam.packageName)
&& MariBankRegionProfile.isShpssdkLegacyPackage(name)) {
return;
}
installLate(lpparam);
}
}
});
XposedBridge.log(TAG + " ClassLoader attestation watcher OK");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip ClassLoader watcher: " + t.getMessage());
}
}
/** 逆向确认的 attestation / requestDefense 桥接方法。 */
@@ -44,7 +91,11 @@ public final class MariBankAttestationHook {
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vvuwuuvuu");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "wwvwvwuvv");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuuwv");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuwvw");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuwvvw");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.wvvvuwwu", "vuwuuwvu");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wuvwuvwwu");
count += hookMethodByName(lpparam, "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv", "wwvuwuwvu");
return count;
}
@@ -69,6 +120,9 @@ public final class MariBankAttestationHook {
private static int hookAttestationClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
if (HOOKED_ATTESTATION_CLASSES.contains(className)) {
return 0;
}
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
@@ -80,6 +134,9 @@ public final class MariBankAttestationHook {
count++;
}
}
if (count > 0) {
HOOKED_ATTESTATION_CLASSES.add(className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
}
@@ -87,9 +144,28 @@ public final class MariBankAttestationHook {
}
private static boolean hookAttestationMethod(String className, Method method) {
final String methodName = method.getName();
final boolean isVuwuuwvw = "vuwuuwvw".equals(methodName);
Class<?> rt = method.getReturnType();
if (rt == String.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
private byte[][] vuwuInputs;
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (!isVuwuuwvw) {
return;
}
vuwuInputs = new byte[param.args.length][];
for (int i = 0; i < param.args.length; i++) {
if (param.args[i] instanceof byte[]) {
byte[] b = (byte[]) param.args[i];
vuwuInputs[i] = b;
XposedBridge.log(TAG + " >> vuwuuwvw in" + i + " len=" + b.length);
}
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
@@ -97,6 +173,9 @@ public final class MariBankAttestationHook {
return;
}
String s = (String) result;
if (isVuwuuwvw) {
MariBankAttestationJsonUtil.logVuwuuwvwCall(vuwuInputs, s);
}
String sanitized = sanitizeAttestationString(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
@@ -215,12 +294,9 @@ public final class MariBankAttestationHook {
if (pb == null || pb.command() == null) {
return;
}
String joined = String.join(" ", pb.command()).toLowerCase();
if (joined.contains(" su") || joined.startsWith("su")
|| joined.contains("magisk") || joined.contains("which su")
|| joined.contains("getprop ro.debuggable")) {
XposedBridge.log(TAG + " blocked ProcessBuilder: " + joined);
throw new SecurityException("blocked root probe");
if (ProbeGuard.isBlockedCommand(pb.command())) {
param.setResult(ProbeGuard.fakeFailedProcess(
String.join(" ", pb.command())));
}
}
}

View File

@@ -0,0 +1,251 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.util.Base64;
import org.json.JSONArray;
import org.json.JSONObject;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.robv.android.xposed.XposedBridge;
/**
* 解析 {@code vuwuuwvw} 返回的 obfuscated-key JSONkey 多为 8 位 hex
*/
final class MariBankAttestationJsonUtil {
private static final String TAG = "notiMessageHook/MariBankAttestJson";
private static final Pattern HEX_KEY = Pattern.compile("^[0-9a-fA-F]{8}$");
private static final Pattern SUSPICIOUS = Pattern.compile(
"(root|hook|xposed|lsposed|magisk|frida|substrate|emulator|debug|adb|"
+ "selinux|su\\b|/proc/|zygisk|riru|shamiko|tamper|integrity|"
+ "jailbreak|virtual|mock|proxy|vpn|developer)",
Pattern.CASE_INSENSITIVE);
private MariBankAttestationJsonUtil() {
}
static void logVuwuuwvwCall(byte[][] inputs, String output) {
if (inputs != null) {
for (int i = 0; i < inputs.length; i++) {
if (inputs[i] != null && inputs[i].length > 0) {
MariBankCaptureUtil.logBytes("vuwuuwvw.in" + i, inputs[i]);
}
}
}
if (output == null || output.isEmpty()) {
return;
}
boolean registerLike = isRegisterAttestationCall(inputs, output);
String label = registerLike ? "vuwuuwvw.out REGISTER" : "vuwuuwvw.out";
MariBankCaptureUtil.logText(label, output);
parseAndSummarize(output, registerLike);
}
static boolean isRegisterAttestationCall(byte[][] inputs, String output) {
if (inputs != null) {
for (byte[] in : inputs) {
if (in != null && in.length >= 200 && in.length <= 400) {
return true;
}
}
}
return output != null && output.length() >= 2200 && output.length() <= 2500;
}
static void parseAndSummarize(String json, boolean highlight) {
try {
JSONObject obj = new JSONObject(json);
List<String> keys = new ArrayList<>();
Iterator<String> it = obj.keys();
while (it.hasNext()) {
keys.add(it.next());
}
Collections.sort(keys);
XposedBridge.log(TAG + " keys=" + keys.size()
+ (highlight ? " [REGISTER-LIKE]" : "")
+ " sample=" + keys.subList(0, Math.min(6, keys.size())));
List<String> hits = new ArrayList<>();
for (String key : keys) {
Object val = obj.get(key);
scanValue(key, val, hits);
}
if (!hits.isEmpty()) {
XposedBridge.log(TAG + " SUSPICIOUS count=" + hits.size());
for (int i = 0; i < Math.min(hits.size(), 24); i++) {
XposedBridge.log(TAG + " " + hits.get(i));
}
} else {
XposedBridge.log(TAG + " no plain suspicious strings in values");
}
logKeyGuesses(keys);
} catch (Throwable t) {
XposedBridge.log(TAG + " parse fail: " + t.getMessage()
+ " head=" + json.substring(0, Math.min(120, json.length())));
}
}
private static void scanValue(String key, Object val, List<String> hits) {
if (val instanceof String) {
String s = (String) val;
noteIfSuspicious(key, "str", s, hits);
if (s.length() >= 8 && s.length() <= 512 && looksBase64(s)) {
byte[] decoded = tryBase64(s);
if (decoded != null) {
String inner = new String(decoded, StandardCharsets.UTF_8);
if (isMostlyPrintable(inner)) {
noteIfSuspicious(key, "b64utf8", inner, hits);
} else {
noteIfSuspicious(key, "b64hex", hexPreview(decoded, 32), hits);
}
}
}
return;
}
if (val instanceof Number || val instanceof Boolean) {
String s = String.valueOf(val);
if ("1".equals(s) || "true".equalsIgnoreCase(s)) {
hits.add(key + " =" + s + " (flag?)");
}
return;
}
if (val instanceof JSONObject) {
JSONObject nested = (JSONObject) val;
Iterator<String> it = nested.keys();
while (it.hasNext()) {
String nk = it.next();
try {
scanValue(key + "." + nk, nested.get(nk), hits);
} catch (Throwable ignored) {
}
}
return;
}
if (val instanceof JSONArray) {
JSONArray arr = (JSONArray) val;
for (int i = 0; i < arr.length(); i++) {
try {
scanValue(key + "[" + i + "]", arr.get(i), hits);
} catch (Throwable ignored) {
}
}
}
}
private static void noteIfSuspicious(String key, String kind, String text, List<String> hits) {
if (text == null || text.isEmpty()) {
return;
}
Matcher m = SUSPICIOUS.matcher(text);
if (m.find()) {
String show = text.length() > 120 ? text.substring(0, 120) + "..." : text;
hits.add(key + " " + kind + " hit=" + m.group().toLowerCase(Locale.ROOT) + " val=" + show);
}
}
private static void logKeyGuesses(List<String> keys) {
Set<String> candidates = knownFieldNames();
List<String> matched = new ArrayList<>();
for (String key : keys) {
if (!HEX_KEY.matcher(key).matches()) {
continue;
}
String lower = key.toLowerCase(Locale.ROOT);
for (String name : candidates) {
if (hashKey(name).equals(lower)) {
matched.add(key + "=>" + name);
}
}
}
if (!matched.isEmpty()) {
XposedBridge.log(TAG + " key guesses: " + matched);
}
}
private static Set<String> knownFieldNames() {
Set<String> names = new LinkedHashSet<>();
String[] base = {
"root", "hook", "xposed", "lsposed", "magisk", "frida", "adb", "debug",
"debuggable", "emulator", "simulator", "vpn", "proxy", "mock",
"selinux", "su", "supersu", "zygisk", "riru", "shamiko", "substrate",
"integrity", "safetynet", "playIntegrity", "deviceId", "androidId",
"serial", "fingerprint", "model", "brand", "manufacturer", "board",
"host", "tags", "type", "user", "display", "product", "hardware",
"usb", "wifi", "adb_enabled", "development_settings_enabled",
"RISK_ROOT", "RISK_HOOK", "RISK_USB_ADB", "RISK_WIFI_ADB", "RISK_ADB",
"RISK_EMULATOR", "RISK_DEBUG", "RISK_VPN", "RISK_PROXY", "RISK_MOCK",
"rdVerifyInfo", "deviceFingerprint", "data", "dataKey", "riskToken",
"isRoot", "isHook", "isDebug", "isAdb", "isEmulator", "isVirtual",
"tamper", "jailbreak", "bootloader", "verifiedbootstate", "vbmeta",
"init.svc.adbd", "/proc/self/maps", "RealInterceptorChain",
};
Collections.addAll(names, base);
return names;
}
private static String hashKey(String name) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(name.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 4; i++) {
sb.append(String.format(Locale.ROOT, "%02x", dig[i] & 0xff));
}
return sb.toString();
} catch (Throwable t) {
return "";
}
}
private static boolean looksBase64(String s) {
return s.matches("^[A-Za-z0-9+/=]+$");
}
private static byte[] tryBase64(String s) {
try {
return Base64.decode(s, Base64.DEFAULT);
} catch (Throwable t) {
return null;
}
}
private static boolean isMostlyPrintable(String s) {
if (s == null || s.isEmpty()) {
return false;
}
int ok = 0;
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c >= 0x20 && c < 0x7f) {
ok++;
}
}
return ok * 100 / s.length() >= 85;
}
private static String hexPreview(byte[] b, int max) {
if (b == null) {
return "";
}
int n = Math.min(b.length, max);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
sb.append(String.format(Locale.ROOT, "%02x", b[i] & 0xff));
}
if (b.length > n) {
sb.append("...");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,139 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.util.Base64;
import java.nio.charset.StandardCharsets;
import de.robv.android.xposed.XposedBridge;
/**
* 分段 logcat 输出,避免单条超过 ~4KB 被截断。
*/
final class MariBankCaptureUtil {
static final String TAG = "notiMessageHook/MariBankCapture";
private static final int CHUNK = 3500;
private static final int HEX_PREVIEW = 96;
private MariBankCaptureUtil() {
}
static void logText(String section, String text) {
if (text == null) {
XposedBridge.log(TAG + " [" + section + "] null");
return;
}
if (text.length() <= CHUNK) {
XposedBridge.log(TAG + " [" + section + "] len=" + text.length() + " " + text);
return;
}
int parts = (text.length() + CHUNK - 1) / CHUNK;
XposedBridge.log(TAG + " [" + section + "] len=" + text.length() + " parts=" + parts);
for (int i = 0; i < parts; i++) {
int start = i * CHUNK;
int end = Math.min(start + CHUNK, text.length());
XposedBridge.log(TAG + " [" + section + "] " + (i + 1) + "/" + parts + " "
+ text.substring(start, end));
}
}
static void logBytes(String section, byte[] bytes) {
if (bytes == null) {
XposedBridge.log(TAG + " [" + section + "] null");
return;
}
String utf8 = tryUtf8(bytes);
if (isMostlyPrintable(utf8)) {
logText(section + " utf8", utf8);
} else {
XposedBridge.log(TAG + " [" + section + "] byte[" + bytes.length + "] hex="
+ hexPreview(bytes) + " b64=" + base64(bytes));
}
}
static void logArg(String section, Object arg) {
if (arg == null) {
XposedBridge.log(TAG + " [" + section + "] null");
return;
}
if (arg instanceof String) {
logText(section, (String) arg);
return;
}
if (arg instanceof byte[]) {
logBytes(section, (byte[]) arg);
return;
}
if (arg instanceof String[]) {
String[] arr = (String[]) arg;
XposedBridge.log(TAG + " [" + section + "] String[" + arr.length + "]");
for (int i = 0; i < arr.length; i++) {
logText(section + "[" + i + "]", arr[i]);
}
return;
}
if (arg instanceof byte[][]) {
byte[][] arr = (byte[][]) arg;
XposedBridge.log(TAG + " [" + section + "] byte[][] len=" + arr.length);
for (int i = 0; i < arr.length; i++) {
logBytes(section + "[" + i + "]", arr[i]);
}
return;
}
XposedBridge.log(TAG + " [" + section + "] " + arg.getClass().getName()
+ " = " + String.valueOf(arg));
}
static boolean isBankUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
String lower = url.toLowerCase();
return lower.contains("maribank.com")
|| lower.contains("seabank.ph")
|| lower.contains("/uapi/");
}
private static String tryUtf8(byte[] bytes) {
try {
return new String(bytes, StandardCharsets.UTF_8);
} catch (Throwable t) {
return "";
}
}
static boolean isMostlyPrintable(String s) {
if (s == null || s.isEmpty()) {
return false;
}
int printable = 0;
int sample = Math.min(s.length(), 512);
for (int i = 0; i < sample; i++) {
char c = s.charAt(i);
if (c == '\n' || c == '\r' || c == '\t' || (c >= 32 && c < 127)) {
printable++;
}
}
return printable * 100 / sample >= 85;
}
private static String hexPreview(byte[] bytes) {
int n = Math.min(bytes.length, HEX_PREVIEW);
StringBuilder sb = new StringBuilder(n * 2);
for (int i = 0; i < n; i++) {
sb.append(String.format("%02x", bytes[i] & 0xff));
}
if (bytes.length > n) {
sb.append("...");
}
return sb.toString();
}
private static String base64(byte[] bytes) {
try {
return Base64.encodeToString(bytes, Base64.NO_WRAP);
} catch (Throwable t) {
return "<b64 err>";
}
}
}

View File

@@ -0,0 +1,209 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
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;
/**
* DFPdevice fingerprint初始化诊断register 前若 dfp 为空会弹
* "The system is currently unavailable",且不会发 /uapi/v2/register。
*/
public final class MariBankDfpHook {
private static final String TAG = "notiMessageHook/MariBankDfp";
private static final Set<String> HOOKED_CLASSES = new HashSet<>();
private static volatile boolean loadClassWatcherInstalled = false;
private static volatile boolean registerVmHooked = false;
private MariBankDfpHook() {
}
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
hookRegisterViewModel(lpparam);
hookKnownDfpClasses(lpparam);
installLoadClassWatcher(lpparam);
}
private static void hookRegisterViewModel(XC_LoadPackage.LoadPackageParam lpparam) {
if (registerVmHooked) {
return;
}
String className = "com.shopee.bke.biz.user.viewmodel.RegisterViewModel";
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
registerVmHooked = true;
for (Method method : clazz.getDeclaredMethods()) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String args = param.args == null ? "[]" : Arrays.toString(param.args);
if (args.length() > 800) {
args = args.substring(0, 800) + "...";
}
XposedBridge.log(TAG + " RegisterViewModel."
+ method.getName() + " args=" + args);
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (result == null) {
return;
}
String rs = String.valueOf(result);
if (rs.length() > 600) {
rs = rs.substring(0, 600) + "...";
}
if (rs.isEmpty()
|| rs.toLowerCase().contains("dfp")
|| rs.toLowerCase().contains("error")
|| rs.toLowerCase().contains("unavailable")
|| rs.contains("3100012")
|| rs.contains("4067")) {
XposedBridge.log(TAG + " RegisterViewModel."
+ method.getName() + " result=" + rs);
}
}
});
}
XposedBridge.log(TAG + " RegisterViewModel hooked methods="
+ clazz.getDeclaredMethods().length);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip RegisterViewModel: " + t.getMessage());
}
}
/** 启动时类可能已加载,先尝试已知命名。 */
private static void hookKnownDfpClasses(XC_LoadPackage.LoadPackageParam lpparam) {
String[] candidates = {
"com.shopee.bke.lib.jni.security.CharacterCryptoManager",
"com.shopee.bke.lib.jni.security.CharacterCryptoManagerWrapper",
"com.shopee.bke.lib.jni.utils.NativeEncryptUtilsWrapper",
};
for (String cn : candidates) {
tryHookDfpClass(lpparam, cn);
}
}
private static void installLoadClassWatcher(XC_LoadPackage.LoadPackageParam lpparam) {
if (loadClassWatcherInstalled) {
return;
}
loadClassWatcherInstalled = true;
try {
XposedHelpers.findAndHookMethod(
ClassLoader.class,
"loadClass",
String.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() != null) {
return;
}
String name = (String) param.args[0];
if (name == null) {
return;
}
if (name.contains("CharacterCrypto")
|| name.contains("NativeEncryptUtils")
|| (name.contains("dfp") && name.contains("bke"))) {
tryHookDfpClass(lpparam, name);
}
}
});
XposedBridge.log(TAG + " ClassLoader dfp watcher OK");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip ClassLoader watcher: " + t.getMessage());
}
}
private static void tryHookDfpClass(XC_LoadPackage.LoadPackageParam lpparam, String className) {
if (HOOKED_CLASSES.contains(className)) {
return;
}
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int count = 0;
for (Method method : clazz.getDeclaredMethods()) {
String mn = method.getName();
if (!mn.startsWith("getDfp") && !mn.contains("Fingerprint")) {
continue;
}
if (method.getReturnType() != String.class) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
String s = result instanceof String ? (String) result : null;
if (s == null || s.isEmpty()) {
XposedBridge.log(TAG + " EMPTY " + className + "."
+ method.getName() + " → register may fail");
} else {
String tail = s.length() > 80
? s.substring(0, 40) + "..." + s.substring(s.length() - 20)
: s;
XposedBridge.log(TAG + " " + className + "."
+ method.getName() + " len=" + s.length() + " val=" + tail);
}
}
});
count++;
}
if (count > 0) {
HOOKED_CLASSES.add(className);
XposedBridge.log(TAG + " hooked " + count + " dfp getters in " + className);
}
} catch (Throwable ignored) {
}
}
/** 拦截 dfp 空检查日志源(含 register scene 文案的类方法)。 */
public static void hookDfpEmptyGuards(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"android.util.Log",
lpparam.classLoader,
"e",
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String msg = param.args[1] instanceof String ? (String) param.args[1] : null;
if (msg == null) {
return;
}
String lower = msg.toLowerCase();
if (lower.contains("dfp is empty")
|| lower.contains("getdfp empty")
|| lower.contains("getdfp onerror")) {
XposedBridge.log(TAG + " Log.e: " + msg);
logStack();
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Log.e dfp guard: " + t.getMessage());
}
}
private static void logStack() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
String cn = frame.getClassName();
if (cn.startsWith("com.shopee.bke") || cn.contains("CharacterCrypto")) {
XposedBridge.log(TAG + " at " + cn + "." + frame.getMethodName());
}
}
}
}

View File

@@ -0,0 +1,153 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.util.concurrent.atomic.AtomicInteger;
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;
/**
* MariBank / SeaBank HTTP 全量抓包(仅 OkHttp 头 + 响应体)。
* 加密明文 / attestation 由 {@link MariBankSdkUtilsHook} 负责,勿在此重复 Hook Gson/native。
*/
public final class MariBankFullCaptureHook {
private static final String TAG = MariBankCaptureUtil.TAG;
private static final AtomicInteger SEQ = new AtomicInteger();
private static volatile boolean installed = false;
private MariBankFullCaptureHook() {
}
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
}
installed = true;
int n = hookOkHttpRealCall(lpparam);
XposedBridge.log(TAG + " http capture hooks=" + n);
}
private static int hookOkHttpRealCall(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook hook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object request = XposedHelpers.getObjectField(param.thisObject, "originalRequest");
if (request == null) {
return;
}
captureRequest(request);
} catch (Throwable t) {
XposedBridge.log(TAG + " req capture err: " + t.getMessage());
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!"execute".equals(param.method.getName())) {
return;
}
try {
Object response = param.getResult();
if (response != null) {
captureResponse(response);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " resp capture err: " + t.getMessage());
}
}
};
int count = 0;
for (String className : new String[]{
"okhttp3.RealCall", "okhttp3.internal.connection.RealCall"
}) {
try {
XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "execute", hook);
XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "enqueue",
"okhttp3.Callback", hook);
count += 2;
} catch (Throwable ignored) {
}
}
return count;
}
private static void captureRequest(Object request) {
Object urlObj = XposedHelpers.callMethod(request, "url");
String url = urlObj != null ? String.valueOf(urlObj) : "?";
if (!MariBankCaptureUtil.isBankUrl(url)) {
return;
}
int seq = SEQ.incrementAndGet();
String method = (String) XposedHelpers.callMethod(request, "method");
XposedBridge.log(TAG + " >>> #" + seq + " " + method + " " + url);
logHeaders("reqHdr #" + seq, XposedHelpers.callMethod(request, "headers"));
}
private static void captureResponse(Object response) {
try {
Object request = XposedHelpers.callMethod(response, "request");
Object urlObj = request != null ? XposedHelpers.callMethod(request, "url") : null;
String url = urlObj != null ? String.valueOf(urlObj) : "?";
if (!MariBankCaptureUtil.isBankUrl(url)) {
return;
}
int code = (int) XposedHelpers.callMethod(response, "code");
XposedBridge.log(TAG + " <<< HTTP " + code + " " + url);
logHeaders("respHdr", XposedHelpers.callMethod(response, "headers"));
try {
Object peek = XposedHelpers.callMethod(response, "peekBody", 256L * 1024L);
if (peek != null) {
Object ctObj = XposedHelpers.callMethod(peek, "contentType");
String contentType = ctObj != null ? String.valueOf(ctObj).toLowerCase() : "";
if (contentType.contains("pdf") || contentType.contains("octet-stream")) {
long size = 0L;
try {
size = (long) XposedHelpers.callMethod(peek, "contentLength");
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " <<< respBody skipped binary ct="
+ contentType + " len=" + size + " " + url);
} else {
String body = (String) XposedHelpers.callMethod(peek, "string");
if (body != null && !body.isEmpty()) {
if (body.length() > 512
&& !MariBankCaptureUtil.isMostlyPrintable(body)) {
XposedBridge.log(TAG + " <<< respBody skipped non-text len="
+ body.length() + " " + url);
} else {
MariBankCaptureUtil.logText("respBody", body);
}
}
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " peekBody err: " + t.getMessage());
}
} catch (Throwable t) {
XposedBridge.log(TAG + " captureResponse err: " + t.getMessage());
}
}
private static void logHeaders(String section, Object headers) {
if (headers == null) {
return;
}
try {
int size = (int) XposedHelpers.callMethod(headers, "size");
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
String name = (String) XposedHelpers.callMethod(headers, "name", i);
String value = (String) XposedHelpers.callMethod(headers, "value", i);
sb.append(name).append(": ").append(value).append("\n");
}
MariBankCaptureUtil.logText(section, sb.toString().trim());
} catch (Throwable t) {
XposedBridge.log(TAG + " [" + section + "] headers err: " + t.getMessage());
}
}
}

View File

@@ -0,0 +1,123 @@
package com.miraclegarden.smsmessage.xposed.hook;
/**
* PH vs SG MariBank 差异配置(基于 reverse/apks 扫描,非「换包名即相同」)。
* <p>
* SG 3.2.2dex 内无 {@code com.shopee.shpssdk.*}、无 {@code safemode.b/catchs.a/util.c}、
* 无 {@code jni.utils.d};风控仅 {@code shpssdkbank}。
*/
public final class MariBankRegionProfile {
private MariBankRegionProfile() {
}
public static boolean isSingapore(String packageName) {
return MariBankRootBypassHook.PACKAGE_SG.equals(packageName);
}
public static boolean isPhilippines(String packageName) {
return MariBankRootBypassHook.PACKAGE.equals(packageName);
}
public static String label(String packageName) {
if (isSingapore(packageName)) {
return "SG";
}
if (isPhilippines(packageName)) {
return "PH";
}
return "?";
}
/** PH SafeMode boolean 类SG base/split dex 均无,勿 findClass。 */
public static String[] safeModeBooleanClasses(String packageName) {
if (isPhilippines(packageName)) {
return new String[]{
"com.shopee.bke.lib.safemode.b",
"com.shopee.bke.lib.safemode.catchs.a",
"com.shopee.bke.lib.safemode.util.c",
};
}
return new String[0];
}
/** 两区共有或 SG 专用 risk 类classes3.dex。 */
public static String[] sharedRiskClasses() {
return new String[]{"com.shopee.bke.biz.base.risk.a"};
}
/** SHPSSDK Java 入口SG 仅 bank 包。 */
public static String[] shpsSdkClasses(String packageName) {
if (isSingapore(packageName)) {
return new String[]{"com.shopee.shpssdkbank.SHPSSDK"};
}
return new String[]{
"com.shopee.shpssdk.SHPSSDK",
"com.shopee.shpssdkbank.SHPSSDK",
};
}
public static String[] shpsAssessRiskClasses(String packageName) {
if (isSingapore(packageName)) {
return new String[]{"com.shopee.shpssdkbank.SPSAssessRisk"};
}
return new String[]{
"com.shopee.shpssdkbank.SPSAssessRisk",
"com.shopee.shpssdk.SPSAssessRisk",
};
}
public static String[] shpsCallbackAdapterClasses(String packageName) {
if (isSingapore(packageName)) {
return new String[]{"com.shopee.shpssdkbank.SPSCallbackAdapter"};
}
return new String[]{
"com.shopee.shpssdk.SPSCallbackAdapter",
"com.shopee.shpssdkbank.SPSCallbackAdapter",
};
}
/** attestation / native-coreSG 无 com.shopee.shpssdk.wvvvuwwu。 */
public static String[] shpsAttestationCoreClasses(String packageName) {
if (isSingapore(packageName)) {
return new String[]{
"com.shopee.shpssdkbank.wvvvuwwu",
"com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu",
"com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv",
"com.shopee.shpssdkbank.uwuvuvvww.wvvuuwvwu",
"com.shopee.shpssdkbank.uvuwwuvwv.uvwwuuvvw",
};
}
return new String[]{
"com.shopee.shpssdkbank.wvvvuwwu",
"com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu",
"com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv",
"com.shopee.shpssdkbank.uwuvuvvww.wvvuuwvwu",
"com.shopee.shpssdk.wvvvuwwu",
};
}
/** 加密 JNIPH 有 utils.d 包装SG 仅 uvwuvwuv + uvwwwwuvclasses6.dex。 */
public static boolean hasEncryptWrapperD(String packageName) {
return isPhilippines(packageName);
}
public static boolean hasEncryptHelperUvwwwwuv(String packageName) {
return isSingapore(packageName);
}
/** late hooks 完成条件SG 不依赖 PH safemode booleanHooks。 */
public static boolean isLateHooksReady(
String packageName, int booleanHooks, int encryptHooks, int attestHooks) {
if (isSingapore(packageName)) {
return encryptHooks > 0 || attestHooks > 0;
}
return booleanHooks > 0 || encryptHooks > 0 || attestHooks > 0;
}
public static boolean isShpssdkLegacyPackage(String className) {
return className != null
&& className.startsWith("com.shopee.shpssdk.")
&& !className.startsWith("com.shopee.shpssdkbank.");
}
}

View File

@@ -66,6 +66,45 @@ final class MariBankRegisterPayloadUtil {
out = sb.toString();
XposedBridge.log(TAG + " deviceFingerprint sanitized in register JSON");
}
logRegisterSummary(out);
return out;
}
private static void logRegisterSummary(String json) {
if (json == null) {
return;
}
String cy = extractJsonString(json, "cyCode");
String scene = extractJsonString(json, "scene");
String step = extractJsonString(json, "step");
Matcher fp = DEVICE_FINGERPRINT.matcher(json);
String fpTail = fp.find() ? MariBankRiskTokenUtil.tail(fp.group(1)) : "?";
int dataLen = lengthOfJsonString(json, "\"data\"");
int dataKeyLen = lengthOfJsonString(json, "\"dataKey\"");
XposedBridge.log(TAG + " register summary cy=" + cy + " scene=" + scene
+ " step=" + step + " fpTail=" + fpTail
+ " dataLen=" + dataLen + " dataKeyLen=" + dataKeyLen);
}
private static String extractJsonString(String json, String key) {
Pattern p = Pattern.compile("\"" + Pattern.quote(key) + "\"\\s*:\\s*\"([^\"]*)\"");
Matcher m = p.matcher(json);
return m.find() ? m.group(1) : "?";
}
private static int lengthOfJsonString(String json, String key) {
int idx = json.indexOf(key);
if (idx < 0) {
return -1;
}
int start = json.indexOf('"', idx + key.length());
if (start < 0) {
return -1;
}
int end = json.indexOf('"', start + 1);
if (end < 0) {
return -1;
}
return end - start - 1;
}
}

View File

@@ -4,6 +4,8 @@ import android.app.Activity;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.res.Resources;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
@@ -15,7 +17,9 @@ import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
@@ -63,13 +67,6 @@ public final class MariBankRootBypassHook {
private static final long FINISH_BURST_WINDOW_MS = 800L;
private static final int FINISH_BURST_THRESHOLD = 2;
private static final String[] SAFE_MODE_CLASSES = {
"com.shopee.bke.lib.safemode.b",
"com.shopee.bke.lib.safemode.catchs.a",
"com.shopee.bke.lib.safemode.util.c",
"com.shopee.bke.biz.base.risk.a",
};
private static final String[] ERROR_FLOW_CLASSES = {
"com.shopee.bke.biz.user.errorcodehandler.a",
"com.shopee.bke.biz.user.errorcodehandler.b",
@@ -77,18 +74,33 @@ public final class MariBankRootBypassHook {
"com.shopee.bke.biz.user.viewmodel.RegisterViewModel",
};
private static String[] booleanHookTargets(XC_LoadPackage.LoadPackageParam lpparam) {
String[] ph = MariBankRegionProfile.safeModeBooleanClasses(lpparam.packageName);
String[] shared = MariBankRegionProfile.sharedRiskClasses();
String[] out = new String[ph.length + shared.length];
System.arraycopy(ph, 0, out, 0, ph.length);
System.arraycopy(shared, 0, out, ph.length, shared.length);
return out;
}
private MariBankRootBypassHook() {
}
private static volatile boolean deferredHooksInstalled = false;
private static volatile boolean lateAppHooksInstalled = false;
private static volatile boolean lateHooksComplete = false;
private static volatile boolean lateHooksCoreInstalled = false;
private static volatile boolean lateHandlerRetriesDone = false;
private static volatile int lateHookAttempts = 0;
private static final int MAX_LATE_HOOK_ATTEMPTS = 10;
private static final Set<String> HOOKED_SAFE_MODE_CLASSES = new HashSet<>();
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!isTargetPackage(lpparam.packageName)) {
return;
}
XposedBridge.log(TAG + " install for " + lpparam.packageName);
XposedBridge.log(TAG + " install for " + lpparam.packageName
+ " region=" + MariBankRegionProfile.label(lpparam.packageName));
hookAntiSuicide(lpparam);
hookAntiSoftCrash(lpparam);
scheduleAppHooks(lpparam);
@@ -110,7 +122,7 @@ public final class MariBankRootBypassHook {
@Override
protected void afterHookedMethod(MethodHookParam param) {
MariBankShpsNativeHook.installLateNativeHooks(lpparam);
installLateAppHooks(lpparam);
scheduleLateAppHooks(lpparam);
}
};
try {
@@ -125,34 +137,159 @@ public final class MariBankRootBypassHook {
lpparam.classLoader,
"onCreate",
afterOnCreate);
hookMainActivityForLateHooks(lpparam);
XposedBridge.log(TAG + " waiting attachBaseContext + onCreate for app hooks");
} catch (Throwable t) {
XposedBridge.log(TAG + " BkeApplication hook failed, install now: " + t.getMessage());
installDeferredHooks(lpparam);
MariBankShpsNativeHook.installDeferred(lpparam);
MariBankShpsNativeHook.installLateNativeHooks(lpparam);
installLateAppHooks(lpparam);
scheduleLateAppHooks(lpparam);
}
}
/** onCreate 之后补装:此时 classes11 / SHPSSDK 与 RN SO 均已就绪。 */
private static void installLateAppHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (lateAppHooksInstalled) {
/** SG split-dex 在 onCreate 时类可能未加载MainActivity 再试。 */
private static void hookMainActivityForLateHooks(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"com.shopee.bke.digitalbank.ui.MainActivity",
lpparam.classLoader,
"onResume",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
scheduleLateAppHooks(lpparam);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip MainActivity.onResume retry: " + t.getMessage());
}
}
private static void scheduleLateAppHooks(XC_LoadPackage.LoadPackageParam lpparam) {
tryInstallLateAppHooks(lpparam, false);
}
/** onCreate 之后补装SG 3.2.2 split dex 常需延迟重试。 */
private static void tryInstallLateAppHooks(XC_LoadPackage.LoadPackageParam lpparam) {
tryInstallLateAppHooks(lpparam, false);
}
private static void tryInstallLateAppHooks(
XC_LoadPackage.LoadPackageParam lpparam, boolean fromSplitDex) {
if (lateHooksComplete) {
return;
}
lateAppHooksInstalled = true;
int hooked = 0;
for (String className : SAFE_MODE_CLASSES) {
hooked += hookAllBooleanChecks(lpparam, className);
if (!lateHooksCoreInstalled) {
lateHooksCoreInstalled = true;
hookShpsRisk(lpparam);
hookShpsToken(lpparam);
hookErrorFlowLogging(lpparam);
MariBankFullCaptureHook.installLate(lpparam);
}
hooked += hookShpsRisk(lpparam);
hookShpsToken(lpparam);
hookErrorFlowLogging(lpparam);
MariBankSdkUtilsHook.installLate(lpparam);
MariBankAttestationHook.installLate(lpparam);
XposedBridge.log(TAG + " late app hooks installed, booleanHooks=" + hooked);
int booleanHooks = 0;
for (String className : booleanHookTargets(lpparam)) {
booleanHooks += hookAllBooleanChecks(lpparam, className);
}
int encryptHooks = MariBankSdkUtilsHook.installLate(lpparam);
int attestHooks = MariBankAttestationHook.installLate(lpparam);
MariBankDfpHook.installLate(lpparam);
MariBankDfpHook.hookDfpEmptyGuards(lpparam);
if (MariBankRegionProfile.isLateHooksReady(
lpparam.packageName, booleanHooks, encryptHooks, attestHooks)) {
lateHooksComplete = true;
XposedBridge.log(TAG + " late app hooks ready region="
+ MariBankRegionProfile.label(lpparam.packageName)
+ " booleanHooks=" + booleanHooks
+ " encryptHooks=" + encryptHooks + " attestHooks=" + attestHooks
+ " attempts=" + lateHookAttempts
+ (fromSplitDex ? " viaSplitDex" : ""));
return;
}
if (fromSplitDex) {
XposedBridge.log(TAG + " split-dex class loaded, safemode still pending encryptHooks="
+ encryptHooks + " attestHooks=" + attestHooks);
return;
}
if (lateHandlerRetriesDone) {
return;
}
lateHookAttempts++;
if (lateHookAttempts >= MAX_LATE_HOOK_ATTEMPTS) {
lateHandlerRetriesDone = true;
XposedBridge.log(TAG + " late handler retries done (split-dex may load later) attempts="
+ lateHookAttempts + " encryptHooks=" + encryptHooks
+ " attestHooks=" + attestHooks);
return;
}
long delay = Math.min(400L * lateHookAttempts, 2500L);
XposedBridge.log(TAG + " late hooks retry #" + lateHookAttempts + " in " + delay + "ms");
new Handler(Looper.getMainLooper()).postDelayed(
() -> tryInstallLateAppHooks(lpparam, false), delay);
}
private static boolean isLateHookTargetClass(String name, String packageName) {
if (name == null) {
return false;
}
for (String cn : MariBankRegionProfile.safeModeBooleanClasses(packageName)) {
if (cn.equals(name)) {
return true;
}
}
for (String cn : MariBankRegionProfile.sharedRiskClasses()) {
if (cn.equals(name)) {
return true;
}
}
for (String prefix : ERROR_FLOW_CLASSES) {
if (name.equals(prefix) || name.startsWith(prefix.substring(0, prefix.lastIndexOf('.') + 1))) {
return true;
}
}
return name.startsWith("com.shopee.bke.lib.safemode.")
|| name.equals("com.shopee.bke.lib.jni.utils.uvwuvwuv")
|| name.equals("com.shopee.bke.lib.jni.utils.uvwwwwuv")
|| (MariBankRegionProfile.hasEncryptWrapperD(packageName)
&& name.equals("com.shopee.bke.lib.jni.utils.d"))
|| name.startsWith("com.shopee.shpssdkbank.")
|| (!MariBankRegionProfile.isSingapore(packageName)
&& name.startsWith("com.shopee.shpssdk."));
}
/** SG split-dexsafemode / utils.d 延迟加载时在 loadClass 补装 Hook。 */
private static void hookClassLoaderSplitDex(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
ClassLoader.class,
"loadClass",
String.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() != null) {
return;
}
String name = (String) param.args[0];
if (isLateHookTargetClass(name, lpparam.packageName)) {
tryInstallLateAppHooks(lpparam, true);
}
}
}
);
XposedBridge.log(TAG + " ClassLoader.loadClass split-dex watcher OK");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip ClassLoader.loadClass watcher: " + t.getMessage());
}
}
private static void installDeferredHooks(XC_LoadPackage.LoadPackageParam lpparam) {
@@ -165,10 +302,14 @@ public final class MariBankRootBypassHook {
hookRootDialogBlock(lpparam);
hookAdbBypass(lpparam);
hookNetworkLogging(lpparam);
hookClassLoaderSplitDex(lpparam);
XposedBridge.log(TAG + " early app hooks installed");
}
private static int hookAllBooleanChecks(XC_LoadPackage.LoadPackageParam lpparam, String className) {
if (HOOKED_SAFE_MODE_CLASSES.contains(className)) {
return 0;
}
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
@@ -197,6 +338,7 @@ public final class MariBankRootBypassHook {
count++;
}
if (count > 0) {
HOOKED_SAFE_MODE_CLASSES.add(className);
XposedBridge.log(TAG + " hooked " + count + " checks in " + className);
}
} catch (Throwable t) {
@@ -480,11 +622,7 @@ public final class MariBankRootBypassHook {
/** SHPSSDK 风控:仅 Hook 返回 boolean 的实例方法。 */
private static int hookShpsRisk(XC_LoadPackage.LoadPackageParam lpparam) {
int count = 0;
String[] riskClasses = {
"com.shopee.shpssdkbank.SPSAssessRisk",
"com.shopee.shpssdk.SPSAssessRisk",
};
for (String className : riskClasses) {
for (String className : MariBankRegionProfile.shpsAssessRiskClasses(lpparam.packageName)) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
@@ -519,11 +657,7 @@ public final class MariBankRootBypassHook {
*/
private static void hookShpsToken(XC_LoadPackage.LoadPackageParam lpparam) {
final String contextClass = "android.content.Context";
String[] shpsSdkClasses = {
"com.shopee.shpssdk.SHPSSDK",
"com.shopee.shpssdkbank.SHPSSDK",
};
for (String className : shpsSdkClasses) {
for (String className : MariBankRegionProfile.shpsSdkClasses(lpparam.packageName)) {
hookEmptyRiskList(lpparam, className, "getRiskSync", contextClass);
hookEmptyRiskList(lpparam, className, "getExtRiskSync", contextClass);
hookRiskAsyncCallback(lpparam, className, "getRiskAsync", contextClass,
@@ -601,11 +735,7 @@ public final class MariBankRootBypassHook {
} catch (Throwable ignored) {
}
String[] assessRiskClasses = {
"com.shopee.shpssdkbank.SPSAssessRisk",
"com.shopee.shpssdk.SPSAssessRisk",
};
for (String className : assessRiskClasses) {
for (String className : MariBankRegionProfile.shpsAssessRiskClasses(lpparam.packageName)) {
try {
XposedHelpers.findAndHookMethod(
className,
@@ -622,11 +752,7 @@ public final class MariBankRootBypassHook {
}
}
String[] callbackAdapters = {
"com.shopee.shpssdk.SPSCallbackAdapter",
"com.shopee.shpssdkbank.SPSCallbackAdapter",
};
for (String className : callbackAdapters) {
for (String className : MariBankRegionProfile.shpsCallbackAdapterClasses(lpparam.packageName)) {
try {
XposedHelpers.findAndHookMethod(
className,
@@ -1285,8 +1411,11 @@ public final class MariBankRootBypassHook {
XposedBridge.invokeOriginalMethod(
param.method, param.thisObject, new Object[]{buffer});
String content = (String) XposedHelpers.callMethod(buffer, "readUtf8");
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(content);
String url = CURRENT_REQUEST_URL.get();
if (!content.isEmpty() && url != null && MariBankCaptureUtil.isBankUrl(url)) {
MariBankCaptureUtil.logText("reqBody wire " + url, content);
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(content);
if (url != null && url.contains("/register") && !content.isEmpty()) {
int show = Math.min(content.length(), 400);
XposedBridge.log(TAG + " register body(raw): "
@@ -1633,6 +1762,19 @@ public final class MariBankRootBypassHook {
XposedBridge.hookMethod(method, fakeDisabled);
}
}
// getInt(cr, key, def) — 部分 SDK 走三参数重载
if ("getInt".equals(target[1])) {
try {
XposedHelpers.findAndHookMethod(
clazz,
"getInt",
ContentResolver.class,
String.class,
int.class,
fakeDisabled);
} catch (Throwable ignored) {
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Settings hook " + target[0] + ": " + t.getMessage());
}

View File

@@ -16,26 +16,124 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage;
public final class MariBankSdkUtilsHook {
private static final String TAG = "notiMessageHook/MariBankEncrypt";
private static final int MAX_LOG = 2000;
private static final String WRAPPER = "com.shopee.bke.lib.jni.utils.d";
private static final String NATIVE_ENCRYPT = "com.shopee.bke.lib.jni.utils.uvwuvwuv";
private static final String SG_ENCRYPT_HELPER = "com.shopee.bke.lib.jni.utils.uvwwwwuv";
private static volatile boolean installed = false;
private static volatile boolean encryptHooksInstalled = false;
private static volatile boolean gsonHookInstalled = false;
private static volatile boolean encryptLoadClassWatcherInstalled = false;
private static volatile String installedForPackage;
private MariBankSdkUtilsHook() {
}
/** BkeApplication.onCreate 之后安装libsdkutils 已加载)。 */
public static void installLate(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
public static int installLate(XC_LoadPackage.LoadPackageParam lpparam) {
installEncryptLoadClassWatcher(lpparam);
if (encryptHooksInstalled && lpparam.packageName.equals(installedForPackage)) {
return 0;
}
int n = 0;
if (MariBankRegionProfile.hasEncryptWrapperD(lpparam.packageName)) {
n += hookEncryptWrapper(lpparam);
}
n += hookNativeEncryptUtils(lpparam);
if (MariBankRegionProfile.hasEncryptHelperUvwwwwuv(lpparam.packageName)) {
n += hookEncryptHelperClass(lpparam, SG_ENCRYPT_HELPER, "uvwwwwuv");
}
if (n > 0) {
encryptHooksInstalled = true;
installedForPackage = lpparam.packageName;
}
if (!gsonHookInstalled) {
int g = hookGsonRegister(lpparam);
if (g > 0) {
gsonHookInstalled = true;
n += g;
}
}
if (n > 0) {
XposedBridge.log(TAG + " late encrypt hooks=" + n + " region="
+ MariBankRegionProfile.label(lpparam.packageName));
}
return n;
}
private static void installEncryptLoadClassWatcher(XC_LoadPackage.LoadPackageParam lpparam) {
if (encryptLoadClassWatcherInstalled) {
return;
}
installed = true;
int n = hookEncryptWrapper(lpparam);
n += hookNativeEncryptUtils(lpparam);
n += hookGsonRegister(lpparam);
XposedBridge.log(TAG + " late encrypt hooks=" + n);
encryptLoadClassWatcherInstalled = true;
final String pkg = lpparam.packageName;
try {
XposedHelpers.findAndHookMethod(
ClassLoader.class,
"loadClass",
String.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() != null) {
return;
}
String name = (String) param.args[0];
if (NATIVE_ENCRYPT.equals(name)
|| SG_ENCRYPT_HELPER.equals(name)
|| (MariBankRegionProfile.hasEncryptWrapperD(pkg)
&& WRAPPER.equals(name))) {
installLate(lpparam);
}
}
});
XposedBridge.log(TAG + " ClassLoader encrypt watcher OK");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip encrypt ClassLoader watcher: " + t.getMessage());
}
}
private static int hookEncryptHelperClass(
XC_LoadPackage.LoadPackageParam lpparam, String className, String logLabel) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isStatic(method.getModifiers())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " >> " + logLabel + "." + method.getName());
for (int i = 0; i < param.args.length; i++) {
Object sanitized = sanitizeArg(param.args[i]);
if (sanitized != param.args[i]) {
param.args[i] = sanitized;
}
logArg(" in" + i, param.args[i]);
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (result instanceof byte[]) {
logArg(" out", result);
} else if (result instanceof String) {
logArg(" out", result);
}
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked " + logLabel + " methods=" + count);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + logLabel + ": " + t.getMessage());
}
return count;
}
private static int hookEncryptWrapper(XC_LoadPackage.LoadPackageParam lpparam) {
@@ -143,7 +241,7 @@ public final class MariBankSdkUtilsHook {
json = sanitized;
}
if (MariBankRegisterPayloadUtil.isRegistrationPayload(json)) {
XposedBridge.log(TAG + " Gson REGISTRATION: " + truncate(json));
MariBankCaptureUtil.logText("Gson REGISTRATION plaintext", json);
}
}
}
@@ -195,7 +293,7 @@ public final class MariBankSdkUtilsHook {
if (arg instanceof String) {
String s = (String) arg;
if (s.length() > 4 || s.contains("|") || looksLikeRegisterJson(s)) {
XposedBridge.log(TAG + label + " String(" + s.length() + ") " + truncate(s));
MariBankCaptureUtil.logText(label, s);
}
return;
}
@@ -207,19 +305,9 @@ public final class MariBankSdkUtilsHook {
} catch (Throwable t) {
text = "<bin>";
}
if (text.contains("|") || looksLikeRegisterJson(text) || bytes.length < 512) {
XposedBridge.log(TAG + label + " byte[" + bytes.length + "] " + truncate(text));
if (text.contains("|") || looksLikeRegisterJson(text) || bytes.length < 8192) {
MariBankCaptureUtil.logBytes(label, bytes);
}
}
}
private static String truncate(String s) {
if (s == null) {
return "";
}
if (s.length() <= MAX_LOG) {
return s;
}
return s.substring(0, MAX_LOG) + "...";
}
}

View File

@@ -72,7 +72,9 @@ public final class MariBankShpsNativeHook {
}
private static volatile boolean deferredInstalled = false;
private static volatile boolean lateNativeInstalled = false;
private static volatile boolean nativeBridgeInstalled = false;
private static volatile boolean nativeLoadClassWatcherInstalled = false;
private static final Set<String> HOOKED_NATIVE_CORE_CLASSES = new HashSet<>();
/**
* 勿 Hook负责 SoUtils.loadSoLibrary / libshpssdk_bank.so 加载Hook 会导致 SO 找不到。
@@ -104,13 +106,45 @@ public final class MariBankShpsNativeHook {
/** BkeApplication.onCreate 之后RN / shpssdk SO 已加载,再装 native 桥接 Hook。 */
public static void installLateNativeHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (lateNativeInstalled) {
if (!nativeBridgeInstalled) {
nativeBridgeInstalled = true;
hookShpsNativeBridge(lpparam);
installNativeLoadClassWatcher(lpparam);
}
hookShpsNativeCore(lpparam);
}
private static void installNativeLoadClassWatcher(XC_LoadPackage.LoadPackageParam lpparam) {
if (nativeLoadClassWatcherInstalled) {
return;
}
lateNativeInstalled = true;
hookShpsNativeBridge(lpparam);
hookShpsNativeCore(lpparam);
XposedBridge.log(TAG + " late native hooks installed for " + lpparam.packageName);
nativeLoadClassWatcherInstalled = true;
try {
XposedHelpers.findAndHookMethod(
ClassLoader.class,
"loadClass",
String.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getThrowable() != null) {
return;
}
String name = (String) param.args[0];
if (name != null && name.contains("shpssdk")) {
if (MariBankRegionProfile.isSingapore(lpparam.packageName)
&& MariBankRegionProfile.isShpssdkLegacyPackage(name)) {
return;
}
hookShpsNativeCore(lpparam);
}
}
});
XposedBridge.log(TAG + " ClassLoader native-core watcher OK");
} catch (Throwable t) {
XposedBridge.log(TAG + " skip native ClassLoader watcher: " + t.getMessage());
}
}
/** native 直接读 /proc/self/maps 查 hook 库;过滤内容。 */
@@ -251,10 +285,7 @@ public final class MariBankShpsNativeHook {
/** 保留 requestDefense 执行(生成 x-sap-fixme仅净化返回值中的 risk 字段。 */
private static void hookRequestDefense(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : new String[]{
"com.shopee.shpssdkbank.SHPSSDK",
"com.shopee.shpssdk.SHPSSDK",
}) {
for (String className : MariBankRegionProfile.shpsSdkClasses(lpparam.packageName)) {
try {
XposedHelpers.findAndHookMethod(
className,
@@ -403,21 +434,19 @@ public final class MariBankShpsNativeHook {
* SHPSSDK 核心 native 桥wvvvuwwu.wwvwvwuvv / vvuwuuvuu 等直接生成 risk 数据。
*/
private static void hookShpsNativeCore(XC_LoadPackage.LoadPackageParam lpparam) {
String[] coreClasses = {
"com.shopee.shpssdkbank.wvvvuwwu",
"com.shopee.shpssdkbank.uwuvuvvww.uvwuuuuuw.vvvvuwwvu",
"com.shopee.shpssdkbank.uwuvuvvww.wvvuuwvwu",
"com.shopee.shpssdk.wvvvuwwu",
};
int total = 0;
for (String className : coreClasses) {
for (String className : MariBankRegionProfile.shpsAttestationCoreClasses(lpparam.packageName)) {
total += hookNativeCoreClass(lpparam, className);
}
XposedBridge.log(TAG + " native-core total hooks=" + total);
XposedBridge.log(TAG + " native-core total hooks=" + total + " region="
+ MariBankRegionProfile.label(lpparam.packageName));
}
private static int hookNativeCoreClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
if (HOOKED_NATIVE_CORE_CLASSES.contains(className)) {
return 0;
}
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
@@ -425,9 +454,6 @@ public final class MariBankShpsNativeHook {
if (!Modifier.isStatic(method.getModifiers())) {
continue;
}
if (Modifier.isNative(method.getModifiers())) {
continue;
}
Class<?> rt = method.getReturnType();
if (rt == String.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@@ -443,12 +469,41 @@ public final class MariBankShpsNativeHook {
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " core String sanitized "
+ className + "#" + method.getName()
+ (Modifier.isNative(method.getModifiers()) ? " (native)" : ""));
}
} else if (s.length() > 40) {
String sanitized = MariBankRegisterPayloadUtil.isRegistrationPayload(s)
? MariBankRegisterPayloadUtil.sanitizeRegistrationJson(s)
: MariBankRiskTokenUtil.sanitizeAllInText(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " core register String sanitized "
+ className + "#" + method.getName());
}
}
}
});
count++;
} else if (rt == byte[].class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (!(result instanceof byte[])) {
return;
}
byte[] bytes = (byte[]) result;
byte[] out = MariBankRegisterPayloadUtil.sanitizeRegistrationBytes(bytes);
if (out != bytes) {
param.setResult(out);
XposedBridge.log(TAG + " core byte[] sanitized "
+ className + "#" + method.getName()
+ (Modifier.isNative(method.getModifiers()) ? " (native)" : ""));
}
}
});
count++;
} else if (rt == boolean.class || rt == Boolean.class
|| rt == int.class || rt == Integer.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@@ -465,7 +520,8 @@ public final class MariBankShpsNativeHook {
}
}
if (count > 0) {
XposedBridge.log(TAG + " hooked " + count + " core natives in " + className);
HOOKED_NATIVE_CORE_CLASSES.add(className);
XposedBridge.log(TAG + " hooked " + count + " core methods in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip core " + className + ": " + t.getMessage());

View File

@@ -0,0 +1,77 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Locale;
import de.robv.android.xposed.XposedBridge;
/**
* 拦截 root 探测命令时返回「空 / 失败」假进程,避免抛异常被 SHPSSDK 记为 tamper。
*/
public final class ProbeGuard {
private static final String TAG = "notiMessageHook/ProbeGuard";
private ProbeGuard() {
}
public static boolean isBlockedCommand(String command) {
if (command == null || command.isEmpty()) {
return false;
}
String lower = command.toLowerCase(Locale.US);
return lower.contains(" magisk")
|| lower.contains("/su")
|| lower.startsWith("su")
|| lower.contains("which su")
|| lower.contains("getprop ro.debuggable")
|| lower.contains("busybox")
|| lower.equals("su");
}
public static boolean isBlockedCommand(List<String> commands) {
if (commands == null || commands.isEmpty()) {
return false;
}
return isBlockedCommand(String.join(" ", commands));
}
public static Process fakeFailedProcess(String reason) {
XposedBridge.log(TAG + " fake probe process: " + reason);
return new FakeProcess();
}
private static final class FakeProcess extends Process {
@Override
public OutputStream getOutputStream() {
return OutputStream.nullOutputStream();
}
@Override
public InputStream getInputStream() {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public InputStream getErrorStream() {
return new ByteArrayInputStream(new byte[0]);
}
@Override
public int waitFor() {
return 1;
}
@Override
public int exitValue() {
return 1;
}
@Override
public void destroy() {
}
}
}

View File

@@ -13,6 +13,9 @@ 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;
import android.os.Message;
@@ -20,7 +23,10 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Collections;
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;
@@ -78,10 +84,13 @@ 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",
"login.options", "callingcode", "pin.token", "module.whitelist",
"secauth",
};
private TngRootBypassHook() {
@@ -212,6 +221,7 @@ public final class TngRootBypassHook {
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) {
@@ -224,11 +234,14 @@ public final class TngRootBypassHook {
// Splash 常卡死Instrumentation 强拉即可。双通道会抢跑导致 Login 重载/卡死。
hookEarlyAttachLog(lpparam);
hookConscryptStatsLogGuard();
hookRegistrationFlowGuard(lpparam);
hookSplashForceLogin(lpparam);
hookLoginDismissSplash(lpparam);
hookHardwareRendererSetName();
hookHwuiBySurfacePolicy(lpparam);
hookLoadingDialogSkip();
hookBottomSelectDialogDiag(lpparam);
hookCaptchaDiag(lpparam);
hookActivityLifecycleDiag(lpparam);
hookPromonApService(lpparam);
hookPromonBroadcastReceiver(lpparam);
@@ -240,6 +253,7 @@ public final class TngRootBypassHook {
ProcMapsFilterHook.install(lpparam);
hookAntiSuicide();
hookPromonSuicideUpstream(lpparam);
hookUncaughtPromonException(lpparam);
hookKillApplicationHandler(lpparam);
hookBlockSecurityErrorLaunch(lpparam);
@@ -263,6 +277,47 @@ public final class TngRootBypassHook {
hookWebViewErrorDiag(lpparam);
}
/**
* Zygisk/命名空间下 android.util.StatsLog native 常 UnsatisfiedLinkError
* Conscrypt TLS 指标线程一写就炸 → ART fatal。直接 noop 指标写入。
*/
private static void hookConscryptStatsLogGuard() {
int hooked = 0;
for (String className : new String[]{
"com.android.org.conscrypt.metrics.StatsLogImpl",
"com.android.org.conscrypt.metrics.ConscryptStatsLog",
"com.google.android.gms.org.conscrypt.metrics.StatsLogImpl",
}) {
try {
Class<?> clazz = Class.forName(className);
for (Method method : clazz.getDeclaredMethods()) {
String n = method.getName();
if (!n.startsWith("write")
&& !n.startsWith("report")
&& !n.startsWith("count")
&& !"startWriterThread".equals(n)) {
continue;
}
hookMethodNoopSilent(method);
hooked++;
}
} catch (Throwable ignored) {
}
}
try {
Class<?> statsLog = Class.forName("android.util.StatsLog");
for (Method method : statsLog.getDeclaredMethods()) {
String n = method.getName();
if ("loadNativeLibrary".equals(n) || n.startsWith("write")) {
hookMethodNoopSilent(method);
hooked++;
}
}
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " conscrypt/StatsLog guard hooked n=" + hooked);
}
private static void logActivityDiag(String phase, Activity activity) {
if (activity == null) {
return;
@@ -392,8 +447,14 @@ public final class TngRootBypassHook {
}
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")
@@ -457,22 +518,6 @@ public final class TngRootBypassHook {
} 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 定位卡点。 */
@@ -528,44 +573,722 @@ public final class TngRootBypassHook {
}
/**
* 区号选择弹窗诊断。勿关 HW 加速——Android 16 上会导致列表黑屏。
* ANR 由 HardwareRenderer.setName 拦截兜底。
* Android 16 HWUI 策略(经 APK 逆向核对):
* - i7.l一律 skip show真 show → gralloc ABRT
* - Login / Splash / 普通 View 页:清 HW flag + 拦 enableHW软件绘制
* - UserSearchCallingCodeActivityCompose CallingListScreen/LazyColumn
* 软件绘制 = 黑屏;必须完整 HW与 BottomSelect 相同)
* - BottomSelect完整 HW关了会黑
* - captcha软件层弹窗不放行 enableHW
*/
private static void hookBottomSelectDialogDiag(XC_LoadPackage.LoadPackageParam lpparam) {
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 放行。
*/
private static volatile boolean callingCodeSurfaceActive = false;
private static boolean isCaptchaDialog(String owner) {
return owner != null
&& (owner.contains("CaptchaWebViewDialog") || owner.contains("com.aliyun.captcha"));
}
private static boolean isLoadingDialog(String owner) {
return owner != null
&& (owner.contains("i7.") || owner.contains("Loading") || owner.contains("Progress"));
}
private static boolean isCallingCodeActivity(String className) {
return className != null && className.contains("UserSearchCallingCodeActivity");
}
private static boolean isCallingCodeIntent(Intent intent) {
if (intent == null) {
return false;
}
if (intent.getComponent() != null) {
String cn = intent.getComponent().getClassName();
if (isCallingCodeActivity(cn)) {
return true;
}
}
String action = intent.getAction();
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 {
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) {
XposedBridge.log(TAG + " BottomSelectDialogFragment.onStart");
}
});
XposedBridge.log(TAG + " hooked BottomSelectDialogFragment.onStart (diag only)");
Window window = activity.getWindow();
if (window != null) {
window.clearFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " BottomSelectDialog hook failed: " + t.getMessage());
XposedBridge.log(TAG + " clear HW flag failed: " + t.getMessage());
}
}
/**
* ANR 栈Dialog.show → enableHardwareAcceleration → HardwareRenderer.setName → future.get 卡死
* 仅拦截 setName勿全局关 Dialog HW区号选择会黑屏
* 区号 BottomSelect须 hook 父类 DialogFragment.show/onStartKotlin 子类不 override show
* 勿关 BottomSheet HW 加速Android 16 黑屏);可见期间放行主线程 setName
*/
private static void hookHardwareRendererSetName() {
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();
int n = 0;
for (StackTraceElement el : stack) {
String cn = el.getClassName();
if (cn.startsWith("java.") || cn.startsWith("dalvik.")
|| cn.startsWith("android.os.") || cn.startsWith("de.robv.android.xposed")
|| cn.contains("TngRootBypassHook") || cn.contains("LSPosed")
|| cn.contains("XposedBridge")) {
continue;
}
if (n > 0) {
sb.append(" <- ");
}
sb.append(el.getClassName()).append("#").append(el.getMethodName())
.append(":").append(el.getLineNumber());
if (++n >= maxFrames) {
break;
}
}
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 标记。
*/
private static void hookLoadingDialogSkip() {
try {
XposedHelpers.findAndHookMethod(Dialog.class, "show", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Dialog dialog = (Dialog) param.thisObject;
String owner = dialog.getClass().getName();
if (isLoadingDialog(owner)) {
skippedLoadingDialogs.add(dialog);
XposedBridge.log(TAG + " Dialog.show skip loading " + owner
+ " callingCode=" + callingCodeSurfaceActive
+ " regFlow=" + registrationFlowActive);
param.setResult(null);
return;
}
if (owner.contains("BottomSelect") || owner.contains("BottomSheet")) {
bottomSelectDialogActive = true;
allowHwForDialog.set(Boolean.TRUE);
} else if (isCaptchaDialog(owner)) {
// Android 16captcha 放行 enableHW → gralloc ABRT 黑屏闪退
// 改为软件层弹窗,不进白名单
try {
Dialog captchaDialog = (Dialog) param.thisObject;
Window cw = captchaDialog.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);
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
blockSetNameForDialog.remove();
allowHwForDialog.remove();
}
});
XposedBridge.log(TAG + " hooked Dialog.show (skip loading only)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.show hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(Dialog.class, "isShowing", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (skippedLoadingDialogs.contains(param.thisObject)) {
param.setResult(true);
}
}
});
XposedBridge.log(TAG + " hooked Dialog.isShowing (skipped loading)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.isShowing hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(Dialog.class, "dismiss", new XC_MethodHook() {
@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);
}
}
});
XposedBridge.log(TAG + " hooked Dialog.dismiss (loading cleanup)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.dismiss hook failed: " + t.getMessage());
}
}
/**
* 非区号:清 HW flag + 拦 enableHW成对防半开黑屏
* 区号 Compose / BottomSelect完整 HW软件绘制 Compose 必黑)。
* Dialog.show 窗口期可拦 setName。
*/
private static void hookHwuiBySurfacePolicy(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"android.graphics.HardwareRenderer", null, "setName", String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (Looper.myLooper() != Looper.getMainLooper()) {
return;
}
if (!shouldBlockSetNameOnMain()) {
return;
}
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked HardwareRenderer.setName (blocked)");
XposedBridge.log(TAG + " hooked HardwareRenderer.setName (dialog-only block)");
} catch (Throwable t) {
XposedBridge.log(TAG + " HardwareRenderer.setName hook failed: " + t.getMessage());
}
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 已注入。 */
@@ -622,6 +1345,9 @@ public final class TngRootBypassHook {
XposedBridge.log(TAG + " registration flow active: " + name);
} else if (name.endsWith(".SplashActivity")) {
registrationFlowActive = false;
callingCodeSurfaceActive = false;
} else if (name.endsWith(".UserLoginActivity")) {
registrationFlowActive = false;
}
}
};
@@ -908,93 +1634,76 @@ public final class TngRootBypassHook {
}
}
private static volatile long lastSuicideLogAt = 0L;
private static volatile int suicideBlockCount = 0;
private static void hookAntiSuicide() {
XC_MethodHook blockSelfKill = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (!shouldBlockThisSuicideCall(param)) {
return;
}
lastBlockedSuicideAt = System.currentTimeMillis();
suicideBlockCount++;
long now = lastBlockedSuicideAt;
if (now - lastSuicideLogAt > 3000L) {
lastSuicideLogAt = now;
XposedBridge.log(TAG + " blocked suicide #" + suicideBlockCount
+ " " + param.method.getDeclaringClass().getSimpleName()
+ "#" + param.method.getName()
+ argsSummary(param.args)
+ " thread=" + Thread.currentThread().getName()
+ " stack=" + shortStack(6));
}
param.setResult(null);
// 非主线程:打断自杀循环线程,避免只拦 kill 却卡死 UI
freezeSuicideCallerThread();
}
};
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);
}
}
});
XposedHelpers.findAndHookMethod(Process.class, "killProcess", int.class, blockSelfKill);
} 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);
}
});
XposedHelpers.findAndHookMethod(System.class, "exit", int.class, blockSelfKill);
} 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);
}
});
XposedHelpers.findAndHookMethod(Runtime.class, "exit", int.class, blockSelfKill);
} 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);
}
});
XposedHelpers.findAndHookMethod(Runtime.class, "halt", int.class, blockSelfKill);
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.halt hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Process.class,
"sendSignal",
int.class,
int.class,
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);
if (pid != Process.myPid()) {
return;
}
if (signal != 9 && signal != 15 && signal != 6) {
return;
}
lastBlockedSuicideAt = System.currentTimeMillis();
suicideBlockCount++;
if (lastBlockedSuicideAt - lastSuicideLogAt > 3000L) {
lastSuicideLogAt = lastBlockedSuicideAt;
XposedBridge.log(TAG + " blocked sendSignal(self," + signal
+ ") #" + suicideBlockCount);
}
param.setResult(null);
freezeSuicideCallerThread();
}
});
} catch (Throwable t) {
@@ -1023,6 +1732,109 @@ public final class TngRootBypassHook {
} catch (Throwable t) {
XposedBridge.log(TAG + " finish hooks failed: " + t.getMessage());
}
XposedBridge.log(TAG + " anti-suicide armed (rate-limited + freeze caller)");
}
private static boolean shouldBlockThisSuicideCall(XC_MethodHook.MethodHookParam param) {
// 真崩溃KillApplicationHandler放行避免僵尸进程卡 Splash/黑屏
if (stackHasClass("com.android.internal.os.RuntimeInit$KillApplicationHandler")) {
return false;
}
String name = param.method.getName();
if ("killProcess".equals(name)) {
return ((Integer) param.args[0]) == Process.myPid();
}
// System/Runtime.exit/halt一律拦TNG 正常退出极少走这里)
return true;
}
private static boolean stackHasClass(String className) {
try {
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
if (className.equals(e.getClassName())) {
return true;
}
}
} catch (Throwable ignored) {
}
return false;
}
private static String argsSummary(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(",");
}
sb.append(args[i]);
}
return sb.append(")").toString();
}
private static void freezeSuicideCallerThread() {
Thread t = Thread.currentThread();
if (t.getId() == Looper.getMainLooper().getThread().getId()) {
return;
}
// 栈里有 Promon 包才打断,避免误伤业务线程
if (!stackHasPromon()) {
return;
}
try {
t.interrupt();
} catch (Throwable ignored) {
}
}
private static boolean stackHasPromon() {
try {
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
if (isPromonPackageClass(e.getClassName())) {
return true;
}
}
} catch (Throwable ignored) {
}
return false;
}
/**
* 自杀链上游:只补 AppSecurityManager 杀进程路径 noop。
* 勿拦 Handler.post / 勿 short-circuit G/H 等:会弄死 ContentProvider 初始化Splash 僵尸卡住。
* bl/R/a 仍由 hookPromonNativeGuard 处理。
*/
private static void hookPromonSuicideUpstream(XC_LoadPackage.LoadPackageParam lpparam) {
hookAppSecurityKillPaths(lpparam);
XposedBridge.log(TAG + " promon suicide upstream armed (kill-path only)");
}
/** AppSecurityManager 里明确杀进程/退出的路径直接 noop勿匹配 destroy/shutdown。 */
private static void hookAppSecurityKillPaths(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 lower = method.getName().toLowerCase(Locale.US);
if (!lower.contains("kill")
&& !lower.contains("exit")
&& !lower.contains("force")
&& !lower.contains("die")
&& !lower.contains("suicide")
&& !lower.contains("terminate")) {
continue;
}
hookMethodNoopSilent(method);
hooked++;
}
XposedBridge.log(TAG + " AppSecurityManager kill-path noop n=" + hooked);
} catch (Throwable t) {
XposedBridge.log(TAG + " AppSecurityManager kill-path failed: " + t.getMessage());
}
}
private static boolean isSecurityRelatedActivity(Activity activity) {
@@ -1230,12 +2042,16 @@ public final class TngRootBypassHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Throwable t = (Throwable) param.args[0];
if (t == null || !isPromonThrowableName(t.getClass().getName())) {
if (t == null) {
return;
}
XposedBridge.log(TAG + " swallowed uncaught " + t.getClass().getSimpleName()
+ " in " + lpparam.processName);
param.setResult(null);
if (isPromonThrowableName(t.getClass().getName())
|| isStatsLogNoise(t)) {
XposedBridge.log(TAG + " swallowed uncaught " + t.getClass().getSimpleName()
+ " in " + lpparam.processName
+ " msg=" + t.getMessage());
param.setResult(null);
}
}
});
XposedBridge.log(TAG + " hooked Thread.dispatchUncaughtException");
@@ -1379,6 +2195,25 @@ public final class TngRootBypassHook {
return simple.length() == 1;
}
private static boolean isStatsLogNoise(Throwable t) {
for (Throwable cur = t; cur != null; cur = cur.getCause()) {
String name = cur.getClass().getName();
String msg = cur.getMessage();
if (name != null && name.contains("StatsLog")) {
return true;
}
if (msg != null && msg.contains("StatsLog")) {
return true;
}
if (cur instanceof UnsatisfiedLinkError
&& msg != null
&& (msg.contains("stats") || msg.contains("Stats"))) {
return true;
}
}
return false;
}
private static boolean isPromonPackageClass(String className) {
for (String prefix : PROMON_PKG_PREFIXES) {
if (className.startsWith(prefix + ".")) {
@@ -1587,7 +2422,10 @@ public final class TngRootBypassHook {
}
}
/** 短路 TigerTally 启动初始化native 会 fork getprop 等待 → seccomp 卡死 ANR。 */
/**
* TigerTallyinit/initCommon + genericNt1 必须短路。
* 只短路 genericNt1 而放行 init 会导致 encodeUid 缺失(cloudauth 403)、JNI 损坏、主线程 ABRT 无响应。
*/
private static boolean isTigerShortCircuit(String className, String methodName) {
if ("com.aliyun.TigerTally.TigerTallyAPI".equals(className)
&& ("init".equals(methodName) || "initCommon".equals(methodName))) {
@@ -2154,8 +2992,8 @@ public final class TngRootBypassHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String op = extractRpcOperation(param.args);
if (op != null && isRegistrationRpc(op)) {
XposedBridge.log(TAG + " RPC reg/login op=" + op + " "
if (op != null && (isRegistrationRpc(op) || op.contains("phoneCheck"))) {
XposedBridge.log(TAG + " RPC req op=" + op + " "
+ describeRpcInvocation(param.args));
}
if (!isJailBrokenRpcInvocation(param.args)) {
@@ -2170,6 +3008,26 @@ public final class TngRootBypassHook {
}
param.setResult(null);
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
String op = extractRpcOperation(param.args);
if (op == null || (!isRegistrationRpc(op) && !op.contains("phoneCheck"))) {
return;
}
if (param.hasThrowable()) {
XposedBridge.log(TAG + " RPC rsp op=" + op + " err "
+ param.getThrowable().getClass().getSimpleName()
+ ": " + param.getThrowable().getMessage());
return;
}
Object result = param.getResult();
String snippet = result != null ? String.valueOf(result) : "null";
if (snippet.length() > 800) {
snippet = snippet.substring(0, 800) + "...";
}
XposedBridge.log(TAG + " RPC rsp op=" + op + " body=" + snippet);
}
};
for (String className : rpcClasses) {
try {