Downgrade to Zygisk api v2

This commit is contained in:
chiteroman
2024-08-12 02:43:44 +02:00
parent 1e7c98b389
commit bf87ea42b8

View File

@@ -19,7 +19,7 @@
#include <jni.h> #include <jni.h>
#define ZYGISK_API_VERSION 4 #define ZYGISK_API_VERSION 2
/* /*
@@ -97,195 +97,182 @@ REGISTER_ZYGISK_COMPANION(example_handler)
namespace zygisk { namespace zygisk {
struct Api; struct Api;
struct AppSpecializeArgs; struct AppSpecializeArgs;
struct ServerSpecializeArgs; struct ServerSpecializeArgs;
class ModuleBase { class ModuleBase {
public: public:
// This method is called as soon as the module is loaded into the target process. // 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. // A Zygisk API handle will be passed as an argument.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This method 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 // 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 // is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote. // still runs with the same privilege of zygote.
// //
// All the arguments that will be sent and used for app specialization is passed as a single // All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized. // process will be specialized.
// //
// If you need to run some operations as superuser, you can call Api::connectCompanion() to // 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. // get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info. // See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This method 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. // At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs with the same privilege of the app's own code. // This means that this method runs with the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method 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. // See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This method 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. // At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
}; };
struct AppSpecializeArgs { struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions. // Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid; jint &uid;
jint &gid; jint &gid;
jintArray &gids; jintArray &gids;
jint &runtime_flags; jint &runtime_flags;
jobjectArray &rlimits; jint &mount_external;
jint &mount_external; jstring &se_info;
jstring &se_info; jstring &nice_name;
jstring &nice_name; jstring &instruction_set;
jstring &instruction_set; jstring &app_data_dir;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing // Optional arguments. Please check whether the pointer is null before de-referencing
jintArray *const fds_to_ignore; jboolean *const is_child_zygote;
jboolean *const is_child_zygote; jboolean *const is_top_app;
jboolean *const is_top_app; jobjectArray *const pkg_data_info_list;
jobjectArray *const pkg_data_info_list; jobjectArray *const whitelisted_data_info_list;
jobjectArray *const whitelisted_data_info_list; jboolean *const mount_data_dirs;
jboolean *const mount_data_dirs; jboolean *const mount_storage_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete; AppSpecializeArgs() = delete;
}; };
struct ServerSpecializeArgs { struct ServerSpecializeArgs {
jint &uid; jint &uid;
jint &gid; jint &gid;
jintArray &gids; jintArray &gids;
jint &runtime_flags; jint &runtime_flags;
jlong &permitted_capabilities; jlong &permitted_capabilities;
jlong &effective_capabilities; jlong &effective_capabilities;
ServerSpecializeArgs() = delete; ServerSpecializeArgs() = delete;
}; };
namespace internal { namespace internal {
struct api_table; struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
template<class T> }
void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option) // These values are used in Api::setOption(Option)
enum Option : int { enum Option : int {
// Force Magisk's denylist unmount routines to run on this process. // Force Magisk's denylist unmount routines to run on this process.
// //
// Setting this option only makes sense in preAppSpecialize. // Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization. // The actual unmounting happens during app process specialization.
// //
// Set this option to force all Magisk and modules' files to be unmounted from the // 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. // mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0, FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. // 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. // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1, DLCLOSE_MODULE_LIBRARY = 1,
}; };
// Bit masks of the return value of Api::getFlags() // Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t { enum StateFlag : uint32_t {
// The user has granted root access to the current process // The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0), PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist // The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1), PROCESS_ON_DENYLIST = (1u << 1),
}; };
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded // All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards. // from the specialized process afterwards.
struct Api { struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC. // Connect to a root companion process and get a Unix domain socket for IPC.
// //
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
// //
// The pre[XXX]Specialize methods run with the same privilege of zygote. // 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 // 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). // 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 // 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. // 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 // 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. // 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 // 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. // module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion(); int connectCompanion();
// Get the file descriptor of the root folder of the current module. // Get the file descriptor of the root folder of the current module.
// //
// This API only works in the pre[XXX]Specialize methods. // This API only works in the pre[XXX]Specialize methods.
// Accessing the directory returned is only possible 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). // or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID. // Both restrictions are due to SELinux and UID.
// //
// Returns -1 if errors occurred. // Returns -1 if errors occurred.
int getModuleDir(); int getModuleDir();
// Set various options for your module. // Set various options for your module.
// Please note that this method 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. // Check zygisk::Option for the full list of options available.
void setOption(Option opt); void setOption(Option opt);
// Get information about the current process. // Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values. // Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags(); uint32_t getFlags();
// Exempt the provided file descriptor from being automatically closed. // Hook JNI native methods for a class
// //
// This API only make sense in preAppSpecialize; calling this method in any other situation // Lookup all registered JNI native methods and replace it with your own methods.
// is either a no-op (returns true) or an error (returns false). // 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
// When false is returned, the provided file descriptor will eventually be closed by zygote. // will be set to nullptr.
bool exemptFd(int fd); void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// Hook JNI native methods for a class // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
// //
// Lookup all registered JNI native methods and replace it with your own methods. // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
// 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 // <address> <perms> <offset> <dev> <inode> <pathname>
// will be set to nullptr. // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, // (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
int numMethods); //
// 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);
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. // For ELFs loaded in memory with pathname matching `regex`, exclude hooks registered for `symbol`.
// // If `symbol` is nullptr, then all symbols will be excluded.
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: void pltHookExclude(const char *regex, const char *symbol);
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
//
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void
pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
// Commit all the hooks that was previously registered. // Commit all the hooks that was previously registered.
// Returns false if an error occurred. // Returns false if an error occurred.
bool pltHookCommit(); bool pltHookCommit();
private: private:
internal::api_table *tbl; internal::api_table *tbl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
template<class T> };
friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module // Register a class as a Zygisk module
@@ -312,99 +299,77 @@ void zygisk_companion_entry(int client) { func(client); }
* You do not have to understand what it is doing. * You do not have to understand what it is doing.
*********************************************************/ *********************************************************/
namespace internal { namespace internal {
struct module_abi { struct module_abi {
long api_version; long api_version;
ModuleBase *impl; ModuleBase *impl;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); postServerSpecialize = [](auto m, auto args) { m->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 {
// Base
void *impl;
bool (*registerModule)(api_table *, module_abi *);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
bool (*exemptFd)(int);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
template<class T>
void entry_impl(api_table *table, JNIEnv *env) {
static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
} }
};
inline int Api::getModuleDir() { struct api_table {
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; // Base
} void *impl;
bool (*registerModule)(api_table *, module_abi *);
inline void Api::setOption(Option opt) { void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
if (tbl->setOption) tbl->setOption(tbl->impl, opt); void (*pltHookRegister)(const char *, const char *, void *, void **);
} void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
inline uint32_t Api::getFlags() { template <class T>
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; void entry_impl(api_table *table, JNIEnv *env) {
} static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
inline bool Api::exemptFd(int fd) { } // namespace internal
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
}
inline void inline int Api::connectCompanion() {
Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
int numMethods) { }
if (tbl->hookJniNativeMethods) inline int Api::getModuleDir() {
tbl->hookJniNativeMethods(env, className, methods, numMethods); return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
} }
inline void Api::setOption(Option opt) {
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, if (tbl->setOption) tbl->setOption(tbl->impl, opt);
void **oldFunc) { }
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc); inline uint32_t Api::getFlags() {
} return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
}
inline bool Api::pltHookCommit() { inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); 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 bool Api::pltHookCommit() {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
}
} // namespace zygisk } // namespace zygisk