zygisk.hpp 391 lines ยท 15 KB
Raw
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#pragma once
19
20#include <jni.h>
21
22#define ZYGISK_API_VERSION 4
23
24/*
25
26***************
27* Introduction
28***************
29
30On Android, all app processes are forked from a special daemon called "Zygote".
31For each new app process, zygote will fork a new process and perform "specialization".
32This specialization operation enforces the Android security sandbox on the newly forked
33process to make sure that 3rd party application code is only loaded after it is being
34restricted within a sandbox.
35
36On Android, there is also this special process called "system_server". This single
37process hosts a significant portion of system services, which controls how the
38Android operating system and apps interact with each other.
39
40The Zygisk framework provides a way to allow developers to build modules and run custom
41code before and after system_server and any app processes' specialization.
42This enable developers to inject code and alter the behavior of system_server and app processes.
43
44Please note that modules will only be loaded after zygote has forked the child process.
45THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
46
47*********************
48* Development Guide
49*********************
50
51Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
52Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
53
54Example code:
55
56static jint (*orig_logger_entry_max)(JNIEnv *env);
57static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
58
59class ExampleModule : public zygisk::ModuleBase {
60public:
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 }
72private:
73 zygisk::Api *api;
74 JNIEnv *env;
75};
76
77REGISTER_ZYGISK_MODULE(ExampleModule)
78
79-----------------------------------------------------------------------------------------
80
81Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
82or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
83never runs in a true superuser environment.
84
85If your module require access to superuser permissions, you can create and register
86a root companion handler function. This function runs in a separate root companion
87daemon process, and an Unix domain socket is provided to allow you to perform IPC between
88your target process and the root companion process.
89
90Example code:
91
92static void example_handler(int socket) { ... }
93
94REGISTER_ZYGISK_COMPANION(example_handler)
95
96*/
97
98namespace zygisk {
99
100struct Api;
101struct AppSpecializeArgs;
102struct ServerSpecializeArgs;
103
104class ModuleBase {
105public:
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
139struct 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
164struct 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
175namespace internal {
176struct api_table;
177template <class T> void entry_impl(api_table *, JNIEnv *);
178}
179
180// These values are used in Api::setOption(Option)
181enum 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()
198enum 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.
208struct 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
279private:
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#define REGISTER_ZYGISK_MODULE(clazz) \
287void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
288 zygisk::internal::entry_impl<clazz>(table, env); \
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#define REGISTER_ZYGISK_COMPANION(func) \
302void zygisk_companion_entry(int client) { func(client); }
303
304/*********************************************************
305 * The following is internal ABI implementation detail.
306 * You do not have to understand what it is doing.
307 *********************************************************/
308
309namespace internal {
310
311struct 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
328struct 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
343template <class T>
344void 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
356inline int Api::connectCompanion() {
357 return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
358}
359inline int Api::getModuleDir() {
360 return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
361}
362inline void Api::setOption(Option opt) {
363 if (tbl->setOption) tbl->setOption(tbl->impl, opt);
364}
365inline uint32_t Api::getFlags() {
366 return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
367}
368inline bool Api::exemptFd(int fd) {
369 return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
370}
371inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
372 if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
373}
374inline 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}
377inline bool Api::pltHookCommit() {
378 return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
379}
380
381} // namespace zygisk
382
383extern "C" {
384
385[[gnu::visibility("default"), maybe_unused]]
386void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
387
388[[gnu::visibility("default"), maybe_unused]]
389void zygisk_companion_entry(int);
390
391} // extern "C"