feat: MariBank 风控 bypass、澳洲银行 Hook 与 reverse 逆向工作区

新增 MariBank/SeaBank PH Root 与 SHPSSDK bypass、riskToken 净化及 Up/Suncorp/ubank 消息 Hook;整理 reverse/ 脚本与 Frida 工具链,并补充当日工作说明文档。
This commit is contained in:
2026-07-03 17:15:16 +08:00
parent 125dfe583b
commit 59970a84a8
121 changed files with 7606 additions and 37 deletions

View File

@@ -0,0 +1,201 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Notification;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import com.miraclegarden.smsmessage.xposed.HookForwarder;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Map;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
/**
* 银行 App Hook 公共工具RemoteMessage / Notification 解析与去重转发。
*/
public final class BankHookHelper {
private static final String TAG = "notiMessageHook/Bank";
private static final int DEDUP_SIZE = 256;
private static final ArrayDeque<String> RECENT_KEYS = new ArrayDeque<>();
private static final HashSet<String> RECENT_SET = new HashSet<>();
private BankHookHelper() {
}
public static void hookFcmService(de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam lpparam,
String serviceClass, String source) {
try {
de.robv.android.xposed.XposedHelpers.findAndHookMethod(
serviceClass,
lpparam.classLoader,
"onMessageReceived",
"com.google.firebase.messaging.RemoteMessage",
new de.robv.android.xposed.XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.args == null || param.args.length == 0 || param.args[0] == null) {
return;
}
Context context = getContext();
if (context == null) {
return;
}
CharSequence[] parts = extractRemoteMessage(param.args[0]);
forward(context, lpparam.packageName, parts[0], parts[1], source);
}
}
);
XposedBridge.log(TAG + " FCM hook installed: " + serviceClass + " (" + lpparam.packageName + ")");
} catch (Throwable t) {
XposedBridge.log(TAG + " FCM hook failed " + serviceClass + ": " + t.getMessage());
}
}
public static void forwardFromNotification(Context context, String packageName,
Notification notification, String source) {
if (notification == null) {
return;
}
CharSequence[] parts = extractNotification(notification);
forward(context, packageName, parts[0], parts[1], source);
}
private static void forward(Context context, String packageName,
CharSequence title, CharSequence content, String source) {
String titleStr = title != null ? title.toString().trim() : "";
String contentStr = content != null ? content.toString().trim() : "";
if (TextUtils.isEmpty(titleStr) && TextUtils.isEmpty(contentStr)) {
return;
}
if (TextUtils.isEmpty(titleStr)) {
titleStr = packageName;
}
if (TextUtils.isEmpty(contentStr)) {
contentStr = titleStr;
}
String dedupKey = packageName + "|" + titleStr + "|" + contentStr;
if (!remember(dedupKey)) {
return;
}
XposedBridge.log(TAG + " forward [" + source + "] " + titleStr + " / " + contentStr);
HookForwarder.forward(context, packageName, titleStr, contentStr, source);
}
static CharSequence[] extractRemoteMessage(Object remoteMessage) {
String title = "";
String body = "";
try {
Object notification = XposedHelpers.callMethod(remoteMessage, "getNotification");
if (notification != null) {
Object t = XposedHelpers.callMethod(notification, "getTitle");
Object b = XposedHelpers.callMethod(notification, "getBody");
if (t != null) {
title = String.valueOf(t).trim();
}
if (b != null) {
body = String.valueOf(b).trim();
}
}
} catch (Throwable ignored) {
}
if (TextUtils.isEmpty(body)) {
body = stringifyDataMap(remoteMessage);
}
return new CharSequence[]{title, body};
}
private static String stringifyDataMap(Object remoteMessage) {
try {
Object dataObj = XposedHelpers.callMethod(remoteMessage, "getData");
if (!(dataObj instanceof Map)) {
return "";
}
Map<?, ?> data = (Map<?, ?>) dataObj;
if (data.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<?, ?> entry : data.entrySet()) {
if (entry.getKey() == null) {
continue;
}
if (sb.length() > 0) {
sb.append('\n');
}
sb.append(entry.getKey()).append('=');
if (entry.getValue() != null) {
sb.append(entry.getValue());
}
}
return sb.toString().trim();
} catch (Throwable ignored) {
return "";
}
}
static CharSequence[] extractNotification(Notification notification) {
Bundle extras = notification.extras;
if (extras == null) {
return new CharSequence[]{"", ""};
}
CharSequence title = firstNonEmpty(
extras.getCharSequence(Notification.EXTRA_TITLE),
extras.getCharSequence(Notification.EXTRA_TITLE_BIG),
extras.getString(Notification.EXTRA_TITLE)
);
CharSequence text = firstNonEmpty(
extras.getCharSequence(Notification.EXTRA_TEXT),
extras.getCharSequence(Notification.EXTRA_BIG_TEXT),
extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT),
extras.getString(Notification.EXTRA_TEXT)
);
if (TextUtils.isEmpty(text)) {
text = extras.getString("gcm.n.body");
}
if (TextUtils.isEmpty(title)) {
title = extras.getString("gcm.n.title");
}
return new CharSequence[]{title, text};
}
private static CharSequence firstNonEmpty(CharSequence... values) {
for (CharSequence value : values) {
if (!TextUtils.isEmpty(value)) {
return value;
}
}
return "";
}
private static synchronized boolean remember(String key) {
if (RECENT_SET.contains(key)) {
return false;
}
RECENT_SET.add(key);
RECENT_KEYS.addLast(key);
while (RECENT_KEYS.size() > DEDUP_SIZE) {
String oldest = RECENT_KEYS.removeFirst();
RECENT_SET.remove(oldest);
}
return true;
}
static Context getContext() {
try {
Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
Object app = XposedHelpers.callStaticMethod(activityThread, "currentApplication");
if (app instanceof Context) {
return (Context) app;
}
} catch (Throwable ignored) {
}
return null;
}
}

View File

@@ -0,0 +1,76 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Notification;
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;
/**
* 银行 App 前台兜底Hook NotificationManager.notify从 Notification.extras 取标题/正文。
*/
public final class BankNotificationHook {
private static final String TAG = "notiMessageHook/BankNotify";
private BankNotificationHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam, String source) {
try {
XposedHelpers.findAndHookMethod(
"android.app.NotificationManager",
lpparam.classLoader,
"notify",
String.class,
int.class,
Notification.class,
new NotifyHook(lpparam.packageName, source)
);
XposedHelpers.findAndHookMethod(
"android.app.NotificationManager",
lpparam.classLoader,
"notify",
int.class,
Notification.class,
new NotifyHook(lpparam.packageName, source)
);
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
} catch (Throwable t) {
XposedBridge.log(TAG + " install failed: " + t.getMessage());
}
}
private static final class NotifyHook extends XC_MethodHook {
private final String packageName;
private final String source;
NotifyHook(String packageName, String source) {
this.packageName = packageName;
this.source = source;
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.args == null || param.args.length == 0) {
return;
}
Notification notification = null;
for (Object arg : param.args) {
if (arg instanceof Notification) {
notification = (Notification) arg;
break;
}
}
if (notification == null) {
return;
}
android.content.Context context = BankHookHelper.getContext();
if (context == null) {
return;
}
BankHookHelper.forwardFromNotification(context, packageName, notification, source);
}
}
}

View File

@@ -0,0 +1,100 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.nio.charset.StandardCharsets;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.robv.android.xposed.XposedBridge;
/**
* SHPSSDK riskToken 净化:尾部 |09|1 → |00|0Root+Emulator+Hook 标记)。
*/
final class MariBankRiskTokenUtil {
private static final String TAG = "notiMessageHook/MariBankRoot";
private static final Pattern RISK_TOKEN_JSON = Pattern.compile(
"\"riskToken\"\\s*:\\s*\"([^\"]+)\"");
private static final Pattern DEVICE_TOKEN_JSON = Pattern.compile(
"\"deviceToken\"\\s*:\\s*\"([^\"]+)\"");
private static final Pattern RISK_TOKEN_BODY = Pattern.compile(
"([A-Za-z0-9+/=]{8,}\\|[A-Za-z0-9+/=_-]{8,}\\|[A-Za-z0-9+/=_-]{3,}\\|)\\d+(\\|\\d+)");
private MariBankRiskTokenUtil() {
}
static String sanitizeRiskToken(String token) {
if (token == null || token.isEmpty() || !token.contains("|")) {
return token;
}
if (!RISK_TOKEN_BODY.matcher(token).find() && !token.matches(".*\\|\\d+\\|\\d+$")) {
return token;
}
int secondLast = token.lastIndexOf('|');
if (secondLast <= 0) {
return token;
}
secondLast = token.lastIndexOf('|', secondLast - 1);
if (secondLast <= 0) {
return token;
}
String oldTail = token.substring(secondLast + 1);
String neu = token.substring(0, secondLast) + "|00|0";
XposedBridge.log(TAG + " sanitized riskToken tail " + oldTail + " -> 00|0");
return neu;
}
static String sanitizeAllInText(String text) {
if (text == null || !text.contains("|")) {
return text;
}
Matcher m = RISK_TOKEN_BODY.matcher(text);
StringBuffer sb = new StringBuffer();
boolean changed = false;
while (m.find()) {
m.appendReplacement(sb, Matcher.quoteReplacement(m.group(1) + "00|0"));
changed = true;
}
if (!changed) {
String out = sanitizeJsonField(text, RISK_TOKEN_JSON);
out = sanitizeJsonField(out, DEVICE_TOKEN_JSON);
return out;
}
m.appendTail(sb);
XposedBridge.log(TAG + " sanitized riskToken in text len=" + text.length());
return sb.toString();
}
static byte[] sanitizeBytes(byte[] data, int offset, int length) {
if (data == null || length <= 0) {
return data;
}
String text = new String(data, offset, length, StandardCharsets.UTF_8);
if (!text.contains("|")) {
return data;
}
String sanitized = sanitizeAllInText(text);
return sanitized.equals(text) ? data : sanitized.getBytes(StandardCharsets.UTF_8);
}
private static String sanitizeJsonField(String text, Pattern fieldPattern) {
Matcher json = fieldPattern.matcher(text);
if (!json.find()) {
return text;
}
String oldToken = json.group(1);
String newToken = sanitizeRiskToken(oldToken);
return oldToken.equals(newToken) ? text : text.replace(oldToken, newToken);
}
static String tail(String token) {
if (token == null || !token.contains("|")) {
return "n/a";
}
int last = token.lastIndexOf('|');
int second = token.lastIndexOf('|', last - 1);
if (second < 0) {
return "n/a";
}
return token.substring(second + 1);
}
}

