feat(tng): TNG eWallet Promon bypass、Zygisk 信号守卫与注册链保护

新增 TngRootBypassHook 与 tng_exit_guard 模块,修复 BAL 强拉、SIGABRT/pc==lr SEGV 崩溃,验证可进入注册页。
This commit is contained in:
mars
2026-07-31 17:55:13 +08:00
parent 5a232213f3
commit 13e407623b
16 changed files with 3960 additions and 17 deletions

View File

@@ -0,0 +1,85 @@
# TNG eWallet 逆向笔记 — 自杀链2026-07-31
## 运行时铁证
| 现象 | 含义 |
|------|------|
| `Fatal signal 6 (SIGABRT), code 0 (SI_USER)` | 用户态 `kill(pid, SIGABRT)`**不是** `exit_group`,也不是 `abort()``SI_TKILL` |
| PLT hook `kill/raise` **从不打日志** | Promon 走 **内联 SVC**,不经 PLT |
| `E Report : Exiting:` + `xwwqazamx.W: 16` + `bl.a` | Java 层检测入口仍是 `xwwqazamx.bl#a/#b` |
| `Displayed ...UserLoginActivity` | UI 能到登录页;随后数秒内被杀 |
| `Lcom/aliyun/TigerTally/t/B` 反射 `Thread.dispatchUncaughtException` | 另有一套阿里 TigerTally SDK |
## 自杀阶梯(当前理解)
```
Promon native 检测 (Thread-N 扫 /proc、selinux、wifi prop…)
→ xwwqazamx.bl#a / #b
→ 抛 W:16Report 打 Exiting
→ 同时/随后 inline kill(SIGABRT, SI_USER)
→ KillApplicationHandlerXposed 可拦)
→ 若 ABRT 被 ignore可能改 SIGKILL / BRK待证
```
并行:
- `AppSecurityManager.handle*Callback`Xposed 已拦)
- `openSecurityUrl` Root FAQ已拦
- `UnhandledEvent``finishAllActivityAndKillApp`(已 hook
- ForceExitCountdown已 hook
- TigerTally / SecurityGuard `JNICLibrary.doCommand`(当前 stub可能过度
## 静态 SO
`libtngdigital_ewallet.so`~7.7MB,打包态):
- `brk` 假阳性/加密指令很多imm 离散)
- 真实 `svc #0` 极少;运行时解密后才出现 exit 序列
- **禁止**在 `JNI_OnLoad` 前/中 patch SO会 Bad JNI / SIGILL
## 已验证无效/有害手段
- seccomp 拦 `exit_group` → Promon 改 UDF/SIGILL
- 改 libc exit SVC→RET → 同上
- 改 Promon SO 字节 → Bad JNI / SIGILL
- 高频 `sigaction` 重装 → 易被当成 hook 指纹
## 较有效手段
- Xposed拦 FAQ / KillApplicationHandler / handle*Callback / ForceExit / UnhandledEvent
- Xposed`bl#a/#b` **beforeHook short-circuit**(避免跑进 native 杀进程)
- ZygiskPLT 观察 + `SIGABRT` ignore对 SI_USER 理论有效Promon 可能复位 handler
## 新发现2026-07-31 续)
### `libtiger_tally.so`~4.6MB
- 与 Promon `libtngdigital_ewallet.so` **并行加载**
- 动态依赖:`abort@LIBC``signal@LIBC`**走 PLT**,可被 Zygisk PLT hook
- 内嵌少量 syscall stubopenat/read/faccessat…**无** kill/exit stub
- `BIND_NOW`
### 打包态 SO
- `libtngdigital_ewallet.so` / `libtiger_tally.so` 静态 **0**`movz x8,#129 + svc`
- 自杀用的 inline `kill` 仍可能在 **运行时解密** 后出现 → Zygisk **延迟 2s** 扫描 patch
### 日志结论2026-07-31 11:3711:47
| 现象 | 含义 |
|------|------|
| `exited cleanly (1)` | inline `exit_group(1)`PLT 拦不住) |
| kill SVC→RET 后 `signal 9` | SO 完整性/备用路径 → **SIGKILL** |
| 过早 patch exit_group | 启动即崩CEM |
| seccomp 拦 `exit`+`exit_group` | ~1s 死(误伤线程 exit |
| seccomp **只拦 exit_group** | 存活 **~16s**,随后 **SIGILL 风暴**count 7000+ |
| `caught sig=4 si_code=1` | Promon UDF 兜底;+4 skip 无效 |
### Rest 配置2026-07-31 12:55已装机
| 层 | 策略 |
|----|------|
| Zygisk | PLT 拦 exit/abort/kill**exit_group-only seccomp @2s****禁止**改 SO会 SIGKILL |
| 信号 | ABRT ignoreSEGV/ILL **+4 skip****不** freeze 线程freeze 会 ANR |
| Xposed | Splash `reportFullyDrawn` + **2.5s 强拉 UserLogin** + finish Splashbl SC / 自杀 Java / JNIC stub |
实测:进程 **≥40s** 存活,焦点 `UserLoginActivity``phase done seccomp=1 caught=0`
注意:点亮屏幕后再开 App`svc power stayon true`);勿把 TNG 放进 Magisk DenyList。

View File

@@ -0,0 +1,11 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := tng_exit_guard
LOCAL_SRC_FILES := main.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_LDLIBS := -llog -ldl
LOCAL_CFLAGS := -Wall -Wextra -fno-rtti -fvisibility=hidden
LOCAL_CPPFLAGS := -std=c++17
LOCAL_LDFLAGS := -Wl,--exclude-libs,ALL
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,4 @@
APP_ABI := arm64-v8a
APP_PLATFORM := android-24
APP_STL := c++_static
APP_CPPFLAGS := -std=c++17

View File

@@ -0,0 +1,390 @@
/*
* TNG eWallet — Zygisk companion.
*
* stable: PLT + ABRT/SIGTRAP swallow + exit_group seccomp@400ms.
* Promon worker SIGSEGV (libtngdigital_ewallet.so null deref): LR-return skip (cap N).
* pc==lr 循环 SEGV 也 skiplibc++abi __cxa_guard_acquire → SIGABRT 吞掉。
*/
#include <android/log.h>
#include <errno.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <pthread.h>
#include <signal.h>
#include <stddef.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <ucontext.h>
#include <unistd.h>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include "zygisk.hpp"
#define SC_RET_ALLOW 0x7fff0000U
#define SC_RET_ERRNO_EPERM (0x00050000U | 1U)
#define LOG_TAG "TngExitGuard"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
static constexpr const char *kTargetPkg = "my.com.tngdigital.ewallet";
static constexpr const char *kPromonSo = "libtngdigital_ewallet.so";
static bool g_enabled = false;
static std::atomic<int> g_seccomp_ok{0};
static std::atomic<int> g_stack_chk{0};
static std::atomic<int> g_promon_segv{0};
static std::atomic<uintptr_t> g_promon_start{0};
static std::atomic<uintptr_t> g_promon_end{0};
/* 隔离进程 :goacqowmmt 会循环 SEGVcap=16 打满后 freeze 反拖累主进程 */
static constexpr int kMaxPromonSegvSkip = 0; /* 0 = unlimited LR-return skip */
static std::atomic<int> g_soft_sig_logged{0};
static void freeze_forever() {
for (;;) pause();
}
static void refresh_promon_so_range() {
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return;
char line[1024];
uintptr_t start = 0, end = 0;
while (fgets(line, sizeof(line), fp)) {
unsigned long s = 0, e = 0;
char path[512] = {};
int n = sscanf(line, "%lx-%lx %*s %*s %*s %*s %511[^\n]", &s, &e, path);
if (n < 3) continue;
char *p = path;
while (*p == ' ') ++p;
if (strstr(p, kPromonSo) == nullptr) continue;
if (start == 0 || s < start) start = s;
if (e > end) end = e;
}
fclose(fp);
if (start != 0 && end > start) {
g_promon_start.store(start);
g_promon_end.store(end);
}
}
static bool pc_in_promon_so(uintptr_t pc) {
uintptr_t start = g_promon_start.load();
uintptr_t end = g_promon_end.load();
return start != 0 && pc >= start && pc < end;
}
static void promon_segv_handler(int sig, siginfo_t *info, void *ctx) {
(void)sig;
(void)info;
ucontext_t *uc = reinterpret_cast<ucontext_t *>(ctx);
#if defined(__aarch64__)
uintptr_t pc = uc->uc_mcontext.pc;
uintptr_t lr = uc->uc_mcontext.regs[30];
#else
uintptr_t pc = 0;
uintptr_t lr = 0;
#endif
refresh_promon_so_range();
/* Promon 典型 pc==lr 自旋 null derefmaps 尚未刷新时也按此 skip */
if (pc != 0 && pc == lr) {
int n = ++g_promon_segv;
if (n <= 3 || n % 50 == 0) {
LOGI("promon pc==lr SIGSEGV tid=%d pc=%lx n=%d — skip to pc+4",
(int)gettid(), (unsigned long)pc, n);
}
uc->uc_mcontext.pc = pc + 4;
return;
}
if (pc_in_promon_so(pc) || pc_in_promon_so(lr)) {
int n = ++g_promon_segv;
uintptr_t target = lr;
if (target == 0 || target == pc) {
target = pc + 4;
}
if (kMaxPromonSegvSkip <= 0 || n <= kMaxPromonSegvSkip) {
if (n <= 3 || n % 50 == 0) {
LOGI("promon SIGSEGV tid=%d pc=%lx lr=%lx n=%d — skip to %lx",
(int)gettid(), (unsigned long)pc, (unsigned long)lr, n,
(unsigned long)target);
}
uc->uc_mcontext.pc = target;
return;
}
LOGI("promon SIGSEGV tid=%d n=%d — cap hit, freeze", (int)gettid(), n);
freeze_forever();
}
signal(SIGSEGV, SIG_DFL);
raise(SIGSEGV);
}
/** libc++abi __cxa_guard_acquire 递归初始化 → abort跳回 LR 继续而非杀进程。 */
static void fatal_skip_handler(int sig, siginfo_t *info, void *ctx) {
(void)info;
ucontext_t *uc = reinterpret_cast<ucontext_t *>(ctx);
#if defined(__aarch64__)
uintptr_t pc = uc->uc_mcontext.pc;
uintptr_t lr = uc->uc_mcontext.regs[30];
LOGI("swallowed signal %d tid=%d pc=%lx lr=%lx", sig, (int)gettid(),
(unsigned long)pc, (unsigned long)lr);
if (lr != 0) {
uc->uc_mcontext.pc = lr;
return;
}
if (pc != 0) {
uc->uc_mcontext.pc = pc + 4;
return;
}
#endif
freeze_forever();
}
static void install_fatal_skip_handlers() {
struct sigaction sa {};
sa.sa_sigaction = fatal_skip_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGTRAP, &sa, nullptr);
LOGI("fatal skip handlers (ABRT+TRAP→LR)");
}
static void install_promon_segv_handler() {
refresh_promon_so_range();
struct sigaction sa {};
sa.sa_sigaction = promon_segv_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, nullptr) != 0) {
LOGE("SIGSEGV handler install failed errno=%d", errno);
return;
}
LOGI("promon SIGSEGV handler armed range=%lx-%lx",
(unsigned long)g_promon_start.load(),
(unsigned long)g_promon_end.load());
}
static void install_soft_signals() {
struct sigaction sa {};
sa.sa_sigaction = fatal_skip_handler;
sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
sigemptyset(&sa.sa_mask);
sigaction(SIGABRT, &sa, nullptr);
sigaction(SIGTRAP, &sa, nullptr);
if (g_soft_sig_logged.fetch_add(1) == 0) {
LOGI("soft signals (ABRT+TRAP skip→LR)");
}
}
#if defined(__aarch64__)
#define _BPFI(code, jt, jf, k) \
((struct sock_filter){(unsigned short)(code), (jt), (jf), (unsigned int)(k)})
static int install_seccomp_exit_group_only() {
struct sock_filter filter[] = {
_BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, arch)),
_BPFI(BPF_JMP | BPF_JEQ | BPF_K, 1, 0, AUDIT_ARCH_AARCH64),
_BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ALLOW),
_BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, nr)),
_BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, 94),
_BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ERRNO_EPERM),
_BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
long rc = syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER,
SECCOMP_FILTER_FLAG_TSYNC, &prog);
if (rc != 0) {
rc = prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
if (rc != 0) {
LOGE("seccomp failed errno=%d", errno);
return -1;
}
LOGI("seccomp exit_group via prctl");
} else {
LOGI("seccomp exit_group via TSYNC");
}
g_seccomp_ok.store(1);
return 0;
}
#else
static int install_seccomp_exit_group_only() { return -1; }
#endif
using exit_fn = void (*)(int);
using kill_fn = int (*)(pid_t, int);
using tgkill_fn = int (*)(int, int, int);
using raise_fn = int (*)(int);
using pthread_kill_fn = int (*)(pthread_t, int);
static exit_fn orig_exit = nullptr;
static exit_fn orig__exit = nullptr;
static void (*orig_abort)() = nullptr;
static void (*orig_stack_chk_fail)() = nullptr;
static kill_fn orig_kill = nullptr;
static tgkill_fn orig_tgkill = nullptr;
static raise_fn orig_raise = nullptr;
static pthread_kill_fn orig_pthread_kill = nullptr;
static bool deadly(int sig) {
return sig == SIGKILL || sig == SIGABRT || sig == SIGTERM ||
sig == SIGTRAP || sig == SIGILL ||
sig == 9 || sig == 6 || sig == 5 || sig == 4 || sig == 15;
}
static void hooked_exit(int code) { LOGI("blocked exit(%d)", code); }
static void hooked__exit(int code) { LOGI("blocked _exit(%d)", code); }
static void hooked_abort() {
LOGI("blocked abort() tid=%d", (int)gettid());
}
static void hooked_stack_chk_fail() {
int n = ++g_stack_chk;
LOGI("blocked __stack_chk_fail tid=%d n=%d", (int)gettid(), n);
}
static int hooked_raise(int sig) {
if (deadly(sig)) {
LOGI("blocked raise(%d)", sig);
return 0;
}
return orig_raise ? orig_raise(sig) : -1;
}
static int hooked_kill(pid_t pid, int sig) {
if (deadly(sig)) {
LOGI("blocked kill(%d,%d)", (int)pid, sig);
return 0;
}
return orig_kill ? orig_kill(pid, sig) : -1;
}
static int hooked_tgkill(int tgid, int tid, int sig) {
if (deadly(sig)) {
LOGI("blocked tgkill(%d,%d,%d)", tgid, tid, sig);
return 0;
}
return orig_tgkill ? orig_tgkill(tgid, tid, sig) : -1;
}
static int hooked_pthread_kill(pthread_t thread, int sig) {
if (deadly(sig)) {
LOGI("blocked pthread_kill(sig=%d)", sig);
return 0;
}
return orig_pthread_kill ? orig_pthread_kill(thread, sig) : -1;
}
static bool find_libc(dev_t *dev, ino_t *ino) {
FILE *fp = fopen("/proc/self/maps", "r");
if (!fp) return false;
char line[1024];
bool ok = false;
while (fgets(line, sizeof(line), fp)) {
uintptr_t start = 0, end = 0;
char perms[8] = {};
unsigned long long offset = 0;
char deststr[32] = {};
unsigned long inode = 0;
char path[512] = {};
int n = sscanf(line, "%lx-%lx %7s %llx %31s %lu %511[^\n]",
&start, &end, perms, &offset, deststr, &inode, path);
if (n < 7 || inode == 0) continue;
char *p = path;
while (*p == ' ') ++p;
if (strstr(p, "libc.so") == nullptr) continue;
unsigned maj = 0, min = 0;
if (sscanf(deststr, "%x:%x", &maj, &min) != 2) continue;
*dev = makedev(maj, min);
*ino = inode;
ok = true;
break;
}
fclose(fp);
return ok;
}
static void install_plt(zygisk::Api *api) {
if (!api) return;
dev_t dev = 0;
ino_t ino = 0;
if (!find_libc(&dev, &ino)) return;
api->pltHookRegister(dev, ino, "exit", (void *)hooked_exit, (void **)&orig_exit);
api->pltHookRegister(dev, ino, "_exit", (void *)hooked__exit, (void **)&orig__exit);
api->pltHookRegister(dev, ino, "abort", (void *)hooked_abort, (void **)&orig_abort);
api->pltHookRegister(dev, ino, "__stack_chk_fail",
(void *)hooked_stack_chk_fail, (void **)&orig_stack_chk_fail);
api->pltHookRegister(dev, ino, "raise", (void *)hooked_raise, (void **)&orig_raise);
api->pltHookRegister(dev, ino, "kill", (void *)hooked_kill, (void **)&orig_kill);
api->pltHookRegister(dev, ino, "tgkill", (void *)hooked_tgkill, (void **)&orig_tgkill);
api->pltHookRegister(dev, ino, "pthread_kill",
(void *)hooked_pthread_kill, (void **)&orig_pthread_kill);
bool ok = api->pltHookCommit();
LOGI("PLT commit=%d", ok ? 1 : 0);
}
static void *phase_thread(void *) {
install_promon_segv_handler();
usleep(400 * 1000);
install_seccomp_exit_group_only();
LOGI("seccomp armed @400ms ok=%d", g_seccomp_ok.load());
for (int i = 0; i < 40; i++) {
usleep(1000 * 1000);
install_soft_signals();
if (i % 5 == 0) refresh_promon_so_range();
}
LOGI("phase done seccomp=%d stack_chk=%d promon_segv=%d",
g_seccomp_ok.load(), g_stack_chk.load(), g_promon_segv.load());
return nullptr;
}
static void install_all(zygisk::Api *api) {
LOGI("install pid=%d (PLT+ABRT/TRAP-skip+pc==lr-SEGV+exit_group@400ms)", getpid());
install_fatal_skip_handlers();
install_soft_signals();
install_plt(api);
pthread_t th;
if (pthread_create(&th, nullptr, phase_thread, nullptr) == 0) {
pthread_detach(th);
}
LOGI("ready");
}
class TngExitGuardModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
const char *nice = nullptr;
if (args->nice_name) {
nice = env->GetStringUTFChars(args->nice_name, nullptr);
}
bool match = nice && (
std::strncmp(nice, kTargetPkg, std::strlen(kTargetPkg)) == 0);
if (nice) env->ReleaseStringUTFChars(args->nice_name, nice);
g_enabled = match;
if (!match) {
api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY);
return;
}
LOGI("preAppSpecialize matched");
}
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
(void)args;
if (!g_enabled) return;
install_all(api);
}
private:
zygisk::Api *api = nullptr;
JNIEnv *env = nullptr;
};
REGISTER_ZYGISK_MODULE(TngExitGuardModule)

