#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dl.h" #include "daemon.h" #include "zygisk.hpp" #include "module.hpp" #include "files.hpp" #include "misc.hpp" #include "solist.hpp" #include "art_method.hpp" using namespace std; static void hook_unloader(); static void unhook_functions(); namespace { enum { POST_SPECIALIZE, APP_FORK_AND_SPECIALIZE, APP_SPECIALIZE, SERVER_FORK_AND_SPECIALIZE, DO_REVERT_UNMOUNT, SKIP_FD_SANITIZATION, FLAG_MAX }; #define DCL_PRE_POST(name) \ void name##_pre(); \ void name##_post(); #define MAX_FD_SIZE 1024 struct ZygiskContext; // Current context ZygiskContext *g_ctx; struct ZygiskContext { JNIEnv *env; union { void *ptr; AppSpecializeArgs_v5 *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; ZygiskContext(JNIEnv *env, void *args) : env(env), args{args}, process(nullptr), pid(-1), info_flags(0), hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; } ~ZygiskContext(); /* Zygisksu changed: Load module fds */ void run_modules_pre(); void run_modules_post(); DCL_PRE_POST(fork) DCL_PRE_POST(app_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; } // 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 // Global variables vector> *plt_hook_list; map, StringCmp> *jni_hook_list; bool should_unmap_zygisk = false; } // namespace namespace { #define DCL_HOOK_FUNC(ret, func, ...) \ ret (*old_##func)(__VA_ARGS__); \ ret new_##func(__VA_ARGS__) // Skip actual fork and return cached result if applicable DCL_HOOK_FUNC(int, fork) { return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); } // Unmount stuffs in the process's private mount namespace DCL_HOOK_FUNC(int, unshare, int flags) { int res = old_unshare(flags); if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0 && // For some unknown reason, unmounting app_process in SysUI can break. // This is reproducible on the official AVD running API 26 and 27. // Simply avoid doing any unmounts for SysUI to avoid potential issues. (g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) { if (g_ctx->flags[DO_REVERT_UNMOUNT]) { if (g_ctx->info_flags & PROCESS_ROOT_IS_KSU) { revert_unmount_ksu(); } else if (g_ctx->info_flags & PROCESS_ROOT_IS_APATCH){ revert_unmount_apatch(); } else if (g_ctx->info_flags & PROCESS_ROOT_IS_MAGISK) { revert_unmount_magisk(); } } /* Zygisksu changed: No umount app_process */ // Restore errno back to 0 errno = 0; } return res; } // Close logd_fd if necessary to prevent crashing // For more info, check comments in zygisk_log_write DCL_HOOK_FUNC(void, android_log_close) { if (g_ctx == nullptr) { // Happens during un-managed fork like nativeForkApp, nativeForkUsap logging::setfd(-1); } else if (!g_ctx->flags[SKIP_FD_SANITIZATION]) { logging::setfd(-1); } old_android_log_close(); } // We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns, // it will return to our code which has been unmapped, causing segmentation fault. // Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start. DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { int res = old_pthread_attr_destroy((pthread_attr_t *)target); // Only perform unloading on the main thread if (gettid() != getpid()) return res; LOGV("pthread_attr_destroy\n"); if (should_unmap_zygisk) { unhook_functions(); if (should_unmap_zygisk) { // 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`. [[clang::musttail]] return dlclose(self_handle); } } return res; } void initialize_jni_hook(); DCL_HOOK_FUNC(char *, strdup, const char *s) { if (strcmp(s, "com.android.internal.os.ZygoteInit") == 0) { LOGV("strdup %s\n", s); initialize_jni_hook(); } return old_strdup(s); } #undef DCL_HOOK_FUNC // ----------------------------------------------------------------- static bool can_hook_jni = false; static jint MODIFIER_NATIVE = 0; static jmethodID member_getModifiers = nullptr; void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { if (!can_hook_jni) return; auto clazz = env->FindClass(clz); if (clazz == nullptr) { env->ExceptionClear(); for (int i = 0; i < numMethods; i++) { methods[i].fnPtr = nullptr; } return; } vector hooks; for (int i = 0; i < numMethods; i++) { auto &nm = methods[i]; auto mid = env->GetMethodID(clazz, nm.name, nm.signature); bool is_static = false; if (mid == nullptr) { env->ExceptionClear(); mid = env->GetStaticMethodID(clazz, nm.name, nm.signature); is_static = true; } if (mid == nullptr) { env->ExceptionClear(); nm.fnPtr = nullptr; continue; } auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static); auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers); if ((modifier & MODIFIER_NATIVE) == 0) { nm.fnPtr = nullptr; continue; } auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method); hooks.push_back(nm); auto orig = artMethod->GetData(); LOGV("replaced %s %s orig %p", clz, nm.name, orig); nm.fnPtr = orig; } if (hooks.empty()) return; env->RegisterNatives(clazz, hooks.data(), hooks.size()); } // JNI method hook definitions, auto generated #include "jni_hooks.hpp" void initialize_jni_hook() { auto get_created_java_vms = reinterpret_cast( dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); if (!get_created_java_vms) { for (auto &map: lsplt::MapInfo::Scan()) { if (!map.path.ends_with("/libnativehelper.so")) continue; void *h = dlopen(map.path.data(), RTLD_LAZY); if (!h) { LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror()); break; } get_created_java_vms = reinterpret_cast(dlsym(h, "JNI_GetCreatedJavaVMs")); dlclose(h); break; } if (!get_created_java_vms) { LOGW("JNI_GetCreatedJavaVMs not found\n"); return; } } JavaVM *vm = nullptr; jsize num = 0; jint res = get_created_java_vms(&vm, 1, &num); if (res != JNI_OK || vm == nullptr) return; JNIEnv *env = nullptr; res = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (res != JNI_OK || env == nullptr) return; auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member"); if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I"); auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier"); if (classModifier != nullptr) { auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I"); if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId); } if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return; if (!lsplant::art::ArtMethod::Init(env)) { LOGE("failed to init ArtMethod"); return; } can_hook_jni = true; do_hook_zygote(env); } // ----------------------------------------------------------------- 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 ZygiskContext::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 ZygiskContext::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 ZygiskContext::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 ZygiskContext::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; } } /* Zygisksu changed: Use own zygiskd */ int ZygiskModule::connectCompanion() const { return zygiskd::ConnectCompanion(id); } /* Zygisksu changed: Use own zygiskd */ int ZygiskModule::getModuleDir() const { return zygiskd::GetModuleDir(id); } 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 ZygiskContext::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 (pid != 0 || flags[SKIP_FD_SANITIZATION]) return; // Record all open fds auto dir = xopen_dir("/proc/self/fd"); for (dirent *entry; (entry = readdir(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 should not be allowed allowed_fds[dirfd(dir.get())] = false; } void ZygiskContext::sanitize_fds() { if (flags[SKIP_FD_SANITIZATION]) return; if (flags[APP_FORK_AND_SPECIALIZE]) { auto update_fd_array = [&](int off) -> jintArray { if (exempted_fds.empty()) return nullptr; jintArray array = env->NewIntArray(static_cast(off + exempted_fds.size())); if (array == nullptr) return nullptr; env->SetIntArrayRegion(array, off, 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; flags[SKIP_FD_SANITIZATION] = true; 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); } } if (pid != 0) return; // Close all forbidden fds to prevent crashing auto dir = open_dir("/proc/self/fd"); int dfd = dirfd(dir.get()); for (dirent *entry; (entry = readdir(dir.get()));) { int fd = parse_int(entry->d_name); if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) { close(fd); } } } void ZygiskContext::fork_post() { // Unblock SIGCHLD in case the original method didn't sigmask(SIG_UNBLOCK, SIGCHLD); g_ctx = nullptr; } /* Zygisksu changed: Load module fds */ void ZygiskContext::run_modules_pre() { auto ms = zygiskd::ReadModules(); auto size = ms.size(); for (size_t i = 0; i < size; i++) { auto& m = ms[i]; if (void* handle = DlopenMem(m.memfd, RTLD_NOW); void* entry = handle ? dlsym(handle, "zygisk_module_entry") : nullptr) { modules.emplace_back(i, handle, entry); } } for (auto &m : modules) { m.onLoad(env); if (flags[APP_SPECIALIZE]) { m.preAppSpecialize(args.app); } else if (flags[SERVER_FORK_AND_SPECIALIZE]) { m.preServerSpecialize(args.server); } } } void ZygiskContext::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(); } // Remove from SoList to avoid detection bool solist_res = SoList::Initialize(); if (!solist_res) { LOGE("Failed to initialize SoList\n"); } else { SoList::NullifySoName("jit-cache"); } // Remap as well to avoid checking of /memfd:jit-cache for (auto &info : lsplt::MapInfo::Scan()) { if (strstr(info.path.c_str(), "jit-cache-zygisk")) { void *addr = (void *)info.start; size_t size = info.end - info.start; // MAP_SHARED should fix the suspicious mapping. void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); if (copy == MAP_FAILED) { LOGE("Failed to mmap jit-cache-zygisk\n"); continue; } if ((info.perms & PROT_READ) == 0) { mprotect(addr, size, PROT_READ); } memcpy(copy, addr, size); mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); mprotect(addr, size, info.perms); } } // Don't know if there's a header for things like this // so I just put it into a lambda auto generateRandomString = [](char *str, int length) { const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; srand(time(NULL)); for (int i = 0; i < length; i++) { int key = rand() % (sizeof(charset) - 1); str[i] = charset[key]; } str[length] = '\0'; }; // Randomize name of anonymous mappings // We don't run this in the previous loop because LSPosed might also add // mappings that are not related to /memfd:jit-zygisk-cache // // Since we changed to MAP_SHARED, I don't think this is still needed but let's // leave it here just in case. for (auto info : lsplt::MapInfo::Scan()) { // I had some problems with info.perms & PROT_EXEC so I had to change lsplt source a bit. // If that problem occurs here, do strchr(info.perms_str.c_str(), 'x') instead and add perms_str // to the lsplt MapInfo struct and set it to the raw perms string in Scan(); if (info.perms & PROT_EXEC && info.path.empty()) { // Generate Random Name char randomString[11]; generateRandomString(randomString, 10); LOGI("Randomized Memory map name: %s", randomString); // Memory address of random string uintptr_t strAddr = (uintptr_t)&randomString; // https://lore.kernel.org/lkml/1383170047-21074-2-git-send-email-ccross@android.com/ prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, info.start, info.end - info.start, strAddr); } // Remap as MAP_SHARED if (info.perms & PROT_EXEC && info.dev == 0 && info.path.find("anon") != std::string::npos) { void *addr = reinterpret_cast(info.start); size_t size = info.end - info.start; void *copy = mmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); if ((info.perms & PROT_READ) == 0) { mprotect(addr, size, PROT_READ); } memcpy(copy, addr, size); mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); mprotect(addr, size, info.perms); } } } /* Zygisksu changed: Load module fds */ void ZygiskContext::app_specialize_pre() { flags[APP_SPECIALIZE] = true; info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid); if ((info_flags & PROCESS_ON_DENYLIST) == PROCESS_ON_DENYLIST) { flags[DO_REVERT_UNMOUNT] = true; } if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) { LOGI("Manager process detected. Notifying that Zygisk has been enabled.\n"); setenv("ZYGISK_ENABLED", "1", 1); } else { run_modules_pre(); } } void ZygiskContext::app_specialize_post() { run_modules_post(); // Cleanups env->ReleaseStringUTFChars(args.app->nice_name, process); g_ctx = nullptr; logging::setfd(-1); } bool ZygiskContext::exempt_fd(int fd) { if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION]) return true; if (!flags[APP_FORK_AND_SPECIALIZE]) return false; exempted_fds.push_back(fd); return true; } // ----------------------------------------------------------------- void ZygiskContext::nativeSpecializeAppProcess_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); LOGV("pre specialize [%s]\n", process); // App specialize does not check FD flags[SKIP_FD_SANITIZATION] = true; app_specialize_pre(); } void ZygiskContext::nativeSpecializeAppProcess_post() { LOGV("post specialize [%s]\n", process); app_specialize_post(); } /* Zygisksu changed: No system_server status write back */ void ZygiskContext::nativeForkSystemServer_pre() { LOGV("pre forkSystemServer\n"); flags[SERVER_FORK_AND_SPECIALIZE] = true; fork_pre(); if (pid != 0) return; run_modules_pre(); zygiskd::SystemServerStarted(); sanitize_fds(); } void ZygiskContext::nativeForkSystemServer_post() { if (pid == 0) { LOGV("post forkSystemServer\n"); run_modules_post(); } fork_post(); } void ZygiskContext::nativeForkAndSpecialize_pre() { process = env->GetStringUTFChars(args.app->nice_name, nullptr); LOGV("pre forkAndSpecialize [%s]\n", process); flags[APP_FORK_AND_SPECIALIZE] = true; /* Zygisksu changed: No args.app->fds_to_ignore check since we are Android 10+ */ if (logging::getfd() != -1) { exempted_fds.push_back(logging::getfd()); } fork_pre(); if (pid == 0) { app_specialize_pre(); } sanitize_fds(); } void ZygiskContext::nativeForkAndSpecialize_post() { if (pid == 0) { LOGV("post forkAndSpecialize [%s]\n", process); app_specialize_post(); } fork_post(); } ZygiskContext::~ZygiskContext() { // This global pointer points to a variable on the stack. // Set this to nullptr to prevent leaking local variable. // This also disables most plt hooked functions. g_ctx = nullptr; if (!is_child()) return; should_unmap_zygisk = true; // Unhook JNI methods for (const auto &[clz, methods] : *jni_hook_list) { if (!methods.empty() && env->RegisterNatives( env->FindClass(clz.data()), methods.data(), static_cast(methods.size())) != 0) { LOGE("Failed to restore JNI hook of class [%s]\n", clz.data()); should_unmap_zygisk = false; } } delete jni_hook_list; jni_hook_list = nullptr; // Strip out all API function pointers for (auto &m : modules) { m.clearApi(); } hook_unloader(); } } // namespace static bool hook_commit() { if (lsplt::CommitHook()) { return true; } else { LOGE("plt_hook failed\n"); return false; } } static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) { if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) { LOGE("Failed to register plt_hook \"%s\"\n", symbol); return; } plt_hook_list->emplace_back(dev, inode, symbol, old_func); } #define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME) \ hook_register(DEV, INODE, SYM, (void*) new_##NAME, (void **) &old_##NAME) #define PLT_HOOK_REGISTER(DEV, INODE, NAME) \ PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME) void hook_functions() { default_new(plt_hook_list); default_new(jni_hook_list); ino_t android_runtime_inode = 0; dev_t android_runtime_dev = 0; /* TODO by ThePedroo: Implement injection via native bridge */ // ino_t native_bridge_inode = 0; // dev_t native_bridge_dev = 0; for (auto &map : lsplt::MapInfo::Scan()) { if (map.path.ends_with("libandroid_runtime.so")) { android_runtime_inode = map.inode; android_runtime_dev = map.dev; break; } } PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup); PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); hook_commit(); // Remove unhooked methods plt_hook_list->erase( std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), [](auto &t) { return *std::get<3>(t) == nullptr;}), plt_hook_list->end()); } static void hook_unloader() { ino_t art_inode = 0; dev_t art_dev = 0; for (auto &map : lsplt::MapInfo::Scan()) { if (map.path.ends_with("/libart.so")) { art_inode = map.inode; art_dev = map.dev; break; } } PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy); hook_commit(); } static void unhook_functions() { // Unhook plt_hook for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) { if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) { LOGE("Failed to register plt_hook [%s]\n", sym); } } delete plt_hook_list; if (!hook_commit()) { LOGE("Failed to restore plt_hook\n"); should_unmap_zygisk = false; } }