/* * 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 也 skip;libc++abi __cxa_guard_acquire → SIGABRT 吞掉。 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 zygisk::Api *g_api = nullptr; static std::atomic g_cxx_plt{0}; static std::atomic g_seccomp_ok{0}; static std::atomic g_stack_chk{0}; static std::atomic g_promon_segv{0}; static std::atomic g_promon_start{0}; static std::atomic g_promon_end{0}; static std::atomic g_main_tid{0}; static std::atomic g_abrt_swallow{0}; static std::atomic g_abrt_last_tid{0}; static std::atomic g_abrt_last_pc{0}; static std::atomic g_abrt_streak{0}; /* 隔离进程 :goacqowmmt 会循环 SEGV;主进程 worker 也狂刷。cap 后 freeze 该线程 */ static constexpr int kMaxPromonSegvSkip = 200; static constexpr int kMaxAbrtStreak = 3; static std::atomic 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(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 deref;maps 尚未刷新时也按此 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(); } /* 1.9.10:Login 后出现 Promon so 外 SEGV → 旧逻辑 re-raise 直接闪退 */ { int n = ++g_promon_segv; pid_t tid = gettid(); if (n <= 5 || n % 50 == 0) { LOGI("non-promon SIGSEGV tid=%d pc=%lx lr=%lx n=%d — pc+4", (int)tid, (unsigned long)pc, (unsigned long)lr, n); } if (n > 200 && tid != g_main_tid.load()) { LOGI("non-promon SEGV storm tid=%d — freeze", (int)tid); freeze_forever(); } if (pc != 0) { uc->uc_mcontext.pc = pc + 4; return; } } freeze_forever(); } /** * ABRT/TRAP:一律 pc+4。跳远距 LR 会弄坏主线程 Looper(闪退观感)。 * 工作线程同 PC 连 abort 超限 → freeze;主线程始终 pc+4。 */ static void fatal_skip_handler(int sig, siginfo_t *info, void *ctx) { (void)info; ucontext_t *uc = reinterpret_cast(ctx); #if defined(__aarch64__) uintptr_t pc = uc->uc_mcontext.pc; uintptr_t lr = uc->uc_mcontext.regs[30]; pid_t tid = gettid(); if (sig == SIGABRT) { int streak = 1; if (g_abrt_last_tid.load() == tid && g_abrt_last_pc.load() == pc) { streak = g_abrt_streak.fetch_add(1) + 1; } else { g_abrt_last_tid.store(tid); g_abrt_last_pc.store(pc); g_abrt_streak.store(1); } if (streak > kMaxAbrtStreak && tid != g_main_tid.load()) { LOGI("ABRT streak cap tid=%d pc=%lx n=%d — freeze worker", (int)tid, (unsigned long)pc, streak); freeze_forever(); } int n = ++g_abrt_swallow; /* 主线程:abort 后 _exit 被 seccomp 拦 → abort 内部死循环(还复位 handler)→ * 主线程永久卡死 → 黑屏/ANR。主线程 ABRT 时跳回 lr(Looper pollOnce)恢复; * 仅当 lr 距 pc 远(不在 abort 内部)才跳,否则仍在 abort epilogue 内跳 LR 会再 abort。 */ uintptr_t target = pc + 4; if (tid == g_main_tid.load() && lr != 0 && pc != 0 && (lr < pc - 0x1000 || lr > pc + 0x1000)) { target = lr; } if (n <= 5 || n % 50 == 0) { LOGI("ABRT tid=%d pc=%lx lr=%lx streak=%d -> %lx", (int)tid, (unsigned long)pc, (unsigned long)lr, streak, (unsigned long)target); } uc->uc_mcontext.pc = target; return; } if (sig == SIGTRAP) { int n = ++g_abrt_swallow; if (n <= 5 || n % 50 == 0) { LOGI("TRAP pc+4 tid=%d pc=%lx", (int)tid, (unsigned long)pc); } uc->uc_mcontext.pc = pc != 0 ? pc + 4 : lr; 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 always pc+4)"); } 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 always pc+4)"); } } #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() { /* exit_group 拦 + 精准拦 Promon 自杀 kill(SIGABRT)(SI_USER)。 * 只拦 kill()(nr=129):Promon 自杀是 kill(pid,SIGABRT) → si_code=SI_USER。 * 不拦 tgkill/tkill:ART 的 abort() 用 tgkill(self) → si_code=SI_TKILL, * 拦它会让 libart 状态错乱 → 进程崩(之前验证)。 */ struct sock_filter filter[] = { // 0: arch _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), // 2: not aarch64 _BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, nr)), _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, __NR_exit_group), // 4: exit_group? true→5 _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ERRNO_EPERM), // 5: EPERM exit_group _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 3, __NR_kill), // 6: kill? true→7 _BPFI(BPF_LD | BPF_W | BPF_ABS, 0, 0, offsetof(struct seccomp_data, args) + 8), // 7: args[1]=sig _BPFI(BPF_JMP | BPF_JEQ | BPF_K, 0, 1, SIGABRT), // 8: sig==SIGABRT? true→9 _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ERRNO_EPERM), // 9: EPERM kill ABRT _BPFI(BPF_RET | BPF_K, 0, 0, SC_RET_ALLOW), // 10: 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+kill-ABRT via prctl"); } else { LOGI("seccomp exit_group+kill-ABRT 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); using cxa_guard_acquire_fn = int (*)(void *); using cxa_guard_abort_fn = void (*)(); using dlopen_fn = void *(*)(const char *, int); using android_dlopen_ext_fn = void *(*)(const char *, int, const void *); using sphal_load_fn = void *(*)(const char *, int); using open_passthrough_hal_fn = void *(*)(const char *, const char *, int); static cxa_guard_acquire_fn orig_cxa_guard_acquire = nullptr; static cxa_guard_abort_fn orig_cxa_guard_abort = nullptr; static dlopen_fn orig_dlopen = nullptr; static android_dlopen_ext_fn orig_android_dlopen_ext = nullptr; static sphal_load_fn orig_sphal_load = nullptr; static open_passthrough_hal_fn orig_open_passthrough_hal = nullptr; static void *g_libandroid_handle = nullptr; static void *g_mapper_pixel_handle = nullptr; #ifndef RTLD_NOW #define RTLD_NOW 2 #endif #ifndef RTLD_GLOBAL #define RTLD_GLOBAL 0x100 #endif #ifndef RTLD_NOLOAD #define RTLD_NOLOAD 0x4 #endif #ifndef RTLD_DEFAULT #define RTLD_DEFAULT reinterpret_cast(static_cast(-1)) #endif 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; } /** 仅打断递归初始化;系统 libc++ 静态 ctor(含 gralloc)必须真实执行。 */ static thread_local void *tl_cxa_guard = nullptr; static thread_local int tl_cxa_depth = 0; static int hooked_cxa_guard_acquire(void *guard) { if (guard != nullptr && tl_cxa_guard == guard) { LOGI("cxa_guard recursive skip tid=%d", (int)gettid()); return 1; } if (!orig_cxa_guard_acquire) { return 1; } void *prev = tl_cxa_guard; tl_cxa_guard = guard; ++tl_cxa_depth; int r = orig_cxa_guard_acquire(guard); --tl_cxa_depth; tl_cxa_guard = prev; return r; } static void hooked_cxa_guard_abort() { LOGI("blocked __cxa_guard_abort tid=%d depth=%d", (int)gettid(), tl_cxa_depth); } static void *try_dlopen_noload(const char *name) { if (!orig_dlopen || name == nullptr) { return nullptr; } void *h = orig_dlopen(name, RTLD_NOW | RTLD_NOLOAD); if (h != nullptr) { return h; } const char *base = strrchr(name, '/'); if (base != nullptr && base[1] != '\0') { h = orig_dlopen(base + 1, RTLD_NOW | RTLD_NOLOAD); } return h; } /** 校验 dlopen 句柄:拒绝空/明显毒化指针,并用 dlsym 探活。 */ static bool libandroid_handle_ok(void *h) { if (h == nullptr) return false; uintptr_t p = reinterpret_cast(h); // 用户态典型映射;排除明显垃圾(如 0x...c5c5 / 高熵毒化) if (p < 0x10000UL) return false; if ((p & 0xffffUL) == 0xc5c5UL) return false; void *sym = dlsym(h, "ANativeWindow_fromSurface"); if (sym == nullptr) { sym = dlsym(h, "AAssetManager_fromJava"); } return sym != nullptr; } static void preload_hwui_libs() { if (!orig_dlopen) { // PLT 尚未拿到 orig 时,用 libc 直调 orig_dlopen = reinterpret_cast(dlsym(RTLD_DEFAULT, "dlopen")); } if (!orig_sphal_load) { orig_sphal_load = reinterpret_cast( dlsym(RTLD_DEFAULT, "android_load_sphal_library")); } if (g_libandroid_handle != nullptr && !libandroid_handle_ok(g_libandroid_handle)) { LOGI("drop invalid cached libandroid %p", g_libandroid_handle); g_libandroid_handle = nullptr; } if (g_libandroid_handle == nullptr && orig_dlopen) { static const char *kPaths[] = { "libandroid.so", "/system/lib64/libandroid.so", "/apex/com.android.runtime/lib64/libandroid.so", }; for (const char *path : kPaths) { void *h = orig_dlopen(path, RTLD_NOW | RTLD_GLOBAL); if (h == nullptr) { h = try_dlopen_noload(path); } if (libandroid_handle_ok(h)) { g_libandroid_handle = h; LOGI("preload libandroid ok path=%s -> %p", path, h); break; } if (h != nullptr) { LOGI("preload libandroid reject path=%s -> %p", path, h); } } if (g_libandroid_handle == nullptr) { LOGI("preload libandroid FAILED"); } } if (g_mapper_pixel_handle == nullptr) { if (orig_sphal_load) { g_mapper_pixel_handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW); LOGI("preload mapper via sphal -> %p", g_mapper_pixel_handle); } if (g_mapper_pixel_handle == nullptr && orig_dlopen) { g_mapper_pixel_handle = orig_dlopen( "/vendor/lib64/hw/mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL); LOGI("preload mapper via path -> %p", g_mapper_pixel_handle); } } } static void *hooked_dlopen(const char *name, int flags) { void *handle = orig_dlopen ? orig_dlopen(name, flags) : nullptr; if (name == nullptr) { return handle; } if (handle != nullptr) { // 命名空间下偶发返回毒化非空句柄,HWUI 随后 FATAL if (strstr(name, "libandroid.so") != nullptr && !libandroid_handle_ok(handle)) { LOGI("dlopen got bad handle %p for %s — recover", handle, name); handle = nullptr; } else { if (strstr(name, "libandroid.so") != nullptr) { g_libandroid_handle = handle; } return handle; } } // 已映射库:命名空间下按名 dlopen 会失败,RTLD_NOLOAD 可取回句柄 handle = try_dlopen_noload(name); if (handle != nullptr) { if (strstr(name, "libandroid.so") == nullptr || libandroid_handle_ok(handle)) { LOGI("dlopen NOLOAD hit name=%s -> %p", name, handle); if (strstr(name, "libandroid.so") != nullptr) { g_libandroid_handle = handle; } return handle; } } if (strstr(name, "libandroid.so") != nullptr) { if (g_libandroid_handle != nullptr && !libandroid_handle_ok(g_libandroid_handle)) { LOGI("drop bad cached libandroid %p", g_libandroid_handle); g_libandroid_handle = nullptr; } if (g_libandroid_handle != nullptr) { LOGI("dlopen return cached libandroid %p (from %s)", g_libandroid_handle, name); return g_libandroid_handle; } static const char *kAndroidPaths[] = { "/system/lib64/libandroid.so", "libandroid.so", }; LOGI("dlopen miss name=%s flags=0x%x tid=%d — try fallback", name, flags, (int)gettid()); for (const char *path : kAndroidPaths) { handle = try_dlopen_noload(path); if (handle == nullptr && orig_dlopen) { handle = orig_dlopen(path, flags | RTLD_GLOBAL); } if (libandroid_handle_ok(handle)) { g_libandroid_handle = handle; LOGI("dlopen fallback %s -> %p (from %s)", path, handle, name); return handle; } if (handle != nullptr) { LOGI("dlopen fallback reject %s -> %p", path, handle); } } LOGI("dlopen fallback failed name=%s tid=%d", name, (int)gettid()); return nullptr; } if (strstr(name, "mapper.pixel") != nullptr || strstr(name, "mapper.") != nullptr) { if (g_mapper_pixel_handle != nullptr) { LOGI("dlopen return cached mapper %p (from %s)", g_mapper_pixel_handle, name); return g_mapper_pixel_handle; } if (orig_sphal_load) { handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW); if (handle != nullptr) { g_mapper_pixel_handle = handle; LOGI("dlopen sphal mapper -> %p (from %s)", handle, name); return handle; } } if (orig_dlopen) { handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so", flags | RTLD_GLOBAL); if (handle != nullptr) { g_mapper_pixel_handle = handle; LOGI("dlopen path mapper -> %p (from %s)", handle, name); return handle; } } LOGI("dlopen mapper failed name=%s tid=%d", name, (int)gettid()); } return handle; } static void *hooked_android_dlopen_ext(const char *name, int flags, const void *extinfo) { void *handle = orig_android_dlopen_ext ? orig_android_dlopen_ext(name, flags, extinfo) : nullptr; if (handle != nullptr || name == nullptr) { return handle; } if (strstr(name, "libandroid.so") != nullptr || strstr(name, "mapper") != nullptr) { LOGI("android_dlopen_ext miss name=%s — try dlopen fallback", name); return hooked_dlopen(name, flags); } return handle; } static bool find_lib_match(const char *suffix, const char *contains, 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; bool match = false; if (suffix != nullptr) { size_t plen = strlen(p); size_t slen = strlen(suffix); match = plen >= slen && strcmp(p + plen - slen, suffix) == 0; } else if (contains != nullptr) { match = strstr(p, contains) != nullptr; } if (!match) 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 bool find_lib_by_suffix(const char *suffix, dev_t *dev, ino_t *ino) { return find_lib_match(suffix, nullptr, dev, ino); } static bool find_libc(dev_t *dev, ino_t *ino) { return find_lib_by_suffix("libc.so", dev, ino); } /** 收集 maps 里所有匹配后缀的已加载库(去重)。Zygisk commit 前必须覆盖全部副本, * 否则 libc++ 多副本(/system、/vendor、/apex)时只 hook 一份,调用点仍走原生实现。 */ struct lib_devino { dev_t dev; ino_t ino; }; static int find_all_lib_by_suffix(const char *suffix, lib_devino *out, int max) { FILE *fp = fopen("/proc/self/maps", "r"); if (!fp) return 0; char line[1024]; int n = 0; 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 got = sscanf(line, "%lx-%lx %7s %llx %31s %lu %511[^\n]", &start, &end, perms, &offset, deststr, &inode, path); if (got < 7 || inode == 0) continue; char *p = path; while (*p == ' ') ++p; size_t plen = strlen(p); size_t slen = strlen(suffix); if (plen < slen || strcmp(p + plen - slen, suffix) != 0) continue; unsigned maj = 0, min = 0; if (sscanf(deststr, "%x:%x", &maj, &min) != 2) continue; dev_t d = makedev(maj, min); ino_t in = inode; bool dup = false; for (int i = 0; i < n; i++) { if (out[i].dev == d && out[i].ino == in) { dup = true; break; } } if (dup) continue; if (n < max) { out[n].dev = d; out[n].ino = in; n++; } } fclose(fp); return n; } static void register_plt(zygisk::Api *api, dev_t dev, ino_t ino, const char *sym, void *hook, void **orig) { if (!api || dev == 0 || ino == 0) return; api->pltHookRegister(dev, ino, sym, hook, orig); } static void *hooked_sphal_load(const char *name, int flags) { void *handle = orig_sphal_load ? orig_sphal_load(name, flags) : nullptr; if (handle != nullptr || name == nullptr) { return handle; } LOGI("sphal miss name=%s flags=0x%x — try cache/path", name, flags); if (strstr(name, "mapper") != nullptr) { if (g_mapper_pixel_handle != nullptr) { LOGI("sphal return cached mapper %p", g_mapper_pixel_handle); return g_mapper_pixel_handle; } if (orig_dlopen) { handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL); if (handle != nullptr) { g_mapper_pixel_handle = handle; LOGI("sphal path mapper -> %p", handle); return handle; } handle = orig_dlopen("mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL); if (handle != nullptr) { g_mapper_pixel_handle = handle; LOGI("sphal name mapper -> %p", handle); return handle; } } } return nullptr; } static void *hooked_open_passthrough_hal(const char *interface, const char *instance, int dlopen_flags) { void *handle = orig_open_passthrough_hal ? orig_open_passthrough_hal(interface, instance, dlopen_flags) : nullptr; if (handle != nullptr) { return handle; } LOGI("passthroughHal miss iface=%s inst=%s — try mapper path", interface ? interface : "?", instance ? instance : "?"); if ((interface && strstr(interface, "mapper") != nullptr) || (instance && strstr(instance, "pixel") != nullptr)) { if (g_mapper_pixel_handle != nullptr) { return g_mapper_pixel_handle; } if (orig_sphal_load) { handle = orig_sphal_load("mapper.pixel.so", RTLD_NOW); } if (handle == nullptr && orig_dlopen) { handle = orig_dlopen("/vendor/lib64/hw/mapper.pixel.so", RTLD_NOW | RTLD_GLOBAL); } if (handle != nullptr) { g_mapper_pixel_handle = handle; LOGI("passthroughHal mapper recovered -> %p", handle); } } return handle; } static void register_dlopen_on_lib(zygisk::Api *api, const char *suffix) { dev_t dev = 0; ino_t ino = 0; if (!find_lib_by_suffix(suffix, &dev, &ino)) { return; } // 必须 hook 调用方 PLT(libhwui/libui),只 hook libc 拦不到 HWUI 的 dlopen register_plt(api, dev, ino, "dlopen", (void *)hooked_dlopen, (void **)&orig_dlopen); register_plt(api, dev, ino, "android_dlopen_ext", (void *)hooked_android_dlopen_ext, (void **)&orig_android_dlopen_ext); register_plt(api, dev, ino, "android_load_sphal_library", (void *)hooked_sphal_load, (void **)&orig_sphal_load); register_plt(api, dev, ino, "AServiceManager_openDeclaredPassthroughHal", (void *)hooked_open_passthrough_hal, (void **)&orig_open_passthrough_hal); LOGI("dlopen PLT on %s", suffix); } /** 注册所有已加载 libc++ 副本的 __cxa_guard_acquire/abort。返回注册的副本数。 */ static int register_cxx_guard_hooks(zygisk::Api *api) { if (!api) return 0; lib_devino libs[8]; int n = find_all_lib_by_suffix("libc++.so", libs, 8); if (n == 0) { n = find_all_lib_by_suffix("libc++_shared.so", libs, 8); } for (int i = 0; i < n; i++) { register_plt(api, libs[i].dev, libs[i].ino, "__cxa_guard_acquire", (void *)hooked_cxa_guard_acquire, (void **)&orig_cxa_guard_acquire); register_plt(api, libs[i].dev, libs[i].ino, "__cxa_guard_abort", (void *)hooked_cxa_guard_abort, (void **)&orig_cxa_guard_abort); } if (n > 0) { LOGI("cxx guard target libc++ copies=%d", n); } return n; } static void install_plt(zygisk::Api *api) { if (!api) return; dev_t dev = 0; ino_t ino = 0; if (find_libc(&dev, &ino)) { register_plt(api, dev, ino, "exit", (void *)hooked_exit, (void **)&orig_exit); register_plt(api, dev, ino, "_exit", (void *)hooked__exit, (void **)&orig__exit); register_plt(api, dev, ino, "abort", (void *)hooked_abort, (void **)&orig_abort); register_plt(api, dev, ino, "__stack_chk_fail", (void *)hooked_stack_chk_fail, (void **)&orig_stack_chk_fail); register_plt(api, dev, ino, "raise", (void *)hooked_raise, (void **)&orig_raise); register_plt(api, dev, ino, "kill", (void *)hooked_kill, (void **)&orig_kill); register_plt(api, dev, ino, "tgkill", (void *)hooked_tgkill, (void **)&orig_tgkill); register_plt(api, dev, ino, "pthread_kill", (void *)hooked_pthread_kill, (void **)&orig_pthread_kill); register_plt(api, dev, ino, "dlopen", (void *)hooked_dlopen, (void **)&orig_dlopen); register_plt(api, dev, ino, "android_dlopen_ext", (void *)hooked_android_dlopen_ext, (void **)&orig_android_dlopen_ext); } // HWUI / libui 直接 PLT→linker,必须单独挂 register_dlopen_on_lib(api, "libhwui.so"); register_dlopen_on_lib(api, "libui.so"); register_dlopen_on_lib(api, "libandroid_runtime.so"); register_dlopen_on_lib(api, "libbinder_ndk.so"); register_dlopen_on_lib(api, "libvndksupport.so"); // libc++ cxa guard 必须在首次 commit 前注册:Zygisk pltHookCommit 二次调用会失败, // 导致 __cxa_guard_acquire 递归 abort 保护从未生效(主线程反复 SIGABRT → 黑屏)。 int cxx = register_cxx_guard_hooks(api); bool ok = api->pltHookCommit(); LOGI("PLT commit=%d cxx_guard_copies=%d", ok ? 1 : 0, cxx); if (ok && cxx > 0) { g_cxx_plt.store(1); } // commit 后立刻预加载,抢在 Promon/命名空间收紧之前拿到句柄 preload_hwui_libs(); } static void try_install_cxx_guard_plt() { if (g_cxx_plt.load() || !g_api) return; dev_t dev = 0; ino_t ino = 0; bool any = false; // libc++ 副本已由首次 PLT commit 覆盖;此处仅补 libtngdigital_ewallet.so 自身 PLT。 if (find_lib_by_suffix("libtngdigital_ewallet.so", &dev, &ino)) { register_plt(g_api, dev, ino, "__cxa_guard_acquire", (void *)hooked_cxa_guard_acquire, (void **)&orig_cxa_guard_acquire); register_plt(g_api, dev, ino, "__cxa_guard_abort", (void *)hooked_cxa_guard_abort, (void **)&orig_cxa_guard_abort); any = true; LOGI("cxx guard target libtngdigital_ewallet"); } if (!any) return; bool ok = g_api->pltHookCommit(); LOGI("cxx guard commit=%d", ok ? 1 : 0); if (ok) { g_cxx_plt.store(1); LOGI("PLT cxx guards committed (orig acquire + recursive skip)"); } } static void *phase_thread(void *) { install_promon_segv_handler(); /* Promon 用 SVC exit_group 绕过 PLT;必须 seccomp。延迟 400ms 避开最早的 fork/getprop。 */ usleep(400 * 1000); if (install_seccomp_exit_group_only() == 0) { LOGI("seccomp exit_group armed @400ms"); } else { LOGE("seccomp install failed"); } for (int i = 0; i < 40; i++) { usleep(1000 * 1000); install_soft_signals(); install_promon_segv_handler(); try_install_cxx_guard_plt(); 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) { g_api = api; g_main_tid.store(gettid()); LOGI("install pid=%d main_tid=%d (PLT+cxx-guard+ABRT-pc+4+SEGV-skip+seccomp@400ms)", getpid(), (int)g_main_tid.load()); 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)