View File

@@ -0,0 +1,1738 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Activity;
import android.app.Dialog;
import android.content.res.Resources;
import android.os.Process;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
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 PHph.seabank.seabankRoot 检测绕过。
* 逆向SafeMode SDK + SHPSSDK检测到 Root 后会 Toast 并 Process.killProcess 自杀。
*/
public final class MariBankRootBypassHook {
private static final String TAG = "notiMessageHook/MariBankRoot";
public static final String PACKAGE = "ph.seabank.seabank";
/** 服务端注册被拒错误码logcat 实测)。 */
private static final int ERROR_CODE_SECURITY_BLOCKED = 4067004;
private static final int ERROR_CODE_SECURITY_BLOCKED_ALT = 4067012;
private static final ThreadLocal<String> CURRENT_REQUEST_URL = new ThreadLocal<>();
/** killProcess 被拦截后的宽限期:此期间阻止 finish 造成「假闪退」。 */
private static volatile long lastBlockedSuicideAt = 0L;
private static final long SOFT_CRASH_GUARD_MS = 8000L;
private static volatile int finishBurstCount = 0;
private static volatile long finishBurstStartMs = 0L;
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",
"com.shopee.bke.biz.user.rn.helper.ErrorFlowHelper",
};
private MariBankRootBypassHook() {
}
private static volatile boolean deferredHooksInstalled = false;
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!PACKAGE.equals(lpparam.packageName)) {
return;
}
hookAntiSuicide(lpparam);
hookAntiSoftCrash(lpparam);
scheduleAppHooks(lpparam);
}
/**
* 必须在 BkeApplication.attachBaseContext 完成之后安装:
* loadPackage 时 ClassLoader 未绑定 split APK过早 Hook SHPSSDK 会导致 libsdkutils.so 死循环白屏。
*/
private static void scheduleAppHooks(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook afterAttach = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
installDeferredHooks(lpparam);
MariBankShpsNativeHook.installDeferred(lpparam);
}
};
try {
XposedHelpers.findAndHookMethod(
"com.shopee.bke.digitalbank.BkeApplication",
lpparam.classLoader,
"attachBaseContext",
"android.content.Context",
afterAttach);
XposedBridge.log(TAG + " waiting attachBaseContext for app hooks");
} catch (Throwable t) {
XposedBridge.log(TAG + " attachBaseContext hook failed, install now: " + t.getMessage());
installDeferredHooks(lpparam);
MariBankShpsNativeHook.installDeferred(lpparam);
}
}
private static void installDeferredHooks(XC_LoadPackage.LoadPackageParam lpparam) {
if (deferredHooksInstalled) {
return;
}
deferredHooksInstalled = true;
int hooked = 0;
for (String className : SAFE_MODE_CLASSES) {
hooked += hookAllBooleanChecks(lpparam, className);
}
hookSafeModeDialog(lpparam);
hookRootDialogBlock(lpparam);
hookErrorFlowLogging(lpparam);
hooked += hookShpsRisk(lpparam);
hookShpsToken(lpparam);
hookNetworkLogging(lpparam);
XposedBridge.log(TAG + " app hooks installed, booleanHooks=" + hooked);
}
/** SafeMode 类方法名被混淆Hook 所有返回 boolean/int 的实例方法。 */
private static int hookAllBooleanChecks(XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
continue;
}
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class && returnType != Boolean.class
&& returnType != int.class && returnType != Integer.class) {
continue;
}
if (method.getParameterTypes().length > 2) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (returnType == boolean.class || returnType == Boolean.class) {
param.setResult(false);
} else {
param.setResult(0);
}
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked " + count + " checks in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
return count;
}
/** 阻止检测到 Root 后 Process.killProcess / System.exit 自杀。 */
private static void hookAntiSuicide(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
Process.class,
"killProcess",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int pid = (Integer) param.args[0];
if (pid == Process.myPid()) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked killProcess(self)");
param.setResult(null);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " killProcess hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
System.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked System.exit");
param.setResult(null);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " System.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked Runtime.exit");
param.setResult(null);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Process.class,
"sendSignal",
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int pid = (Integer) param.args[0];
int signal = (Integer) param.args[1];
if (pid == Process.myPid() && (signal == 9 || signal == 15)) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked sendSignal(self, " + signal + ")");
param.setResult(null);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " sendSignal hook failed: " + t.getMessage());
}
}
/** 阻止 killProcess 失败后通过 finish / finishAffinity 把界面关掉(用户感知为闪退,进程其实还在)。 */
private static void hookAntiSoftCrash(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blockFinishHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
String methodName = param.method.getName();
if (!shouldBlockFinish(activity, methodName)) {
return;
}
XposedBridge.log(TAG + " blocked " + methodName
+ " after suicide attempt: " + activity.getClass().getSimpleName());
param.setResult(null);
}
};
try {
XposedHelpers.findAndHookMethod(Activity.class, "finish", blockFinishHook);
} catch (Throwable t) {
XposedBridge.log(TAG + " finish hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(Activity.class, "finishAfterTransition", blockFinishHook);
} catch (Throwable t) {
XposedBridge.log(TAG + " finishAfterTransition hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Activity.class,
"finishAffinity",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (!shouldBlockFinish(activity, "finishAffinity")) {
return;
}
XposedBridge.log(TAG + " blocked finishAffinity after suicide attempt: "
+ activity.getClass().getSimpleName());
param.setResult(null);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " finishAffinity hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Activity.class,
"finishAndRemoveTask",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (!shouldBlockFinish(activity, "finishAndRemoveTask")) {
return;
}
XposedBridge.log(TAG + " blocked finishAndRemoveTask after suicide attempt: "
+ activity.getClass().getSimpleName());
param.setResult(null);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " finishAndRemoveTask hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(Activity.class, "moveTaskToBack", boolean.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (!shouldBlockFinish(activity, "moveTaskToBack")) {
return;
}
XposedBridge.log(TAG + " blocked moveTaskToBack after suicide attempt");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " moveTaskToBack hook failed: " + t.getMessage());
}
}
private static boolean shouldBlockSoftCrash() {
return System.currentTimeMillis() - lastBlockedSuicideAt < SOFT_CRASH_GUARD_MS;
}
private static boolean isBkeActivity(Activity activity) {
String name = activity.getClass().getName();
return name.startsWith("com.shopee.bke") || name.startsWith("com.shopee.bke.digitalbank");
}
private static boolean isUserBackNavigation() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
String method = frame.getMethodName();
if ("onBackPressed".equals(method) || "onBackInvoked".equals(method)) {
return true;
}
}
return false;
}
private static void trackFinishBurst() {
long now = System.currentTimeMillis();
if (now - finishBurstStartMs > FINISH_BURST_WINDOW_MS) {
finishBurstCount = 0;
finishBurstStartMs = now;
}
finishBurstCount++;
if (finishBurstCount >= FINISH_BURST_THRESHOLD) {
lastBlockedSuicideAt = now;
}
}
/**
* finish / finishAffinity 往往先于 killProcess
* finishAffinity 为 Root 检测自杀常用路径,对 bke Activity 直接拦截(保留返回键)。
*/
private static boolean shouldBlockFinish(Activity activity, String methodName) {
String name = activity.getClass().getName();
if (name.contains("SafeModeRecoverActivity")) {
return false;
}
if (isUserBackNavigation()) {
return false;
}
if (!isBkeActivity(activity)) {
return false;
}
if ("finishAffinity".equals(methodName) || "finishAndRemoveTask".equals(methodName)) {
return true;
}
trackFinishBurst();
if (shouldBlockSoftCrash()) {
return true;
}
if (finishBurstCount >= FINISH_BURST_THRESHOLD) {
return true;
}
return isRiskRelatedStackTrace();
}
private static boolean isRiskRelatedStackTrace() {
for (StackTraceElement frame : Thread.currentThread().getStackTrace()) {
String cn = frame.getClassName();
if (cn.contains("safemode")
|| cn.contains("shpssdk")
|| cn.contains("com.shopee.bke")
|| cn.contains("bke.biz.base.risk")
|| cn.contains("errorcodehandler")
|| cn.contains("ErrorFlow")) {
return true;
}
}
return false;
}
/** 记录注册/OTP 错误码路径,便于 logcat 定位 -1201 来源。 */
private static void hookErrorFlowLogging(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : ERROR_FLOW_CLASSES) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args == null || param.args.length == 0) {
return;
}
String args = Arrays.toString(param.args);
if (args.contains("1201") || args.contains("-1201")
|| args.toLowerCase().contains("error")) {
XposedBridge.log(TAG + " " + className + "."
+ method.getName() + " args=" + args);
}
}
});
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip error flow " + className + ": " + t.getMessage());
}
}
}
/** 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) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
continue;
}
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class && returnType != Boolean.class) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked SHPS boolean checks in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip SHPS " + className + ": " + t.getMessage());
}
}
return count;
}
/**
* SHPSSDK 风控 token清空本地 risk 列表,避免 Root/Hook 标记写入 token 上报服务端。
* 逆向getRiskSync / getRiskAsync / assessRisk → List&lt;SPSAssessRisk&gt;RISK_ROOT=1, RISK_HOOK=4 ...
*/
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) {
hookEmptyRiskList(lpparam, className, "getRiskSync", contextClass);
hookEmptyRiskList(lpparam, className, "getExtRiskSync", contextClass);
hookRiskAsyncCallback(lpparam, className, "getRiskAsync", contextClass,
className.contains("bank")
? "com.shopee.shpssdkbank.SPSResultCallback"
: "com.shopee.shpssdk.SPSResultCallback");
hookRiskAsyncCallback(lpparam, className, "getExtRiskAsync", contextClass,
className.contains("bank")
? "com.shopee.shpssdkbank.SPSExtResultCallback"
: "com.shopee.shpssdk.SPSExtResultCallback");
hookRiskTokenAsync(lpparam, className, contextClass);
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
"getRiskToken",
contextClass,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object token = param.getResult();
if (token instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken((String) token);
param.setResult(sanitized);
XposedBridge.log(TAG + " getRiskToken len="
+ sanitized.length() + " tail="
+ MariBankRiskTokenUtil.tail(sanitized));
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ".getRiskToken: " + t.getMessage());
}
hookTokenStringMethod(lpparam, className, "getLongToken");
hookTokenStringMethod(lpparam, className, "getShortToken");
hookShpsSecData(lpparam, className, contextClass);
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
"getSoftToken",
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object token = param.getResult();
if (token instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken((String) token);
param.setResult(sanitized);
logTokenResult("getSoftToken", sanitized);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ".getSoftToken: " + t.getMessage());
}
}
try {
XposedHelpers.findAndHookMethod(
"com.shopee.shpssdkbank.SHPSSDK",
lpparam.classLoader,
"assessRisk",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(new ArrayList<>());
XposedBridge.log(TAG + " assessRisk -> empty");
}
}
);
} catch (Throwable ignored) {
}
String[] assessRiskClasses = {
"com.shopee.shpssdkbank.SPSAssessRisk",
"com.shopee.shpssdk.SPSAssessRisk",
};
for (String className : assessRiskClasses) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
"getType",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(0);
}
}
);
} catch (Throwable ignored) {
}
}
String[] callbackAdapters = {
"com.shopee.shpssdk.SPSCallbackAdapter",
"com.shopee.shpssdkbank.SPSCallbackAdapter",
};
for (String className : callbackAdapters) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
"onGetRiskTokenFail",
int.class,
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " suppressed onGetRiskTokenFail: " + param.args[1]);
param.setResult(null);
}
}
);
} catch (Throwable ignored) {
}
}
hookShpsTokenCore(lpparam);
}
/**
* classes11 真实 token 生成链(早于 SHPSSDK 门面):
* getRiskToken → vvuuuuvvv.wwvuwuwvu(Context)
* getRiskSync → vvuuuuvvv.uuuuuuwvw(Context)
*/
private static void hookShpsTokenCore(XC_LoadPackage.LoadPackageParam lpparam) {
final String coreClass = "com.shopee.shpssdkbank.uwuvuvvww.vvuuuuvvv";
final String contextClass = "android.content.Context";
int hooked = 0;
try {
XposedHelpers.findAndHookMethod(
coreClass,
lpparam.classLoader,
"wwvuwuwvu",
contextClass,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object token = param.getResult();
if (token instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken((String) token);
param.setResult(sanitized);
XposedBridge.log(TAG + " core.wwvuwuwvu len="
+ sanitized.length() + " tail="
+ MariBankRiskTokenUtil.tail(sanitized));
}
}
});
hooked++;
} catch (Throwable t) {
XposedBridge.log(TAG + " skip core.wwvuwuwvu: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
coreClass,
lpparam.classLoader,
"uuuuuuwvw",
contextClass,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(new ArrayList<>());
XposedBridge.log(TAG + " core.uuuuuuwvw -> empty");
}
});
hooked++;
} catch (Throwable t) {
XposedBridge.log(TAG + " skip core.uuuuuuwvw: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
coreClass,
lpparam.classLoader,
"wwwuvwwuu",
contextClass,
String.class,
boolean.class,
boolean.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (result instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText((String) result);
param.setResult(sanitized);
XposedBridge.log(TAG + " core.wwwuvwwuu len=" + sanitized.length());
}
}
});
hooked++;
} catch (Throwable t) {
XposedBridge.log(TAG + " skip core.wwwuvwwuu: " + t.getMessage());
}
if (hooked > 0) {
XposedBridge.log(TAG + " shps token core hooks=" + hooked);
}
}
private static void hookEmptyRiskList(
XC_LoadPackage.LoadPackageParam lpparam,
String className,
String methodName,
String contextClassName) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
methodName,
contextClassName,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(new ArrayList<>());
XposedBridge.log(TAG + " " + methodName + " -> empty");
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + "." + methodName + ": " + t.getMessage());
}
}
private static void hookRiskAsyncCallback(
XC_LoadPackage.LoadPackageParam lpparam,
String sdkClass,
String methodName,
String contextClassName,
String callbackClassName) {
try {
Class<?> callbackClass = XposedHelpers.findClass(callbackClassName, lpparam.classLoader);
XposedHelpers.findAndHookMethod(sdkClass, lpparam.classLoader, methodName,
contextClassName, callbackClass, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Object original = param.args[1];
if (original == null) {
return;
}
param.args[1] = wrapRiskCallback(lpparam.classLoader, callbackClass, original);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " skip async " + sdkClass + "." + methodName + ": " + t.getMessage());
}
}
private static Object wrapRiskCallback(
ClassLoader loader,
Class<?> callbackClass,
Object original) {
return Proxy.newProxyInstance(loader, new Class[]{callbackClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("result".equals(method.getName()) && args != null && args.length > 0
&& args[0] instanceof List) {
List<?> list = (List<?>) args[0];
XposedBridge.log(TAG + " async risk callback cleared size=" + list.size());
args[0] = new ArrayList<>();
}
return method.invoke(original, args);
}
});
}
private static void hookRiskTokenAsync(
XC_LoadPackage.LoadPackageParam lpparam,
String sdkClass,
String contextClassName) {
String callbackClassName = sdkClass.contains("bank")
? "com.shopee.shpssdkbank.SPSRiskTokenCallback"
: "com.shopee.shpssdk.SPSRiskTokenCallback";
try {
Class<?> callbackClass = XposedHelpers.findClass(callbackClassName, lpparam.classLoader);
XposedHelpers.findAndHookMethod(
sdkClass,
lpparam.classLoader,
"getRiskTokenAsync",
contextClassName,
callbackClass,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Object original = param.args[1];
if (original == null) {
return;
}
param.args[1] = wrapRiskTokenCallback(
lpparam.classLoader, callbackClass, original);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + sdkClass + ".getRiskTokenAsync: " + t.getMessage());
}
}
private static Object wrapRiskTokenCallback(
ClassLoader loader,
Class<?> callbackClass,
Object original) {
return Proxy.newProxyInstance(loader, new Class[]{callbackClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("onResult".equals(method.getName()) && args != null && args.length > 0) {
if (args[0] instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken((String) args[0]);
XposedBridge.log(TAG + " getRiskTokenAsync onResult len="
+ sanitized.length() + " tail="
+ MariBankRiskTokenUtil.tail(sanitized));
args[0] = sanitized;
}
}
return method.invoke(original, args);
}
});
}
private static void hookShpsSecData(
XC_LoadPackage.LoadPackageParam lpparam,
String className,
String contextClass) {
String[][] methods = {
{"getSHPSECData", contextClass, "java.lang.String", "boolean"},
{"getSHPSECAllData", contextClass, "java.lang.String", "boolean"},
};
for (String[] sig : methods) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
sig[0],
sig[1],
sig[2],
"boolean",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.getResult() instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(
(String) param.getResult());
param.setResult(sanitized);
}
}
}
);
} catch (Throwable ignored) {
}
}
}
private static void hookTokenStringMethod(
XC_LoadPackage.LoadPackageParam lpparam,
String className,
String methodName) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
methodName,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object token = param.getResult();
if (token instanceof String) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken((String) token);
param.setResult(sanitized);
logTokenResult(methodName, sanitized);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + "." + methodName + ": " + t.getMessage());
}
}
private static void logTokenResult(String methodName, Object token) {
if (token instanceof String) {
String s = MariBankRiskTokenUtil.sanitizeRiskToken((String) token);
XposedBridge.log(TAG + " " + methodName + " len=" + s.length()
+ " tail=" + MariBankRiskTokenUtil.tail(s));
}
}
/** 记录注册 API 响应,净化 riskToken JSON定位 4067004 来源 URL。 */
private static void hookNetworkLogging(XC_LoadPackage.LoadPackageParam lpparam) {
// 勿 Hook RealInterceptorChain.proceed — libshpssdk.so 字符串硬编码检测该 Hook。
hookRequestBuilderBuild(lpparam);
hookRequestBuilderBody(lpparam);
hookOkHttpNewCall(lpparam);
hookRealCallExecute(lpparam);
hookGsonFromJson(lpparam);
hookRequestBodyWriteTo(lpparam);
hookOkHttpResponseUrl(lpparam);
hookOkioBufferWrite(lpparam);
hookOutgoingRequestBody(lpparam);
hookOutgoingRequestBytes(lpparam);
hookJsonRiskTokenPut(lpparam);
hookGsonRiskToken(lpparam);
hookRetrofitGsonConverter(lpparam);
try {
XposedHelpers.findAndHookMethod(
"okhttp3.ResponseBody",
lpparam.classLoader,
"string",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String body = (String) param.getResult();
if (body == null) {
return;
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(body);
if (!sanitized.equals(body)) {
param.setResult(sanitized);
body = sanitized;
}
String lower = body.toLowerCase();
if (lower.contains("blocked")
|| body.contains(String.valueOf(ERROR_CODE_SECURITY_BLOCKED))
|| body.contains(String.valueOf(ERROR_CODE_SECURITY_BLOCKED_ALT))
|| lower.contains("risktoken")
|| lower.contains("\"code\"")) {
String url = CURRENT_REQUEST_URL.get();
String snippet = body.length() > 600
? body.substring(0, 600) + "..." : body;
XposedBridge.log(TAG + " HTTP"
+ (url != null ? " " + url : "")
+ " body: " + snippet);
}
CURRENT_REQUEST_URL.remove();
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip okhttp ResponseBody.string: " + t.getMessage());
}
}
/** 出站 Request 构建时记录 URL并在 post/put 阶段净化 body。 */
private static void hookRequestBuilderBuild(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Request$Builder",
lpparam.classLoader,
"build",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object url = XposedHelpers.callMethod(param.getResult(), "url");
if (url != null) {
String urlStr = String.valueOf(url);
CURRENT_REQUEST_URL.set(urlStr);
}
} catch (Throwable ignored) {
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Request.Builder.build: " + t.getMessage());
}
}
/** 在 RequestBody 挂到 Request 时净化(比抽象 writeTo Hook 更可靠)。 */
private static void hookRequestBuilderBody(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook bodyHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
for (int i = 0; i < param.args.length; i++) {
Object arg = param.args[i];
if (arg == null || !isRequestBody(lpparam.classLoader, arg)) {
continue;
}
Object sanitized = sanitizeRequestBody(lpparam.classLoader, arg);
if (sanitized != arg) {
param.args[i] = sanitized;
XposedBridge.log(TAG + " Request.Builder body riskToken sanitized");
}
}
}
};
String[][] sigs = {
{"post", "okhttp3.RequestBody"},
{"put", "okhttp3.RequestBody"},
{"patch", "okhttp3.RequestBody"},
};
for (String[] sig : sigs) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Request$Builder",
lpparam.classLoader,
sig[0],
sig[1],
bodyHook);
} catch (Throwable ignored) {
}
}
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Request$Builder",
lpparam.classLoader,
"method",
"java.lang.String",
"okhttp3.RequestBody",
bodyHook);
} catch (Throwable ignored) {
}
}
private static void hookOkHttpNewCall(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook callHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object sanitized = sanitizeOkHttpRequest(lpparam.classLoader, param.args[0]);
if (sanitized != param.args[0]) {
param.args[0] = sanitized;
}
} catch (Throwable ignored) {
}
}
};
try {
XposedHelpers.findAndHookMethod(
"okhttp3.OkHttpClient",
lpparam.classLoader,
"newCall",
"okhttp3.Request",
callHook);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip OkHttpClient.newCall: " + t.getMessage());
}
}
/** Retrofit 异步/同步最终走 RealCall.execute/enqueue。 */
private static void hookRealCallExecute(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook execHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object request = XposedHelpers.getObjectField(param.thisObject, "originalRequest");
if (request == null) {
return;
}
Object sanitized = sanitizeOkHttpRequest(lpparam.classLoader, request);
if (sanitized != request) {
XposedHelpers.setObjectField(param.thisObject, "originalRequest", sanitized);
}
} catch (Throwable ignored) {
}
}
};
for (String className : new String[]{"okhttp3.RealCall", "okhttp3.internal.connection.RealCall"}) {
try {
XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "execute", execHook);
XposedHelpers.findAndHookMethod(className, lpparam.classLoader, "enqueue",
"okhttp3.Callback", execHook);
XposedBridge.log(TAG + " hooked " + className);
} catch (Throwable ignored) {
}
}
}
private static Object sanitizeOkHttpRequest(ClassLoader loader, Object request) {
try {
Object url = XposedHelpers.callMethod(request, "url");
if (url == null) {
return request;
}
String urlStr = String.valueOf(url);
CURRENT_REQUEST_URL.set(urlStr);
Object body = XposedHelpers.callMethod(request, "body");
if (body == null) {
if (urlStr.contains("/register")) {
XposedBridge.log(TAG + " register request has null body");
}
return request;
}
String content = readRequestBodyText(loader, body);
if (content.isEmpty()) {
if (urlStr.contains("/register")) {
XposedBridge.log(TAG + " register request body unreadable (encrypted or one-shot)");
}
return request;
}
if (urlStr.contains("/register")) {
int show = Math.min(content.length(), 500);
XposedBridge.log(TAG + " outbound register body: "
+ content.substring(0, show)
+ (content.length() > show ? "..." : ""));
}
Object sanitizedBody = sanitizeRequestBody(loader, body);
if (sanitizedBody == body) {
return request;
}
String method = (String) XposedHelpers.callMethod(request, "method");
Object builder = XposedHelpers.callMethod(request, "newBuilder");
XposedHelpers.callMethod(builder, "method", method, sanitizedBody);
Object newRequest = XposedHelpers.callMethod(builder, "build");
XposedBridge.log(TAG + " sanitized outbound body for " + urlStr);
return newRequest;
} catch (Throwable t) {
return request;
}
}
/** dfp 响应若走 Gson.fromJson(String) 而非 ResponseBody.string需净化入参。 */
private static void hookGsonFromJson(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook fromJsonHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (!(param.args[0] instanceof String)) {
return;
}
String json = (String) param.args[0];
if (!json.contains("riskToken") && !json.contains("deviceToken")) {
return;
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(json);
if (!sanitized.equals(json)) {
param.args[0] = sanitized;
XposedBridge.log(TAG + " Gson.fromJson riskToken sanitized");
}
}
};
try {
XposedHelpers.findAndHookMethod(
"com.google.gson.Gson",
lpparam.classLoader,
"fromJson",
String.class,
Class.class,
fromJsonHook);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"com.google.gson.Gson",
lpparam.classLoader,
"fromJson",
String.class,
"java.lang.reflect.Type",
fromJsonHook);
} catch (Throwable ignored) {
}
}
private static boolean isRequestBody(ClassLoader loader, Object obj) {
try {
Class<?> rb = XposedHelpers.findClass("okhttp3.RequestBody", loader);
return rb.isInstance(obj);
} catch (Throwable t) {
return obj.getClass().getName().contains("RequestBody");
}
}
private static Object sanitizeRequestBody(ClassLoader loader, Object body) {
try {
ClassLoader effective = loaderFor(body, loader);
String content = readRequestBodyText(effective, body);
if (content.isEmpty()) {
return body;
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(content);
if (sanitized.equals(content)) {
return body;
}
Object mediaType = XposedHelpers.callMethod(body, "contentType");
Class<?> rbClass = findClassSafe(effective, "okhttp3.RequestBody");
return XposedHelpers.callStaticMethod(rbClass, "create", mediaType, sanitized);
} catch (Throwable t) {
return body;
}
}
private static String readRequestBodyText(ClassLoader loader, Object body) {
try {
ClassLoader effective = loaderFor(body, loader);
Class<?> bufferClass = findClassSafe(effective, "okio.Buffer");
Object buffer = XposedHelpers.newInstance(bufferClass);
XposedHelpers.callMethod(body, "writeTo", buffer);
return (String) XposedHelpers.callMethod(buffer, "readUtf8");
} catch (Throwable t) {
return "";
}
}
private static ClassLoader loaderFor(Object obj, ClassLoader fallback) {
if (obj != null) {
ClassLoader cl = obj.getClass().getClassLoader();
if (cl != null) {
return cl;
}
}
return fallback;
}
private static Class<?> findClassSafe(ClassLoader loader, String name) {
try {
return XposedHelpers.findClass(name, loader);
} catch (Throwable first) {
ClassLoader ctx = Thread.currentThread().getContextClassLoader();
if (ctx != null && ctx != loader) {
return XposedHelpers.findClass(name, ctx);
}
throw first;
}
}
/**
* 拦截 RequestBody 写入:注册 JSON 只写一次create/Buffer Hook 可能漏掉。
* 读出 body → 净化 riskToken → 写入 sink跳过原方法。
*/
private static void hookRequestBodyWriteTo(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> rbClass = findClassSafe(lpparam.classLoader, "okhttp3.RequestBody");
Class<?> sinkClass = findClassSafe(lpparam.classLoader, "okio.BufferedSink");
XposedHelpers.findAndHookMethod(
rbClass,
"writeTo",
sinkClass,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
ClassLoader cl = loaderFor(param.thisObject, lpparam.classLoader);
Class<?> bufferClass = findClassSafe(cl, "okio.Buffer");
Object buffer = XposedHelpers.newInstance(bufferClass);
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 (url != null && url.contains("/register") && !content.isEmpty()) {
int show = Math.min(content.length(), 400);
XposedBridge.log(TAG + " register body(raw): "
+ content.substring(0, show)
+ (content.length() > show ? "..." : ""));
}
if (!sanitized.equals(content)) {
XposedBridge.log(TAG + " RequestBody.writeTo riskToken sanitized");
}
XposedHelpers.callMethod(param.args[0], "writeUtf8", sanitized);
param.setResult(null);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip RequestBody.writeTo: " + t.getMessage());
}
}
private static void hookRetrofitGsonConverter(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"retrofit2.converter.gson.GsonRequestBodyConverter",
lpparam.classLoader,
"convert",
Object.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object body = param.getResult();
if (body == null) {
return;
}
try {
ClassLoader cl = loaderFor(body, lpparam.classLoader);
Object buffer = XposedHelpers.newInstance(
findClassSafe(cl, "okio.Buffer"));
XposedHelpers.callMethod(body, "writeTo", buffer);
String content = (String) XposedHelpers.callMethod(buffer, "readUtf8");
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(content);
if (!sanitized.equals(content)) {
Object mediaType = XposedHelpers.callMethod(body, "contentType");
param.setResult(XposedHelpers.callStaticMethod(
XposedHelpers.findClass("okhttp3.RequestBody", lpparam.classLoader),
"create",
mediaType,
sanitized));
XposedBridge.log(TAG + " GsonRequestBodyConverter riskToken sanitized");
}
} catch (Throwable ignored) {
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip GsonRequestBodyConverter: " + t.getMessage());
}
}
/** 出站 body 写入 okio.Buffer 时净化 riskToken不 Hook proceed。 */
private static void hookOkioBufferWrite(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook sanitizeHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args.length == 0 || !(param.args[0] instanceof String)) {
return;
}
String s = (String) param.args[0];
if (!s.contains("|")) {
return;
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(s);
if (!sanitized.equals(s)) {
param.args[0] = sanitized;
XposedBridge.log(TAG + " okio.Buffer write riskToken sanitized");
}
}
};
try {
Class<?> bufferClass = findClassSafe(lpparam.classLoader, "okio.Buffer");
XposedHelpers.findAndHookMethod(
bufferClass, "writeUtf8", String.class, sanitizeHook);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip okio.Buffer.writeUtf8: " + t.getMessage());
}
try {
Class<?> bufferClass = findClassSafe(lpparam.classLoader, "okio.Buffer");
XposedHelpers.findAndHookMethod(
bufferClass, "writeString",
String.class, java.nio.charset.Charset.class, sanitizeHook);
} catch (Throwable ignored) {
}
try {
Class<?> bufferClass = findClassSafe(lpparam.classLoader, "okio.Buffer");
XposedHelpers.findAndHookMethod(
bufferClass, "write",
byte[].class, int.class, int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
byte[] data = (byte[]) param.args[0];
int off = (int) param.args[1];
int len = (int) param.args[2];
byte[] sanitized = MariBankRiskTokenUtil.sanitizeBytes(data, off, len);
if (sanitized != data) {
param.args[0] = sanitized;
param.args[1] = 0;
param.args[2] = sanitized.length;
XposedBridge.log(TAG + " okio.Buffer write bytes riskToken sanitized");
}
}
});
} catch (Throwable ignored) {
}
}
/** Retrofit 常用 byte[] RequestBody。 */
private static void hookOutgoingRequestBytes(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook byteHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
for (int i = 0; i < param.args.length; i++) {
if (!(param.args[i] instanceof byte[])) {
continue;
}
byte[] data = (byte[]) param.args[i];
byte[] sanitized = MariBankRiskTokenUtil.sanitizeBytes(data, 0, data.length);
if (sanitized != data) {
param.args[i] = sanitized;
XposedBridge.log(TAG + " outbound RequestBody bytes riskToken sanitized");
}
}
}
};
try {
XposedHelpers.findAndHookMethod(
"okhttp3.RequestBody",
lpparam.classLoader,
"create",
"okhttp3.MediaType",
byte[].class,
byteHook);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"okhttp3.RequestBody",
lpparam.classLoader,
"create",
byte[].class,
"okhttp3.MediaType",
byteHook);
} catch (Throwable ignored) {
}
}
private static void hookOkHttpResponseUrl(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Response",
lpparam.classLoader,
"body",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object request = XposedHelpers.callMethod(param.thisObject, "request");
Object url = XposedHelpers.callMethod(request, "url");
if (url != null) {
CURRENT_REQUEST_URL.set(String.valueOf(url));
}
} catch (Throwable ignored) {
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip okhttp Response.body url: " + t.getMessage());
}
}
/** 出站 JSON 请求体:注册接口 /uapi/v2/register 会携带 riskToken。 */
private static void hookOutgoingRequestBody(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook sanitizeHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
for (int i = 0; i < param.args.length; i++) {
if (param.args[i] instanceof String) {
String body = (String) param.args[i];
if (body.contains("riskToken") && body.contains("|")) {
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(body);
if (!sanitized.equals(body)) {
param.args[i] = sanitized;
XposedBridge.log(TAG + " outbound RequestBody riskToken sanitized");
}
} else if (body.contains("deviceToken") && body.contains("|")) {
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(body);
if (!sanitized.equals(body)) {
param.args[i] = sanitized;
XposedBridge.log(TAG + " outbound RequestBody deviceToken sanitized");
}
}
}
}
}
};
String[][] createSigs = {
{"okhttp3.MediaType", "java.lang.String"},
{"java.lang.String", "okhttp3.MediaType"},
};
for (String[] sig : createSigs) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.RequestBody",
lpparam.classLoader,
"create",
sig[0],
sig[1],
sanitizeHook);
} catch (Throwable ignored) {
}
}
}
private static void hookGsonRiskToken(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook gsonHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof String)) {
return;
}
String json = (String) param.getResult();
if (json.contains("mobileNo") || json.contains("phoneNo")) {
int show = Math.min(json.length(), 500);
XposedBridge.log(TAG + " Gson.toJson mobile: "
+ json.substring(0, show)
+ (json.length() > show ? "..." : ""));
}
if (!json.contains("riskToken") && !json.contains("deviceToken")) {
return;
}
String sanitized = MariBankRiskTokenUtil.sanitizeAllInText(json);
if (!sanitized.equals(json)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " Gson.toJson riskToken sanitized");
}
}
};
try {
XposedHelpers.findAndHookMethod(
"com.google.gson.Gson",
lpparam.classLoader,
"toJson",
Object.class,
gsonHook);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip Gson.toJson: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
"com.google.gson.Gson",
lpparam.classLoader,
"toJson",
Object.class,
"java.lang.reflect.Type",
gsonHook);
} catch (Throwable ignored) {
}
}
private static void hookJsonRiskTokenPut(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"org.json.JSONObject",
lpparam.classLoader,
"put",
String.class,
Object.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (!"riskToken".equals(param.args[0]) && !"deviceToken".equals(param.args[0])) {
return;
}
if (!(param.args[1] instanceof String)) {
return;
}
param.args[1] = MariBankRiskTokenUtil.sanitizeRiskToken((String) param.args[1]);
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip JSONObject.put riskToken: " + t.getMessage());
}
}
private static void hookSafeModeDialog(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"com.shopee.bke.lib.safemode.activity.SafeModeRecoverActivity",
lpparam.classLoader,
"onCreate",
"android.os.Bundle",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedHelpers.callMethod(param.thisObject, "finish");
param.setResult(null);
}
}
);
} catch (Throwable ignored) {
}
}
/** 拦截 Root/Hook/模拟器 警告弹窗与 Toast文案见 bke_toast_not_support_*)。 */
private static void hookRootDialogBlock(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blankRootTextHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args.length > 0 && isRootBlockText(String.valueOf(param.args[0]))) {
param.args[0] = " ";
XposedBridge.log(TAG + " blanked root dialog message");
}
}
};
String[] messageSetters = {
"android.app.AlertDialog$Builder",
"androidx.appcompat.app.AlertDialog$Builder",
"com.shopee.bke.lib.commonui.widget.CommonDialog$Builder",
};
for (String className : messageSetters) {
try {
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "setMessage", CharSequence.class, blankRootTextHook);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "setTitle", CharSequence.class, blankRootTextHook);
} catch (Throwable ignored) {
}
}
try {
XposedHelpers.findAndHookMethod(
Resources.class,
"getString",
int.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String s = (String) param.getResult();
if (isRootBlockText(s)) {
param.setResult(" ");
XposedBridge.log(TAG + " blanked root string resource");
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " getString hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Dialog.class,
"show",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Dialog dialog = (Dialog) param.thisObject;
if (isRootBlockText(extractDialogText(dialog))) {
XposedBridge.log(TAG + " blocked root Dialog.show: "
+ dialog.getClass().getSimpleName());
param.setResult(null);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.show hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Toast.class,
"show",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String text = extractToastText((Toast) param.thisObject);
if (isSecurityBlockText(text)) {
XposedBridge.log(TAG + " security block Toast: " + text);
logBriefStack();
}
if (isRootBlockText(text)) {
XposedBridge.log(TAG + " blocked root Toast.show");
param.setResult(null);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " Toast.show hook failed: " + t.getMessage());
}
}
private static boolean isRootBlockText(String text) {
if (text == null || text.isEmpty()) {
return false;
}
String lower = text.toLowerCase();
return lower.contains("rooted or jailbroken")
|| lower.contains("modified device")
|| lower.contains("magisk/xposed/frida")
|| lower.contains("cannot be accessed on such devices")
|| lower.contains("restore to factory settings");
}
private static boolean isSecurityBlockText(String text) {
if (text == null || text.isEmpty()) {
return false;
}
String lower = text.toLowerCase();
return lower.contains("temporarily blocked")
|| lower.contains("8424 8050");
}
private static void logBriefStack() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
StringBuilder sb = new StringBuilder();
int n = 0;
for (StackTraceElement frame : stack) {
String cn = frame.getClassName();
if (cn.contains("miraclegarden") || cn.contains("lsposed") || cn.contains("XposedBridge")) {
continue;
}
if (cn.startsWith("android.widget.") || cn.startsWith("android.view.")) {
continue;
}
sb.append("\n at ").append(cn).append(".").append(frame.getMethodName());
if (++n >= 10) {
break;
}
}
XposedBridge.log(TAG + " stack:" + sb);
}
private static String extractDialogText(Dialog dialog) {
StringBuilder sb = new StringBuilder();
try {
Object alert = XposedHelpers.getObjectField(dialog, "mAlert");
if (alert != null) {
appendFieldText(sb, alert, "mMessage");
appendFieldText(sb, alert, "mTitle");
}
} catch (Throwable ignored) {
}
try {
if (dialog.getWindow() != null) {
collectTextViews(dialog.getWindow().getDecorView(), sb);
}
} catch (Throwable ignored) {
}
return sb.toString();
}
private static void appendFieldText(StringBuilder sb, Object target, String field) {
try {
Object value = XposedHelpers.getObjectField(target, field);
if (value != null) {
sb.append(value);
}
} catch (Throwable ignored) {
}
}
private static void collectTextViews(View view, StringBuilder sb) {
if (view instanceof TextView) {
CharSequence text = ((TextView) view).getText();
if (text != null) {
sb.append(text);
}
}
if (view instanceof ViewGroup) {
ViewGroup group = (ViewGroup) view;
for (int i = 0; i < group.getChildCount(); i++) {
collectTextViews(group.getChildAt(i), sb);
}
}
}
private static String extractToastText(Toast toast) {
try {
View view = toast.getView();
if (view instanceof TextView) {
CharSequence text = ((TextView) view).getText();
return text != null ? text.toString() : "";
}
if (view instanceof ViewGroup) {
StringBuilder sb = new StringBuilder();
collectTextViews(view, sb);
return sb.toString();
}
} catch (Throwable ignored) {
}
try {
Object text = XposedHelpers.getObjectField(toast, "mText");
return text != null ? text.toString() : "";
} catch (Throwable ignored) {
}
return "";
}
}

