zygisk.hpp
| 1 | /* Copyright 2022-2023 John "topjohnwu" Wu |
| 2 | * |
| 3 | * Permission to use, copy, modify, and/or distribute this software for any |
| 4 | * purpose with or without fee is hereby granted. |
| 5 | * |
| 6 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH |
| 7 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
| 8 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, |
| 9 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM |
| 10 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR |
| 11 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR |
| 12 | * PERFORMANCE OF THIS SOFTWARE. |
| 13 | */ |
| 14 | |
| 15 | // This is the public API for Zygisk modules. |
| 16 | // DO NOT MODIFY ANY CODE IN THIS HEADER. |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | /* |
| 25 | |
| 26 | *************** |
| 27 | * Introduction |
| 28 | *************** |
| 29 | |
| 30 | On Android, all app processes are forked from a special daemon called "Zygote". |
| 31 | For each new app process, zygote will fork a new process and perform "specialization". |
| 32 | This specialization operation enforces the Android security sandbox on the newly forked |
| 33 | process to make sure that 3rd party application code is only loaded after it is being |
| 34 | restricted within a sandbox. |
| 35 | |
| 36 | On Android, there is also this special process called "system_server". This single |
| 37 | process hosts a significant portion of system services, which controls how the |
| 38 | Android operating system and apps interact with each other. |
| 39 | |
| 40 | The Zygisk framework provides a way to allow developers to build modules and run custom |
| 41 | code before and after system_server and any app processes' specialization. |
| 42 | This enable developers to inject code and alter the behavior of system_server and app processes. |
| 43 | |
| 44 | Please note that modules will only be loaded after zygote has forked the child process. |
| 45 | THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON! |
| 46 | |
| 47 | ********************* |
| 48 | * Development Guide |
| 49 | ********************* |
| 50 | |
| 51 | Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. |
| 52 | Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. |
| 53 | |
| 54 | Example code: |
| 55 | |
| 56 | static jint (*orig_logger_entry_max)(JNIEnv *env); |
| 57 | static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } |
| 58 | |
| 59 | class ExampleModule : public zygisk::ModuleBase { |
| 60 | public: |
| 61 | void onLoad(zygisk::Api *api, JNIEnv *env) override { |
| 62 | this->api = api; |
| 63 | this->env = env; |
| 64 | } |
| 65 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { |
| 66 | JNINativeMethod methods[] = { |
| 67 | { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, |
| 68 | }; |
| 69 | api->hookJniNativeMethods(env, "android/util/Log", methods, 1); |
| 70 | *(void **) &orig_logger_entry_max = methods[0].fnPtr; |
| 71 | } |
| 72 | private: |
| 73 | zygisk::Api *api; |
| 74 | JNIEnv *env; |
| 75 | }; |
| 76 | |
| 77 | REGISTER_ZYGISK_MODULE(ExampleModule) |
| 78 | |
| 79 | ----------------------------------------------------------------------------------------- |
| 80 | |
| 81 | Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize, |
| 82 | or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class |
| 83 | never runs in a true superuser environment. |
| 84 | |
| 85 | If your module require access to superuser permissions, you can create and register |
| 86 | a root companion handler function. This function runs in a separate root companion |
| 87 | daemon process, and an Unix domain socket is provided to allow you to perform IPC between |
| 88 | your target process and the root companion process. |
| 89 | |
| 90 | Example code: |
| 91 | |
| 92 | static void example_handler(int socket) { ... } |
| 93 | |
| 94 | REGISTER_ZYGISK_COMPANION(example_handler) |
| 95 | |
| 96 | */ |
| 97 | |
| 98 | namespace zygisk { |
| 99 | |
| 100 | struct Api; |
| 101 | struct AppSpecializeArgs; |
| 102 | struct ServerSpecializeArgs; |
| 103 | |
| 104 | class ModuleBase { |
| 105 | public: |
| 106 | |
| 107 | // This method is called as soon as the module is loaded into the target process. |
| 108 | // A Zygisk API handle will be passed as an argument. |
| 109 | virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} |
| 110 | |
| 111 | // This method is called before the app process is specialized. |
| 112 | // At this point, the process just got forked from zygote, but no app specific specialization |
| 113 | // is applied. This means that the process does not have any sandbox restrictions and |
| 114 | // still runs with the same privilege of zygote. |
| 115 | // |
| 116 | // All the arguments that will be sent and used for app specialization is passed as a single |
| 117 | // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app |
| 118 | // process will be specialized. |
| 119 | // |
| 120 | // If you need to run some operations as superuser, you can call Api::connectCompanion() to |
| 121 | // get a socket to do IPC calls with a root companion process. |
| 122 | // See Api::connectCompanion() for more info. |
| 123 | virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} |
| 124 | |
| 125 | // This method is called after the app process is specialized. |
| 126 | // At this point, the process has all sandbox restrictions enabled for this application. |
| 127 | // This means that this method runs with the same privilege of the app's own code. |
| 128 | virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} |
| 129 | |
| 130 | // This method is called before the system server process is specialized. |
| 131 | // See preAppSpecialize(args) for more info. |
| 132 | virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} |
| 133 | |
| 134 | // This method is called after the system server process is specialized. |
| 135 | // At this point, the process runs with the privilege of system_server. |
| 136 | virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} |
| 137 | }; |
| 138 | |
| 139 | struct AppSpecializeArgs { |
| 140 | // Required arguments. These arguments are guaranteed to exist on all Android versions. |
| 141 | jint &uid; |
| 142 | jint &gid; |
| 143 | jintArray &gids; |
| 144 | jint &runtime_flags; |
| 145 | jobjectArray &rlimits; |
| 146 | jint &mount_external; |
| 147 | jstring &se_info; |
| 148 | jstring &nice_name; |
| 149 | jstring &instruction_set; |
| 150 | jstring &app_data_dir; |
| 151 | |
| 152 | // Optional arguments. Please check whether the pointer is null before de-referencing |
| 153 | jintArray *const fds_to_ignore; |
| 154 | jboolean *const is_child_zygote; |
| 155 | jboolean *const is_top_app; |
| 156 | jobjectArray *const pkg_data_info_list; |
| 157 | jobjectArray *const whitelisted_data_info_list; |
| 158 | jboolean *const mount_data_dirs; |
| 159 | jboolean *const mount_storage_dirs; |
| 160 | |
| 161 | AppSpecializeArgs() = delete; |
| 162 | }; |
| 163 | |
| 164 | struct ServerSpecializeArgs { |
| 165 | jint &uid; |
| 166 | jint &gid; |
| 167 | jintArray &gids; |
| 168 | jint &runtime_flags; |
| 169 | jlong &permitted_capabilities; |
| 170 | jlong &effective_capabilities; |
| 171 | |
| 172 | ServerSpecializeArgs() = delete; |
| 173 | }; |
| 174 | |
| 175 | namespace internal { |
| 176 | struct api_table; |
| 177 | template <class T> void entry_impl(api_table *, JNIEnv *); |
| 178 | } |
| 179 | |
| 180 | // These values are used in Api::setOption(Option) |
| 181 | enum Option : int { |
| 182 | // Force Magisk's denylist unmount routines to run on this process. |
| 183 | // |
| 184 | // Setting this option only makes sense in preAppSpecialize. |
| 185 | // The actual unmounting happens during app process specialization. |
| 186 | // |
| 187 | // Set this option to force all Magisk and modules' files to be unmounted from the |
| 188 | // mount namespace of the process, regardless of the denylist enforcement status. |
| 189 | FORCE_DENYLIST_UNMOUNT = 0, |
| 190 | |
| 191 | // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. |
| 192 | // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. |
| 193 | // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. |
| 194 | DLCLOSE_MODULE_LIBRARY = 1, |
| 195 | }; |
| 196 | |
| 197 | // Bit masks of the return value of Api::getFlags() |
| 198 | enum StateFlag : uint32_t { |
| 199 | // The user has granted root access to the current process |
| 200 | PROCESS_GRANTED_ROOT = (1u << 0), |
| 201 | |
| 202 | // The current process was added on the denylist |
| 203 | PROCESS_ON_DENYLIST = (1u << 1), |
| 204 | }; |
| 205 | |
| 206 | // All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded |
| 207 | // from the specialized process afterwards. |
| 208 | struct Api { |
| 209 | |
| 210 | // Connect to a root companion process and get a Unix domain socket for IPC. |
| 211 | // |
| 212 | // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. |
| 213 | // |
| 214 | // The pre[XXX]Specialize methods run with the same privilege of zygote. |
| 215 | // If you would like to do some operations with superuser permissions, register a handler |
| 216 | // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). |
| 217 | // Another good use case for a companion process is that if you want to share some resources |
| 218 | // across multiple processes, hold the resources in the companion process and pass it over. |
| 219 | // |
| 220 | // The root companion process is ABI aware; that is, when calling this method from a 32-bit |
| 221 | // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. |
| 222 | // |
| 223 | // Returns a file descriptor to a socket that is connected to the socket passed to your |
| 224 | // module's companion request handler. Returns -1 if the connection attempt failed. |
| 225 | int connectCompanion(); |
| 226 | |
| 227 | // Get the file descriptor of the root folder of the current module. |
| 228 | // |
| 229 | // This API only works in the pre[XXX]Specialize methods. |
| 230 | // Accessing the directory returned is only possible in the pre[XXX]Specialize methods |
| 231 | // or in the root companion process (assuming that you sent the fd over the socket). |
| 232 | // Both restrictions are due to SELinux and UID. |
| 233 | // |
| 234 | // Returns -1 if errors occurred. |
| 235 | int getModuleDir(); |
| 236 | |
| 237 | // Set various options for your module. |
| 238 | // Please note that this method accepts one single option at a time. |
| 239 | // Check zygisk::Option for the full list of options available. |
| 240 | void setOption(Option opt); |
| 241 | |
| 242 | // Get information about the current process. |
| 243 | // Returns bitwise-or'd zygisk::StateFlag values. |
| 244 | uint32_t getFlags(); |
| 245 | |
| 246 | // Exempt the provided file descriptor from being automatically closed. |
| 247 | // |
| 248 | // This API only make sense in preAppSpecialize; calling this method in any other situation |
| 249 | // is either a no-op (returns true) or an error (returns false). |
| 250 | // |
| 251 | // When false is returned, the provided file descriptor will eventually be closed by zygote. |
| 252 | bool exemptFd(int fd); |
| 253 | |
| 254 | // Hook JNI native methods for a class |
| 255 | // |
| 256 | // Lookup all registered JNI native methods and replace it with your own methods. |
| 257 | // The original function pointer will be saved in each JNINativeMethod's fnPtr. |
| 258 | // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr |
| 259 | // will be set to nullptr. |
| 260 | void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); |
| 261 | |
| 262 | // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. |
| 263 | // |
| 264 | // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: |
| 265 | // |
| 266 | // <address> <perms> <offset> <dev> <inode> <pathname> |
| 267 | // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 |
| 268 | // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) |
| 269 | // |
| 270 | // The `dev` and `inode` pair uniquely identifies a file being mapped into memory. |
| 271 | // For matching ELFs loaded in memory, replace function `symbol` with `newFunc`. |
| 272 | // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. |
| 273 | void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc); |
| 274 | |
| 275 | // Commit all the hooks that was previously registered. |
| 276 | // Returns false if an error occurred. |
| 277 | bool pltHookCommit(); |
| 278 | |
| 279 | private: |
| 280 | internal::api_table *tbl; |
| 281 | template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *); |
| 282 | }; |
| 283 | |
| 284 | // Register a class as a Zygisk module |
| 285 | |
| 286 | |
| 287 | |
| 288 | |
| 289 | |
| 290 | |
| 291 | // Register a root companion request handler function for your module |
| 292 | // |
| 293 | // The function runs in a superuser daemon process and handles a root companion request from |
| 294 | // your module running in a target process. The function has to accept an integer value, |
| 295 | // which is a Unix domain socket that is connected to the target process. |
| 296 | // See Api::connectCompanion() for more info. |
| 297 | // |
| 298 | // NOTE: the function can run concurrently on multiple threads. |
| 299 | // Be aware of race conditions if you have globally shared resources. |
| 300 | |
| 301 | |
| 302 | |
| 303 | |
| 304 | /********************************************************* |
| 305 | * The following is internal ABI implementation detail. |
| 306 | * You do not have to understand what it is doing. |
| 307 | *********************************************************/ |
| 308 | |
| 309 | namespace internal { |
| 310 | |
| 311 | struct module_abi { |
| 312 | long api_version; |
| 313 | ModuleBase *impl; |
| 314 | |
| 315 | void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); |
| 316 | void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); |
| 317 | void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); |
| 318 | void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); |
| 319 | |
| 320 | module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) { |
| 321 | preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); }; |
| 322 | postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); }; |
| 323 | preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); }; |
| 324 | postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); }; |
| 325 | } |
| 326 | }; |
| 327 | |
| 328 | struct api_table { |
| 329 | // Base |
| 330 | void *impl; |
| 331 | bool (*registerModule)(api_table *, module_abi *); |
| 332 | |
| 333 | void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); |
| 334 | void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **); |
| 335 | bool (*exemptFd)(int); |
| 336 | bool (*pltHookCommit)(); |
| 337 | int (*connectCompanion)(void * /* impl */); |
| 338 | void (*setOption)(void * /* impl */, Option); |
| 339 | int (*getModuleDir)(void * /* impl */); |
| 340 | uint32_t (*getFlags)(void * /* impl */); |
| 341 | }; |
| 342 | |
| 343 | template <class T> |
| 344 | void entry_impl(api_table *table, JNIEnv *env) { |
| 345 | static Api api; |
| 346 | api.tbl = table; |
| 347 | static T module; |
| 348 | ModuleBase *m = &module; |
| 349 | static module_abi abi(m); |
| 350 | if (!table->registerModule(table, &abi)) return; |
| 351 | m->onLoad(&api, env); |
| 352 | } |
| 353 | |
| 354 | } // namespace internal |
| 355 | |
| 356 | inline int Api::connectCompanion() { |
| 357 | return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; |
| 358 | } |
| 359 | inline int Api::getModuleDir() { |
| 360 | return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; |
| 361 | } |
| 362 | inline void Api::setOption(Option opt) { |
| 363 | if (tbl->setOption) tbl->setOption(tbl->impl, opt); |
| 364 | } |
| 365 | inline uint32_t Api::getFlags() { |
| 366 | return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; |
| 367 | } |
| 368 | inline bool Api::exemptFd(int fd) { |
| 369 | return tbl->exemptFd != nullptr && tbl->exemptFd(fd); |
| 370 | } |
| 371 | inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { |
| 372 | if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); |
| 373 | } |
| 374 | inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { |
| 375 | if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc); |
| 376 | } |
| 377 | inline bool Api::pltHookCommit() { |
| 378 | return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); |
| 379 | } |
| 380 | |
| 381 | } // namespace zygisk |
| 382 | |
| 383 | extern "C" { |
| 384 | |
| 385 | [[gnu::visibility("default"), maybe_unused]] |
| 386 | void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); |
| 387 | |
| 388 | [[gnu::visibility("default"), maybe_unused]] |
| 389 | void zygisk_companion_entry(int); |
| 390 | |
| 391 | } // extern "C" |