diff options
author | Jason A. Donenfeld <Jason@zx2c4.com> | 2023-10-21 17:21:21 +0200 |
---|---|---|
committer | Jason A. Donenfeld <Jason@zx2c4.com> | 2023-10-21 18:26:15 +0200 |
commit | 3f3c5eba134b7155d5e011d6496b4b585b41a484 (patch) | |
tree | 0a14dc3aaefb2cc6d96f5d1ceadd5fc115cdf1e5 | |
download | wireguard-vnet-hdr-zygisk-3f3c5eba134b7155d5e011d6496b4b585b41a484.tar.xz wireguard-vnet-hdr-zygisk-3f3c5eba134b7155d5e011d6496b4b585b41a484.zip |
Initial commit
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 8 | ||||
-rw-r--r-- | README.md | 1 | ||||
-rw-r--r-- | jni/Android.mk | 6 | ||||
-rw-r--r-- | jni/Application.mk | 4 | ||||
-rw-r--r-- | jni/tunflags.cpp | 270 | ||||
-rw-r--r-- | jni/zygisk.hpp | 391 | ||||
-rw-r--r-- | magisk/META-INF/com/google/android/update-binary | 33 | ||||
-rw-r--r-- | magisk/META-INF/com/google/android/updater-script | 1 | ||||
-rw-r--r-- | magisk/module.prop | 6 |
10 files changed, 724 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..607e8a3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/libs +/obj +wireguard-vnet-hdr.zip +*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e308b72 --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +wireguard-vnet-hdr.zip: $(wildcard magisk/* jni/*) + $(firstword $(wildcard $(ANDROID_HOME)/ndk/*/ndk-build)) + bsdtar -cavf $@ -s '#^magisk/\(.*\)#\1#p' -s '#libs/\([^/]\+\)/libtunflags.so#zygisk/\1.so#p' magisk/* libs/*/libtunflags.so + +clean: + $(RM) -r libs obj wireguard-vnet-hdr.zip + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..af4a582 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Work in progress trashbin. Nothing to see here. diff --git a/jni/Android.mk b/jni/Android.mk new file mode 100644 index 0000000..85e45ef --- /dev/null +++ b/jni/Android.mk @@ -0,0 +1,6 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) +LOCAL_MODULE := tunflags +LOCAL_SRC_FILES := tunflags.cpp +LOCAL_LDLIBS := -llog -lstdc++ +include $(BUILD_SHARED_LIBRARY) diff --git a/jni/Application.mk b/jni/Application.mk new file mode 100644 index 0000000..96948f8 --- /dev/null +++ b/jni/Application.mk @@ -0,0 +1,4 @@ +APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 +APP_CPPFLAGS := -std=c++17 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden +APP_STL := none +APP_PLATFORM := android-21 diff --git a/jni/tunflags.cpp b/jni/tunflags.cpp new file mode 100644 index 0000000..1abdc5d --- /dev/null +++ b/jni/tunflags.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2023 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved. + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <sys/xattr.h> +#include <linux/if.h> +#include <linux/if_tun.h> +#include <android/log.h> + +#include "zygisk.hpp" + +using zygisk::Api; +using zygisk::ServerSpecializeArgs; + +#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, "WireGuard/TunFlags", __VA_ARGS__) + +static int setfilecon(const char *path, const char *context) +{ + return setxattr(path, XATTR_NAME_SELINUX, context, strlen(context) + 1, 0); +} + +static int setsockcreatecon(const char *context) +{ + int fd = open("/proc/thread-self/attr/sockcreate", O_WRONLY | O_CLOEXEC); + if (fd < 0) + return fd; + size_t len = strlen(context) + 1; + int ret = write(fd, context, len); + close(fd); + return ret == len ? 0 : -1; +} + +static sockaddr_un offload_addr = { + .sun_family = AF_UNIX, + .sun_path = "/dev/socket/tunflags_setoffload_helper" +}; + +static void offload_setter(int companion_fd) +{ + jint ugid[2]; + if (read(companion_fd, ugid, sizeof(ugid)) != sizeof(ugid)) { + ALOGE("Cannot receive system server uid/gid: %s", strerror(errno)); + close(companion_fd); + return; + } + close(companion_fd); + + if (setsockcreatecon("u:r:system_server:s0") < 0) { + ALOGE("Cannot change thread socket creation context: %s", strerror(errno)); + return; + } + int listener = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (listener < 0) { + ALOGE("Cannot create persistent unix socket: %s", strerror(errno)); + return; + } + umask(0077); + unlink(offload_addr.sun_path); + if (bind(listener, reinterpret_cast<sockaddr *>(&offload_addr), sizeof(offload_addr)) < 0) { + close(listener); + ALOGE("Cannot bind to abstract domain: %s", strerror(errno)); + return; + } + if (chown(offload_addr.sun_path, ugid[0], ugid[1]) < 0) { + ALOGE("Cannot change ownership of %s: %s", offload_addr.sun_path, strerror(errno)); + close(listener); + return; + } + if (setfilecon(offload_addr.sun_path, "u:object_r:statsdw_socket:s0") < 0) { + ALOGE("Cannot set file context of %s: %s", offload_addr.sun_path, strerror(errno)); + close(listener); + return; + } + + for (;;) { + int tun; + char cmsgbuf[CMSG_SPACE(sizeof(tun))], x; + iovec iov = { + .iov_base = &x, + .iov_len = sizeof(x) + }; + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf) + }; + int ret = recvmsg(listener, &msg, MSG_WAITALL); + cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + if (ret < 0 || msg.msg_controllen != sizeof(cmsgbuf) || cmsg == nullptr || + cmsg->cmsg_len != CMSG_LEN(sizeof(tun)) || cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) + continue; + memcpy(&tun, CMSG_DATA(cmsg), sizeof(tun)); + + if (ioctl(tun, TUNSETOFFLOAD, TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6) < 0) + ALOGE("Cannot set offloads on tunnel: %s", strerror(errno)); + close(tun); + } +} + +static bool is_wireguard(JNIEnv *env, jobject vpn_obj) +{ + jclass clazz = env->GetObjectClass(vpn_obj); + if (!clazz) { + ALOGE("Could not determine class of VPN object"); + return false; + } + + jfieldID package_field = env->GetFieldID(clazz, "mPackage", "Ljava/lang/String;"); + if (!package_field) { + ALOGE("Could not find package field ID"); + return false; + } + + jstring package_obj = static_cast<jstring>(env->GetObjectField(vpn_obj, package_field)); + if (!package_obj) { + ALOGE("Could not get package field"); + return false; + } + + const char *package = env->GetStringUTFChars(package_obj, NULL); + bool ret = !strncmp("com.wireguard.android", package, 21); + env->ReleaseStringUTFChars(package_obj, package); + return ret; +} + +static jint(*orig_create)(JNIEnv *, jobject, jint); + +/* Reimplementation of https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/jni/com_android_server_connectivity_Vpn.cpp;l=59;drc=586d0eb1fef2114f01606f4275043abc0f40ff0b */ +static jint create(JNIEnv *env, jobject thiz, jint mtu) +{ + if (!is_wireguard(env, thiz)) + return orig_create(env, thiz, mtu); + + ALOGE("Using hooked function to create IFF_VNET_HDR tun device"); + + static int companion = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + static int inet4 = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0); + int tun; + char cmsgbuf[CMSG_SPACE(sizeof(tun))]; + iovec iov = { + .iov_base = &tun, /* Insignificant memory */ + .iov_len = 1 + }; + msghdr msg = { + .msg_name = &offload_addr, + .msg_namelen = sizeof(offload_addr), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf) + }; + cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(tun)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + + tun = open("/dev/tun", O_RDWR | O_NONBLOCK | O_CLOEXEC); + if (tun < 0) { + ALOGE("Cannot allocate TUN: %s", strerror(errno)); + return -1; + } + + /* Allocate interface */ + ifreq ifr4 = { 0 }; + ifr4.ifr_flags = IFF_TUN | IFF_NO_PI | IFF_VNET_HDR; + if (ioctl(tun, TUNSETIFF, &ifr4) < 0) { + ALOGE("Cannot allocate TUN: %s", strerror(errno)); + goto error; + } + + /* Set offloads by passing the tun to the root companion helper */ + memcpy(CMSG_DATA(cmsg), &tun, sizeof(tun)); + if (sendmsg(companion, &msg, 0) < 0) { + ALOGE("Cannot send TUN fd to companion to set offloads: %s", strerror(errno)); + goto error; + } + + /* Activate interface */ + ifr4.ifr_flags = IFF_UP; + if (ioctl(inet4, SIOCSIFFLAGS, &ifr4) < 0) { + ALOGE("Cannot activate %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + + /* Set MTU if it is specified */ + ifr4.ifr_mtu = mtu; + if (mtu > 0 && ioctl(inet4, SIOCSIFMTU, &ifr4) < 0) { + ALOGE("Cannot set MTU on %s: %s", ifr4.ifr_name, strerror(errno)); + goto error; + } + return tun; + +error: + close(tun); + return -1; +} + +static jint(*orig_register_natives)(JNIEnv *, jclass, const JNINativeMethod *, jint); + +static jint register_natives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint num_methods) +{ + static jmethodID class_getname = env->GetMethodID(env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); + jstring name_string = static_cast<jstring>(env->CallObjectMethod(clazz, class_getname)); + const char *name = env->GetStringUTFChars(name_string, nullptr); + bool is_vpn = !strcmp(name, "com.android.server.connectivity.Vpn"); + env->ReleaseStringUTFChars(name_string, name); + if (!is_vpn) + goto out; + for (jint i = 0; i < num_methods; ++i) { + if (strcmp(methods[i].name, "jniCreate")) + continue; + + orig_create = reinterpret_cast<decltype(orig_create)>(methods[i].fnPtr); + JNINativeMethod *new_methods = new JNINativeMethod[num_methods]; + memcpy(new_methods, methods, num_methods * sizeof(*new_methods)); + new_methods[i].fnPtr = reinterpret_cast<decltype(new_methods[i].fnPtr)>(create); + methods = new_methods; + break; + } + +out: + return orig_register_natives(env, clazz, methods, num_methods); +} + +class TunFlags : public zygisk::ModuleBase +{ +public: + void onLoad(Api *api, JNIEnv *env) override + { + this->api = api; + this->env = env; + } + + void preServerSpecialize(ServerSpecializeArgs *args) override + { + jint ugid[2] = { args->uid, args->gid }; + int companion_fd = api->connectCompanion(); + if (write(companion_fd, ugid, sizeof(ugid)) != sizeof(ugid)) + ALOGE("Cannot communicate uid/gid of system server: %s", strerror(errno)); + close(companion_fd); + } + + void postServerSpecialize(const ServerSpecializeArgs *args) override + { + orig_register_natives = env->functions->RegisterNatives; + JNINativeInterface *new_functions = new JNINativeInterface; + *new_functions = *env->functions; + new_functions->RegisterNatives = register_natives; + env->functions = new_functions; + } + +private: + Api *api; + JNIEnv *env; +}; + +REGISTER_ZYGISK_MODULE(TunFlags) +REGISTER_ZYGISK_COMPANION(offload_setter) diff --git a/jni/zygisk.hpp b/jni/zygisk.hpp new file mode 100644 index 0000000..7c861ad --- /dev/null +++ b/jni/zygisk.hpp @@ -0,0 +1,391 @@ +/* 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. + +#pragma once + +#include <jni.h> + +#define ZYGISK_API_VERSION 4 + +/* + +*************** +* 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! + +********************* +* 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); } + +class ExampleModule : public zygisk::ModuleBase { +public: + void onLoad(zygisk::Api *api, JNIEnv *env) override { + this->api = api; + this->env = env; + } + void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { + JNINativeMethod methods[] = { + { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, + }; + api->hookJniNativeMethods(env, "android/util/Log", methods, 1); + *(void **) &orig_logger_entry_max = methods[0].fnPtr; + } +private: + zygisk::Api *api; + JNIEnv *env; +}; + +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) + +*/ + +namespace zygisk { + +struct Api; +struct AppSpecializeArgs; +struct ServerSpecializeArgs; + +class ModuleBase { +public: + + // 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 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. + // + // 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 + // process will be specialized. + // + // 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([[maybe_unused]] AppSpecializeArgs *args) {} + + // 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 method runs with the same privilege of the app's own code. + virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} + + // This method is called before the system server process is specialized. + // See preAppSpecialize(args) for more info. + virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} + + // 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([[maybe_unused]] const ServerSpecializeArgs *args) {} +}; + +struct AppSpecializeArgs { + // Required arguments. These arguments are guaranteed to exist on all Android versions. + jint &uid; + jint &gid; + jintArray &gids; + jint &runtime_flags; + jobjectArray &rlimits; + jint &mount_external; + jstring &se_info; + jstring &nice_name; + jstring &instruction_set; + jstring &app_data_dir; + + // 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_top_app; + jobjectArray *const pkg_data_info_list; + jobjectArray *const whitelisted_data_info_list; + jboolean *const mount_data_dirs; + jboolean *const mount_storage_dirs; + + AppSpecializeArgs() = delete; +}; + +struct ServerSpecializeArgs { + jint &uid; + jint &gid; + jintArray &gids; + jint &runtime_flags; + jlong &permitted_capabilities; + jlong &effective_capabilities; + + ServerSpecializeArgs() = delete; +}; + +namespace internal { +struct api_table; +template <class T> void entry_impl(api_table *, JNIEnv *); +} + +// These values are used in Api::setOption(Option) +enum Option : int { + // Force Magisk's denylist unmount routines to run on this process. + // + // Setting this option only makes sense in preAppSpecialize. + // The actual unmounting happens during app process specialization. + // + // 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 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 methods due to SELinux restrictions. + // + // 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 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(); + + // Exempt the provided file descriptor from being automatically closed. + // + // This API only make sense in preAppSpecialize; calling this method in any other situation + // is either a no-op (returns true) or an error (returns false). + // + // When false is returned, the provided file descriptor will eventually be closed by zygote. + bool exemptFd(int fd); + + // Hook JNI native methods for a class + // + // 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); + + // 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: + // + // <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. + // Returns false if an error occurred. + bool pltHookCommit(); + +private: + internal::api_table *tbl; + template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *); +}; + +// Register a class as a Zygisk module + +#define REGISTER_ZYGISK_MODULE(clazz) \ +void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ + zygisk::internal::entry_impl<clazz>(table, env); \ +} + +// Register a root companion request handler function for your module +// +// 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 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 globally shared resources. + +#define REGISTER_ZYGISK_COMPANION(func) \ +void zygisk_companion_entry(int client) { func(client); } + +/********************************************************* + * 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 *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), 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() { + return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; +} +inline void Api::setOption(Option opt) { + if (tbl->setOption) tbl->setOption(tbl->impl, opt); +} +inline uint32_t Api::getFlags() { + return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; +} +inline bool Api::exemptFd(int fd) { + return tbl->exemptFd != nullptr && tbl->exemptFd(fd); +} +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(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { + if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc); +} +inline bool Api::pltHookCommit() { + return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); +} + +} // namespace zygisk + +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); + +} // extern "C" diff --git a/magisk/META-INF/com/google/android/update-binary b/magisk/META-INF/com/google/android/update-binary new file mode 100644 index 0000000..28b48e5 --- /dev/null +++ b/magisk/META-INF/com/google/android/update-binary @@ -0,0 +1,33 @@ +#!/sbin/sh + +################# +# Initialization +################# + +umask 022 + +# echo before loading util_functions +ui_print() { echo "$1"; } + +require_new_magisk() { + ui_print "*******************************" + ui_print " Please install Magisk v20.4+! " + ui_print "*******************************" + exit 1 +} + +######################### +# Load util_functions.sh +######################### + +OUTFD=$2 +ZIPFILE=$3 + +mount /data 2>/dev/null + +[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk +. /data/adb/magisk/util_functions.sh +[ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk + +install_module +exit 0 diff --git a/magisk/META-INF/com/google/android/updater-script b/magisk/META-INF/com/google/android/updater-script new file mode 100644 index 0000000..11d5c96 --- /dev/null +++ b/magisk/META-INF/com/google/android/updater-script @@ -0,0 +1 @@ +#MAGISK diff --git a/magisk/module.prop b/magisk/module.prop new file mode 100644 index 0000000..2e2c84e --- /dev/null +++ b/magisk/module.prop @@ -0,0 +1,6 @@ +id=wireguard-vnet-hdr +name=WireGuard IFF_VNET_HDR Twiddler +version=1.0 +versionCode=1 +author=zx2c4 +description=Adds IFF_VNET_HDR to tun devices made by WireGuard app |