fix(tng): Splash 卡住才救援,并跳过首页强制 eKYC
避免已登录冷启动被强拉回登录页;HomeEkycVerify 测试期 finish + canBypass/enforce stub。
This commit is contained in:
52
docs/TNG_开放问题与抓包能力.md
Normal file
52
docs/TNG_开放问题与抓包能力.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# TNG 开放问题与抓包能力(2026-08-04)
|
||||
|
||||
## 待办:冷启动假掉登录(先不改)
|
||||
|
||||
**现象:** 登录成功后退出再进,界面像未登录,需重新登录。
|
||||
|
||||
**根因判断(已基本确认):**
|
||||
旧逻辑:`SplashActivity.onCreate` **无条件** 2s 强拉 `UserLoginActivity`。
|
||||
|
||||
**已修复(2026-08-04):** 改为 4s **卡住救援**——已自行跳到 PIN/首页则取消;仅仍停在 Splash 时救援(有本地会话优先 `UserPinActivity`,否则 `UserLoginActivity`)。
|
||||
|
||||
---
|
||||
|
||||
## Money Packet 领取统计 — 网络/明文获取能力
|
||||
|
||||
目标:群红包 Leaderboard 的 **昵称 + 已领金额**(字段预期 `receiverList` / `claimedAmount` 等)。
|
||||
|
||||
### 路线对比
|
||||
|
||||
| 路线 | 状态 | 说明 |
|
||||
|------|------|------|
|
||||
| 外部 mitm(Charles / mitmproxy) | **不可行(当前)** | TNG API(如 `mpaasgw.tngdigital.com.my`)有 **证书 pinning**,只能看到域名,解不开 HTTPS 正文。`reverse/dumps/mitm_mmp/` 里曾落盘的多为官网 HTML/新闻,**不是** 红包 API。 |
|
||||
| 进程内 Xposed(`TngMoneyPacketHook`) | **代码已接,运行时未实证** | 在 TLS 之后读明文:Hook `OkHttp ResponseBody.string` + `Gson.fromJson`,匹配 `receiverList` / `claimedAmount` / `Mmp*` 模型,经 `HookForwarder` 转发。`MainHook` 已 `install`。 |
|
||||
| 纯日志/UI 自动化 | 兜底 | 无 API 时可读界面,不稳定,不作主路径。 |
|
||||
|
||||
### 当前缺口
|
||||
|
||||
1. **尚未在真机打开 Money Packet Leaderboard 做过一次捕获验证** → logcat 里暂无 `TngMmp captured …`。
|
||||
2. TNG 主业务多为 **mPaaS / Quake RPC**,若响应不走 `ResponseBody.string()` / 目标 Gson 类名不符,现有 Hook 会漏;需补 **RPC invoke 返回值 / 其它 JSON 入口**。
|
||||
3. Splash 强拉登录不影响「登录后进群点红包」时的抓包,但影响复测效率。
|
||||
|
||||
### 建议验证步骤(下次动手)
|
||||
|
||||
1. 保持登录态,进群 → 打开红包详情 / Leaderboard。
|
||||
2. 看 LSPosed:`notiMessageHook/TngMmp installed` 与 `captured packet=… claims=N`。
|
||||
3. 若无:对同一次操作抓 logcat 里 URL / 类名,补 Hook 点(Quake `RpcInvocationHandler` 等)。
|
||||
4. 确认 notiMessage / debug-server 是否收到转发内容。
|
||||
|
||||
### eKYC「验证您的帐户」强制页(2026-08-04)
|
||||
|
||||
`HomeEkycVerifyActivity` 挡首页。测试期:`hookHomeEkycVerifySkip` — finish 该页 + 拦 Intent + `canBypassEkyc`/`enforceEkyc` stub。
|
||||
**注意:** 服务端仍可能在部分功能(转账/红包)二次校验 eKYC,首页跳过不等于全功能可用。
|
||||
|
||||
|
||||
```text
|
||||
packetId / title
|
||||
receiverList[]:
|
||||
- nickname / displayName
|
||||
- claimedAmount(或 amount)
|
||||
```
|
||||
|
||||
按昵称聚合 `claimedAmount` 即可做领取排行。
|
||||
@@ -18,6 +18,7 @@ import android.view.WindowManager;
|
||||
|
||||
import android.os.Message;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
@@ -131,6 +132,8 @@ public final class TngRootBypassHook {
|
||||
hookAppSecurityManager(lpparam);
|
||||
hookAppSecurityCallbacks(lpparam);
|
||||
hookSecurityErrorActivity(lpparam);
|
||||
// 首页强制 eKYC「验证您的帐户」— 测试期直接跳过
|
||||
hookHomeEkycVerifySkip(lpparam);
|
||||
// seccomp 开着时必须 stub TigerTally init,否则 fork getprop 永不退出 → App.onCreate ANR
|
||||
hookTigerTally(lpparam);
|
||||
hookTigerTallyAppWrappers(lpparam);
|
||||
@@ -156,51 +159,54 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
}
|
||||
|
||||
/** Splash 优先生效;新 schedule 会取消旧 Runnable(Application 兜底 vs Splash 2000ms)。 */
|
||||
/** Splash 卡住救援;新 schedule 会取消旧 Runnable。 */
|
||||
private static final Handler FORCE_LOGIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||
private static Runnable pendingForceLoginRunnable;
|
||||
/** Splash 已离开(进 PIN/首页等)则取消救援。 */
|
||||
private static volatile boolean splashNavigationDone = false;
|
||||
|
||||
private static void scheduleForceLoginToUserLogin(
|
||||
/**
|
||||
* 仅当 Splash 超时仍停在自身时才救援,避免已登录冷启动被强拉回 UserLogin。
|
||||
* 有本地会话痕迹 → 优先 UserPin;否则 → UserLogin。
|
||||
*/
|
||||
private static void scheduleSplashStuckRescue(
|
||||
final Context appCtx, final Activity splashAct, final String reason, final long delayMs) {
|
||||
if (appCtx == null) {
|
||||
return;
|
||||
}
|
||||
splashNavigationDone = false;
|
||||
if (pendingForceLoginRunnable != null) {
|
||||
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
|
||||
}
|
||||
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
|
||||
pendingForceLoginRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
pendingForceLoginRunnable = null;
|
||||
try {
|
||||
if (isTopActivityRegistrationFlow(appCtx)) {
|
||||
XposedBridge.log(TAG + " skip force login (" + reason + ", registration flow)");
|
||||
if (splashNavigationDone) {
|
||||
XposedBridge.log(TAG + " skip splash rescue (" + reason + ", already left)");
|
||||
return;
|
||||
}
|
||||
ActivityManager am =
|
||||
(ActivityManager) appCtx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am != null) {
|
||||
for (ActivityManager.AppTask task : am.getAppTasks()) {
|
||||
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
|
||||
if (info == null || info.topActivity == null) {
|
||||
continue;
|
||||
}
|
||||
String top = info.topActivity.getClassName();
|
||||
if (top.endsWith(".UserLoginActivity")
|
||||
|| top.endsWith(".UserPinActivity")
|
||||
|| isRegistrationFlowActivity(top)) {
|
||||
XposedBridge.log(TAG + " skip force login (" + reason + ", on " + top + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (isTopActivityRegistrationFlow(appCtx)) {
|
||||
XposedBridge.log(TAG + " skip splash rescue (" + reason + ", registration flow)");
|
||||
return;
|
||||
}
|
||||
String top = getTopActivityClassName(appCtx);
|
||||
if (top != null && !isSplashActivityName(top)) {
|
||||
XposedBridge.log(TAG + " skip splash rescue (" + reason + ", on " + top + ")");
|
||||
splashNavigationDone = true;
|
||||
return;
|
||||
}
|
||||
if (!isUiVisibleForForceLogin(appCtx)) {
|
||||
XposedBridge.log(TAG + " skip force login (" + reason + ", no visible UI / BAL)");
|
||||
XposedBridge.log(TAG + " skip splash rescue (" + reason + ", no visible UI / BAL)");
|
||||
return;
|
||||
}
|
||||
boolean hasSession = hasLocalLoginSession(appCtx);
|
||||
String target = hasSession
|
||||
? "my.com.tngdigital.user.view.UserPinActivity"
|
||||
: "my.com.tngdigital.user.view.UserLoginActivity";
|
||||
Intent intent = new Intent();
|
||||
intent.setClassName(PACKAGE, login);
|
||||
intent.setClassName(PACKAGE, target);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
if (splashAct != null && !splashAct.isFinishing()) {
|
||||
@@ -209,20 +215,141 @@ public final class TngRootBypassHook {
|
||||
splashAct.finish();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from Splash)");
|
||||
XposedBridge.log(TAG + " splash stuck → " + shortActivityName(target)
|
||||
+ " (" + reason + ", session=" + hasSession + ", from Splash)");
|
||||
} else {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
appCtx.startActivity(intent);
|
||||
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from AppCtx)");
|
||||
XposedBridge.log(TAG + " splash stuck → " + shortActivityName(target)
|
||||
+ " (" + reason + ", session=" + hasSession + ", from AppCtx)");
|
||||
}
|
||||
splashNavigationDone = true;
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " force login failed (" + reason + "): " + t.getMessage());
|
||||
XposedBridge.log(TAG + " splash rescue failed (" + reason + "): " + t.getMessage());
|
||||
}
|
||||
}
|
||||
};
|
||||
FORCE_LOGIN_HANDLER.postDelayed(pendingForceLoginRunnable, delayMs);
|
||||
}
|
||||
|
||||
private static String shortActivityName(String className) {
|
||||
if (className == null) {
|
||||
return "?";
|
||||
}
|
||||
int dot = className.lastIndexOf('.');
|
||||
return dot >= 0 ? className.substring(dot + 1) : className;
|
||||
}
|
||||
|
||||
private static boolean isSplashActivityName(String className) {
|
||||
return className != null
|
||||
&& (className.endsWith(".SplashActivity") || className.contains(".SplashActivity"));
|
||||
}
|
||||
|
||||
private static String getTopActivityClassName(Context ctx) {
|
||||
try {
|
||||
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (am == null) {
|
||||
return null;
|
||||
}
|
||||
for (ActivityManager.AppTask task : am.getAppTasks()) {
|
||||
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
|
||||
if (info == null || info.topActivity == null) {
|
||||
continue;
|
||||
}
|
||||
if (PACKAGE.equals(info.topActivity.getPackageName())) {
|
||||
return info.topActivity.getClassName();
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 粗判本地是否已有登录痕迹(有则冷启动应走 PIN,而不是登录页)。
|
||||
* SharedPreferences 文件名/键含 session、token、user、pin、login 等即视为已登录。
|
||||
*/
|
||||
private static boolean hasLocalLoginSession(Context ctx) {
|
||||
try {
|
||||
File prefsDir = new File(ctx.getApplicationInfo().dataDir, "shared_prefs");
|
||||
if (!prefsDir.isDirectory()) {
|
||||
return false;
|
||||
}
|
||||
File[] files = prefsDir.listFiles();
|
||||
if (files == null) {
|
||||
return false;
|
||||
}
|
||||
for (File f : files) {
|
||||
String name = f.getName().toLowerCase(Locale.US);
|
||||
if (!name.endsWith(".xml")) {
|
||||
continue;
|
||||
}
|
||||
if (name.contains("session") || name.contains("token") || name.contains("user")
|
||||
|| name.contains("login") || name.contains("account")
|
||||
|| name.contains("auth") || name.contains("pin")
|
||||
|| name.contains("credential") || name.contains("wallet")) {
|
||||
if (f.length() > 64) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 扫键名:任意 prefs 里出现登录相关 key
|
||||
if (prefsXmlLooksLikeLoggedIn(f)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " hasLocalLoginSession failed: " + t.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean prefsXmlLooksLikeLoggedIn(File xmlFile) {
|
||||
java.io.BufferedReader reader = null;
|
||||
try {
|
||||
reader = new java.io.BufferedReader(new java.io.FileReader(xmlFile));
|
||||
String line;
|
||||
int lines = 0;
|
||||
while ((line = reader.readLine()) != null && lines < 200) {
|
||||
lines++;
|
||||
String lower = line.toLowerCase(Locale.US);
|
||||
if ((lower.contains("name=\"") || lower.contains("name='"))
|
||||
&& (lower.contains("token") || lower.contains("session")
|
||||
|| lower.contains("userid") || lower.contains("user_id")
|
||||
|| lower.contains("loginid") || lower.contains("mobile")
|
||||
|| lower.contains("phonenumber") || lower.contains("islogin")
|
||||
|| lower.contains("logged") || lower.contains("access_token"))) {
|
||||
// 排除空值
|
||||
if (lower.contains(">true<") || lower.contains("value=\"true\"")
|
||||
|| (lower.contains("value=\"") && !lower.contains("value=\"\"")
|
||||
&& !lower.contains("value=\"0\"") && !lower.contains("value=\"false\""))
|
||||
|| (lower.contains(">") && lower.contains("</string>")
|
||||
&& !lower.contains("><"))) {
|
||||
return true;
|
||||
}
|
||||
if (lower.contains("<string") && lower.contains("</string>")) {
|
||||
int a = lower.indexOf('>');
|
||||
int b = lower.lastIndexOf("</string>");
|
||||
if (a >= 0 && b > a + 1 && (b - a) > 8) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (lower.contains("<boolean") && lower.contains("value=\"true\"")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 后台 Service 重启(如 Firebase SessionLifecycle)无可见 Activity,强拉会被 BAL 拦截。 */
|
||||
private static boolean isUiVisibleForForceLogin(Context ctx) {
|
||||
ActivityManager.RunningAppProcessInfo state = new ActivityManager.RunningAppProcessInfo();
|
||||
@@ -657,7 +784,9 @@ public final class TngRootBypassHook {
|
||||
}
|
||||
|
||||
/**
|
||||
* Splash.onCreate 常被 Promon 堵死永远不返回;必须在 onCreate 入口(before)就调度强拉。
|
||||
* Splash 卡住救援:不立刻强拉 Login。
|
||||
* onCreate 只调度延迟检查;若已自行跳到 PIN/首页则取消。
|
||||
* 超时仍停在 Splash(Promon 堵死)才救援。
|
||||
*/
|
||||
private static void hookSplashForceLogin(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
final String splash = "my.com.tngdigital.ewallet.ui.SplashActivity";
|
||||
@@ -675,15 +804,20 @@ public final class TngRootBypassHook {
|
||||
return;
|
||||
}
|
||||
String name = activity.getClass().getName();
|
||||
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
|
||||
if (isSplashActivityName(name)) {
|
||||
XposedBridge.log(TAG
|
||||
+ " Splash.onCreate enter — schedule stuck rescue @4s");
|
||||
scheduleSplashStuckRescue(
|
||||
activity.getApplicationContext(),
|
||||
activity,
|
||||
"Splash/beforeOnCreate",
|
||||
4000L);
|
||||
return;
|
||||
}
|
||||
XposedBridge.log(TAG + " Splash.onCreate enter — schedule force login");
|
||||
scheduleForceLoginToUserLogin(
|
||||
activity.getApplicationContext(),
|
||||
activity,
|
||||
"Splash/beforeOnCreate",
|
||||
2000L);
|
||||
// 任何非 Splash Activity 创建 → 取消 Splash 救援
|
||||
if (PACKAGE.equals(activity.getPackageName())) {
|
||||
markSplashNavigationDone(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -693,7 +827,7 @@ public final class TngRootBypassHook {
|
||||
return;
|
||||
}
|
||||
String name = activity.getClass().getName();
|
||||
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
|
||||
if (!isSplashActivityName(name)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -703,12 +837,42 @@ public final class TngRootBypassHook {
|
||||
dismissSplashScreen(activity);
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked Instrumentation Splash force→UserLogin (before+after)");
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Instrumentation.class,
|
||||
"callActivityOnResume",
|
||||
Activity.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.args[0];
|
||||
if (activity == null) {
|
||||
return;
|
||||
}
|
||||
String name = activity.getClass().getName();
|
||||
if (PACKAGE.equals(activity.getPackageName())
|
||||
&& !isSplashActivityName(name)) {
|
||||
markSplashNavigationDone(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked Splash stuck-rescue (not always→Login)");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " Splash force hook failed: " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void markSplashNavigationDone(String activityName) {
|
||||
if (splashNavigationDone) {
|
||||
return;
|
||||
}
|
||||
splashNavigationDone = true;
|
||||
if (pendingForceLoginRunnable != null) {
|
||||
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
|
||||
pendingForceLoginRunnable = null;
|
||||
XposedBridge.log(TAG + " cancel splash rescue — now on " + activityName);
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile long lastSuicideLogAt = 0L;
|
||||
private static volatile int suicideBlockCount = 0;
|
||||
|
||||
@@ -1010,7 +1174,7 @@ public final class TngRootBypassHook {
|
||||
if (intent == null) {
|
||||
return false;
|
||||
}
|
||||
if (isSecurityErrorIntent(intent)) {
|
||||
if (isSecurityErrorIntent(intent) || isHomeEkycVerifyIntent(intent)) {
|
||||
return true;
|
||||
}
|
||||
Uri data = intent.getData();
|
||||
@@ -1024,6 +1188,19 @@ public final class TngRootBypassHook {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isHomeEkycVerifyIntent(Intent intent) {
|
||||
if (intent == null) {
|
||||
return false;
|
||||
}
|
||||
if (intent.getComponent() != null) {
|
||||
String cls = intent.getComponent().getClassName();
|
||||
if (cls != null && cls.contains("HomeEkycVerify")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isBlockedSupportUrl(String url) {
|
||||
if (url == null || url.isEmpty()) {
|
||||
return false;
|
||||
@@ -2160,6 +2337,128 @@ public final class TngRootBypassHook {
|
||||
XposedBridge.log(TAG + " skip launchProcessNext noop (avoid native exit fallback)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 首页强制「验证您的帐户」(HomeEkycVerifyActivity) — 测试期跳过。
|
||||
* 拦启动 + onCreate finish;canBypassEkyc=true;enforceEkyc=false。
|
||||
*/
|
||||
private static void hookHomeEkycVerifySkip(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
try {
|
||||
XposedHelpers.findAndHookMethod(
|
||||
Activity.class,
|
||||
"onCreate",
|
||||
Bundle.class,
|
||||
new XC_MethodHook() {
|
||||
@Override
|
||||
protected void afterHookedMethod(MethodHookParam param) {
|
||||
Activity activity = (Activity) param.thisObject;
|
||||
String name = activity.getClass().getName();
|
||||
if (name != null && name.contains("HomeEkycVerify")) {
|
||||
XposedBridge.log(TAG + " skip eKYC — finish " + name);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
XposedBridge.log(TAG + " hooked HomeEkycVerify Activity finish");
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " HomeEkycVerify finish hook failed: " + t.getMessage());
|
||||
}
|
||||
|
||||
// 白名单绕过:返回 true
|
||||
hookBooleanMethodsByName(lpparam,
|
||||
"my.com.tngdigital.home.viewmodel.BypassEkycWhiteListChecker",
|
||||
true, "canBypassEkyc", "bypassEkyc", "isWhitelist", "inWhitelist");
|
||||
// 强制 eKYC 开关:返回 false
|
||||
for (String className : new String[]{
|
||||
"my.com.tngdigital.home.help.HomeEkycCheckHelper",
|
||||
"my.com.tngdigital.home.ekyc.KycHomepagePopUpManager",
|
||||
"my.com.tngdigital.home.ekyc.HomeEkycVerifyViewModel",
|
||||
"my.com.tngdigital.home.viewmodel.HomeListActivityViewModel",
|
||||
}) {
|
||||
hookBooleanMethodsByName(lpparam, className, false,
|
||||
"getEnforceEkyc", "enforceEkyc", "needShowEkyc", "needForceEkyc",
|
||||
"isForceEkyc", "checkEkyc", "firstCheckEkyc", "needShowEkycCddAudit");
|
||||
hookVoidMethodsByName(lpparam, className,
|
||||
"checkEkyc", "checkEkycStatus", "checkEkycRequest",
|
||||
"requestEkycStatus", "showEkyc", "launchEkyc", "openEkyc");
|
||||
}
|
||||
}
|
||||
|
||||
private static void hookBooleanMethodsByName(
|
||||
XC_LoadPackage.LoadPackageParam lpparam,
|
||||
String className,
|
||||
boolean result,
|
||||
String... nameHints) {
|
||||
try {
|
||||
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
|
||||
int hooked = 0;
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.getReturnType() != boolean.class && method.getReturnType() != Boolean.class) {
|
||||
continue;
|
||||
}
|
||||
String n = method.getName();
|
||||
boolean match = false;
|
||||
for (String hint : nameHints) {
|
||||
if (n.equals(hint) || n.toLowerCase(Locale.US).contains(hint.toLowerCase(Locale.US))) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
final boolean ret = result;
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
param.setResult(ret);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
}
|
||||
XposedBridge.log(TAG + " eKYC bool stub " + className + " n=" + hooked + " -> " + result);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " eKYC bool stub skip " + className + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void hookVoidMethodsByName(
|
||||
XC_LoadPackage.LoadPackageParam lpparam,
|
||||
String className,
|
||||
String... nameHints) {
|
||||
try {
|
||||
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
|
||||
int hooked = 0;
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
if (method.getReturnType() != void.class) {
|
||||
continue;
|
||||
}
|
||||
String n = method.getName();
|
||||
boolean match = false;
|
||||
for (String hint : nameHints) {
|
||||
if (n.equals(hint) || n.startsWith(hint)) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
XposedBridge.hookMethod(method, new XC_MethodHook() {
|
||||
@Override
|
||||
protected void beforeHookedMethod(MethodHookParam param) {
|
||||
param.setResult(null);
|
||||
}
|
||||
});
|
||||
hooked++;
|
||||
}
|
||||
if (hooked > 0) {
|
||||
XposedBridge.log(TAG + " eKYC void noop " + className + " n=" + hooked);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(TAG + " eKYC void noop skip " + className + ": " + t.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void hookReturnFalse(
|
||||
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user