View File

@@ -0,0 +1,391 @@
/* Copyright 2022-2023 John "topjohnwu" Wu
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
// This is the public API for Zygisk modules.
// DO NOT MODIFY ANY CODE IN THIS HEADER.
#pragma once
#include <jni.h>
#define ZYGISK_API_VERSION 4
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This method is called as soon as the module is loaded into the target process.
// A Zygisk API handle will be passed as an argument.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This method is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
//
// All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized.
//
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
// get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs with the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This method is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jobjectArray &rlimits;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jintArray *const fds_to_ignore;
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
//
// Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization.
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t {
// The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
//
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
//
// The pre[XXX]Specialize methods run with the same privilege of zygote.
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
//
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
//
// This API only works in the pre[XXX]Specialize methods.
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
// or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID.
//
// Returns -1 if errors occurred.
int getModuleDir();
// Set various options for your module.
// Please note that this method accepts one single option at a time.
// Check zygisk::Option for the full list of options available.
void setOption(Option opt);
// Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags();
// Exempt the provided file descriptor from being automatically closed.
//
// This API only make sense in preAppSpecialize; calling this method in any other situation
// is either a no-op (returns true) or an error (returns false).
//
// When false is returned, the provided file descriptor will eventually be closed by zygote.
bool exemptFd(int fd);
// Hook JNI native methods for a class
//
// Lookup all registered JNI native methods and replace it with your own methods.
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
//
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
//
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
// Commit all the hooks that was previously registered.
// Returns false if an error occurred.
bool pltHookCommit();
private:
internal::api_table *tbl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz) \
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
zygisk::internal::entry_impl<clazz>(table, env); \
}
// Register a root companion request handler function for your module
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a Unix domain socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have globally shared resources.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/*********************************************************
* The following is internal ABI implementation detail.
* You do not have to understand what it is doing.
*********************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *impl;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
}
};
struct api_table {
// Base
void *impl;
bool (*registerModule)(api_table *, module_abi *);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
bool (*exemptFd)(int);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
}
inline int Api::getModuleDir() {
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
}
inline void Api::setOption(Option opt) {
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
}
inline uint32_t Api::getFlags() {
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
}
inline bool Api::exemptFd(int fd) {
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
}
inline bool Api::pltHookCommit() {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
}
} // namespace zygisk
extern "C" {
[[gnu::visibility("default"), maybe_unused]]
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default"), maybe_unused]]
void zygisk_companion_entry(int);
} // extern "C"

View File

@@ -0,0 +1,6 @@
id=tng_exit_guard
name=TNG Exit Guard
version=v1.0
versionCode=1
author=miraclegarden
description=Zygisk: block Promon native exit_group for my.com.tngdigital.ewallet. Pair with notiMessage Xposed TngRoot hooks. Do NOT put TNG on Magisk DenyList.

Binary file not shown.

View File

@@ -0,0 +1,55 @@
# Build + install TNG Zygisk exit guard
param(
[switch]$SkipInstall,
[switch]$NoReboot
)
$ErrorActionPreference = "Stop"
$Root = Split-Path -Parent $PSScriptRoot
$Mod = Join-Path $Root "magisk-modules\tng_exit_guard"
$Ndk = "C:\Users\Administrator\AppData\Local\Android\Sdk\ndk\21.4.7075529"
$NdkBuild = Join-Path $Ndk "ndk-build.cmd"
$Adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
if (-not (Test-Path $NdkBuild)) {
throw "ndk-build not found: $NdkBuild"
}
Write-Host "=== ndk-build ===" -ForegroundColor Cyan
Push-Location $Mod
& $NdkBuild NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=jni/Android.mk NDK_APPLICATION_MK=jni/Application.mk -j8
if ($LASTEXITCODE -ne 0) { Pop-Location; throw "ndk-build failed" }
$built = Join-Path $Mod "libs\arm64-v8a\libtng_exit_guard.so"
if (-not (Test-Path $built)) {
# some ndk versions omit lib prefix based on LOCAL_MODULE
$built = Get-ChildItem (Join-Path $Mod "libs\arm64-v8a") -Filter "*.so" | Select-Object -First 1 -ExpandProperty FullName
}
if (-not $built -or -not (Test-Path $built)) { Pop-Location; throw "built .so missing" }
$zygiskDir = Join-Path $Mod "zygisk"
New-Item -ItemType Directory -Force -Path $zygiskDir | Out-Null
Copy-Item $built (Join-Path $zygiskDir "arm64-v8a.so") -Force
Write-Host "built -> zygisk\arm64-v8a.so ($((Get-Item (Join-Path $zygiskDir 'arm64-v8a.so')).Length) bytes)"
Pop-Location
if ($SkipInstall) { return }
Write-Host "=== install Magisk module ===" -ForegroundColor Cyan
& $Adb wait-for-device
& $Adb shell "su -c 'mkdir -p /data/adb/modules/tng_exit_guard/zygisk'"
& $Adb push (Join-Path $Mod "module.prop") /data/local/tmp/tng_exit_guard_module.prop
& $Adb push (Join-Path $zygiskDir "arm64-v8a.so") /data/local/tmp/tng_exit_guard.so
& $Adb shell "su -c 'cp /data/local/tmp/tng_exit_guard_module.prop /data/adb/modules/tng_exit_guard/module.prop; cp /data/local/tmp/tng_exit_guard.so /data/adb/modules/tng_exit_guard/zygisk/arm64-v8a.so; chmod 644 /data/adb/modules/tng_exit_guard/module.prop; chmod 755 /data/adb/modules/tng_exit_guard/zygisk/arm64-v8a.so; rm -f /data/adb/modules/tng_exit_guard/disable /data/adb/modules/tng_exit_guard/remove; ls -la /data/adb/modules/tng_exit_guard/ /data/adb/modules/tng_exit_guard/zygisk/'"
if (-not $NoReboot) {
Write-Host "=== soft reboot zygote (module loads on next specialize) ===" -ForegroundColor Yellow
Write-Host "Magisk Zygisk modules usually need a FULL reboot. Rebooting device..."
& $Adb reboot
Write-Host "Waiting for device..."
& $Adb wait-for-device
Start-Sleep -Seconds 25
& $Adb shell "getprop sys.boot_completed"
}
Write-Host "Done. Launch TNG and check: adb logcat -s TngExitGuard:I LSPosed-Bridge:I" -ForegroundColor Green

View File

@@ -0,0 +1,21 @@
#!/system/bin/sh
set -x
echo "=== magisk ver ==="
magisk -c
magisk -v
echo "=== zygisk setting ==="
magisk --sqlite 'SELECT * FROM settings'
echo "=== path ==="
MAGISK_PATH=$(magisk --path)
echo "MAGISK_PATH=$MAGISK_PATH"
ls -la "$MAGISK_PATH" 2>/dev/null | head -30
ls -la "$MAGISK_PATH/zygisk" 2>/dev/null
ls -la /data/adb/modules/
ls -la /data/adb/modules/tng_exit_guard/
ls -la /data/adb/modules/tng_exit_guard/zygisk/
echo "=== denylist tng ==="
magisk --denylist ls 2>/dev/null | grep -i tng || echo "TNG not on denylist"
echo "=== processes ==="
ps -A | grep -iE 'magiskd|zygisk|lspd|vector|tngdigital' || true
echo "=== logcat zygisk ==="
logcat -d | grep -iE 'zygisk|TngExit|tng_exit' | tail -40

14
scripts/logcat-tng.ps1 Normal file
View File

@@ -0,0 +1,14 @@
# Capture TNG eWallet hook logs (clears buffer first if -Clear switch passed)
param([switch]$Clear)
$adb = "C:\Users\Administrator\AppData\Local\Android\Sdk\platform-tools\adb.exe"
if (-not (Test-Path $adb)) {
$adb = "adb"
}
if ($Clear) {
& $adb logcat -c
Write-Host "Logcat cleared. Launch TNG eWallet, then run without -Clear:" -ForegroundColor Yellow
Write-Host " .\scripts\logcat-tng.ps1"
exit 0
}
& $adb logcat -d 2>&1 | Select-String -Pattern "notiMessageHook/TngRoot|LSPosed-Bridge.*TngRoot|LSPosed-Bridge.*notiMessageHook|support.tngdigital|SecurityError|xwwqazamx|UserLogin|blocked intent|blocked Promon" |
Select-Object -Last 80

4
scripts/pull-tng-log.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/system/bin/sh
logcat -d > /data/local/tmp/tnglog.txt
grep -E '7500|exited cleanly|Process my.com.tngdigital|TngRoot|UserLogin|FATAL|Abort message|tombstone|blocked syscall|has died' /data/local/tmp/tnglog.txt | tail -120 > /data/local/tmp/tnglog2.txt
wc -l /data/local/tmp/tnglog2.txt

View File

@@ -0,0 +1,16 @@
#!/system/bin/sh
logcat -c
am force-stop my.com.tngdigital.ewallet
sleep 1
monkey -p my.com.tngdigital.ewallet -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1
sleep 2
echo "=== @2s ==="; ps -A | grep -i tng || echo NONE
sleep 5
echo "=== @7s ==="; ps -A | grep -i tng || echo NONE
sleep 8
echo "=== @15s ==="; ps -A | grep -i tng || echo NONE
logcat -d > /data/local/tmp/tngfull.txt
echo "=== guard ==="
grep TngExitGuard /data/local/tmp/tngfull.txt | tail -30
echo "=== outcome ==="
grep -E 'exited cleanly|exited due|has died|skipped SIGILL|blocked kill|blocked tgkill|UserLogin|Displayed|PLT blocked' /data/local/tmp/tngfull.txt | tail -30

View File

@@ -3,6 +3,7 @@ package com.miraclegarden.smsmessage.xposed;
import com.miraclegarden.smsmessage.xposed.hook.MariBankRootBypassHook;
import com.miraclegarden.smsmessage.xposed.hook.MariBankShpsNativeHook;
import com.miraclegarden.smsmessage.xposed.hook.SuncorpBankMessageHook;
import com.miraclegarden.smsmessage.xposed.hook.TngRootBypassHook;
import com.miraclegarden.smsmessage.xposed.hook.SqliteMessageHook;
import com.miraclegarden.smsmessage.xposed.hook.TelegramMessageHook;
import com.miraclegarden.smsmessage.xposed.hook.UpBankMessageHook;
@@ -63,6 +64,11 @@ public class MainHook implements IXposedHookLoadPackage {
return;
}
if (TngRootBypassHook.isTargetPackage(lpparam.packageName)) {
TngRootBypassHook.install(lpparam);
return;
}
SqliteMessageHook.install(lpparam);
}
}

View File

@@ -0,0 +1,370 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.os.Build;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.WeakHashMap;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* 过滤 /proc/self/maps 等敏感路径,隐藏 Xposed / Magisk / Zygisk 库名。
*/
public final class ProcMapsFilterHook {
private static final String TAG = "notiMessageHook/ProcMaps";
private static final Set<String> PROC_SENSITIVE = new HashSet<>(Arrays.asList(
"/proc/self/maps",
"/proc/version",
"/proc/self/status",
"/proc/mounts",
"/proc/self/attr/current",
"/proc/self/mountinfo"
));
private static final String[] MAPS_HIDE_MARKERS = {
"xposed", "lsposed", "edxposed", "magisk", "frida", "substrate",
"libpine", "pine.so", "zygisk", "riru", "shamiko", "notimessage",
"miraclegarden", "libbytehook", "libgadget", "libfrida", "libriru",
"liblspd", "libzygisk", "libvector", "zygisk_vector", "vector",
};
private static final String FAKE_SELINUX_CTX =
"u:r:untrusted_app:s0:c512,c768";
private static final WeakHashMap<Object, String> TRACKED_INPUTS = new WeakHashMap<>();
private static volatile boolean installed = false;
private ProcMapsFilterHook() {
}
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (installed) {
return;
}
installed = true;
hookProcAccess(lpparam);
hookProcViaRandomAccessFile(lpparam);
hookBufferedReader(lpparam);
hookSystemProperties(lpparam);
XposedBridge.log(TAG + " installed for " + lpparam.packageName);
}
private static void hookProcAccess(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookConstructor(
FileInputStream.class,
File.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
File file = (File) param.args[0];
if (file != null) {
String path = normalizeProcPath(file.getAbsolutePath());
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
}
);
} catch (Throwable ignored) {
}
XC_MethodHook readFilter = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || param.getResult() == null) {
return;
}
if (param.getResult() instanceof Integer) {
int read = (Integer) param.getResult();
if (read <= 0 || param.args.length == 0 || !(param.args[0] instanceof byte[])) {
return;
}
byte[] buf = (byte[]) param.args[0];
int off = param.args.length > 1 ? (Integer) param.args[1] : 0;
filterProcBytes(path, buf, off, read);
} else if (param.getResult() instanceof byte[]) {
byte[] data = (byte[]) param.getResult();
param.setResult(filterProcBytesAll(path, data));
} else if (param.getResult() instanceof String) {
param.setResult(filterProcText(path, (String) param.getResult()));
}
}
};
try {
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, readFilter);
XposedHelpers.findAndHookMethod(
FileInputStream.class, "read", byte[].class, int.class, int.class, readFilter);
} catch (Throwable t) {
XposedBridge.log(TAG + " FileInputStream.read hook failed: " + t.getMessage());
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
XposedHelpers.findAndHookMethod(
"java.nio.file.Files",
lpparam.classLoader,
"readAllBytes",
"java.nio.file.Path",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof byte[])) {
return;
}
String norm = normalizeProcPath(String.valueOf(param.args[0]));
if (norm != null) {
param.setResult(filterProcBytesAll(
norm, (byte[]) param.getResult()));
}
}
}
);
} catch (Throwable ignored) {
}
}
}
private static void hookProcViaRandomAccessFile(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookConstructor(
"java.io.RandomAccessFile",
lpparam.classLoader,
String.class,
String.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = normalizeProcPath((String) param.args[0]);
if (path != null) {
TRACKED_INPUTS.put(param.getResult(), path);
}
}
}
);
} catch (Throwable ignored) {
}
try {
XposedHelpers.findAndHookMethod(
"java.io.RandomAccessFile",
lpparam.classLoader,
"read",
byte[].class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String path = TRACKED_INPUTS.get(param.thisObject);
if (path == null || !(param.getResult() instanceof Integer)) {
return;
}
int read = (Integer) param.getResult();
if (read > 0 && param.args[0] instanceof byte[]) {
filterProcBytes(path, (byte[]) param.args[0], 0, read);
}
}
}
);
} catch (Throwable ignored) {
}
}
private static void hookBufferedReader(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
BufferedReader.class,
"readLine",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!(param.getResult() instanceof String)) {
return;
}
String line = (String) param.getResult();
if (shouldHideMapsLine(line)) {
param.setResult(readNextSafeLine((BufferedReader) param.thisObject));
}
}
}
);
} catch (Throwable t) {
XposedBridge.log(TAG + " BufferedReader hook failed: " + t.getMessage());
}
}
private static String readNextSafeLine(BufferedReader reader) {
try {
String line;
while ((line = reader.readLine()) != null) {
if (!shouldHideMapsLine(line)) {
return line;
}
}
} catch (Throwable ignored) {
}
return "";
}
private static void hookSystemProperties(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> sp = XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader);
for (java.lang.reflect.Method method : sp.getDeclaredMethods()) {
if (!"get".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (param.args.length == 0 || !(param.args[0] instanceof String)) {
return;
}
String key = (String) param.args[0];
String spoofed = spoofProperty(key, param.getResult());
if (spoofed != null) {
param.setResult(spoofed);
}
}
});
}
} catch (Throwable t) {
XposedBridge.log(TAG + " SystemProperties hook failed: " + t.getMessage());
}
}
private static String spoofProperty(String key, Object current) {
if ("ro.debuggable".equals(key)) {
return "0";
}
if ("init.svc.adbd".equals(key) || "init.svc.adb".equals(key)) {
return "stopped";
}
if ("service.adb.root".equals(key)) {
return "0";
}
if ("ro.secure".equals(key)) {
return "1";
}
if ("ro.build.tags".equals(key)) {
if (current instanceof String && String.valueOf(current).contains("test-keys")) {
return "release-keys";
}
}
if ("ro.boot.verifiedbootstate".equals(key)) {
return "green";
}
if ("ro.boot.vbmeta.device_state".equals(key)
|| "vendor.boot.vbmeta.device_state".equals(key)) {
return "locked";
}
return null;
}
private static String normalizeProcPath(String path) {
if (path == null) {
return null;
}
String norm = path.trim();
for (String p : PROC_SENSITIVE) {
if (norm.equals(p) || norm.endsWith(p)) {
return p;
}
}
return null;
}
private static byte[] filterProcBytesAll(String path, byte[] data) {
return filterProcText(path, new String(data)).getBytes();
}
private static void filterProcBytes(String path, byte[] buf, int off, int len) {
String text = new String(buf, off, len);
String filtered = filterProcText(path, text);
if (filtered.equals(text)) {
return;
}
byte[] out = filtered.getBytes();
int copy = Math.min(len, out.length);
System.arraycopy(out, 0, buf, off, copy);
if (copy < len) {
Arrays.fill(buf, off + copy, off + len, (byte) 0);
}
}
private static String filterProcText(String path, String text) {
if ("/proc/self/maps".equals(path) || "/proc/self/mountinfo".equals(path)
|| "/proc/mounts".equals(path)) {
StringBuilder sb = new StringBuilder();
for (String line : text.split("\n")) {
if (!shouldHideMapsLine(line)) {
if (sb.length() > 0) {
sb.append('\n');
}
sb.append(line);
}
}
return sb.toString();
}
if ("/proc/self/attr/current".equals(path)) {
String lower = text.toLowerCase(Locale.US);
if (lower.contains("magisk") || lower.contains("su") || lower.contains("zygisk")
|| lower.contains("xposed")) {
return FAKE_SELINUX_CTX;
}
return text;
}
if ("/proc/version".equals(path)) {
return text.replace("dirty", "").replace("test-keys", "release-keys");
}
if ("/proc/self/status".equals(path)) {
return text.replaceAll("(?m)^TracerPid:\\s*[1-9]\\d*",
"TracerPid:\t0");
}
return text;
}
private static boolean shouldHideMapsLine(String line) {
if (line == null || line.isEmpty()) {
return false;
}
String lower = line.toLowerCase(Locale.US);
for (String marker : MAPS_HIDE_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return false;
}
}

