Files
notiMessage/magisk-modules/tng_exit_guard/jni/main.cpp
mars bc51fb35cc chore(tng): 清理死代码并停止跟踪 reverse/dumps
精简 login-first 不可达 Hook,忽略调试产物目录,从仓库移除已入库的 dumps 文件。
2026-08-04 13:32:57 +08:00

917 lines
33 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 <dlfcn.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 zygisk::Api *g_api = nullptr;
static std::atomic<int> g_cxx_plt{0};
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};
static std::atomic<pid_t> g_main_tid{0};
static std::atomic<int> g_abrt_swallow{0};
static std::atomic<pid_t> g_abrt_last_tid{0};
static std::atomic<uintptr_t> g_abrt_last_pc{0};
static std::atomic<int> g_abrt_streak{0};
/* 隔离进程 :goacqowmmt 会循环 SEGV主进程 worker 也狂刷。cap 后 freeze 该线程 */
static constexpr int kMaxPromonSegvSkip = 200;
static constexpr int kMaxAbrtStreak = 3;
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();
}
/* 1.9.10Login 后出现 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<ucontext_t *>(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 时跳回 lrLooper 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=129Promon 自杀是 kill(pid,SIGABRT) → si_code=SI_USER。
* 不拦 tgkill/tkillART 的 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<void *>(static_cast<uintptr_t>(-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<uintptr_t>(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<dlopen_fn>(dlsym(RTLD_DEFAULT, "dlopen"));
}
if (!orig_sphal_load) {
orig_sphal_load = reinterpret_cast<sphal_load_fn>(
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 调用方 PLTlibhwui/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)