Files
notiMessage/magisk-modules/tng_exit_guard/jni/main.cpp
mars 13e407623b feat(tng): TNG eWallet Promon bypass、Zygisk 信号守卫与注册链保护
新增 TngRootBypassHook 与 tng_exit_guard 模块,修复 BAL 强拉、SIGABRT/pc==lr SEGV 崩溃,验证可进入注册页。
2026-07-31 17:55:13 +08:00

391 lines
13 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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)