diff --git a/zygisk/module/jni/zygisk.hpp b/zygisk/module/jni/zygisk.hpp index edf3acbe..7272633a 100644 --- a/zygisk/module/jni/zygisk.hpp +++ b/zygisk/module/jni/zygisk.hpp @@ -1,3 +1,17 @@ +/* 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. @@ -5,23 +19,43 @@ #include -#define ZYGISK_API_VERSION 1 +#define ZYGISK_API_VERSION 2 /* -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. +*************** +* 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! +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); } -static void example_handler(int socket) { ... } - class ExampleModule : public zygisk::ModuleBase { public: void onLoad(zygisk::Api *api, JNIEnv *env) override { @@ -42,6 +76,21 @@ class ExampleModule : public zygisk::ModuleBase { 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) */ @@ -55,12 +104,11 @@ struct ServerSpecializeArgs; class ModuleBase { public: - // This function is called when the module is loaded into the target process. - // A Zygisk API handle will be sent as an argument; call utility functions or interface - // with Zygisk through this handle. - virtual void onLoad(Api *api, JNIEnv *env) {} + // 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 function is called before the app process is specialized. + // 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. @@ -72,20 +120,20 @@ class ModuleBase { // 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(AppSpecializeArgs *args) {} + virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} - // This function is called after the app process is specialized. + // 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 function runs as the same privilege of the app's own code. - virtual void postAppSpecialize(const AppSpecializeArgs *args) {} + // 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 function is called before the system server process is specialized. + // This method is called before the system server process is specialized. // See preAppSpecialize(args) for more info. - virtual void preServerSpecialize(ServerSpecializeArgs *args) {} + virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} - // This function is called after the system server process is specialized. + // 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(const ServerSpecializeArgs *args) {} + virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} }; struct AppSpecializeArgs { @@ -134,54 +182,86 @@ enum Option : int { // Setting this option only makes sense in preAppSpecialize. // The actual unmounting happens during app process specialization. // - // Processes added to Magisk's denylist will have all Magisk and its modules' files unmounted - // from its mount namespace. In addition, all Zygisk code will be unloaded from memory, which - // also implies that no Zygisk modules (including yours) are loaded. - // - // However, if for any reason your module still wants the unmount part of the denylist - // operation to be enabled EVEN IF THE PROCESS IS NOT ON THE DENYLIST, set this option. + // 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. - // YOU SHOULD NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTION IN THE PROCESS. + // 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 functions due to SELinux restrictions. + // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. // - // The pre[XXX]Specialize functions run with the same privilege of zygote. + // 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 function accepts one single option at a time. + // 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(); + // Hook JNI native methods for a class // - // Lookup all registered JNI native methods and replace it with your own functions. + // 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); - // For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`. + // 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: + // + //
+ // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 + // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) + // + // For ELFs loaded in memory with pathname 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`. + // For ELFs loaded in memory with pathname 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); @@ -190,7 +270,7 @@ struct Api { bool pltHookCommit(); private: - internal::api_table *impl; + internal::api_table *tbl; template friend void internal::entry_impl(internal::api_table *, JNIEnv *); }; @@ -205,90 +285,100 @@ void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ // // 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 socket that is connected to the target process. +// 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 a globally shared resource. +// Be aware of race conditions if you have globally shared resources. #define REGISTER_ZYGISK_COMPANION(func) \ void zygisk_companion_entry(int client) { func(client); } -/************************************************************************************ - * All the code after this point is internal code used to interface with Zygisk - * and guarantee ABI stability. You do not have to understand what it is doing. - ************************************************************************************/ +/********************************************************* + * 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 *_this; + 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), _this(module) { - preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); }; - postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); }; - preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); }; - postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); }; + 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 { - // These first 2 entries are permanent, shall never change - void *_this; + // Base + void *impl; bool (*registerModule)(api_table *, module_abi *); - // Utility functions void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); void (*pltHookRegister)(const char *, const char *, void *, void **); void (*pltHookExclude)(const char *, const char *); bool (*pltHookCommit)(); - - // Zygisk functions - int (*connectCompanion)(void * /* _this */); - void (*setOption)(void * /* _this */, Option); + int (*connectCompanion)(void * /* impl */); + void (*setOption)(void * /* impl */, Option); + int (*getModuleDir)(void * /* impl */); + uint32_t (*getFlags)(void * /* impl */); }; template void entry_impl(api_table *table, JNIEnv *env) { - ModuleBase *module = new T(); - if (!table->registerModule(table, new module_abi(module))) - return; - auto api = new Api(); - api->impl = table; - module->onLoad(api, 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 -int Api::connectCompanion() { - return impl->connectCompanion(impl->_this); +inline int Api::connectCompanion() { + return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; } -void Api::setOption(Option opt) { - impl->setOption(impl->_this, opt); +inline int Api::getModuleDir() { + return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; } -void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { - impl->hookJniNativeMethods(env, className, methods, numMethods); +inline void Api::setOption(Option opt) { + if (tbl->setOption) tbl->setOption(tbl->impl, opt); } -void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { - impl->pltHookRegister(regex, symbol, newFunc, oldFunc); +inline uint32_t Api::getFlags() { + return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; } -void Api::pltHookExclude(const char *regex, const char *symbol) { - impl->pltHookExclude(regex, symbol); +inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { + if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); } -bool Api::pltHookCommit() { - return impl->pltHookCommit(); +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 bool Api::pltHookCommit() { + return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); } } // namespace zygisk -[[gnu::visibility("default")]] [[gnu::used]] -extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); +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); -[[gnu::visibility("default")]] [[gnu::used]] -extern "C" void zygisk_companion_entry(int); +} // extern "C"