feat(tng): TNG eWallet Promon bypass、Zygisk 信号守卫与注册链保护
新增 TngRootBypassHook 与 tng_exit_guard 模块,修复 BAL 强拉、SIGABRT/pc==lr SEGV 崩溃,验证可进入注册页。
This commit is contained in:
11
magisk-modules/tng_exit_guard/jni/Android.mk
Normal file
11
magisk-modules/tng_exit_guard/jni/Android.mk
Normal 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)
|
||||
4
magisk-modules/tng_exit_guard/jni/Application.mk
Normal file
4
magisk-modules/tng_exit_guard/jni/Application.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
APP_ABI := arm64-v8a
|
||||
APP_PLATFORM := android-24
|
||||
APP_STL := c++_static
|
||||
APP_CPPFLAGS := -std=c++17
|
||||
390
magisk-modules/tng_exit_guard/jni/main.cpp
Normal file
390
magisk-modules/tng_exit_guard/jni/main.cpp
Normal 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 也 skip;libc++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 会循环 SEGV;cap=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 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();
|
||||
}
|
||||
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)
|
||||
391
magisk-modules/tng_exit_guard/jni/zygisk.hpp
Normal file
391
magisk-modules/tng_exit_guard/jni/zygisk.hpp
Normal 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"
|
||||
6
magisk-modules/tng_exit_guard/module.prop
Normal file
6
magisk-modules/tng_exit_guard/module.prop
Normal 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.
|
||||
BIN
magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so
Normal file
BIN
magisk-modules/tng_exit_guard/zygisk/arm64-v8a.so
Normal file
Binary file not shown.
Reference in New Issue
Block a user