View File

@@ -131,25 +131,26 @@ public final class RootBypassHelper {
}
public static void hookRuntimeExec(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blockExec = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String cmd = null;
if (param.args.length > 0 && param.args[0] instanceof String) {
cmd = (String) param.args[0];
} else if (param.args.length > 0 && param.args[0] instanceof String[]) {
cmd = String.join(" ", (String[]) param.args[0]);
}
if (ProbeGuard.isBlockedCommand(cmd)) {
param.setResult(ProbeGuard.fakeFailedProcess(cmd));
}
}
};
try {
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String.class, blockExec);
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String[].class, blockExec);
XposedHelpers.findAndHookMethod(Runtime.class, "exec", String.class, String[].class, blockExec);
XposedHelpers.findAndHookMethod(
Runtime.class,
"exec",
String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String cmd = (String) param.args[0];
if (cmd == null) {
return;
}
String lower = cmd.toLowerCase(Locale.US);
if (lower.contains("su") || lower.contains("magisk") || lower.contains("which su")) {
throw new SecurityException("blocked root probe");
}
}
}
);
Runtime.class, "exec", String[].class, String[].class, blockExec);
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exec hook failed: " + t.getMessage());
}

View File

@@ -0,0 +1,2569 @@
package com.miraclegarden.smsmessage.xposed.hook;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.app.Application;
import android.app.Dialog;
import android.app.ActivityManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Process;
import android.os.Message;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Locale;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
/**
* TNG eWallet Root / Promon / JailBroken 检测绕过。
* 包名my.com.tngdigital.ewalletv1.9.9+
*/
public final class TngRootBypassHook {
public static final String PACKAGE = "my.com.tngdigital.ewallet";
private static final String TAG = "notiMessageHook/TngRoot";
private static final String SECURITY_ERROR_ACTIVITY =
"my.com.tngdigital.common.security.ui.SecurityErrorActivity";
private static final String[] BOOLEAN_HOOK_CLASSES = {
"my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
};
private static final String[] BLOCKED_SUPPORT_MARKERS = {
"36616543382169-rooting",
"36616508159769-emulator",
"36616480108697-malicious",
"36616444815257-multiboxing",
"45485757513113-private-space",
"48493749288857-malware",
"/articles/",
"support.tngdigital.com.my/hc/",
};
private static volatile long lastBlockedSuicideAt = 0L;
private static final long SOFT_CRASH_GUARD_MS = 10000L;
/** 用户已进入注册/登录后续页时,禁止 Splash 强拉回 Login。 */
private static volatile boolean registrationFlowActive = false;
private static final String[] REGISTRATION_FLOW_MARKERS = {
"GuideActivity",
"UserRegistrationMobileActivity",
"UserOtpVerificationActivity",
"UserRegistrationIdentityActivity",
"UserRegistrationSixPinActivity",
"UserRegistrationStrengthenPinActivity",
"UserRegistrationSecurityQuestionActivity",
"UserRegistrationSuccessActivity",
"UserSearchCallingCodeActivity",
"UserPinActivity",
"UserSecurePinActivity",
"UserPinVerifyActivity",
"EmailOtpVerificationActivity",
};
private static final String[] REGISTRATION_RPC_MARKERS = {
"phonecheck", "com.abl.wallet.phone", "com.abl.wallet.otp",
"customer.registration", "customer.verify", "customer.login",
"login.options", "callingcode", "pin.token", "module.whitelist",
};
private TngRootBypassHook() {
}
public static boolean isTargetPackage(String packageName) {
return PACKAGE.equals(packageName);
}
private static final ThreadLocal<String> CURRENT_REQUEST_URL = new ThreadLocal<>();
/** 进程启动最早打点,便于确认 LSPosed 是否注入(注册闪退常因 hook 未生效)。 */
private static void hookEarlyAttachLog(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(Application.class, "attachBaseContext", Context.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context ctx = (Context) param.args[0];
if (ctx != null && PACKAGE.equals(ctx.getPackageName())) {
XposedBridge.log(TAG + " attachBaseContext pid="
+ Process.myPid());
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " early attach log failed: " + t.getMessage());
}
}
/** Splash 优先生效;新 schedule 会取消旧 RunnableApplication 兜底 vs Splash 2000ms。 */
private static final Handler FORCE_LOGIN_HANDLER = new Handler(Looper.getMainLooper());
private static Runnable pendingForceLoginRunnable;
/** Promon native-bridge short-circuit 重入保护,避免 __cxa_guard_acquire 递归 abort。 */
private static final ThreadLocal<Integer> PROMON_BRIDGE_DEPTH = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
private static void scheduleForceLoginToUserLogin(
final Context appCtx, final Activity splashAct, final String reason, final long delayMs) {
if (appCtx == null) {
return;
}
if (pendingForceLoginRunnable != null) {
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
}
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
pendingForceLoginRunnable = new Runnable() {
@Override
public void run() {
pendingForceLoginRunnable = null;
try {
if (registrationFlowActive || isTopActivityRegistrationFlow(appCtx)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", registration flow)");
return;
}
ActivityManager am =
(ActivityManager) appCtx.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
for (ActivityManager.AppTask task : am.getAppTasks()) {
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info == null || info.topActivity == null) {
continue;
}
String top = info.topActivity.getClassName();
if (top.endsWith(".UserLoginActivity")
|| top.endsWith(".UserPinActivity")
|| isRegistrationFlowActivity(top)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", on " + top + ")");
return;
}
}
}
if (!isUiVisibleForForceLogin(appCtx)) {
XposedBridge.log(TAG + " skip force login (" + reason + ", no visible UI / BAL)");
return;
}
Intent intent = new Intent();
intent.setClassName(PACKAGE, login);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
if (splashAct != null && !splashAct.isFinishing()) {
splashAct.startActivity(intent);
try {
splashAct.finish();
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from Splash)");
} else {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
appCtx.startActivity(intent);
XposedBridge.log(TAG + " forced → UserLogin (" + reason + ", from AppCtx)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " force login failed (" + reason + "): " + t.getMessage());
}
}
};
FORCE_LOGIN_HANDLER.postDelayed(pendingForceLoginRunnable, delayMs);
}
/** 后台 Service 重启(如 Firebase SessionLifecycle无可见 Activity强拉会被 BAL 拦截。 */
private static boolean isUiVisibleForForceLogin(Context ctx) {
ActivityManager.RunningAppProcessInfo state = new ActivityManager.RunningAppProcessInfo();
ActivityManager.getMyMemoryState(state);
if (state.importance > ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
return false;
}
try {
ActivityManager am = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
for (ActivityManager.AppTask task : am.getAppTasks()) {
ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info != null && info.topActivity != null
&& PACKAGE.equals(info.topActivity.getPackageName())) {
return true;
}
}
}
} catch (Throwable ignored) {
}
return state.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
}
private static final String[] HTTP_LOG_MARKERS = {
"otp", "verify", "pin", "register", "auth", "login", "sms", "mobile",
"risk", "token", "error", "code", "unexpected", "reference",
};
public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
if (!isTargetPackage(lpparam.packageName)) {
return;
}
XposedBridge.log(TAG + " install for " + lpparam.packageName
+ " pid=" + Process.myPid()
+ " proc=" + getProcessName());
// Splash 常卡死Instrumentation 强拉即可。双通道会抢跑导致 Login 重载/卡死。
hookEarlyAttachLog(lpparam);
hookRegistrationFlowGuard(lpparam);
hookSplashForceLogin(lpparam);
hookLoginDismissSplash(lpparam);
hookDialogNoHwAccel();
hookActivityLifecycleDiag(lpparam);
hookHardwareRendererSetName();
hookBottomSelectDialogSafe(lpparam);
hookPromonApService(lpparam);
hookPromonBroadcastReceiver(lpparam);
hookAppAttachForceLogin(lpparam);
RootBypassHelper.hookFileExists(lpparam);
RootBypassHelper.hookRuntimeExec(lpparam);
RootBypassHelper.hookSystemGetProperty(lpparam);
ProcMapsFilterHook.install(lpparam);
hookAntiSuicide();
hookUncaughtPromonException(lpparam);
hookKillApplicationHandler(lpparam);
hookBlockSecurityErrorLaunch(lpparam);
hookPromonNativeGuard(lpparam);
hookPromonLifecycle(lpparam);
hookJnicLibrary(lpparam);
hookTigerTally(lpparam);
hookActivityThreadExit(lpparam);
hookForceExitFlow(lpparam);
hookFinishAllActivityAndKillApp(lpparam);
hookShowSecurityScreenForState(lpparam);
hookSecurityUrlOpeners(lpparam);
hookJailBroken(lpparam);
hookJailBrokenRpc(lpparam);
hookAppSecurityManager(lpparam);
hookAppSecurityCallbacks(lpparam);
hookPromonNativeBridge(lpparam);
hookSecurityBooleanChecks(lpparam);
hookSecurityErrorActivity(lpparam);
hookNetworkDiag(lpparam);
hookWebViewErrorDiag(lpparam);
}
private static void logActivityDiag(String phase, Activity activity) {
if (activity == null) {
return;
}
String name = activity.getClass().getName();
String lower = name.toLowerCase(Locale.US);
if (lower.contains("userpin")
|| lower.contains("userlogin")
|| lower.contains("registration")
|| lower.contains("otp")
|| lower.contains("verify")
|| lower.contains("sms")
|| lower.contains("webview")
|| lower.contains("issue")
|| lower.contains("guide")
|| lower.contains("error")
|| lower.contains("dialog")) {
XposedBridge.log(TAG + " ACT " + phase + " " + name);
}
}
/** 记录 OTP/登录相关 HTTP 请求与响应体,定位验证码提交失败原因。 */
private static void hookNetworkDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"okhttp3.Request$Builder",
lpparam.classLoader,
"build",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object url = XposedHelpers.callMethod(param.getResult(), "url");
if (url != null) {
CURRENT_REQUEST_URL.set(String.valueOf(url));
}
} catch (Throwable ignored) {
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " diag Request.Builder.build failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
"okhttp3.ResponseBody",
lpparam.classLoader,
"string",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
String body = (String) param.getResult();
String url = CURRENT_REQUEST_URL.get();
CURRENT_REQUEST_URL.remove();
if (body == null) {
return;
}
if (!shouldLogHttp(url, body)) {
return;
}
String snippet = body.length() > 800
? body.substring(0, 800) + "..." : body;
XposedBridge.log(TAG + " HTTP rsp"
+ (url != null ? " " + url : "")
+ " body=" + snippet);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " diag ResponseBody.string failed: " + t.getMessage());
}
XC_MethodHook callRequestHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object req = XposedHelpers.callMethod(param.thisObject, "request");
logHttpRequest(lpparam.classLoader, req);
} catch (Throwable ignored) {
}
}
};
XC_MethodHook enqueueHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object req = XposedHelpers.callMethod(param.thisObject, "request");
logHttpRequest(lpparam.classLoader, req);
} catch (Throwable ignored) {
}
}
};
for (String className : new String[]{
"okhttp3.RealCall", "okhttp3.internal.connection.RealCall"}) {
try {
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "execute", callRequestHook);
XposedHelpers.findAndHookMethod(
className, lpparam.classLoader, "enqueue",
"okhttp3.Callback", enqueueHook);
} catch (Throwable ignored) {
}
}
XposedBridge.log(TAG + " hooked network diag (okhttp)");
}
private static void logHttpRequest(ClassLoader loader, Object req) {
if (req == null) {
return;
}
Object url = XposedHelpers.callMethod(req, "url");
if (url == null) {
return;
}
String urlStr = String.valueOf(url);
if (!shouldLogHttpUrl(urlStr)) {
return;
}
XposedBridge.log(TAG + " HTTP req " + urlStr);
Object body = XposedHelpers.callMethod(req, "body");
if (body != null) {
logRequestBodySnippet(loader, body);
}
}
private static boolean shouldLogHttpUrl(String url) {
if (url == null) {
return false;
}
String lower = url.toLowerCase(Locale.US);
return lower.contains("tngdigital")
|| lower.contains("alipay")
|| lower.contains("aliyun")
|| lower.contains("otp")
|| lower.contains("verify")
|| lower.contains("register")
|| lower.contains("auth")
|| lower.contains("login")
|| lower.contains("pin")
|| lower.contains("sms");
}
private static boolean shouldLogHttp(String url, String body) {
if (shouldLogHttpUrl(url)) {
return true;
}
String lower = body.toLowerCase(Locale.US);
for (String marker : HTTP_LOG_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return lower.contains("\"code\"") || lower.contains("reference");
}
private static void logRequestBodySnippet(ClassLoader loader, Object body) {
try {
Class<?> bufferClass = XposedHelpers.findClass("okio.Buffer", loader);
Object buffer = XposedHelpers.newInstance(bufferClass);
XposedHelpers.callMethod(body, "writeTo", buffer);
String text = (String) XposedHelpers.callMethod(buffer, "readUtf8");
if (text == null || text.isEmpty()) {
return;
}
String snippet = text.length() > 500 ? text.substring(0, 500) + "..." : text;
XposedBridge.log(TAG + " HTTP req body=" + snippet);
} catch (Throwable ignored) {
}
}
private static void hookWebViewErrorDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
"android.webkit.WebViewClient",
lpparam.classLoader,
"onReceivedError",
"android.webkit.WebView",
"android.webkit.WebResourceRequest",
"android.webkit.WebResourceError",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
try {
Object error = param.args[2];
Object code = XposedHelpers.callMethod(error, "getDescription");
Object url = XposedHelpers.callMethod(param.args[1], "getUrl");
XposedBridge.log(TAG + " WebView error url=" + url + " desc=" + code);
} catch (Throwable t) {
XposedBridge.log(TAG + " WebView error: " + t.getMessage());
}
}
});
XposedBridge.log(TAG + " hooked WebViewClient.onReceivedError");
} catch (Throwable t) {
XposedBridge.log(TAG + " WebView error hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Dialog.class,
"show",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String owner = param.thisObject.getClass().getName();
if (owner.contains("TNG") || owner.contains("Dialog")
|| owner.contains("Error") || owner.contains("i7.")) {
XposedBridge.log(TAG + " Dialog.show " + owner);
}
}
});
} catch (Throwable ignored) {
}
}
/** 仅记录注册/登录链 Activity 生命周期,便于 logcat 定位卡点。 */
private static void hookActivityLifecycleDiag(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(Application.class, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Context ctx = (Context) param.thisObject;
if (ctx == null || !PACKAGE.equals(ctx.getPackageName())) {
return;
}
Application app = (Application) param.thisObject;
app.registerActivityLifecycleCallbacks(
new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle bundle) {
logActivityDiag("onCreate", activity);
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
logActivityDiag("onResume", activity);
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(
Activity activity, Bundle bundle) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
XposedBridge.log(TAG + " registered activity lifecycle diag");
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " activity lifecycle diag failed: " + t.getMessage());
}
}
/**
* ANR 栈Dialog.show → enableHardwareAcceleration → HardwareRenderer.setName → future.get 卡死。
* 兜底拦截 setName避免 RenderThread 未就绪时主线程永久阻塞。
*/
private static void hookHardwareRendererSetName() {
try {
XposedHelpers.findAndHookMethod(
"android.graphics.HardwareRenderer", null, "setName", String.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked HardwareRenderer.setName (blocked)");
} catch (Throwable t) {
XposedBridge.log(TAG + " HardwareRenderer.setName hook failed: " + t.getMessage());
}
}
/** Dialog.show 走 HW 加速会等 RenderThread复进时 RenderThread 易 abort → 主线程 ANR。 */
private static void hookDialogNoHwAccel() {
try {
XposedHelpers.findAndHookMethod(Dialog.class, "show", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Dialog dialog = (Dialog) param.thisObject;
android.view.Window window = dialog.getWindow();
if (window != null) {
window.setFlags(
0,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
} catch (Throwable ignored) {
}
}
});
XposedBridge.log(TAG + " hooked Dialog.show (no HW accel)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Dialog.show hook failed: " + t.getMessage());
}
}
/**
* 注册/登录页点「下一步」会弹国家区号 BottomSelectDialogFragment。
* HardwareRenderer.setName 已拦截 ANR此处仅关 Dialog HW 加速,保留区号选择。
*/
private static void hookBottomSelectDialogSafe(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.widget.BottomSelectDialogFragment",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(clazz, "onStart", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
try {
Object dialog = XposedHelpers.callMethod(param.thisObject, "getDialog");
if (dialog instanceof Dialog) {
Dialog d = (Dialog) dialog;
if (d.getWindow() != null) {
d.getWindow().setFlags(
0,
android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}
}
} catch (Throwable ignored) {
}
XposedBridge.log(TAG + " BottomSelectDialogFragment.onStart (safe, no HW)");
}
});
XposedBridge.log(TAG + " hooked BottomSelectDialogFragment.onStart (safe)");
} catch (Throwable t) {
XposedBridge.log(TAG + " BottomSelectDialog hook failed: " + t.getMessage());
}
}
/** Promon 隔离 Service打点确认 :goacqowmmt 进程 hook 已注入。 */
private static void hookPromonApService(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> svc = XposedHelpers.findClass("xwwqazamx.ap", lpparam.classLoader);
XposedHelpers.findAndHookMethod(svc, "onCreate", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " xwwqazamx.ap onCreate pid=" + Process.myPid()
+ " proc=" + getProcessName());
}
});
for (Method method : svc.getDeclaredMethods()) {
if (!"onStartCommand".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " xwwqazamx.ap onStartCommand pid="
+ Process.myPid());
}
});
}
XposedBridge.log(TAG + " hooked xwwqazamx.ap Service");
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.ap hook failed: " + t.getMessage());
}
}
/** 标记注册链 Activity 活跃,防止 Splash 强拉 Login 清栈。 */
private static void hookRegistrationFlowGuard(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XC_MethodHook flowGuardHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (isRegistrationFlowActivity(name)) {
registrationFlowActive = true;
if (pendingForceLoginRunnable != null) {
FORCE_LOGIN_HANDLER.removeCallbacks(pendingForceLoginRunnable);
pendingForceLoginRunnable = null;
}
XposedBridge.log(TAG + " registration flow active: " + name);
} else if (name.endsWith(".SplashActivity")) {
registrationFlowActive = false;
}
}
};
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnCreate",
Activity.class,
Bundle.class,
flowGuardHook);
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnResume",
Activity.class,
flowGuardHook);
hookLoginOptionsDiag(lpparam);
XposedBridge.log(TAG + " hooked registration flow guard");
} catch (Throwable t) {
XposedBridge.log(TAG + " registration flow guard failed: " + t.getMessage());
}
}
/** Login 页 startLoginOptions 是进入注册/登录选项的网关 RPC 入口。 */
private static void hookLoginOptionsDiag(XC_LoadPackage.LoadPackageParam lpparam) {
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
try {
Class<?> clazz = XposedHelpers.findClass(login, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.contains("LoginOptions") && !name.contains("loginOptions")
&& !name.contains("startLogin")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " UserLoginActivity#" + name + " enter");
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " UserLoginActivity#" + name + " done");
}
});
}
XposedBridge.log(TAG + " hooked UserLoginActivity login-options diag");
} catch (Throwable t) {
XposedBridge.log(TAG + " login-options diag failed: " + t.getMessage());
}
}
private static boolean isRegistrationFlowActivity(String className) {
if (className == null) {
return false;
}
for (String marker : REGISTRATION_FLOW_MARKERS) {
if (className.endsWith("." + marker) || className.contains(marker)) {
return true;
}
}
return false;
}
private static String getProcessName() {
try {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Method current = activityThread.getDeclaredMethod("currentProcessName");
current.setAccessible(true);
Object name = current.invoke(null);
return name != null ? String.valueOf(name) : "?";
} catch (Throwable t) {
return "?";
}
}
private static boolean isTopActivityRegistrationFlow(Context ctx) {
try {
android.app.ActivityManager am =
(android.app.ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) {
return false;
}
for (android.app.ActivityManager.AppTask task : am.getAppTasks()) {
android.app.ActivityManager.RecentTaskInfo info = task.getTaskInfo();
if (info == null || info.topActivity == null) {
continue;
}
if (isRegistrationFlowActivity(info.topActivity.getClassName())) {
return true;
}
}
} catch (Throwable ignored) {
}
return false;
}
/** Promon USB 广播 xwwqazamx.N 跑在主线程,复进时拖死 Looper。 */
private static void hookPromonBroadcastReceiver(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass("xwwqazamx.N", lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"onReceive".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked xwwqazamx.N onReceive x" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.N hook failed: " + t.getMessage());
}
}
/**
* 强拉 Login 后系统 Splash 遮罩常挂在 UserLogin 上windows=Splash Screen
* 导致复进「未响应」。onCreate/onResume 强制 dismiss。
*/
private static void hookLoginDismissSplash(XC_LoadPackage.LoadPackageParam lpparam) {
final String login = "my.com.tngdigital.user.view.UserLoginActivity";
try {
XC_MethodHook dismissHook = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!login.equals(name) && !name.endsWith(".UserLoginActivity")) {
return;
}
dismissSplashScreen(activity);
}
};
XposedHelpers.findAndHookMethod(
Instrumentation.class, "callActivityOnCreate",
Activity.class, Bundle.class, dismissHook);
XposedHelpers.findAndHookMethod(
Instrumentation.class, "callActivityOnResume",
Activity.class, dismissHook);
XposedBridge.log(TAG + " hooked UserLogin splash dismiss (onCreate+onResume)");
} catch (Throwable t) {
XposedBridge.log(TAG + " login splash dismiss hook failed: " + t.getMessage());
}
}
/** 移除 Android 12+ / androidx 启动页遮罩,避免挡在 Login 前导致 ANR。 */
private static void dismissSplashScreen(Activity activity) {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
activity.reportFullyDrawn();
} catch (Throwable ignored) {
}
if (Build.VERSION.SDK_INT >= 31) {
try {
android.window.SplashScreen ss = activity.getSplashScreen();
if (ss != null) {
ss.setOnExitAnimationListener(
splashScreenView -> splashScreenView.remove());
}
} catch (Throwable ignored) {
}
}
try {
ClassLoader cl = activity.getClassLoader();
Class<?> splashCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen", cl);
Object splash = XposedHelpers.callStaticMethod(
splashCl, "installSplashScreen", activity);
Class<?> condCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen$KeepOnScreenCondition", cl);
Object keepOff = Proxy.newProxyInstance(
cl, new Class<?>[] { condCl },
(proxy, method, args) -> false);
XposedHelpers.callMethod(splash, "setKeepOnScreenCondition", keepOff);
Class<?> exitCl = XposedHelpers.findClass(
"androidx.core.splashscreen.SplashScreen$OnExitAnimationListener", cl);
Object exitListener = Proxy.newProxyInstance(
cl, new Class<?>[] { exitCl },
(proxy, method, args) -> {
if (args != null && args.length > 0 && args[0] != null) {
XposedHelpers.callMethod(args[0], "remove");
}
return null;
});
XposedHelpers.callMethod(splash, "setOnExitAnimationListener", exitListener);
} catch (Throwable t) {
XposedBridge.log(TAG + " dismissSplashScreen compat: " + t.getMessage());
}
}
});
}
/**
* Splash.onCreate 常被 Promon 堵死永远不返回;必须在 onCreate 入口before就调度强拉。
*/
private static void hookSplashForceLogin(XC_LoadPackage.LoadPackageParam lpparam) {
final String splash = "my.com.tngdigital.ewallet.ui.SplashActivity";
try {
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"callActivityOnCreate",
Activity.class,
Bundle.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
final Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
return;
}
XposedBridge.log(TAG + " Splash.onCreate enter — schedule force login");
scheduleForceLoginToUserLogin(
activity.getApplicationContext(),
activity,
"Splash/beforeOnCreate",
2000L);
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
final Activity activity = (Activity) param.args[0];
if (activity == null) {
return;
}
String name = activity.getClass().getName();
if (!splash.equals(name) && !name.endsWith(".SplashActivity")) {
return;
}
try {
activity.reportFullyDrawn();
} catch (Throwable ignored) {
}
dismissSplashScreen(activity);
}
});
XposedBridge.log(TAG + " hooked Instrumentation Splash force→UserLogin (before+after)");
} catch (Throwable t) {
XposedBridge.log(TAG + " Splash force hook failed: " + t.getMessage());
}
}
/** Application.onCreate 兜底Splash beforeHook 未触发时仍强拉 Login跳过纯 Service 进程)。 */
private static void hookAppAttachForceLogin(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
android.app.Application.class,
"onCreate",
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
final Context appCtx = (Context) param.thisObject;
if (appCtx == null || !PACKAGE.equals(appCtx.getPackageName())) {
return;
}
String proc = getProcessName();
if (proc != null && proc.contains(":")) {
return;
}
XposedBridge.log(TAG + " Application.onCreate — schedule force login fallback");
scheduleForceLoginToUserLogin(appCtx, null, "Application/onCreate", 3500L);
}
});
XposedBridge.log(TAG + " hooked Application.onCreate force→UserLogin fallback");
} catch (Throwable t) {
XposedBridge.log(TAG + " Application force hook failed: " + t.getMessage());
}
}
private static void hookAntiSuicide() {
try {
XposedHelpers.findAndHookMethod(
Process.class,
"killProcess",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (((Integer) param.args[0]) == Process.myPid()) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked killProcess(self)");
param.setResult(null);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " killProcess hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
System.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked System.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " System.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked Runtime.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.exit hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Runtime.class,
"halt",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked Runtime.halt(" + param.args[0] + ")");
param.setResult(null);
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " Runtime.halt hook failed: " + t.getMessage());
}
try {
XposedHelpers.findAndHookMethod(
Process.class,
"sendSignal",
int.class,
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int pid = (Integer) param.args[0];
int signal = (Integer) param.args[1];
if (pid == Process.myPid() && (signal == 9 || signal == 15)) {
lastBlockedSuicideAt = System.currentTimeMillis();
XposedBridge.log(TAG + " blocked sendSignal(self, " + signal + ")");
param.setResult(null);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " sendSignal hook failed: " + t.getMessage());
}
XC_MethodHook blockFinish = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
if (!isSecurityRelatedActivity(activity)) {
return;
}
if (System.currentTimeMillis() - lastBlockedSuicideAt > SOFT_CRASH_GUARD_MS) {
return;
}
XposedBridge.log(TAG + " blocked " + param.method.getName()
+ " on " + activity.getClass().getSimpleName());
param.setResult(null);
}
};
try {
XposedHelpers.findAndHookMethod(Activity.class, "finish", blockFinish);
XposedHelpers.findAndHookMethod(Activity.class, "finishAffinity", blockFinish);
XposedHelpers.findAndHookMethod(Activity.class, "finishAndRemoveTask", blockFinish);
} catch (Throwable t) {
XposedBridge.log(TAG + " finish hooks failed: " + t.getMessage());
}
}
private static boolean isSecurityRelatedActivity(Activity activity) {
String name = activity.getClass().getName();
return name.contains("SecurityError")
|| name.contains("security.ui")
|| name.contains("Promon");
}
private static void hookBlockSecurityErrorLaunch(XC_LoadPackage.LoadPackageParam lpparam) {
XC_MethodHook blockLaunch = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Intent intent = extractIntent(param.args);
if (intent != null && shouldBlockIntent(intent)) {
XposedBridge.log(TAG + " blocked intent via "
+ param.method.getDeclaringClass().getSimpleName()
+ "#" + param.method.getName()
+ " data=" + intent.getDataString());
param.setResult(null);
}
}
};
hookStartActivityOverloads("android.app.Activity", null, blockLaunch);
hookStartActivityOverloads("android.content.ContextWrapper", null, blockLaunch);
hookStartActivityOverloads("android.app.ContextImpl", null, blockLaunch);
try {
XposedHelpers.findAndHookMethod(
Instrumentation.class,
"execStartActivity",
Context.class,
android.os.IBinder.class,
android.os.IBinder.class,
Activity.class,
Intent.class,
int.class,
Bundle.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Intent intent = (Intent) param.args[4];
if (shouldBlockIntent(intent)) {
XposedBridge.log(TAG + " blocked execStartActivity "
+ intent.getDataString());
param.setResult(-1);
}
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " execStartActivity hook failed: " + t.getMessage());
}
}
/** Promon Shield 安全回调由 handle*Callback / bl short-circuit 处理lifecycle 回调勿拦。 */
private static void hookStartActivityOverloads(
String className, ClassLoader classLoader, XC_MethodHook blockLaunch) {
try {
if ("android.app.Activity".equals(className)) {
XposedHelpers.findAndHookMethod(
Activity.class, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
Activity.class, "startActivity", Intent.class, Bundle.class, blockLaunch);
return;
}
if (classLoader == null) {
XposedHelpers.findAndHookMethod(
className, null, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
className, null, "startActivity", Intent.class, Bundle.class, blockLaunch);
return;
}
XposedHelpers.findAndHookMethod(
className, classLoader, "startActivity", Intent.class, blockLaunch);
XposedHelpers.findAndHookMethod(
className, classLoader, "startActivity", Intent.class, Bundle.class, blockLaunch);
} catch (Throwable t) {
XposedBridge.log(TAG + " startActivity hooks failed for " + className + ": "
+ t.getMessage());
}
}
private static Intent extractIntent(Object[] args) {
if (args == null) {
return null;
}
for (Object arg : args) {
if (arg instanceof Intent) {
return (Intent) arg;
}
}
return null;
}
private static boolean shouldBlockIntent(Intent intent) {
if (intent == null) {
return false;
}
if (isSecurityErrorIntent(intent)) {
return true;
}
Uri data = intent.getData();
if (data != null && isBlockedSupportUrl(data.toString())) {
return true;
}
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action) && data != null) {
return isBlockedSupportUrl(data.toString());
}
return false;
}
private static boolean isBlockedSupportUrl(String url) {
if (url == null || url.isEmpty()) {
return false;
}
String lower = url.toLowerCase(Locale.US);
if (!lower.contains("support.tngdigital.com.my")) {
return false;
}
for (String marker : BLOCKED_SUPPORT_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return lower.contains("rooting") || lower.contains("jailbroken")
|| lower.contains("emulator") || lower.contains("malware");
}
/** 阻止 Promon 混淆层抛出 W:16 并触发浏览器 fallback。 */
private static void hookPromonNativeGuard(XC_LoadPackage.LoadPackageParam lpparam) {
hookPromonBlSwallowExceptions(lpparam);
hookPromonExceptionClass(lpparam, "xwwqazamx.W");
hookPromonExceptionClass(lpparam, "xwwqazamx.A");
hookPromonRunnable(lpparam);
}
/**
* Promon lifecycle只吞异常不 short-circuit——全拦会拖死 Splash→Login。
*/
private static void hookPromonLifecycle(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> lifecycleClass = XposedHelpers.findClass("xwwqazamx.w", lpparam.classLoader);
Class<?> promonExc = null;
try {
promonExc = XposedHelpers.findClass("xwwqazamx.W", lpparam.classLoader);
} catch (Throwable ignored) {
}
final Class<?> promonExcFinal = promonExc;
int hooked = 0;
for (Method method : lifecycleClass.getDeclaredMethods()) {
String name = method.getName();
if (!name.startsWith("onActivity") && !name.startsWith("onApplication")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (!param.hasThrowable()) {
return;
}
Throwable t = param.getThrowable();
if (isPromonThrowable(t, promonExcFinal)) {
XposedBridge.log(TAG + " swallowed " + t.getClass().getSimpleName()
+ " in xwwqazamx.w#" + method.getName());
param.setThrowable(null);
}
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked
+ " xwwqazamx.w lifecycle method(s) (afterHook only)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.w lifecycle hook failed: " + t.getMessage());
}
}
/** 吞掉 :tools 进程里 Promon 抛出的未捕获 W:16。 */
private static void hookUncaughtPromonException(XC_LoadPackage.LoadPackageParam lpparam) {
try {
XposedHelpers.findAndHookMethod(
Thread.class,
"dispatchUncaughtException",
Throwable.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Throwable t = (Throwable) param.args[0];
if (t == null || !isPromonThrowableName(t.getClass().getName())) {
return;
}
XposedBridge.log(TAG + " swallowed uncaught " + t.getClass().getSimpleName()
+ " in " + lpparam.processName);
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked Thread.dispatchUncaughtException");
} catch (Throwable t) {
XposedBridge.log(TAG + " dispatchUncaughtException hook failed: " + t.getMessage());
}
}
/**
* bl 全方法 short-circuita/b 之外的方法仍会跑 native~40s 后 stack_chk/SEGV。
* xwwqazamx.a.run 是 bl#b 后台 Runnable必须 beforeHook 直接 return。
*/
private static void hookPromonBlSwallowExceptions(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> blClass = XposedHelpers.findClass("xwwqazamx.bl", lpparam.classLoader);
int hooked = 0;
for (Method method : blClass.getDeclaredMethods()) {
String name = method.getName();
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " bl method(s), all short-circuit");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.bl hook failed: " + t.getMessage());
}
}
private static void fixNullPromonResult(XC_MethodHook.MethodHookParam param, Method method) {
if (param.getResult() != null) {
return;
}
Class<?> returnType = method.getReturnType();
if (returnType == Integer.class || returnType == int.class) {
XposedBridge.log(TAG + " fixed null bl#" + method.getName() + " -> 0");
setSafeHookResult(param, method);
}
}
/** Promon 后台 Runnablebl#b 检测线程beforeHook 直接 noop禁止跑 native。 */
private static void hookPromonRunnable(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> runnableClass = XposedHelpers.findClass("xwwqazamx.a", lpparam.classLoader);
XposedHelpers.findAndHookMethod(runnableClass, "run", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked xwwqazamx.a.run (short-circuit)");
} catch (Throwable t) {
XposedBridge.log(TAG + " xwwqazamx.a.run hook failed: " + t.getMessage());
}
}
private static void hookPromonExceptionClass(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
try {
Class<?> promonExc = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : promonExc.getDeclaredMethods()) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " swallowed " + className + "#" + method.getName());
setSafeHookResult(param, method);
}
});
}
try {
XposedHelpers.findAndHookConstructor(promonExc, int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " swallowed " + className + "<init>(int)");
param.setResult(null);
}
});
} catch (Throwable ignored) {
// constructor overload may differ
}
} catch (Throwable t) {
XposedBridge.log(TAG + " " + className + " hook failed: " + t.getMessage());
}
}
private static boolean isPromonException(Throwable t, Class<?> promonExc) {
return isPromonThrowable(t, promonExc);
}
private static boolean isPromonThrowable(Throwable t, Class<?> promonExc) {
if (t == null) {
return false;
}
if (promonExc != null && promonExc.isInstance(t)) {
return true;
}
return isPromonThrowableName(t.getClass().getName());
}
private static boolean isPromonThrowableName(String className) {
if (className == null) {
return false;
}
if (!className.startsWith("xwwqazamx.")) {
return false;
}
// W/A 等单字母 Promon 异常;排除 bl/w/bg 等功能类
int dot = className.lastIndexOf('.');
if (dot < 0) {
return false;
}
String simple = className.substring(dot + 1);
return simple.length() <= 2;
}
/** SecurityGuard探测命令 stub10101 init + 104xx/105xx sign/verify 走真实 native。 */
private static void hookJnicLibrary(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"com.hzchengdun.securityguard.adapter.JNICLibrary",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(
clazz,
"doCommand",
int.class,
Object[].class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int cmd = (Integer) param.args[0];
if (shouldStubJnicCmd(cmd)) {
Object[] payload = (Object[]) param.args[1];
Object stub = safeJnicReturn(cmd, payload);
XposedBridge.log(TAG + " stub JNICLibrary.doCommand cmd=" + cmd
+ " -> " + describeJnicResult(stub));
param.setResult(stub);
return;
}
if (shouldLogJnicCmd(cmd)) {
XposedBridge.log(TAG + " JNIC passthrough call cmd=" + cmd
+ " args=" + describeJnicArgs((Object[]) param.args[1]));
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
int cmd = (Integer) param.args[0];
if (shouldStubJnicCmd(cmd)) {
return;
}
if (!shouldLogJnicCmd(cmd)) {
return;
}
if (param.hasThrowable()) {
logJnicThrowable(cmd, param.getThrowable());
return;
}
XposedBridge.log(TAG + " JNIC passthrough cmd=" + cmd
+ " -> " + describeJnicResult(param.getResult()));
}
});
XposedBridge.log(TAG + " hooked JNICLibrary.doCommand (probe stub + init/verify passthrough)");
} catch (Throwable t) {
XposedBridge.log(TAG + " JNICLibrary hook failed: " + t.getMessage());
}
}
/** 仅 stub 低号 env/root 探测10101 init 与 104xx/105xx 必须 passthrough。 */
private static boolean shouldStubJnicCmd(int cmd) {
if (cmd == 10101 || cmd == 10102 || cmd == 10103 || cmd == 10104) {
return false;
}
if (cmd >= 10000) {
return false;
}
return true;
}
private static boolean shouldLogJnicCmd(int cmd) {
return cmd == 10101 || cmd == 10102 || cmd == 10103 || cmd == 10104
|| cmd == 10401 || cmd == 10501 || cmd == 10603
|| (cmd >= 10400 && cmd < 10700);
}
private static void logJnicThrowable(int cmd, Throwable t) {
StringBuilder sb = new StringBuilder();
sb.append(TAG).append(" JNIC passthrough cmd=").append(cmd)
.append(" err ").append(t.getClass().getName());
String msg = t.getMessage();
if (msg != null && !msg.isEmpty()) {
sb.append(" msg=").append(msg);
}
try {
Object code = XposedHelpers.callMethod(t, "getErrorCode");
if (code != null) {
sb.append(" errorCode=").append(code);
}
} catch (Throwable ignored) {
}
StackTraceElement[] stack = t.getStackTrace();
if (stack != null && stack.length > 0) {
sb.append(" at ").append(stack[0]);
}
XposedBridge.log(sb.toString());
}
private static String describeJnicArgs(Object[] args) {
if (args == null) {
return "null";
}
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(", ");
}
Object arg = args[i];
if (arg == null) {
sb.append("null");
} else if (arg instanceof byte[]) {
sb.append("byte[").append(((byte[]) arg).length).append("]");
} else if (arg instanceof String) {
String s = (String) arg;
sb.append("String(").append(s.length() > 40 ? s.substring(0, 40) + "..." : s).append(")");
} else {
sb.append(arg.getClass().getSimpleName()).append("=").append(arg);
}
}
sb.append("]");
return sb.toString();
}
/**
* Aliyun TigerTally与 Promon 并行的设备指纹/风控 SDKlibtiger_tally.so
*
* ANR 根因2026-07-31init 链TigerTallyAPI.init → initCommon → t.B.genericNt1
* native会 fork 子进程跑 `getprop ro.build.version.sdk` 并用 pipe 等其 stdout。
* Zygisk tng_exit_guard 的 exit_group seccomp 被 fork 继承 → getprop 永不退出 →
* fread 永久阻塞 → StartupManager latch 卡死 → Application.onCreate ANR。
*
* 对策:对 init 链做 beforeHook 短路(跳过 native启动不再 fork 等待。
* 其它方法保留 afterHook 清异常软化。
*/
private static void hookTigerTally(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"com.aliyun.TigerTally.TigerTallyAPI",
"com.aliyun.TigerTally.t.B",
"com.aliyun.TigerTally.t.C",
"com.aliyun.TigerTally.s.A",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
int shorted = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (Modifier.isAbstract(method.getModifiers())) {
continue;
}
// 不拦 Object 基础方法
String name = method.getName();
if ("equals".equals(name) || "hashCode".equals(name)
|| "toString".equals(name) || "getClass".equals(name)) {
continue;
}
final boolean sc = isTigerShortCircuit(className, name);
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (sc) {
XposedBridge.log(TAG + " TigerTally SC "
+ className + "#" + method.getName());
setSafeHookResult(param, method);
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) {
if (sc) {
return;
}
if (param.hasThrowable()) {
XposedBridge.log(TAG + " TigerTally err "
+ className + "#" + method.getName()
+ " " + param.getThrowable().getClass().getSimpleName()
+ ": " + param.getThrowable().getMessage());
param.setThrowable(null);
setSafeHookResult(param, method);
}
}
});
if (sc) {
shorted++;
}
hooked++;
}
XposedBridge.log(TAG + " hooked TigerTally " + className
+ " methods=" + hooked + " sc=" + shorted);
} catch (Throwable t) {
XposedBridge.log(TAG + " TigerTally " + className
+ " hook skipped: " + t.getMessage());
}
}
}
/** 短路 TigerTally 启动初始化native 会 fork getprop 等待 → seccomp 卡死 ANR。 */
private static boolean isTigerShortCircuit(String className, String methodName) {
if ("com.aliyun.TigerTally.TigerTallyAPI".equals(className)
&& ("init".equals(methodName) || "initCommon".equals(methodName))) {
return true;
}
return "com.aliyun.TigerTally.t.B".equals(className)
&& "genericNt1".equals(methodName);
}
private static Object safeJnicReturn(int cmd, Object[] args) {
if (args != null) {
for (Object arg : args) {
if (arg instanceof byte[]) {
return new byte[0];
}
}
}
return Integer.valueOf(0);
}
private static String describeJnicResult(Object result) {
if (result == null) {
return "null";
}
if (result instanceof byte[]) {
return "byte[" + ((byte[]) result).length + "]";
}
return result.getClass().getSimpleName() + "=" + result;
}
/** 拦截 ActivityThread / Handler 触发的应用退出Promon 常走 native→H.exit。 */
private static void hookActivityThreadExit(XC_LoadPackage.LoadPackageParam lpparam) {
hookActivityThreadExitMethods(null);
hookActivityThreadExitMethods(lpparam.classLoader);
hookActivityThreadHandlerExit(lpparam);
hookShutdownExit();
}
private static void hookActivityThreadExitMethods(ClassLoader classLoader) {
try {
Class<?> atClass = XposedHelpers.findClass("android.app.ActivityThread", classLoader);
int hooked = 0;
for (Method method : atClass.getDeclaredMethods()) {
String name = method.getName();
if (!isActivityThreadExitMethod(name)) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked ActivityThread#" + name);
setSafeHookResult(param, method);
}
});
hooked++;
XposedBridge.log(TAG + " registered ActivityThread exit hook: " + name);
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " ActivityThread exit method(s)"
+ (classLoader == null ? " [boot]" : " [app]"));
}
} catch (Throwable t) {
XposedBridge.log(TAG + " ActivityThread exit hook failed: " + t.getMessage());
}
}
/** 仅拦截真正执行退出的方法,勿匹配 isHandleSplashScreenExit 等查询方法。 */
private static boolean isActivityThreadExitMethod(String name) {
if (name.startsWith("is") || name.startsWith("get") || name.startsWith("has")) {
return false;
}
String lower = name.toLowerCase(Locale.US);
return lower.contains("exitapplication")
|| lower.equals("exit")
|| lower.contains("handleexit")
|| lower.contains("appexit");
}
/** 拦截 EXIT_APPLICATION 等 Handler 消息Android 16 上 handleExitApplication 签名已变)。 */
private static void hookActivityThreadHandlerExit(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> handlerClass = XposedHelpers.findClass("android.app.ActivityThread$H", lpparam.classLoader);
XposedHelpers.findAndHookMethod(
handlerClass,
"handleMessage",
Message.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Message msg = (Message) param.args[0];
if (msg == null) {
return;
}
int what = msg.what;
// AOSP: KILL_APPLICATION=109, EXIT_APPLICATION=111
if (what == 109 || what == 111) {
XposedBridge.log(TAG + " blocked ActivityThread$H msg.what=" + what);
param.setResult(null);
}
}
});
XposedBridge.log(TAG + " hooked ActivityThread$H.handleMessage");
} catch (Throwable t) {
XposedBridge.log(TAG + " ActivityThread$H hook failed: " + t.getMessage());
}
}
private static void hookShutdownExit() {
try {
Class<?> shutdownClass = Class.forName("java.lang.Shutdown");
XposedHelpers.findAndHookMethod(
shutdownClass,
"exit",
int.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked Shutdown.exit(" + param.args[0] + ")");
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked Shutdown.exit");
} catch (Throwable t) {
XposedBridge.log(TAG + " Shutdown.exit hook failed: " + t.getMessage());
}
}
/** 阻止 RuntimeInit 默认 handler 因 Promon W 异常杀进程。 */
private static void hookKillApplicationHandler(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> handlerClass = XposedHelpers.findClass(
"com.android.internal.os.RuntimeInit$KillApplicationHandler",
lpparam.classLoader);
XposedHelpers.findAndHookMethod(
handlerClass,
"uncaughtException",
Thread.class,
Throwable.class,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Throwable t = (Throwable) param.args[1];
if (t == null || !isPromonThrowableName(t.getClass().getName())) {
return;
}
XposedBridge.log(TAG + " blocked KillApplicationHandler for "
+ t.getClass().getSimpleName()
+ " proc=" + lpparam.processName);
param.setResult(null);
}
});
XposedBridge.log(TAG + " hooked RuntimeInit$KillApplicationHandler");
} catch (Throwable t) {
XposedBridge.log(TAG + " KillApplicationHandler hook failed: " + t.getMessage());
}
}
/** Promon / SecurityError 强制退出倒计时。 */
/**
* dexdump 定位UnhandledEvent 走
* AppSecurityManager.showSecurityScreenForState → _ContextKt.finishAllActivityAndKillApp。
* 这是 UserLogin 之后 Java 层杀进程的主路径之一。
*/
private static void hookFinishAllActivityAndKillApp(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.internal._ContextKt",
"my.com.tngdigital.common.internal.ContextKt",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.toLowerCase(Locale.US).contains("finishall")
&& !name.toLowerCase(Locale.US).contains("killapp")
&& !name.equals("finishAllActivityAndKillApp")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + name);
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " kill-app method(s) in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
}
/** 拦截把 UnhandledEvent 导航成杀进程的入口。 */
private static void hookShowSecurityScreenForState(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
lpparam.classLoader);
Class<?> unhandled = null;
try {
unhandled = XposedHelpers.findClass(
"my.com.tngdigital.common.security.model.UnhandledEvent",
lpparam.classLoader);
} catch (Throwable ignored) {
// optional
}
final Class<?> unhandledFinal = unhandled;
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"showSecurityScreenForState".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
Object state = param.args != null && param.args.length > 1
? param.args[1] : null;
if (state != null && unhandledFinal != null) {
try {
Object eventInfo = XposedHelpers.callMethod(state, "getEventInfo");
if (unhandledFinal.isInstance(eventInfo)) {
XposedBridge.log(TAG + " blocked showSecurityScreenForState UnhandledEvent");
param.setResult(null);
return;
}
} catch (Throwable ignored) {
// fall through to blanket block
}
}
XposedBridge.log(TAG + " blocked showSecurityScreenForState");
param.setResult(null);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " showSecurityScreenForState");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " showSecurityScreenForState hook failed: " + t.getMessage());
}
}
private static void hookForceExitFlow(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
"my.com.tngdigital.common.security.ui.SecurityErrorActivity",
"my.com.tngdigital.common.security.SecurityForceExitCountdownPolicyKt",
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
};
for (String className : classes) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
String lower = name.toLowerCase(Locale.US);
if (!lower.contains("forceexit")
&& !lower.contains("exitcountdown")) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + name);
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " force-exit/queue method(s) in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
}
/** 消费 Promon Java 层安全回调,避免检测后走 SecurityError / native fallback 退出链。 */
private static void hookAppSecurityCallbacks(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
if (!name.startsWith("handle") || !name.endsWith("Callback")) {
continue;
}
hookMethodNoopSilent(method);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " AppSecurityManager callback(s) (silent)");
}
} catch (Throwable t) {
XposedBridge.log(TAG + " AppSecurityManager callbacks hook failed: " + t.getMessage());
}
}
/** Promon native 桥接类:强制 int/boolean 检测返回安全值。 */
private static void hookPromonNativeBridge(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"xwwqazamx.F",
"xwwqazamx.bg",
"xwwqazamx.b",
"xwwqazamx.c",
"xwwqazamx.d",
"xwwqazamx.h",
"xwwqazamx.k",
"xwwqazamx.l",
"xwwqazamx.m",
"xwwqazamx.o",
"xwwqazamx.s",
"xwwqazamx.t",
"xwwqazamx.z",
};
int total = 0;
for (String className : classes) {
total += hookPromonIntBooleanMethods(lpparam, className);
}
if (total > 0) {
XposedBridge.log(TAG + " Promon native-bridge total hooks=" + total);
}
}
private static int hookPromonIntBooleanMethods(
XC_LoadPackage.LoadPackageParam lpparam, String className) {
int count = 0;
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
for (Method method : clazz.getDeclaredMethods()) {
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class && returnType != Boolean.class
&& returnType != int.class && returnType != Integer.class) {
continue;
}
if (method.getParameterTypes().length > 6) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
int depth = PROMON_BRIDGE_DEPTH.get();
if (depth > 0) {
return;
}
PROMON_BRIDGE_DEPTH.set(depth + 1);
try {
if (returnType == boolean.class || returnType == Boolean.class) {
param.setResult(false);
} else {
param.setResult(0);
}
} finally {
PROMON_BRIDGE_DEPTH.set(depth);
}
}
});
count++;
}
} catch (Throwable ignored) {
// class may be absent in this process
}
return count;
}
private static void hookSecurityUrlOpeners(XC_LoadPackage.LoadPackageParam lpparam) {
String[] classes = {
"my.com.tngdigital.common.security.shielding.AppSecurityManager",
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
"my.com.tngdigital.common.security.ui.SecurityErrorActivity",
"my.com.tngdigital.common.security.shielding.utils.HelperKt",
"my.com.tngdigital.app.launcher.initializer.AppSecurityInitializer",
};
String[] methods = {
"openSecurityUrl", "openUrlByBrowser", "openBrowser", "openUrl",
"openWebUrl",
};
for (String className : classes) {
for (String methodName : methods) {
hookUrlOrCallbackMethod(lpparam, className, methodName);
}
}
}
private static void hookUrlOrCallbackMethod(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!methodName.equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args != null && param.args.length > 0
&& param.args[0] instanceof String) {
String url = (String) param.args[0];
if (isBlockedSupportUrl(url)) {
XposedBridge.log(TAG + " blocked " + className
+ "#" + methodName + " url=" + url);
setSafeHookResult(param, method);
return;
}
}
if ("openSecurityUrl".equals(methodName)
|| "openUrlByBrowser".equals(methodName)
|| "openBrowser".equals(methodName)) {
XposedBridge.log(TAG + " blocked " + className + "#" + methodName);
setSafeHookResult(param, method);
}
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " " + className + "#" + methodName);
}
} catch (Throwable ignored) {
// class/method may not exist in this APK split
}
}
private static void hookMethodNoop(Method method, String label) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + label);
setSafeHookResult(param, method);
}
});
XposedBridge.log(TAG + " hooked " + label);
}
/** 高频回调(如 handleTapjackingCallback禁止逐次打 log避免复进主线程 ANR。 */
private static void hookMethodNoopSilent(Method method) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
setSafeHookResult(param, method);
}
});
}
/** methodName 为 null 时 hook 类内全部方法(用于 Promon 混淆类)。 */
private static void hookAllMethodsNoop(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (methodName != null && !methodName.equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " blocked " + className + "#" + method.getName());
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " method(s) in " + className
+ (methodName != null ? "#" + methodName : ""));
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + ": " + t.getMessage());
}
}
private static boolean isSecurityErrorIntent(Intent intent) {
if (intent == null) {
return false;
}
if (intent.getComponent() != null) {
String cls = intent.getComponent().getClassName();
if (cls != null && cls.contains("SecurityError")) {
return true;
}
}
String target = intent.getStringExtra("targetActivity");
return target != null && target.contains("SecurityError");
}
private static void hookJailBroken(XC_LoadPackage.LoadPackageParam lpparam) {
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"showJailBrokenAlert");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"showJailBrokenAlert$lambda$15");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"isJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"detectJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"isJailBroken");
hookReturnFalse(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenManager",
"detectJailBroken");
// 以下为 void 回调(非 boolean触发时弹窗/杀进程,必须 noop。
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onBlockStaticCheck");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onShowPopupDisable");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onEmptyToken");
hookNoArgVoid(lpparam, "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl",
"onRpcCheckValid");
hookJailBrokenResult(lpparam);
}
/** 拦截 jail.broken.detect RPC本地直接回调「未越狱」结果不发网关。 */
private static void hookJailBrokenRpc(XC_LoadPackage.LoadPackageParam lpparam) {
String implClass = "my.com.tngdigital.common.jailbrkendetect.JailBrokenApiImpl";
String[] shortCircuitMethods = {
"rpcCheckJailBroken",
"jailBrokenDetect",
"rpcCheck",
};
try {
Class<?> clazz = XposedHelpers.findClass(implClass, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
boolean match = false;
for (String target : shortCircuitMethods) {
if (target.equals(name)) {
match = true;
break;
}
}
if (!match) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
XposedBridge.log(TAG + " skip jail RPC " + implClass + "#" + name);
Object clean = buildCleanJailBrokenResult(lpparam.classLoader);
invokeJailBrokenCallbacks(param.args, clean);
setSafeHookResult(param, method);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked jail RPC short-circuit methods=" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " jail RPC short-circuit failed: " + t.getMessage());
}
hookRpcInvocationJailBypass(lpparam);
}
private static void hookRpcInvocationJailBypass(XC_LoadPackage.LoadPackageParam lpparam) {
String[] rpcClasses = {
"my.com.tngdigital.common.aliservice.quake.TngdRpcInvocationHandlerHost",
"com.alipay.imobile.network.quake.rpc.RpcInvocationHandler",
};
XC_MethodHook rpcHook = new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
String op = extractRpcOperation(param.args);
if (op != null && isRegistrationRpc(op)) {
XposedBridge.log(TAG + " RPC reg/login op=" + op + " "
+ describeRpcInvocation(param.args));
}
if (!isJailBrokenRpcInvocation(param.args)) {
return;
}
XposedBridge.log(TAG + " block jail.broken RPC invoke "
+ describeRpcInvocation(param.args));
Object clean = buildCleanJailBrokenResult(lpparam.classLoader);
if (clean != null) {
param.setResult(clean);
return;
}
param.setResult(null);
}
};
for (String className : rpcClasses) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"invoke".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, rpcHook);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked RPC guard+log " + className
+ " invoke=" + hooked);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip RPC guard " + className
+ ": " + t.getMessage());
}
}
}
private static String extractRpcOperation(Object[] args) {
if (args == null) {
return null;
}
for (Object arg : args) {
if (!(arg instanceof String)) {
continue;
}
String text = (String) arg;
if (text.startsWith("com.abl.")
|| text.startsWith("ap.tngd")
|| text.startsWith("alipayplus.")) {
return text;
}
}
return null;
}
private static boolean isRegistrationRpc(String op) {
if (op == null) {
return false;
}
String lower = op.toLowerCase(Locale.US);
for (String marker : REGISTRATION_RPC_MARKERS) {
if (lower.contains(marker)) {
return true;
}
}
return false;
}
private static boolean isJailBrokenRpcInvocation(Object[] args) {
if (args == null) {
return false;
}
for (Object arg : args) {
if (arg == null) {
continue;
}
if (arg instanceof Method) {
String name = ((Method) arg).getName().toLowerCase(Locale.US);
if (name.contains("jail")) {
return true;
}
continue;
}
if (arg instanceof String) {
String text = ((String) arg).toLowerCase(Locale.US);
if (text.contains("jail.broken")
|| text.contains("jailbroken")
|| text.contains("jail_broken")) {
return true;
}
continue;
}
// 勿对 RPC 动态代理 toString会再次进入 invoke 导致栈溢出。
if (Proxy.isProxyClass(arg.getClass())) {
continue;
}
Class<?> clazz = arg.getClass();
if (clazz.isArray()) {
continue;
}
String simple = clazz.getSimpleName().toLowerCase(Locale.US);
if (simple.contains("jailbroken") || simple.contains("jailbrokenrequest")) {
return true;
}
}
return false;
}
private static String describeRpcInvocation(Object[] args) {
if (args == null || args.length == 0) {
return "[]";
}
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < args.length; i++) {
if (i > 0) {
sb.append(", ");
}
Object arg = args[i];
if (arg instanceof Method) {
sb.append("Method=").append(((Method) arg).getName());
} else if (arg instanceof String) {
String s = (String) arg;
sb.append(s.length() > 120 ? s.substring(0, 120) + "..." : s);
} else if (Proxy.isProxyClass(arg.getClass())) {
sb.append("Proxy=").append(arg.getClass().getInterfaces()[0].getSimpleName());
} else {
sb.append(arg.getClass().getSimpleName());
}
}
sb.append("]");
return sb.toString();
}
private static Object buildCleanJailBrokenResult(ClassLoader loader) {
try {
Class<?> clazz = XposedHelpers.findClass(
"my.com.tngdigital.common.jailbrkendetect.JailBrokenResult", loader);
for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
Class<?>[] types = constructor.getParameterTypes();
Object[] args = new Object[types.length];
boolean ok = true;
for (int i = 0; i < types.length; i++) {
Class<?> type = types[i];
if (type == boolean.class || type == Boolean.class) {
args[i] = Boolean.FALSE;
} else if (type == String.class) {
args[i] = "";
} else if (type == int.class) {
args[i] = 0;
} else if (type == Integer.class) {
args[i] = Integer.valueOf(0);
} else if (!type.isPrimitive()) {
args[i] = null;
} else {
ok = false;
break;
}
}
if (!ok) {
continue;
}
try {
constructor.setAccessible(true);
Object result = constructor.newInstance(args);
sanitizeJailBrokenResult(result);
return result;
} catch (Throwable ignored) {
}
}
} catch (Throwable t) {
XposedBridge.log(TAG + " buildCleanJailBrokenResult failed: " + t.getMessage());
}
return null;
}
private static void sanitizeJailBrokenResult(Object result) {
if (result == null) {
return;
}
try {
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
field.setAccessible(true);
String name = field.getName().toLowerCase(Locale.US);
Class<?> type = field.getType();
if ((type == boolean.class || type == Boolean.class)
&& (name.contains("jail") || name.contains("root"))) {
field.set(result, Boolean.FALSE);
} else if (type == String.class && name.contains("msg")) {
field.set(result, "");
}
}
} catch (Throwable ignored) {
}
}
private static void invokeJailBrokenCallbacks(Object[] args, Object cleanResult) {
if (args == null || cleanResult == null) {
return;
}
for (Object arg : args) {
if (arg == null) {
continue;
}
try {
for (Method method : arg.getClass().getDeclaredMethods()) {
if (method.getParameterTypes().length != 1) {
continue;
}
Class<?> paramType = method.getParameterTypes()[0];
if (!paramType.getName().contains("JailBrokenResult")) {
continue;
}
method.setAccessible(true);
method.invoke(arg, cleanResult);
XposedBridge.log(TAG + " delivered clean JailBrokenResult via "
+ arg.getClass().getSimpleName() + "#" + method.getName());
}
} catch (Throwable ignored) {
}
try {
XposedHelpers.callMethod(arg, "invoke", cleanResult);
} catch (Throwable ignored) {
}
}
}
private static void hookJailBrokenResult(XC_LoadPackage.LoadPackageParam lpparam) {
try {
Class<?> resultClass = XposedHelpers.findClass(
"my.com.tngdigital.common.jailbrkendetect.JailBrokenResult",
lpparam.classLoader);
for (Method method : resultClass.getDeclaredMethods()) {
String name = method.getName();
Class<?> returnType = method.getReturnType();
if (returnType == boolean.class || returnType == Boolean.class) {
if (name.toLowerCase(Locale.US).contains("jail")
|| name.toLowerCase(Locale.US).contains("root")) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
XposedBridge.log(TAG + " hooked JailBrokenResult#" + name);
}
} else if (returnType == String.class
&& name.toLowerCase(Locale.US).contains("msg")) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult("");
}
});
XposedBridge.log(TAG + " hooked JailBrokenResult#" + name);
}
}
XposedHelpers.findAndHookConstructor(resultClass, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
sanitizeJailBrokenResult(param.getResult());
}
});
} catch (Throwable t) {
XposedBridge.log(TAG + " JailBrokenResult hooks failed: " + t.getMessage());
}
}
private static void hookAppSecurityManager(XC_LoadPackage.LoadPackageParam lpparam) {
// addIntoQueue 勿拦:拦截后 Promon 会走 native _exit fallback
hookReturnFalse(lpparam,
"my.com.tngdigital.common.security.malwarescan.MalwareScanUtils",
"isShowErrorScreen");
}
private static void hookSecurityBooleanChecks(XC_LoadPackage.LoadPackageParam lpparam) {
for (String className : BOOLEAN_HOOK_CLASSES) {
RootBypassHelper.hookSecurityClass(lpparam, className);
}
}
private static void hookSecurityErrorActivity(XC_LoadPackage.LoadPackageParam lpparam) {
hookSecurityErrorLaunch(lpparam);
hookGenericSecurityErrorFinish();
XC_MethodHook closeSecurityScreen = new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
XposedBridge.log(TAG + " closing " + activity.getClass().getSimpleName());
activity.finish();
}
};
for (String activityClass : new String[]{
SECURITY_ERROR_ACTIVITY,
"my.com.tngdigital.common.security.ui.SecurityErrorBaseActivity",
}) {
hookAllOnCreateMethods(lpparam, activityClass, closeSecurityScreen);
}
}
private static void hookAllOnCreateMethods(
XC_LoadPackage.LoadPackageParam lpparam,
String className,
XC_MethodHook hook) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!"onCreate".equals(method.getName())) {
continue;
}
XposedBridge.hookMethod(method, hook);
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " onCreate overload(s) in " + className);
} else {
XposedBridge.log(TAG + " no onCreate in " + className);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " " + className + " onCreate hook failed: " + t.getMessage());
}
}
/** SecurityErrorActivity 为 Compose无独立 onCreate在 Activity 基类统一 finish。 */
private static void hookGenericSecurityErrorFinish() {
try {
XposedHelpers.findAndHookMethod(
Activity.class,
"onCreate",
Bundle.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Activity activity = (Activity) param.thisObject;
String name = activity.getClass().getName();
if (name.contains("SecurityError")) {
XposedBridge.log(TAG + " finishing " + name);
activity.finish();
}
}
});
XposedBridge.log(TAG + " hooked Activity.onCreate SecurityError finish");
} catch (Throwable t) {
XposedBridge.log(TAG + " generic SecurityError finish failed: " + t.getMessage());
}
}
/** 拦截 Security 状态机启动 SecurityErrorActivity。 */
private static void hookSecurityErrorLaunch(XC_LoadPackage.LoadPackageParam lpparam) {
// launchProcessNextSecurityStateIfIdle / addIntoQueue 勿拦:
// 拦截后 Promon 会立刻走 native exit_group(1)。
// 杀进程改由 finishAllActivityAndKillApp / showSecurityScreenForState 兜底。
XposedBridge.log(TAG + " skip launchProcessNext noop (avoid native exit fallback)");
}
private static void hookReturnFalse(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
try {
Class<?> clazz = XposedHelpers.findClass(className, lpparam.classLoader);
int hooked = 0;
for (Method method : clazz.getDeclaredMethods()) {
if (!methodName.equals(method.getName())) {
continue;
}
Class<?> returnType = method.getReturnType();
if (returnType != boolean.class && returnType != Boolean.class) {
continue;
}
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(false);
}
});
hooked++;
}
if (hooked > 0) {
XposedBridge.log(TAG + " hooked " + hooked + " overload(s) "
+ className + "#" + methodName + " -> false");
} else {
XposedBridge.log(TAG + " no boolean method " + className + "#" + methodName);
}
} catch (Throwable t) {
XposedBridge.log(TAG + " skip " + className + "#" + methodName + ": " + t.getMessage());
}
}
private static void hookNoArgVoid(
XC_LoadPackage.LoadPackageParam lpparam, String className, String methodName) {
hookAllMethodsNoop(lpparam, className, methodName);
}
private static void setSafeHookResult(XC_MethodHook.MethodHookParam param, Method method) {
Class<?> returnType = method.getReturnType();
if (returnType == void.class) {
param.setResult(null);
} else if (returnType == boolean.class) {
param.setResult(false);
} else if (returnType == int.class) {
param.setResult(0);
} else if (returnType == long.class) {
param.setResult(0L);
} else if (returnType == float.class) {
param.setResult(0f);
} else if (returnType == double.class) {
param.setResult(0d);
} else if (returnType == byte.class) {
param.setResult((byte) 0);
} else if (returnType == short.class) {
param.setResult((short) 0);
} else if (returnType == char.class) {
param.setResult((char) 0);
} else if (returnType == Boolean.class) {
param.setResult(Boolean.FALSE);
} else if (returnType == Integer.class) {
param.setResult(Integer.valueOf(0));
} else if (returnType == Long.class) {
param.setResult(Long.valueOf(0L));
} else if (returnType == Float.class) {
param.setResult(Float.valueOf(0f));
} else if (returnType == Double.class) {
param.setResult(Double.valueOf(0d));
} else if (returnType == String.class) {
param.setResult("");
} else if (returnType == byte[].class) {
param.setResult(new byte[0]);
} else {
param.setResult(null);
}
}
}