From 636223b28908350aef5e5be3b36d602a9b442d73 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Tue, 10 Jan 2023 02:33:07 +0800 Subject: [PATCH] Cleanup APIs --- native/src/zygisk/api.hpp | 34 ++----- native/src/zygisk/hook.cpp | 170 +++++++++++++++++++---------------- native/src/zygisk/module.hpp | 55 ++++-------- 3 files changed, 117 insertions(+), 142 deletions(-) diff --git a/native/src/zygisk/api.hpp b/native/src/zygisk/api.hpp index 5cc980c69..51f05c5a0 100644 --- a/native/src/zygisk/api.hpp +++ b/native/src/zygisk/api.hpp @@ -228,21 +228,9 @@ struct Api { // will be set to nullptr. void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); - // For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`. - // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. - void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); - - // For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`. - // If `symbol` is nullptr, then all symbols will be excluded. - void pltHookExclude(const char *regex, const char *symbol); - // For ELFs loaded in memory matching `inode`, replace function `symbol` with `newFunc`. // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. - void pltHookRegisterInode(ino_t inode, const char *symbol, void *newFunc, void **oldFunc); - - // For ELFs loaded in memory matching `inode`, exclude hooks registered for `symbol`. - // If `symbol` is nullptr, then all symbols will be excluded. - void pltHookExcludeInode(ino_t inode, const char *symbol); + void pltHookRegister(ino_t inode, const char *symbol, void *newFunc, void **oldFunc); // Commit all the hooks that was previously registered. // Returns false if an error occurred. @@ -303,16 +291,13 @@ struct api_table { bool (*registerModule)(api_table *, module_abi *); void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); - void (*pltHookRegister)(const char *, const char *, void *, void **); - void (*pltHookExclude)(const char *, const char *); + void (*pltHookRegister)(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 */); - bool (*exemptFd)(int); - void (*pltHookRegisterInode)(ino_t, const char *, void *, void **); - void (*pltHookExcludeInode)(ino_t, const char *); }; template @@ -342,20 +327,11 @@ inline uint32_t Api::getFlags() { inline bool Api::exemptFd(int fd) { return tbl->exemptFd != nullptr && tbl->exemptFd(fd); } -inline void Api::pltHookRegisterInode(ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { - if (tbl->pltHookExcludeInode) tbl->pltHookRegisterInode(inode, symbol, newFunc, oldFunc); -} -inline void Api::pltHookExcludeInode(ino_t inode, const char *symbol) { - if (tbl->pltHookExcludeInode) tbl->pltHookExcludeInode(inode, symbol); -} 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(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { - if (tbl->pltHookRegister) tbl->pltHookRegister(regex, symbol, newFunc, oldFunc); -} -inline void Api::pltHookExclude(const char *regex, const char *symbol) { - if (tbl->pltHookExclude) tbl->pltHookExclude(regex, symbol); +inline void Api::pltHookRegister(ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { + if (tbl->pltHookRegister) tbl->pltHookRegister(inode, symbol, newFunc, oldFunc); } inline bool Api::pltHookCommit() { return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); diff --git a/native/src/zygisk/hook.cpp b/native/src/zygisk/hook.cpp index 8c83c694c..6d2b55ba0 100644 --- a/native/src/zygisk/hook.cpp +++ b/native/src/zygisk/hook.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -63,7 +64,25 @@ struct HookContext { bitset allowed_fds; vector exempted_fds; - HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0) {} + 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() : + env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0), + hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {} void run_modules_pre(const vector &fds); void run_modules_post(); @@ -76,12 +95,19 @@ struct HookContext { void unload_zygisk(); void sanitize_fds(); bool exempt_fd(int fd); + + // 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> *xhook_list; +vector> *plt_hook_list; map, StringCmp> *jni_hook_list; hash_map>> *jni_method_map; @@ -325,77 +351,64 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) { api->base.impl->mod = { module }; // Fill in API accordingly with module API version - switch (api_version) { - case 4: - api->v4.exemptFd = [](int fd) { return g_ctx != nullptr && g_ctx->exempt_fd(fd); }; - api->v4.pltHookRegisterInode = PltHookRegister; - api->v4.pltHookExcludeInode = PltHookExclude; - [[fallthrough]]; - case 3: - case 2: - api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); }; - api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); }; - [[fallthrough]]; - case 1: + if (api_version >= 1) { api->v1.hookJniNativeMethods = hookJniNativeMethods; - api->v1.pltHookRegister = PltHookRegister; - api->v1.pltHookExclude = PltHookExclude; - api->v1.pltHookCommit = CommitPltHook; + 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); }; - break; - default: - // Unknown version number - return false; + } + 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.pltHookRegister = [](ino_t inode, const char *symbol, void *fn, void **backup) { + if (inode == 0 || symbol == nullptr || fn == nullptr) + return; + lsplt::RegisterHook(inode, symbol, fn, backup); + }; + api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); }; } return true; } -void ZygiskModule::PltHookRegister(const char* regex, const char *symbol, void *callback, void **backup) { - if (regex == nullptr || symbol == nullptr || callback == nullptr) +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_lock); - register_info.emplace_back(RegisterInfo{re, 0, symbol, callback, backup}); + mutex_guard lock(hook_info_lock); + register_info.emplace_back(RegisterInfo{re, symbol, fn, backup}); } -void ZygiskModule::PltHookRegister(ino_t inode, const char *symbol, void *callback, void **backup) { - if (inode == 0 || symbol == nullptr || callback == nullptr) - return; - mutex_guard lock(hook_lock); - register_info.emplace_back(RegisterInfo{{}, inode, symbol, callback, backup}); -} - -void ZygiskModule::PltHookExclude(const char* regex, const char *symbol) { +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_lock); - ignore_info.emplace_back(IgnoreInfo{re, 0, symbol ? symbol : ""}); + mutex_guard lock(hook_info_lock); + ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""}); } -void ZygiskModule::PltHookExclude(ino_t inode, const char *symbol) { - if (inode == 0) return; - mutex_guard lock(hook_lock); - ignore_info.emplace_back(IgnoreInfo{{}, inode, symbol ? symbol : ""}); -} - -bool ZygiskModule::CommitPltHook() { - mutex_guard lock(hook_lock); - for (auto &map: lsplt::MapInfo::Scan()) { +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 ((reg.inode != 0 && reg.inode != map.inode)|| - (reg.inode == 0 && regexec(®.regex, map.path.c_str(), 0, nullptr, 0) != 0)) + if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0) continue; bool ignored = false; for (auto &ign: ignore_info) { - if ((ign.inode != 0 && ign.inode != map.inode) || - (ign.inode == 0 && regexec(&ign.regex, map.path.c_str(), 0, nullptr, 0) != 0)) + if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0) continue; if (ign.symbol.empty() || ign.symbol == reg.symbol) { ignored = true; @@ -407,8 +420,15 @@ bool ZygiskModule::CommitPltHook() { } } } - register_info.clear(); - ignore_info.clear(); +} + +bool HookContext::plt_hook_commit() { + { + mutex_guard lock(hook_info_lock); + plt_hook_process_regex(); + register_info.clear(); + ignore_info.clear(); + } return lsplt::CommitHook(); } @@ -748,31 +768,31 @@ void HookContext::nativeForkAndSpecialize_post() { } // namespace -static bool hook_refresh() { +static bool hook_commit() { if (lsplt::CommitHook()) { return true; } else { - ZLOGE("xhook failed\n"); + ZLOGE("plt_hook failed\n"); return false; } } static void hook_register(ino_t inode, const char *symbol, void *new_func, void **old_func) { if (!lsplt::RegisterHook(inode, symbol, new_func, old_func)) { - ZLOGE("Failed to register hook \"%s\"\n", symbol); + ZLOGE("Failed to register plt_hook \"%s\"\n", symbol); return; } - xhook_list->emplace_back(inode, symbol, old_func); + plt_hook_list->emplace_back(inode, symbol, old_func); } -#define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \ +#define PLT_HOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \ hook_register(PATH_REGEX, SYM, (void*) new_##NAME, (void **) &old_##NAME) -#define XHOOK_REGISTER(PATH_REGEX, NAME) \ - XHOOK_REGISTER_SYM(PATH_REGEX, #NAME, NAME) +#define PLT_HOOK_REGISTER(PATH_REGEX, NAME) \ + PLT_HOOK_REGISTER_SYM(PATH_REGEX, #NAME, NAME) void hook_functions() { - default_new(xhook_list); + default_new(plt_hook_list); default_new(jni_hook_list); default_new(jni_method_map); @@ -784,26 +804,26 @@ void hook_functions() { } } - XHOOK_REGISTER(android_runtime_inode, fork); - XHOOK_REGISTER(android_runtime_inode, unshare); - XHOOK_REGISTER(android_runtime_inode, jniRegisterNativeMethods); - XHOOK_REGISTER(android_runtime_inode, selinux_android_setcontext); - XHOOK_REGISTER_SYM(android_runtime_inode, "__android_log_close", android_log_close); - hook_refresh(); + PLT_HOOK_REGISTER(android_runtime_inode, fork); + PLT_HOOK_REGISTER(android_runtime_inode, unshare); + PLT_HOOK_REGISTER(android_runtime_inode, jniRegisterNativeMethods); + PLT_HOOK_REGISTER(android_runtime_inode, selinux_android_setcontext); + PLT_HOOK_REGISTER_SYM(android_runtime_inode, "__android_log_close", android_log_close); + hook_commit(); // Remove unhooked methods - xhook_list->erase( - std::remove_if(xhook_list->begin(), xhook_list->end(), + plt_hook_list->erase( + std::remove_if(plt_hook_list->begin(), plt_hook_list->end(), [](auto &t) { return *std::get<2>(t) == nullptr;}), - xhook_list->end()); + plt_hook_list->end()); if (old_jniRegisterNativeMethods == nullptr) { ZLOGD("jniRegisterNativeMethods not hooked, using fallback\n"); struct stat self_stat{}; stat("/proc/self/exe", &self_stat); // android::AndroidRuntime::setArgv0(const char*, bool) - XHOOK_REGISTER_SYM(self_stat.st_ino, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0); - hook_refresh(); + PLT_HOOK_REGISTER_SYM(self_stat.st_ino, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0); + hook_commit(); // We still need old_jniRegisterNativeMethods as other code uses it // android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char*, const JNINativeMethod*, int) @@ -835,16 +855,16 @@ static bool unhook_functions() { } delete jni_hook_list; - // Unhook xhook - for (const auto &[inode, sym, old_func] : *xhook_list) { + // Unhook plt_hook + for (const auto &[inode, sym, old_func] : *plt_hook_list) { if (!lsplt::RegisterHook(inode, sym, *old_func, nullptr)) { - ZLOGE("Failed to register xhook [%s]\n", sym); + ZLOGE("Failed to register plt_hook [%s]\n", sym); success = false; } } - delete xhook_list; - if (!hook_refresh()) { - ZLOGE("Failed to restore xhook\n"); + delete plt_hook_list; + if (!hook_commit()) { + ZLOGE("Failed to restore plt_hook\n"); success = false; } diff --git a/native/src/zygisk/module.hpp b/native/src/zygisk/module.hpp index 8c91022be..b188b2ee7 100644 --- a/native/src/zygisk/module.hpp +++ b/native/src/zygisk/module.hpp @@ -1,8 +1,6 @@ #pragma once #include "api.hpp" -#include -#include namespace { @@ -126,24 +124,28 @@ struct api_abi_base { }; struct api_abi_v1 : public api_abi_base { - void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); - void (*pltHookRegister)(const char *, const char *, void *, void **); - void (*pltHookExclude)(const char *, const char *); - bool (*pltHookCommit)(); - - int (*connectCompanion)(ZygiskModule *); - void (*setOption)(ZygiskModule *, zygisk::Option); + /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); + /* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **); + /* 2 */ void (*pltHookExclude)(const char *, const char *); + /* 3 */ bool (*pltHookCommit)(); + /* 4 */ int (*connectCompanion)(ZygiskModule *); + /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option); }; struct api_abi_v2 : public api_abi_v1 { - int (*getModuleDir)(ZygiskModule *); - uint32_t (*getFlags)(ZygiskModule *); + /* 6 */ int (*getModuleDir)(ZygiskModule *); + /* 7 */ uint32_t (*getFlags)(ZygiskModule *); }; -struct api_abi_v4 : public api_abi_v2 { - bool (*exemptFd)(int); - void (*pltHookRegisterInode)(ino_t, const char *, void *, void **); - void (*pltHookExcludeInode)(ino_t, const char *); +struct api_abi_v4 : public api_abi_base { + /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); + /* 1 */ void (*pltHookRegister)(ino_t, const char *, void *, void **); + /* 2 */ bool (*exemptFd)(int); + /* 3 */ bool (*pltHookCommit)(); + /* 4 */ int (*connectCompanion)(ZygiskModule *); + /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option); + /* 6 */ int (*getModuleDir)(ZygiskModule *); + /* 7 */ uint32_t (*getFlags)(ZygiskModule *); }; union ApiTable { @@ -214,29 +216,6 @@ private: long *api_version; module_abi_v1 *v1; } mod; - - struct RegisterInfo { - regex_t regex; - ino_t inode; - std::string symbol; - void *callback; - void **backup; - }; - - struct IgnoreInfo { - regex_t regex; - ino_t inode; - std::string symbol; - }; - inline static pthread_mutex_t hook_lock = PTHREAD_MUTEX_INITIALIZER; - inline static std::list register_info {}; - inline static std::list ignore_info {}; - - static void PltHookRegister(const char *lib, const char *symbol, void *callback, void **backup); - static void PltHookRegister(ino_t inode, const char *symbol, void *callback, void **backup); - static void PltHookExclude(const char *lib, const char *symbol); - static void PltHookExclude(ino_t inode, const char *symbol); - static bool CommitPltHook(); }; } // namespace