View File

@@ -0,0 +1,661 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.regex.Pattern;
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;
/**
* libshpssdk.so / libshpssdk_bank.so native 检测绕过Java 层拦截 native 读路径)。
* 逆向字符串:/proc/self/maps、hook 库名、RealInterceptorChain.proceed 等。
*/
public final class MariBankShpsNativeHook {
private static final String TAG = "notiMessageHook/MariBankNative";
private static final Set<String> PROC_SENSITIVE = new HashSet<>(Arrays.asList(
"/proc/self/maps",
"/proc/version",
"/proc/self/status",
"/proc/mounts",
"/proc/cpuinfo",
"/proc/self/attr/current",
"/proc/self/mountinfo",
"/proc/net/unix",
"/proc/bootconfig",
"/proc/self/cgroup"
));
private static final String[] MAPS_HIDE_MARKERS = {
"xposed", "lsposed", "edxposed", "magisk", "frida", "substrate",
"libpine", "pine.so", "zygisk", "riru", "shamiko", "notimessage",
"miraclegarden", "libbytehook", "libapmhook", "libspxhook",
"liblubanhook", "libsulfuras", "libbugsnag-root-detection",
"libreact_debug", "libmobileffmpeg_abidetect",
"playintegrityfix", "libgadget", "libfrida", "libriru",
"liblspd", "libzygisk", "libvector", "zygisk_vector",
};
private static final WeakHashMap<Object, String> TRACKED_INPUTS = new WeakHashMap<>();
private static final String[] BOOT_SPOOF_KEYS = {
"ro.boot.verifiedbootstate",
"ro.boot.flash.locked",
"ro.boot.vbmeta.device_state",
"ro.boot.veritymode",
"ro.boot.warranty_bit",
"ro.boot.avb_version",
"vendor.boot.vbmeta.device_state",
"ro.crypto.state",
};
private static final String FAKE_SELINUX_CTX =
"u:r:untrusted_app:s0:c512,c768";
private MariBankShpsNativeHook() {
}
private static volatile boolean deferredInstalled = false;
/** loadPackage 阶段只装 /proc 过滤,避免过早触发 SHPSSDK / libsdkutils 死循环白屏。 */
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
hookProcAccess(lpparam);
hookProcViaRandomAccessFile(lpparam);
hookBufferedReader(lpparam);
hookSystemProperties(lpparam);
XposedBridge.log(TAG + " early hooks OK (proc only)");
}
/** attachBaseContext 之后安装 SHPSSDK 相关 HookClassLoader 已就绪)。 */
public static void installDeferred(XC_LoadPackage.LoadPackageParam lpparam) {
if (deferredInstalled) {
return;
}
deferredInstalled = true;
hookLoadLibrary(lpparam);
hookShpssInstall(lpparam);
hookRequestDefense(lpparam);
hookShpsNativeBridge(lpparam);
hookShpsNativeCore(lpparam);
hookBuildFields(lpparam);
XposedBridge.log(TAG + " deferred hooks installed for " + lpparam.packageName);
}
/** native 直接读 /proc/self/maps 查 hook 库;过滤内容。 */
private static void hookProcAccess(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
File.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
File file = (File) param.args[0];
if (file != null) {
String path = normalizeProcPath(file.getAbsolutePath());
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
}
);
} catch (Throwable ignored) {
}
XC_MethodHook readFilter = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || param.getResult() == null) {
return;
}
if (param.getResult() instanceof Integer) {
int read = (Integer) param.getResult();
if (read <= 0 || param.args.length == 0 || !(param.args[0] instanceof byte[])) {
return;
}
byte[] buf = (byte[]) param.args[0];
int off = param.args.length > 1 ? (Integer) param.args[1] : 0;
filterProcBytes(path, buf, off, read);
} else if (param.getResult() instanceof byte[]) {
byte[] data = (byte[]) param.getResult();
param.setResult(filterProcBytesAll(path, data));
} else if (param.getResult() instanceof String) {
param.setResult(filterProcText(path, (String) param.getResult()));
}
}
};
try {
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, readFilter);
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, int.class, int.class, readFilter);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream.read hook failed: " + t.getMessage());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
XposedHelpers.findAndHookMethod(
"java.nio.file.Files",
lpparam.classLoader,
"readAllBytes",
"java.nio.file.Path",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof byte[])) {
return;
}
String path = String.valueOf(param.args[0]);
String norm = normalizeProcPath(path);
if (norm != null) {
param.setResult(filterProcBytesAll(
norm, (byte[]) param.getResult()));
}
}
}
);
} catch (Throwable ignored) {
}
}
}
private static void hookBufferedReader(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
BufferedReader.class,
"readLine",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof String)) {
return;
}
String line = (String) param.getResult();
if (shouldHideMapsLine(line)) {
param.setResult(readNextSafeLine((BufferedReader) param.thisObject));
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " BufferedReader hook failed: " + t.getMessage());
}
}
private static String readNextSafeLine(BufferedReader reader) {
try {
String line;
while ((line = reader.readLine()) != null) {
if (!shouldHideMapsLine(line)) {
return line;
}
}
} catch (Throwable ignored) {
}
return "";
}
private static void hookLoadLibrary(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook logHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String lib = String.valueOf(param.args[param.args.length - 1]);
if (lib.contains("shpssdk")) {
XposedBridge.log(TAG + " loading native lib: " + lib);
}
}
};
try {
XposedHelpers.findAndHookMethod(
Runtime.class, "loadLibrary0", ClassLoader.class, String.class, logHook);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
System.class, "loadLibrary", String.class, logHook);
} catch (Throwable ignored) {
}
}
private static void hookShpssInstall(XC_LoadPackage.LoadPackageParam lpparam) {
// 勿 Hook ShpssInstall / vuvuwwwuw会干扰 SoUtils.loadSoLibrary导致 libsdkutils.so 死循环白屏。
}
private static void hookRequestDefense(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : new String[]{
"com.shopee.shpssdkbank.SHPSSDK",
"com.shopee.shpssdk.SHPSSDK",
}) {
try {
XposedHelpers.findAndHookMethod(
className,
lpparam.classLoader,
"requestDefense",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked requestDefense");
param.setResult(null);
}
}
);
} catch (Throwable ignored) {
}
}
}
/** shpssdkbank 混淆 native 桥接类int/boolean 返回值强制安全。 */
private static void hookShpsNativeBridge(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"com.shopee.shpssdkbank.uvuwwuvwv.uvwwuuvvw",
"com.shopee.shpssdkbank.a",
"com.shopee.shpssdkbank.b",
"com.shopee.shpssdkbank.c",
"com.shopee.shpssdkbank.d",
"com.shopee.shpssdkbank.e",
"com.shopee.shpssdkbank.f",
"com.shopee.shpssdkbank.g",
"com.shopee.shpssdkbank.vuvuwwwuw",
"com.shopee.shpssdkbank.vwuuwwvwv",
"com.shopee.shpssdkbank.vwwuwuuuv",
"com.shopee.shpssdkbank.wvvvuuwuu",
"com.shopee.shpssdkbank.wvvvuuww",
"com.shopee.shpssdkbank.wvvvuvvv",
"com.shopee.shpssdkbank.wvvvuvww",
"com.shopee.shpssdkbank.wvvvuwwu",
};
int total = 0;
for (String className : classes) {
total += hookAllIntBooleanMethods(lpparam, className);
total += hookAllStringSanitize(lpparam, className);
}
XposedBridge.log(TAG + " native-bridge total hooks=" + total);
}
/** native 桥接可能直接返回 riskToken 字符串。 */
private static int hookAllStringSanitize(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (method.getReturnType() != String.class) {
continue;
}
if (method.getParameterTypes().length > 6) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (!(result instanceof String)) {
return;
}
String s = (String) result;
if (!s.contains("|")) {
return;
}
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " native String sanitized in "
+ className + "#" + method.getName());
}
}
});
count++;
}
} catch (Throwable ignored) {
}
return count;
}
private static int hookAllIntBooleanMethods(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
Class<?> rt = method.getReturnType();
if (rt != boolean.class && rt != Boolean.class
&& rt != int.class && rt != Integer.class) {
continue;
}
if (method.getParameterTypes().length > 4) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (rt == boolean.class || rt == Boolean.class) {
param.setResult(false);
} else {
param.setResult(0);
}
}
});
count++;
}
if (count > 0) {
XposedBridge.log(TAG + " hooked " + count + " native-bridge checks in " + className);
}
} catch (Throwable ignored) {
}
return count;
}
/**
* 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) {
total += hookNativeCoreClass(lpparam, className);
}
XposedBridge.log(TAG + " native-core total hooks=" + total);
}
private static int hookNativeCoreClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
if (!Modifier.isStatic(method.getModifiers())) {
continue;
}
Class<?> rt = method.getReturnType();
if (rt == String.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object result = param.getResult();
if (!(result instanceof String)) {
return;
}
String s = (String) result;
if (s.length() > 80 && s.contains("|")) {
String sanitized = MariBankRiskTokenUtil.sanitizeRiskToken(s);
if (!sanitized.equals(s)) {
param.setResult(sanitized);
XposedBridge.log(TAG + " core String sanitized "
+ className + "#" + method.getName());
}
}
}
});
count++;
} else if (rt == boolean.class || rt == Boolean.class
|| rt == int.class || rt == Integer.class) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (rt == boolean.class || rt == Boolean.class) {
param.setResult(false);
} else {
param.setResult(0);
}
}
});
count++;
}
}
if (count > 0) {
XposedBridge.log(TAG + " hooked " + count + " core natives in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip core " + className + ": " + t.getMessage());
}
return count;
}
/** Build.TAGS / FINGERPRINT 等 Java 层可读字段伪装。 */
private static void hookBuildFields(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.setStaticObjectField(Build.class, "TAGS", "release-keys");
if (String.valueOf(Build.FINGERPRINT).contains("test-keys")) {
XposedHelpers.setStaticObjectField(Build.class, "FINGERPRINT",
Build.FINGERPRINT.replace("test-keys", "release-keys"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
try {
XposedHelpers.setStaticObjectField(Build.class, "BOOTLOADER", "unknown");
} catch (Throwable ignored) {
}
}
XposedBridge.log(TAG + " Build fields spoofed");
} catch (Throwable t) {
XposedBridge.log(TAG + " Build spoof failed: " + t.getMessage());
}
}
private static void hookProcViaRandomAccessFile(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
"java.io.RandomAccessFile",
lpparam.classLoader,
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"java.io.RandomAccessFile",
lpparam.classLoader,
"read",
byte[].class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || !(param.getResult() instanceof Integer)) {
return;
}
int read = (Integer) param.getResult();
if (read > 0 && param.args[0] instanceof byte[]) {
filterProcBytes(path, (byte[]) param.args[0], 0, read);
}
}
}
);
} catch (Throwable ignored) {
}
}
private static void hookSystemProperties(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> sp = XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader);
for (Method method : sp.getDeclaredMethods()) {
if (!"get".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.args.length == 0 || !(param.args[0] instanceof String)) {
return;
}
String key = (String) param.args[0];
String spoofed = spoofProperty(key, param.getResult());
if (spoofed != null) {
param.setResult(spoofed);
}
}
});
}
} catch (Throwable t) {
XposedBridge.log(TAG + " SystemProperties hook failed: " + t.getMessage());
}
}
private static String spoofProperty(String key, Object current) {
if ("ro.debuggable".equals(key)) {
return "0";
}
if ("ro.secure".equals(key)) {
return "1";
}
if ("ro.build.tags".equals(key)) {
if (current instanceof String && String.valueOf(current).contains("test-keys")) {
return "release-keys";
}
}
if ("ro.boot.verifiedbootstate".equals(key)) {
return "green";
}
if ("ro.boot.flash.locked".equals(key)) {
return "1";
}
if ("ro.boot.vbmeta.device_state".equals(key)
|| "vendor.boot.vbmeta.device_state".equals(key)) {
return "locked";
}
if ("ro.boot.veritymode".equals(key)) {
return "enforcing";
}
if ("ro.boot.warranty_bit".equals(key)) {
return "0";
}
if ("ro.crypto.state".equals(key)) {
return "encrypted";
}
for (String bootKey : BOOT_SPOOF_KEYS) {
if (bootKey.equals(key) && key.startsWith("ro.boot")) {
// already handled above for known keys
break;
}
}
return null;
}
private static String normalizeProcPath(String path) {
if (path == null) {
return null;
}
String norm = path.trim();
for (String p : PROC_SENSITIVE) {
if (norm.equals(p) || norm.endsWith(p)) {
return p;
}
}
return null;
}
private static byte[] filterProcBytesAll(String path, byte[] data) {
return filterProcText(path, new String(data)).getBytes();
}
private static void filterProcBytes(String path, byte[] buf, int off, int len) {
String text = new String(buf, off, len);
String filtered = filterProcText(path, text);
if (filtered.equals(text)) {
return;
}
byte[] out = filtered.getBytes();
int copy = Math.min(len, out.length);
System.arraycopy(out, 0, buf, off, copy);
if (copy < len) {
Arrays.fill(buf, off + copy, off + len, (byte) 0);
}
}
private static String filterProcText(String path, String text) {
if ("/proc/self/maps".equals(path) || "/proc/self/mountinfo".equals(path)
|| "/proc/mounts".equals(path)) {
StringBuilder sb = new StringBuilder();
for (String line : text.split("\n")) {
if (!shouldHideMapsLine(line)) {
if (sb.length() > 0) {
sb.append('\n');
}
sb.append(line);
}
}
return sb.toString();
}
if ("/proc/self/attr/current".equals(path)) {
String lower = text.toLowerCase(Locale.US);
if (lower.contains("magisk") || lower.contains("su") || lower.contains("zygisk")
|| lower.contains("xposed")) {
return FAKE_SELINUX_CTX;
}
return text;
}
if ("/proc/version".equals(path)) {
return text.replace("dirty", "").replace("test-keys", "release-keys");
}
if ("/proc/self/status".equals(path)) {
return text.replaceAll("(?m)^TracerPid:\\s*[1-9]\\d*",
"TracerPid:\t0");
}
return text;
}
private static boolean shouldHideMapsLine(String line) {
if (line == null || line.isEmpty()) {
return false;
}
String lower = line.toLowerCase(Locale.US);
for (String marker : MAPS_HIDE_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,181 @@
package com.miraclegarden.smsmessage.xposed.hook;
import java.io.File;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Pattern;
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;
/**
* Root / Hook / 模拟器检测通用绕过辅助。
*/
public final class RootBypassHelper {
private static final String TAG = "notiMessageHook/RootBypass";
private static final Pattern UNSAFE_NAME = Pattern.compile(
".*(root|jail|hook|frida|xposed|lsposed|emulator|simulator|debug|tamper|"
+ "integrity|unsafe|risk|magisk|su|cheat|mock).*",
Pattern.CASE_INSENSITIVE
);
private static final Pattern SAFE_NAME = Pattern.compile(
".*(safe|secure|valid|passed|pass|clean|trusted|normal|ok).*",
Pattern.CASE_INSENSITIVE
);
private static final Set<String> ROOT_PATH_MARKERS = new HashSet<>(Arrays.asList(
"/su",
"magisk",
"supersu",
"busybox",
"/xbin/su",
"/sbin/su",
"de.robv.android.xposed",
"org.lsposed",
"com.topjohnwu.magisk"
));
private RootBypassHelper() {
}
public static void hookSecurityClass(XC_LoadPackage.LoadPackageParam lpparam, String className) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
hookMethodIfSecurityCheck(className, method);
}
XposedBridge.log(TAG + " hooked methods in " + className);
} catch (Throwable t) {
XposedBridge.log(TAG + " skip class " + className + ": " + t.getMessage());
}
}
private static void hookMethodIfSecurityCheck(String className, Method method) {
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class
&& returnType != Boolean.class
&& returnType != int.class
&& returnType != Integer.class) {
return;
}
String name = method.getName();
if (!looksLikeSecurityMethod(name) && !className.toLowerCase(Locale.US).contains("safemode")
&& !className.toLowerCase(Locale.US).contains("risk")) {
return;
}
try {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (returnType == boolean.class || returnType == Boolean.class) {
param.setResult(shouldReturnTrue(name));
} else {
param.setResult(0);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " hook failed " + className + "#" + name + ": " + t.getMessage());
}
}
private static boolean looksLikeSecurityMethod(String name) {
return UNSAFE_NAME.matcher(name).matches() || SAFE_NAME.matcher(name).matches();
}
private static boolean shouldReturnTrue(String methodName) {
if (UNSAFE_NAME.matcher(methodName).matches()) {
return false;
}
if (SAFE_NAME.matcher(methodName).matches()) {
return true;
}
return false;
}
public static void hookFileExists(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
File.class,
"exists",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
File file = (File) param.thisObject;
if (file == null) {
return;
}
String path = file.getAbsolutePath().toLowerCase(Locale.US);
for (String marker : ROOT_PATH_MARKERS) {
if (path.contains(marker)) {
param.setResult(false);
return;
}
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " File.exists hook failed: " + t.getMessage());
}
}
public static void hookRuntimeExec(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"exec",
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String cmd = (String) param.args[0];
if (cmd == null) {
return;
}
String lower = cmd.toLowerCase(Locale.US);
if (lower.contains("su") || lower.contains("magisk") || lower.contains("which su")) {
throw new SecurityException("blocked root probe");
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exec hook failed: " + t.getMessage());
}
}
public static void hookSystemGetProperty(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
System.class,
"getProperty",
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String key = (String) param.args[0];
if (key == null) {
return;
}
if ("ro.debuggable".equals(key) || "ro.secure".equals(key)) {
param.setResult("ro.secure".equals(key) ? "1" : "0");
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " System.getProperty hook failed: " + t.getMessage());
}
}
}

