diff --git a/native/src/Android.mk b/native/src/Android.mk index c534bf465..0a6b82942 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -34,8 +34,8 @@ LOCAL_SRC_FILES := \ core/su/su_daemon.cpp \ core/zygisk/entry.cpp \ core/zygisk/main.cpp \ + core/zygisk/module.cpp \ core/zygisk/hook.cpp \ - core/zygisk/native_bridge.cpp \ core/deny/cli.cpp \ core/deny/utils.cpp \ core/deny/revert.cpp diff --git a/native/src/core/zygisk/entry.cpp b/native/src/core/zygisk/entry.cpp index a307878c8..7344aacca 100644 --- a/native/src/core/zygisk/entry.cpp +++ b/native/src/core/zygisk/entry.cpp @@ -22,6 +22,27 @@ extern "C" [[maybe_unused]] void zygisk_inject_entry(void *handle) { ZLOGD("load success\n"); } +static bool is_compatible_with(uint32_t) { + auto name = get_prop(NBPROP); + android_dlextinfo info = { + .flags = ANDROID_DLEXT_FORCE_LOAD + }; + void *handle = android_dlopen_ext(name.data(), RTLD_LAZY, &info); + if (handle) { + auto entry = reinterpret_cast(dlsym(handle, "zygisk_inject_entry")); + if (entry) { + entry(handle); + } + } + return false; +} + +extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf{ + .version = 2, + .padding = {}, + .isCompatibleWith = &is_compatible_with, +}; + // The following code runs in zygote/app process static inline bool should_load_modules(uint32_t flags) { diff --git a/native/src/core/zygisk/hook.cpp b/native/src/core/zygisk/hook.cpp index 7a5ee994d..e52d6a0a8 100644 --- a/native/src/core/zygisk/hook.cpp +++ b/native/src/core/zygisk/hook.cpp @@ -1,10 +1,6 @@ -#include #include #include -#include #include -#include -#include #include @@ -13,110 +9,27 @@ #include "zygisk.hpp" #include "module.hpp" +#include "jni_hooks.hpp" using namespace std; -// Extreme verbose logging -#define ZLOGV(...) ZLOGD(__VA_ARGS__) -//#define ZLOGV(...) (void*)0 - static void hook_unloader(); static void unhook_functions(); static void hook_jni_env(); -static void reload_native_bridge(const string &nb); +static void post_native_bridge_load(); -namespace { - -enum { - POST_SPECIALIZE, - APP_FORK_AND_SPECIALIZE, - APP_SPECIALIZE, - SERVER_FORK_AND_SPECIALIZE, - DO_REVERT_UNMOUNT, - SKIP_CLOSE_LOG_PIPE, - - FLAG_MAX +struct HookState { + vector> plt_backup; + map, StringCmp> jni_backup; + JNINativeInterface new_env{}; + const JNINativeInterface *old_env = nullptr; + const NativeBridgeRuntimeCallbacks *runtime_callbacks = nullptr; }; -#define MAX_FD_SIZE 1024 - -// Global variables -vector> *plt_hook_list; -map, StringCmp> *jni_hook_list; -bool should_unmap_zygisk = false; - -// Current context +// Global contexts HookContext *g_ctx; -const JNINativeInterface *old_functions = nullptr; -JNINativeInterface *new_functions = nullptr; -const NativeBridgeRuntimeCallbacks *runtime_callbacks = nullptr; - -#define DCL_PRE_POST(name) \ -void name##_pre(); \ -void name##_post(); - -struct HookContext { - JNIEnv *env; - union { - void *ptr; - AppSpecializeArgs_v3 *app; - ServerSpecializeArgs_v1 *server; - } args; - - const char *process; - list modules; - - int pid; - bitset flags; - uint32_t info_flags; - bitset allowed_fds; - vector exempted_fds; - - struct RegisterInfo { - regex_t regex; - string symbol; - void *callback; - void **backup; - }; - - struct IgnoreInfo { - regex_t regex; - string symbol; - }; - - pthread_mutex_t hook_info_lock; - vector register_info; - vector ignore_info; - - HookContext(JNIEnv *env, void *args) : - env(env), args{args}, process(nullptr), pid(-1), info_flags(0), - hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; } - - ~HookContext(); - - void run_modules_pre(const vector &fds); - void run_modules_post(); - DCL_PRE_POST(fork) - DCL_PRE_POST(app_specialize) - DCL_PRE_POST(server_specialize) - DCL_PRE_POST(nativeForkAndSpecialize) - DCL_PRE_POST(nativeSpecializeAppProcess) - DCL_PRE_POST(nativeForkSystemServer) - - void sanitize_fds(); - bool exempt_fd(int fd); - bool is_child() const { return pid <= 0; } - bool can_exempt_fd() const { return flags[APP_FORK_AND_SPECIALIZE] && args.app->fds_to_ignore; } - - // Compatibility shim - void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup); - void plt_hook_exclude(const char *regex, const char *symbol); - void plt_hook_process_regex(); - - bool plt_hook_commit(); -}; - -#undef DCL_PRE_POST +static HookState *g_state; +static bool should_unmap_zygisk = false; // ----------------------------------------------------------------- @@ -184,6 +97,8 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { + delete g_state; + // Because both `pthread_attr_destroy` and `dlclose` have the same function signature, // we can use `musttail` to let the compiler reuse our stack frame and thus // `dlclose` will directly return to the caller of `pthread_attr_destroy`. @@ -191,6 +106,7 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { } } + delete g_state; return res; } @@ -200,7 +116,7 @@ DCL_HOOK_FUNC(int, dlclose, void *handle) { if (!kDone) { ZLOGV("dlclose zygisk_loader\n"); kDone = true; - reload_native_bridge(get_prop(NBPROP)); + post_native_bridge_load(); } [[clang::musttail]] return old_dlclose(handle); } @@ -209,399 +125,9 @@ DCL_HOOK_FUNC(int, dlclose, void *handle) { // ----------------------------------------------------------------- -void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { - jclass clazz; - if (!runtime_callbacks || !env || !clz || !(clazz = env->FindClass(clz))) { - for (auto i = 0; i < numMethods; ++i) { - methods[i].fnPtr = nullptr; - } - return; - } - - // Backup existing methods - auto total = runtime_callbacks->getNativeMethodCount(env, clazz); - vector old_methods(total); - runtime_callbacks->getNativeMethods(env, clazz, old_methods.data(), total); - - // Replace the method - for (auto i = 0; i < numMethods; ++i) { - auto &method = methods[i]; - auto res = env->RegisterNatives(clazz, &method, 1); - // It's normal that the method is not found - if (res == JNI_ERR || env->ExceptionCheck()) { - auto exception = env->ExceptionOccurred(); - if (exception) env->DeleteLocalRef(exception); - env->ExceptionClear(); - method.fnPtr = nullptr; - } else { - // Find the old function pointer and return to caller - for (const auto &old_method : old_methods) { - if (strcmp(method.name, old_method.name) == 0 && - strcmp(method.signature, old_method.signature) == 0) { - ZLOGD("replace %s#%s%s %p -> %p\n", clz, - method.name, method.signature, old_method.fnPtr, method.fnPtr); - method.fnPtr = old_method.fnPtr; - } - } - } - } -} - -ZygiskModule::ZygiskModule(int id, void *handle, void *entry) -: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { - // Make sure all pointers are null - memset(&api, 0, sizeof(api)); - api.base.impl = this; - api.base.registerModule = &ZygiskModule::RegisterModuleImpl; -} - -bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { - if (api == nullptr || module == nullptr) - return false; - - long api_version = *module; - // Unsupported version - if (api_version > ZYGISK_API_VERSION) - return false; - - // Set the actual module_abi* - api->base.impl->mod = { module }; - - // Fill in API accordingly with module API version - if (api_version >= 1) { - api->v1.hookJniNativeMethods = hookJniNativeMethods; - api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) { - if (g_ctx) g_ctx->plt_hook_register(a, b, c, d); - }; - api->v1.pltHookExclude = [](auto a, auto b) { - if (g_ctx) g_ctx->plt_hook_exclude(a, b); - }; - api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); }; - api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); }; - api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); }; - } - if (api_version >= 2) { - api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; - api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; - } - if (api_version >= 4) { - api->v4.pltHookCommit = lsplt::CommitHook; - api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) { - if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr) - return; - lsplt::RegisterHook(dev, inode, symbol, fn, backup); - }; - api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); }; - } - - return true; -} - -void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { - if (regex == nullptr || symbol == nullptr || fn == nullptr) - return; - regex_t re; - if (regcomp(&re, regex, REG_NOSUB) != 0) - return; - mutex_guard lock(hook_info_lock); - register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); -} - -void HookContext::plt_hook_exclude(const char *regex, const char *symbol) { - if (!regex) return; - regex_t re; - if (regcomp(&re, regex, REG_NOSUB) != 0) - return; - mutex_guard lock(hook_info_lock); - ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); -} - -void HookContext::plt_hook_process_regex() { - if (register_info.empty()) - return; - for (auto &map : lsplt::MapInfo::Scan()) { - if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue; - for (auto ®: register_info) { - if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) - continue; - bool ignored = false; - for (auto &ign: ignore_info) { - if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0) - continue; - if (ign.symbol.empty() || ign.symbol == reg.symbol) { - ignored = true; - break; - } - } - if (!ignored) { - lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup); - } - } - } -} - -bool HookContext::plt_hook_commit() { - { - mutex_guard lock(hook_info_lock); - plt_hook_process_regex(); - register_info.clear(); - ignore_info.clear(); - } - return lsplt::CommitHook(); -} - -bool ZygiskModule::valid() const { - if (mod.api_version == nullptr) - return false; - switch (*mod.api_version) { - case 4: - case 3: - case 2: - case 1: - return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize && - mod.v1->preServerSpecialize && mod.v1->postServerSpecialize; - default: - return false; - } -} - -int ZygiskModule::connectCompanion() const { - if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) { - write_int(fd, id); - return fd; - } - return -1; -} - -int ZygiskModule::getModuleDir() const { - if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) { - write_int(fd, id); - int dfd = recv_fd(fd); - close(fd); - return dfd; - } - return -1; -} - -void ZygiskModule::setOption(zygisk::Option opt) { - if (g_ctx == nullptr) - return; - switch (opt) { - case zygisk::FORCE_DENYLIST_UNMOUNT: - g_ctx->flags[DO_REVERT_UNMOUNT] = true; - break; - case zygisk::DLCLOSE_MODULE_LIBRARY: - unload = true; - break; - } -} - -uint32_t ZygiskModule::getFlags() { - return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0; -} - -// ----------------------------------------------------------------- - -int sigmask(int how, int signum) { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, signum); - return sigprocmask(how, &set, nullptr); -} - -void HookContext::fork_pre() { - // Do our own fork before loading any 3rd party code - // First block SIGCHLD, unblock after original fork is done - sigmask(SIG_BLOCK, SIGCHLD); - pid = old_fork(); - - if (!is_child()) - return; - - // Record all open fds - auto dir = xopen_dir("/proc/self/fd"); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - int fd = parse_int(entry->d_name); - if (fd < 0 || fd >= MAX_FD_SIZE) { - close(fd); - continue; - } - allowed_fds[fd] = true; - } - // The dirfd will be closed once out of scope - allowed_fds[dirfd(dir.get())] = false; - // logd_fd should be handled separately - if (int fd = zygisk_get_logd(); fd >= 0) { - allowed_fds[fd] = false; - } -} - -void HookContext::fork_post() { - // Unblock SIGCHLD in case the original method didn't - sigmask(SIG_UNBLOCK, SIGCHLD); -} - -void HookContext::sanitize_fds() { - zygisk_close_logd(); - - if (!is_child()) { - return; - } - - if (can_exempt_fd() && !exempted_fds.empty()) { - auto update_fd_array = [&](int old_len) -> jintArray { - jintArray array = env->NewIntArray(static_cast(old_len + exempted_fds.size())); - if (array == nullptr) - return nullptr; - - env->SetIntArrayRegion( - array, old_len, static_cast(exempted_fds.size()), exempted_fds.data()); - for (int fd : exempted_fds) { - if (fd >= 0 && fd < MAX_FD_SIZE) { - allowed_fds[fd] = true; - } - } - *args.app->fds_to_ignore = array; - return array; - }; - - if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { - int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr); - int len = env->GetArrayLength(fdsToIgnore); - for (int i = 0; i < len; ++i) { - int fd = arr[i]; - if (fd >= 0 && fd < MAX_FD_SIZE) { - allowed_fds[fd] = true; - } - } - if (jintArray newFdList = update_fd_array(len)) { - env->SetIntArrayRegion(newFdList, 0, len, arr); - } - env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT); - } else { - update_fd_array(0); - } - } - - // Close all forbidden fds to prevent crashing - auto dir = xopen_dir("/proc/self/fd"); - int dfd = dirfd(dir.get()); - for (dirent *entry; (entry = xreaddir(dir.get()));) { - int fd = parse_int(entry->d_name); - if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) { - close(fd); - } - } -} - -void HookContext::run_modules_pre(const vector &fds) { - for (int i = 0; i < fds.size(); ++i) { - struct stat s{}; - if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) { - close(fds[i]); - continue; - } - android_dlextinfo info { - .flags = ANDROID_DLEXT_USE_LIBRARY_FD, - .library_fd = fds[i], - }; - if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { - if (void *e = dlsym(h, "zygisk_module_entry")) { - modules.emplace_back(i, h, e); - } - } else if (g_ctx->flags[SERVER_FORK_AND_SPECIALIZE]) { - ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror()); - } - close(fds[i]); - } - - for (auto it = modules.begin(); it != modules.end();) { - it->onLoad(env); - if (it->valid()) { - ++it; - } else { - it = modules.erase(it); - } - } - - for (auto &m : modules) { - if (flags[APP_SPECIALIZE]) { - m.preAppSpecialize(args.app); - } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { - m.preServerSpecialize(args.server); - } - } -} - -void HookContext::run_modules_post() { - flags[POST_SPECIALIZE] = true; - for (const auto &m : modules) { - if (flags[APP_SPECIALIZE]) { - m.postAppSpecialize(args.app); - } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { - m.postServerSpecialize(args.server); - } - m.tryUnload(); - } -} - -void HookContext::app_specialize_pre() { - flags[APP_SPECIALIZE] = true; - - vector module_fds; - int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds); - if (args.app->app_data_dir) { - const auto *app_data_dir = env->GetStringUTFChars(args.app->app_data_dir, nullptr); - if (std::string_view(app_data_dir).ends_with("/com.android.systemui")) { - info_flags |= PROCESS_IS_SYS_UI; - } - env->ReleaseStringUTFChars(args.app->app_data_dir, app_data_dir); - } - if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { - ZLOGI("[%s] is on the denylist\n", process); - flags[DO_REVERT_UNMOUNT] = true; - } else if (fd >= 0) { - run_modules_pre(module_fds); - } - close(fd); -} - -void HookContext::app_specialize_post() { - run_modules_post(); - if (info_flags & PROCESS_IS_MAGISK_APP) { - setenv("ZYGISK_ENABLED", "1", 1); - } - - // Cleanups - env->ReleaseStringUTFChars(args.app->nice_name, process); -} - -void HookContext::server_specialize_pre() { - vector module_fds; - int fd = remote_get_info(1000, "system_server", &info_flags, module_fds); - if (fd >= 0) { - if (module_fds.empty()) { - write_int(fd, 0); - } else { - run_modules_pre(module_fds); - - // Send the bitset of module status back to magiskd from system_server - dynamic_bitset bits; - for (const auto &m : modules) - bits[m.getId()] = true; - write_int(fd, static_cast(bits.slots())); - for (int i = 0; i < bits.slots(); ++i) { - auto l = bits.get_slot(i); - xwrite(fd, &l, sizeof(l)); - } - } - close(fd); - } -} - -void HookContext::server_specialize_post() { - run_modules_post(); -} +HookContext::HookContext(JNIEnv *env, void *args) : + env(env), args{args}, process(nullptr), pid(-1), info_flags(0), + hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; } HookContext::~HookContext() { // This global pointer points to a variable on the stack. @@ -618,7 +144,7 @@ HookContext::~HookContext() { should_unmap_zygisk = true; // Unhook JNI methods - for (const auto &[clz, methods] : *jni_hook_list) { + for (const auto &[clz, methods] : g_state->jni_backup) { if (!methods.empty() && env->RegisterNatives( env->FindClass(clz.data()), methods.data(), static_cast(methods.size())) != 0) { @@ -626,8 +152,6 @@ HookContext::~HookContext() { should_unmap_zygisk = false; } } - delete jni_hook_list; - jni_hook_list = nullptr; // Strip out all API function pointers for (auto &m : modules) { @@ -637,71 +161,6 @@ HookContext::~HookContext() { hook_unloader(); } -bool HookContext::exempt_fd(int fd) { - if (flags[POST_SPECIALIZE] || flags[SKIP_CLOSE_LOG_PIPE]) - return true; - if (!can_exempt_fd()) - return false; - exempted_fds.push_back(fd); - return true; -} - -// ----------------------------------------------------------------- - -void HookContext::nativeSpecializeAppProcess_pre() { - process = env->GetStringUTFChars(args.app->nice_name, nullptr); - ZLOGV("pre specialize [%s]\n", process); - // App specialize does not check FD - flags[SKIP_CLOSE_LOG_PIPE] = true; - app_specialize_pre(); -} - -void HookContext::nativeSpecializeAppProcess_post() { - ZLOGV("post specialize [%s]\n", process); - app_specialize_post(); -} - -void HookContext::nativeForkSystemServer_pre() { - ZLOGV("pre forkSystemServer\n"); - flags[SERVER_FORK_AND_SPECIALIZE] = true; - - fork_pre(); - if (is_child()) { - server_specialize_pre(); - } - sanitize_fds(); -} - -void HookContext::nativeForkSystemServer_post() { - if (is_child()) { - ZLOGV("post forkSystemServer\n"); - server_specialize_post(); - } - fork_post(); -} - -void HookContext::nativeForkAndSpecialize_pre() { - process = env->GetStringUTFChars(args.app->nice_name, nullptr); - ZLOGV("pre forkAndSpecialize [%s]\n", process); - flags[APP_FORK_AND_SPECIALIZE] = true; - - fork_pre(); - if (is_child()) { - app_specialize_pre(); - } - sanitize_fds(); -} - -void HookContext::nativeForkAndSpecialize_post() { - if (is_child()) { - ZLOGV("post forkAndSpecialize [%s]\n", process); - app_specialize_post(); - } - fork_post(); -} - -} // namespace - // ----------------------------------------------------------------- inline void *unwind_get_region_start(_Unwind_Context *ctx) { @@ -778,8 +237,8 @@ static const NativeBridgeRuntimeCallbacks* find_runtime_callbacks(struct _Unwind return nullptr; } -static void reload_native_bridge(const string &nb) { - // Use unwind to find the address of android::LoadNativeBridge and NativeBridgeRuntimeCallbacks +static void post_native_bridge_load() { + // Unwind to find the address of android::LoadNativeBridge and NativeBridgeRuntimeCallbacks bool (*load_native_bridge)(const char *nb_library_filename, const NativeBridgeRuntimeCallbacks *runtime_cbs) = nullptr; _Unwind_Backtrace(+[](struct _Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code { @@ -789,15 +248,18 @@ static void reload_native_bridge(const string &nb) { ZLOGV("backtrace: %p %s\n", fp, info.dli_fname ? info.dli_fname : "???"); if (info.dli_fname && std::string_view(info.dli_fname).ends_with("/libnativebridge.so")) { *reinterpret_cast(arg) = fp; - runtime_callbacks = find_runtime_callbacks(ctx); - ZLOGD("cbs: %p\n", runtime_callbacks); + g_state->runtime_callbacks = find_runtime_callbacks(ctx); + ZLOGV("NativeBridgeRuntimeCallbacks: %p\n", g_state->runtime_callbacks); return _URC_END_OF_STACK; } return _URC_NO_REASON; }, &load_native_bridge); + + // Reload the real native bridge if necessary + auto nb = get_prop(NBPROP); auto len = sizeof(ZYGISKLDR) - 1; if (nb.size() > len) { - load_native_bridge(nb.data() + len, runtime_callbacks); + load_native_bridge(nb.data() + len, g_state->runtime_callbacks); } } @@ -806,7 +268,7 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_ ZLOGE("Failed to register plt_hook \"%s\"\n", symbol); return; } - plt_hook_list->emplace_back(dev, inode, symbol, old_func); + g_state->plt_backup.emplace_back(dev, inode, symbol, old_func); } static void hook_commit() { @@ -822,8 +284,7 @@ static void hook_commit() { PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) void hook_functions() { - default_new(plt_hook_list); - default_new(jni_hook_list); + default_new(g_state); ino_t android_runtime_inode = 0; dev_t android_runtime_dev = 0; @@ -849,10 +310,10 @@ void hook_functions() { hook_commit(); // Remove unhooked methods - plt_hook_list->erase( - std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), + g_state->plt_backup.erase( + std::remove_if(g_state->plt_backup.begin(), g_state->plt_backup.end(), [](auto &t) { return *std::get<3>(t) == nullptr;}), - plt_hook_list->end()); + g_state->plt_backup.end()); } static void hook_unloader() { @@ -873,14 +334,12 @@ static void hook_unloader() { static void unhook_functions() { // Unhook plt_hook - for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) { + for (const auto &[dev, inode, sym, old_func] : g_state->plt_backup) { if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) { ZLOGE("Failed to register plt_hook [%s]\n", sym); should_unmap_zygisk = false; } } - delete plt_hook_list; - plt_hook_list = nullptr; if (!lsplt::CommitHook()) { ZLOGE("Failed to restore plt_hook\n"); should_unmap_zygisk = false; @@ -889,8 +348,43 @@ static void unhook_functions() { // ----------------------------------------------------------------- -// JNI method hook definitions, auto generated -#include "jni_hooks.hpp" +void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { + jclass clazz; + if (!g_state || !g_state->runtime_callbacks || !env || !clz || !(clazz = env->FindClass(clz))) { + for (auto i = 0; i < numMethods; ++i) { + methods[i].fnPtr = nullptr; + } + return; + } + + // Backup existing methods + auto total = g_state->runtime_callbacks->getNativeMethodCount(env, clazz); + vector old_methods(total); + g_state->runtime_callbacks->getNativeMethods(env, clazz, old_methods.data(), total); + + // Replace the method + for (auto i = 0; i < numMethods; ++i) { + auto &method = methods[i]; + auto res = env->RegisterNatives(clazz, &method, 1); + // It's normal that the method is not found + if (res == JNI_ERR || env->ExceptionCheck()) { + auto exception = env->ExceptionOccurred(); + if (exception) env->DeleteLocalRef(exception); + env->ExceptionClear(); + method.fnPtr = nullptr; + } else { + // Find the old function pointer and return to caller + for (const auto &old_method : old_methods) { + if (strcmp(method.name, old_method.name) == 0 && + strcmp(method.signature, old_method.signature) == 0) { + ZLOGD("replace %s#%s%s %p -> %p\n", clz, + method.name, method.signature, old_method.fnPtr, method.fnPtr); + method.fnPtr = old_method.fnPtr; + } + } + } + } +} static string get_class_name(JNIEnv *env, jclass clazz) { static auto class_getName = env->GetMethodID( @@ -904,14 +398,15 @@ static string get_class_name(JNIEnv *env, jclass clazz) { } static void replace_jni_methods( - vector &methods, - JNINativeMethod *hook_methods, size_t hook_methods_size, + vector &methods, vector &backup, + const JNINativeMethod *hook_methods, size_t hook_methods_size, void **orig_function) { for (auto &method : methods) { if (strcmp(method.name, hook_methods[0].name) == 0) { for (auto i = 0; i < hook_methods_size; ++i) { const auto &hook = hook_methods[i]; if (strcmp(method.signature, hook.signature) == 0) { + backup.push_back(method); *orig_function = method.fnPtr; method.fnPtr = hook.fnPtr; ZLOGI("replace %s\n", method.name); @@ -924,24 +419,23 @@ static void replace_jni_methods( } #define HOOK_JNI(method) \ -replace_jni_methods(newMethods, method##_methods.data(), method##_methods.size(), &method##_orig) +replace_jni_methods(newMethods, backup, method##_methods.data(), method##_methods.size(), &method##_orig) static jint env_RegisterNatives( JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { auto className = get_class_name(env, clazz); if (className == "com/android/internal/os/Zygote") { // Restore JNIEnv as we no longer need to replace anything - env->functions = old_functions; - delete new_functions; - new_functions = nullptr; + env->functions = g_state->old_env; vector newMethods(methods, methods + numMethods); + vector &backup = g_state->jni_backup[className]; HOOK_JNI(nativeForkAndSpecialize); HOOK_JNI(nativeSpecializeAppProcess); HOOK_JNI(nativeForkSystemServer); - return old_functions->RegisterNatives(env, clazz, newMethods.data(), numMethods); + return g_state->old_env->RegisterNatives(env, clazz, newMethods.data(), numMethods); } else { - return old_functions->RegisterNatives(env, clazz, methods, numMethods); + return g_state->old_env->RegisterNatives(env, clazz, methods, numMethods); } } @@ -982,9 +476,8 @@ static void hook_jni_env() { } // Replace the function table in JNIEnv to hook RegisterNatives - default_new(new_functions); - memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &env_RegisterNatives; - old_functions = env->functions; - env->functions = new_functions; + memcpy(&g_state->new_env, env->functions, sizeof(*env->functions)); + g_state->new_env.RegisterNatives = &env_RegisterNatives; + g_state->old_env = env->functions; + env->functions = &g_state->new_env; } diff --git a/native/src/core/zygisk/module.cpp b/native/src/core/zygisk/module.cpp new file mode 100644 index 000000000..ba54186be --- /dev/null +++ b/native/src/core/zygisk/module.cpp @@ -0,0 +1,468 @@ +#include +#include + +#include + +#include + +#include "zygisk.hpp" +#include "module.hpp" + +using namespace std; + +ZygiskModule::ZygiskModule(int id, void *handle, void *entry) + : id(id), handle(handle), entry{entry}, api{}, mod{nullptr} { + // Make sure all pointers are null + memset(&api, 0, sizeof(api)); + api.base.impl = this; + api.base.registerModule = &ZygiskModule::RegisterModuleImpl; +} + +bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { + if (api == nullptr || module == nullptr) + return false; + + long api_version = *module; + // Unsupported version + if (api_version > ZYGISK_API_VERSION) + return false; + + // Set the actual module_abi* + api->base.impl->mod = { module }; + + // Fill in API accordingly with module API version + if (api_version >= 1) { + api->v1.hookJniNativeMethods = hookJniNativeMethods; + api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) { + if (g_ctx) g_ctx->plt_hook_register(a, b, c, d); + }; + api->v1.pltHookExclude = [](auto a, auto b) { + if (g_ctx) g_ctx->plt_hook_exclude(a, b); + }; + api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); }; + api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); }; + api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); }; + } + if (api_version >= 2) { + api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; + api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; + } + if (api_version >= 4) { + api->v4.pltHookCommit = lsplt::CommitHook; + api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) { + if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr) + return; + lsplt::RegisterHook(dev, inode, symbol, fn, backup); + }; + api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); }; + } + + return true; +} + +bool ZygiskModule::valid() const { + if (mod.api_version == nullptr) + return false; + switch (*mod.api_version) { + case 4: + case 3: + case 2: + case 1: + return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize && + mod.v1->preServerSpecialize && mod.v1->postServerSpecialize; + default: + return false; + } +} + +int ZygiskModule::connectCompanion() const { + if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) { + write_int(fd, id); + return fd; + } + return -1; +} + +int ZygiskModule::getModuleDir() const { + if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) { + write_int(fd, id); + int dfd = recv_fd(fd); + close(fd); + return dfd; + } + return -1; +} + +void ZygiskModule::setOption(zygisk::Option opt) { + if (g_ctx == nullptr) + return; + switch (opt) { + case zygisk::FORCE_DENYLIST_UNMOUNT: + g_ctx->flags[DO_REVERT_UNMOUNT] = true; + break; + case zygisk::DLCLOSE_MODULE_LIBRARY: + unload = true; + break; + } +} + +uint32_t ZygiskModule::getFlags() { + return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0; +} + +void ZygiskModule::tryUnload() const { + if (unload) dlclose(handle); +} + +// ----------------------------------------------------------------- + +#define call_app(method) \ +switch (*mod.api_version) { \ +case 1: \ +case 2: { \ + AppSpecializeArgs_v1 a(args); \ + mod.v1->method(mod.v1->impl, &a); \ + break; \ +} \ +case 3: \ +case 4: \ + mod.v1->method(mod.v1->impl, args);\ + break; \ +} + +void ZygiskModule::preAppSpecialize(AppSpecializeArgs_v3 *args) const { + call_app(preAppSpecialize) +} + +void ZygiskModule::postAppSpecialize(const AppSpecializeArgs_v3 *args) const { + call_app(postAppSpecialize) +} + +void ZygiskModule::preServerSpecialize(ServerSpecializeArgs_v1 *args) const { + mod.v1->preServerSpecialize(mod.v1->impl, args); +} + +void ZygiskModule::postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { + mod.v1->postServerSpecialize(mod.v1->impl, args); +} + +// ----------------------------------------------------------------- + +void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) { + if (regex == nullptr || symbol == nullptr || fn == nullptr) + return; + regex_t re; + if (regcomp(&re, regex, REG_NOSUB) != 0) + return; + mutex_guard lock(hook_info_lock); + register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); +} + +void HookContext::plt_hook_exclude(const char *regex, const char *symbol) { + if (!regex) return; + regex_t re; + if (regcomp(&re, regex, REG_NOSUB) != 0) + return; + mutex_guard lock(hook_info_lock); + ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); +} + +void HookContext::plt_hook_process_regex() { + if (register_info.empty()) + return; + for (auto &map : lsplt::MapInfo::Scan()) { + if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue; + for (auto ®: register_info) { + if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) + continue; + bool ignored = false; + for (auto &ign: ignore_info) { + if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0) + continue; + if (ign.symbol.empty() || ign.symbol == reg.symbol) { + ignored = true; + break; + } + } + if (!ignored) { + lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup); + } + } + } +} + +bool HookContext::plt_hook_commit() { + { + mutex_guard lock(hook_info_lock); + plt_hook_process_regex(); + register_info.clear(); + ignore_info.clear(); + } + return lsplt::CommitHook(); +} + +// ----------------------------------------------------------------- + +static int sigmask(int how, int signum) { + sigset_t set; + sigemptyset(&set); + sigaddset(&set, signum); + return sigprocmask(how, &set, nullptr); +} + +void HookContext::fork_pre() { + // Do our own fork before loading any 3rd party code + // First block SIGCHLD, unblock after original fork is done + sigmask(SIG_BLOCK, SIGCHLD); + pid = old_fork(); + + if (!is_child()) + return; + + // Record all open fds + auto dir = xopen_dir("/proc/self/fd"); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + int fd = parse_int(entry->d_name); + if (fd < 0 || fd >= MAX_FD_SIZE) { + close(fd); + continue; + } + allowed_fds[fd] = true; + } + // The dirfd will be closed once out of scope + allowed_fds[dirfd(dir.get())] = false; + // logd_fd should be handled separately + if (int fd = zygisk_get_logd(); fd >= 0) { + allowed_fds[fd] = false; + } +} + +void HookContext::fork_post() { + // Unblock SIGCHLD in case the original method didn't + sigmask(SIG_UNBLOCK, SIGCHLD); +} + +void HookContext::sanitize_fds() { + zygisk_close_logd(); + + if (!is_child()) { + return; + } + + if (can_exempt_fd() && !exempted_fds.empty()) { + auto update_fd_array = [&](int old_len) -> jintArray { + jintArray array = env->NewIntArray(static_cast(old_len + exempted_fds.size())); + if (array == nullptr) + return nullptr; + + env->SetIntArrayRegion( + array, old_len, static_cast(exempted_fds.size()), exempted_fds.data()); + for (int fd : exempted_fds) { + if (fd >= 0 && fd < MAX_FD_SIZE) { + allowed_fds[fd] = true; + } + } + *args.app->fds_to_ignore = array; + return array; + }; + + if (jintArray fdsToIgnore = *args.app->fds_to_ignore) { + int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr); + int len = env->GetArrayLength(fdsToIgnore); + for (int i = 0; i < len; ++i) { + int fd = arr[i]; + if (fd >= 0 && fd < MAX_FD_SIZE) { + allowed_fds[fd] = true; + } + } + if (jintArray newFdList = update_fd_array(len)) { + env->SetIntArrayRegion(newFdList, 0, len, arr); + } + env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT); + } else { + update_fd_array(0); + } + } + + // Close all forbidden fds to prevent crashing + auto dir = xopen_dir("/proc/self/fd"); + int dfd = dirfd(dir.get()); + for (dirent *entry; (entry = xreaddir(dir.get()));) { + int fd = parse_int(entry->d_name); + if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) { + close(fd); + } + } +} + +bool HookContext::exempt_fd(int fd) { + if (flags[POST_SPECIALIZE] || flags[SKIP_CLOSE_LOG_PIPE]) + return true; + if (!can_exempt_fd()) + return false; + exempted_fds.push_back(fd); + return true; +} + +void HookContext::run_modules_pre(const vector &fds) { + for (int i = 0; i < fds.size(); ++i) { + struct stat s{}; + if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) { + close(fds[i]); + continue; + } + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fds[i], + }; + if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { + if (void *e = dlsym(h, "zygisk_module_entry")) { + modules.emplace_back(i, h, e); + } + } else if (g_ctx->flags[SERVER_FORK_AND_SPECIALIZE]) { + ZLOGW("Failed to dlopen zygisk module: %s\n", dlerror()); + } + close(fds[i]); + } + + for (auto it = modules.begin(); it != modules.end();) { + it->onLoad(env); + if (it->valid()) { + ++it; + } else { + it = modules.erase(it); + } + } + + for (auto &m : modules) { + if (flags[APP_SPECIALIZE]) { + m.preAppSpecialize(args.app); + } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { + m.preServerSpecialize(args.server); + } + } +} + +void HookContext::run_modules_post() { + flags[POST_SPECIALIZE] = true; + for (const auto &m : modules) { + if (flags[APP_SPECIALIZE]) { + m.postAppSpecialize(args.app); + } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { + m.postServerSpecialize(args.server); + } + m.tryUnload(); + } +} + +void HookContext::app_specialize_pre() { + flags[APP_SPECIALIZE] = true; + + vector module_fds; + int fd = remote_get_info(args.app->uid, process, &info_flags, module_fds); + if (args.app->app_data_dir) { + const auto *app_data_dir = env->GetStringUTFChars(args.app->app_data_dir, nullptr); + if (std::string_view(app_data_dir).ends_with("/com.android.systemui")) { + info_flags |= PROCESS_IS_SYS_UI; + } + env->ReleaseStringUTFChars(args.app->app_data_dir, app_data_dir); + } + if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { + ZLOGI("[%s] is on the denylist\n", process); + flags[DO_REVERT_UNMOUNT] = true; + } else if (fd >= 0) { + run_modules_pre(module_fds); + } + close(fd); +} + +void HookContext::app_specialize_post() { + run_modules_post(); + if (info_flags & PROCESS_IS_MAGISK_APP) { + setenv("ZYGISK_ENABLED", "1", 1); + } + + // Cleanups + env->ReleaseStringUTFChars(args.app->nice_name, process); +} + +void HookContext::server_specialize_pre() { + vector module_fds; + int fd = remote_get_info(1000, "system_server", &info_flags, module_fds); + if (fd >= 0) { + if (module_fds.empty()) { + write_int(fd, 0); + } else { + run_modules_pre(module_fds); + + // Send the bitset of module status back to magiskd from system_server + dynamic_bitset bits; + for (const auto &m : modules) + bits[m.getId()] = true; + write_int(fd, static_cast(bits.slots())); + for (int i = 0; i < bits.slots(); ++i) { + auto l = bits.get_slot(i); + xwrite(fd, &l, sizeof(l)); + } + } + close(fd); + } +} + +void HookContext::server_specialize_post() { + run_modules_post(); +} + +// ----------------------------------------------------------------- + +void HookContext::nativeSpecializeAppProcess_pre() { + process = env->GetStringUTFChars(args.app->nice_name, nullptr); + ZLOGV("pre specialize [%s]\n", process); + // App specialize does not check FD + flags[SKIP_CLOSE_LOG_PIPE] = true; + app_specialize_pre(); +} + +void HookContext::nativeSpecializeAppProcess_post() { + ZLOGV("post specialize [%s]\n", process); + app_specialize_post(); +} + +void HookContext::nativeForkSystemServer_pre() { + ZLOGV("pre forkSystemServer\n"); + flags[SERVER_FORK_AND_SPECIALIZE] = true; + + fork_pre(); + if (is_child()) { + server_specialize_pre(); + } + sanitize_fds(); +} + +void HookContext::nativeForkSystemServer_post() { + if (is_child()) { + ZLOGV("post forkSystemServer\n"); + server_specialize_post(); + } + fork_post(); +} + +void HookContext::nativeForkAndSpecialize_pre() { + process = env->GetStringUTFChars(args.app->nice_name, nullptr); + ZLOGV("pre forkAndSpecialize [%s]\n", process); + flags[APP_FORK_AND_SPECIALIZE] = true; + + fork_pre(); + if (is_child()) { + app_specialize_pre(); + } + sanitize_fds(); +} + +void HookContext::nativeForkAndSpecialize_post() { + if (is_child()) { + ZLOGV("post forkAndSpecialize [%s]\n", process); + app_specialize_post(); + } + fork_post(); +} diff --git a/native/src/core/zygisk/module.hpp b/native/src/core/zygisk/module.hpp index fef9fc141..977d77381 100644 --- a/native/src/core/zygisk/module.hpp +++ b/native/src/core/zygisk/module.hpp @@ -1,8 +1,10 @@ #pragma once -#include "api.hpp" +#include +#include +#include -namespace { +#include "api.hpp" struct HookContext; struct ZygiskModule; @@ -155,44 +157,23 @@ union ApiTable { api_abi_v4 v4; }; -#define call_app(method) \ -switch (*mod.api_version) { \ -case 1: \ -case 2: { \ - AppSpecializeArgs_v1 a(args); \ - mod.v1->method(mod.v1->impl, &a); \ - break; \ -} \ -case 3: \ -case 4: \ - mod.v1->method(mod.v1->impl, args);\ - break; \ -} - struct ZygiskModule { void onLoad(void *env) { entry.fn(&api, env); } - void preAppSpecialize(AppSpecializeArgs_v3 *args) const { - call_app(preAppSpecialize) - } - void postAppSpecialize(const AppSpecializeArgs_v3 *args) const { - call_app(postAppSpecialize) - } - void preServerSpecialize(ServerSpecializeArgs_v1 *args) const { - mod.v1->preServerSpecialize(mod.v1->impl, args); - } - void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { - mod.v1->postServerSpecialize(mod.v1->impl, args); - } + + void preAppSpecialize(AppSpecializeArgs_v3 *args) const; + void postAppSpecialize(const AppSpecializeArgs_v3 *args) const; + void preServerSpecialize(ServerSpecializeArgs_v1 *args) const; + void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const; bool valid() const; int connectCompanion() const; int getModuleDir() const; void setOption(zygisk::Option opt); static uint32_t getFlags(); - void tryUnload() const { if (unload) dlclose(handle); } + void tryUnload() const; void clearApi() { memset(&api, 0, sizeof(api)); } int getId() const { return id; } @@ -218,4 +199,82 @@ private: } mod; }; -} // namespace +extern HookContext *g_ctx; +extern int (*old_fork)(void); + +enum { + POST_SPECIALIZE, + APP_FORK_AND_SPECIALIZE, + APP_SPECIALIZE, + SERVER_FORK_AND_SPECIALIZE, + DO_REVERT_UNMOUNT, + SKIP_CLOSE_LOG_PIPE, + + FLAG_MAX +}; + +#define MAX_FD_SIZE 1024 + +#define DCL_PRE_POST(name) \ +void name##_pre(); \ +void name##_post(); + +struct HookContext { + JNIEnv *env; + union { + void *ptr; + AppSpecializeArgs_v3 *app; + ServerSpecializeArgs_v1 *server; + } args; + + const char *process; + std::list modules; + + int pid; + std::bitset flags; + uint32_t info_flags; + std::bitset allowed_fds; + std::vector exempted_fds; + + struct RegisterInfo { + regex_t regex; + std::string symbol; + void *callback; + void **backup; + }; + + struct IgnoreInfo { + regex_t regex; + std::string symbol; + }; + + pthread_mutex_t hook_info_lock; + std::vector register_info; + std::vector ignore_info; + + HookContext(JNIEnv *env, void *args); + ~HookContext(); + + void run_modules_pre(const std::vector &fds); + void run_modules_post(); + DCL_PRE_POST(fork) + DCL_PRE_POST(app_specialize) + DCL_PRE_POST(server_specialize) + DCL_PRE_POST(nativeForkAndSpecialize) + DCL_PRE_POST(nativeSpecializeAppProcess) + DCL_PRE_POST(nativeForkSystemServer) + + void sanitize_fds(); + bool exempt_fd(int fd); + bool is_child() const { return pid <= 0; } + bool can_exempt_fd() const { return flags[APP_FORK_AND_SPECIALIZE] && args.app->fds_to_ignore; } + + // Compatibility shim + void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup); + void plt_hook_exclude(const char *regex, const char *symbol); + void plt_hook_process_regex(); + + bool plt_hook_commit(); +}; + +#undef DCL_PRE_POST diff --git a/native/src/core/zygisk/native_bridge.cpp b/native/src/core/zygisk/native_bridge.cpp deleted file mode 100644 index dd83fc087..000000000 --- a/native/src/core/zygisk/native_bridge.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include -#include -#include - -#include -#include - -#include "zygisk.hpp" - -static bool is_compatible_with(uint32_t) { - auto name = get_prop(NBPROP); - android_dlextinfo info = { - .flags = ANDROID_DLEXT_FORCE_LOAD - }; - void *handle = android_dlopen_ext(name.data(), RTLD_LAZY, &info); - if (handle) { - auto entry = reinterpret_cast(dlsym(handle, "zygisk_inject_entry")); - if (entry) { - entry(handle); - } - } - return false; -} - -extern "C" [[maybe_unused]] NativeBridgeCallbacks NativeBridgeItf{ - .version = 2, - .padding = {}, - .isCompatibleWith = &is_compatible_with, -}; diff --git a/native/src/core/zygisk/zygisk.hpp b/native/src/core/zygisk/zygisk.hpp index fe1247134..5167935a6 100644 --- a/native/src/core/zygisk/zygisk.hpp +++ b/native/src/core/zygisk/zygisk.hpp @@ -27,9 +27,15 @@ enum : int { #define ZLOGW(...) LOGW("zygisk32: " __VA_ARGS__) #endif +// Extreme verbose logging +//#define ZLOGV(...) ZLOGD(__VA_ARGS__) +#define ZLOGV(...) (void*)0 + extern void *self_handle; void hook_functions(); +void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods); + int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector &fds); inline int zygisk_request(int req) {