aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason A. Donenfeld <Jason@zx2c4.com>2023-10-21 17:21:21 +0200
committerJason A. Donenfeld <Jason@zx2c4.com>2023-10-21 18:26:15 +0200
commit3f3c5eba134b7155d5e011d6496b4b585b41a484 (patch)
tree0a14dc3aaefb2cc6d96f5d1ceadd5fc115cdf1e5
downloadwireguard-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--.gitignore4
-rw-r--r--Makefile8
-rw-r--r--README.md1
-rw-r--r--jni/Android.mk6
-rw-r--r--jni/Application.mk4
-rw-r--r--jni/tunflags.cpp270
-rw-r--r--jni/zygisk.hpp391
-rw-r--r--magisk/META-INF/com/google/android/update-binary33
-rw-r--r--magisk/META-INF/com/google/android/updater-script1
-rw-r--r--magisk/module.prop6
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