View File

@@ -0,0 +1,25 @@
package com.miraclegarden.smsmessage.xposed.hook;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* Suncorp Bank — Kotlin 原生 App。
* 逆向结论au.com.suncorp.marketplace.base.application.SuncorpMessagingService#onMessageReceived
*/
public final class SuncorpBankMessageHook {
private static final String MESSAGING_SERVICE =
"au.com.suncorp.marketplace.base.application.SuncorpMessagingService";
private SuncorpBankMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
BankHookHelper.hookFcmService(lpparam, MESSAGING_SERVICE, HookBridge.SOURCE_XPOSED_SUNCORP);
BankNotificationHook.install(lpparam, HookBridge.SOURCE_XPOSED_SUNCORP_NOTIFY);
XposedBridge.log("notiMessageHook/Suncorp installed for " + lpparam.packageName);
}
}

View File

@@ -23,6 +23,7 @@ public final class TelegramMessageHook {
private static final String TAG = "notiMessageHook/Telegram";
private static final String NOTIFICATION_CENTER = "org.telegram.messenger.NotificationCenter";
private static final String NOTIFICATIONS_CONTROLLER = "org.telegram.messenger.NotificationsController";
private static final String MESSAGE_OBJECT = "org.telegram.messenger.MessageObject";
private static final int DEDUP_SIZE = 512;
@@ -64,12 +65,16 @@ public final class TelegramMessageHook {
for (Object arg : args) {
if (arg instanceof List) {
processMessageList(context, lpparam.packageName, (List<?>) arg);
} else if (isMessageObject(arg)) {
forwardMessageObject(context, lpparam.packageName, arg);
}
}
}
}
);
installNotificationsControllerHook(lpparam);
XposedBridge.log(TAG + " installed for " + lpparam.packageName
+ ", didReceiveNewMessages=" + didReceiveNewMessages);
} catch (Throwable t) {
@@ -100,6 +105,38 @@ public final class TelegramMessageHook {
}
}
/** 后台弹通知路径NotificationsController.appendMessage(MessageObject) */
private static void installNotificationsControllerHook(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> controllerClass = XposedHelpers.findClass(
NOTIFICATIONS_CONTROLLER, lpparam.classLoader);
Class<?> messageObjectClass = XposedHelpers.findClass(
MESSAGE_OBJECT, lpparam.classLoader);
XposedHelpers.findAndHookMethod(
controllerClass,
"appendMessage",
messageObjectClass,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context context = getContext();
if (context == null || param.args[0] == null) {
return;
}
forwardMessageObject(context, lpparam.packageName, param.args[0]);
}
}
);
XposedBridge.log(TAG + " appendMessage hook installed for " + lpparam.packageName);
} catch (Throwable t) {
XposedBridge.log(TAG + " appendMessage hook failed: " + t.getMessage());
}
}
private static boolean isMessageObject(Object arg) {
return arg != null && MESSAGE_OBJECT.equals(arg.getClass().getName());
}
private static void processMessageList(Context context, String packageName, List<?> messages) {
for (Object item : messages) {
if (item == null) {
@@ -356,12 +393,7 @@ public final class TelegramMessageHook {
"org.telegram.messenger.MessagesController", cl);
Object mc = XposedHelpers.callStaticMethod(mcClass, "getInstance", account);
Object title = null;
try {
title = XposedHelpers.callMethod(mc, "getPeerTitle", dialogId, false);
} catch (Throwable ignored) {
title = XposedHelpers.callMethod(mc, "getPeerTitle", dialogId);
}
Object title = invokeGetPeerTitle(mc, dialogId);
String text = safeText(title);
if (!TextUtils.isEmpty(text)) {
@@ -392,6 +424,28 @@ public final class TelegramMessageHook {
return null;
}
private static Object invokeGetPeerTitle(Object mc, long dialogId) {
try {
return XposedHelpers.callMethod(mc, "getPeerTitle", dialogId, false);
} catch (Throwable ignored) {
}
try {
return XposedHelpers.callMethod(mc, "getPeerTitle", dialogId);
} catch (Throwable ignored) {
}
try {
java.lang.reflect.Method method = mc.getClass().getMethod("getPeerTitle", long.class, boolean.class);
return method.invoke(mc, dialogId, false);
} catch (Throwable ignored) {
}
try {
java.lang.reflect.Method method = mc.getClass().getMethod("getPeerTitle", long.class);
return method.invoke(mc, dialogId);
} catch (Throwable ignored) {
}
return null;
}
private static String formatUserName(Object user) {
if (user == null) {
return "";

View File

@@ -0,0 +1,29 @@
package com.miraclegarden.smsmessage.xposed.hook;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* ubank — Capacitor + MoEngage 推送。
* 逆向结论:
* - com.moengage.firebase.MoEFireBaseMessagingService交易/营销推送主路径)
* - io.capawesome.capacitorjs.plugins.firebase.messaging.MessagingServiceCapacitor FCM 插件)
*/
public final class UbankMessageHook {
private static final String MOE_SERVICE = "com.moengage.firebase.MoEFireBaseMessagingService";
private static final String CAPACITOR_SERVICE =
"io.capawesome.capacitorjs.plugins.firebase.messaging.MessagingService";
private UbankMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
BankHookHelper.hookFcmService(lpparam, MOE_SERVICE, HookBridge.SOURCE_XPOSED_UBANK);
BankHookHelper.hookFcmService(lpparam, CAPACITOR_SERVICE, HookBridge.SOURCE_XPOSED_UBANK);
BankNotificationHook.install(lpparam, HookBridge.SOURCE_XPOSED_UBANK_NOTIFY);
XposedBridge.log("notiMessageHook/ubank installed for " + lpparam.packageName);
}
}

View File

@@ -0,0 +1,24 @@
package com.miraclegarden.smsmessage.xposed.hook;
import com.miraclegarden.smsmessage.xposed.HookBridge;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* Up Bank — React Native + 原生 FCM HandlerService。
* 逆向结论au.com.up.money.notifications.HandlerService#onMessageReceived(RemoteMessage)
*/
public final class UpBankMessageHook {
private static final String HANDLER_SERVICE = "au.com.up.money.notifications.HandlerService";
private UpBankMessageHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
BankHookHelper.hookFcmService(lpparam, HANDLER_SERVICE, HookBridge.SOURCE_XPOSED_UP);
BankNotificationHook.install(lpparam, HookBridge.SOURCE_XPOSED_UP_NOTIFY);
XposedBridge.log("notiMessageHook/Up installed for " + lpparam.packageName);
}
}