├── module ├── .gitignore ├── src │ └── main │ │ └── AndroidManifest.xml ├── build.gradle └── jni │ ├── Application.mk │ ├── Android.mk │ ├── util.h │ ├── Android-bhook.mk │ ├── example.cpp │ ├── mipushfake.cpp │ └── zygisk.hpp ├── dist └── zygisk-module-mipushfake │ ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── updater-script │ │ └── update-binary │ ├── customize.sh │ └── module.prop ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── .gitmodules ├── gradle.properties ├── gradlew.bat ├── README.md └── gradlew /module/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /obj 4 | -------------------------------------------------------------------------------- /dist/zygisk-module-mipushfake/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK -------------------------------------------------------------------------------- /dist/zygisk-module-mipushfake/customize.sh: -------------------------------------------------------------------------------- 1 | 2 | mkdir -p /data/adb/mi-push-fake/ 3 | touch /data/adb/mi-push-fake/packages.txt -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tinkernels/zygisk-module-mipushfake/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /module/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /dist/zygisk-module-mipushfake/module.prop: -------------------------------------------------------------------------------- 1 | id=zygisk-module-mipushfake 2 | name=zygisk-module-mipushfake 3 | version=v1.0.0 4 | versionCode=10000 5 | author=tinkernels 6 | description=zygisk-module-mipushfake -------------------------------------------------------------------------------- /module/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | externalNativeBuild { 7 | ndkBuild { 8 | path("jni/Android.mk") 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | include ':module' 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libcxx"] 2 | path = module/jni/libcxx 3 | url = https://github.com/topjohnwu/libcxx.git 4 | [submodule "module/jni/bhook"] 5 | path = module/jni/bhook 6 | url = https://github.com/bytedance/bhook.git 7 | branch = main 8 | -------------------------------------------------------------------------------- /module/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 2 | # APP_ABI := x86_64 3 | APP_CPPFLAGS := -std=c++17 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden 4 | APP_STL := none 5 | APP_PLATFORM := android-21 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Sep 26 02:13:18 PDT 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /module/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_MODULE := mipushfake 5 | LOCAL_SRC_FILES := mipushfake.cpp 6 | LOCAL_STATIC_LIBRARIES := libcxx libbhook 7 | LOCAL_LDLIBS := -llog 8 | include $(BUILD_SHARED_LIBRARY) 9 | 10 | include jni/libcxx/Android.mk 11 | 12 | include jni/Android-bhook.mk 13 | 14 | # If you do not want to use libc++, link to system stdc++ 15 | # so that you can at least call the new operator in your code 16 | 17 | # include $(CLEAR_VARS) 18 | # LOCAL_MODULE := example 19 | # LOCAL_SRC_FILES := example.cpp 20 | # LOCAL_LDLIBS := -llog -lstdc++ 21 | # include $(BUILD_SHARED_LIBRARY) 22 | -------------------------------------------------------------------------------- /dist/zygisk-module-mipushfake/META-INF/com/google/android/update-binary: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ################# 4 | # Initialization 5 | ################# 6 | 7 | umask 022 8 | 9 | # echo before loading util_functions 10 | ui_print() { echo "$1"; } 11 | 12 | require_new_magisk() { 13 | ui_print "*******************************" 14 | ui_print " Please install Magisk v20.4+! " 15 | ui_print "*******************************" 16 | exit 1 17 | } 18 | 19 | ######################### 20 | # Load util_functions.sh 21 | ######################### 22 | 23 | OUTFD=$2 24 | ZIPFILE=$3 25 | 26 | mount /data 2>/dev/null 27 | 28 | [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk 29 | . /data/adb/magisk/util_functions.sh 30 | [ $MAGISK_VER_CODE -lt 20400 ] && require_new_magisk 31 | 32 | install_module 33 | exit 0 -------------------------------------------------------------------------------- /module/jni/util.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Don Johnny on 2022/4/11. 3 | // 4 | 5 | #ifndef ZYGISK_MODULE_MIPUSHFAKE_UTIL_H 6 | #define ZYGISK_MODULE_MIPUSHFAKE_UTIL_H 7 | 8 | // trim from start (in place) 9 | static inline void l_trim(std::string &s) { 10 | s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) { 11 | return !std::isspace(ch); 12 | })); 13 | } 14 | 15 | // trim from end (in place) 16 | static inline void r_trim(std::string &s) { 17 | s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) { 18 | return !std::isspace(ch); 19 | }).base(), 20 | s.end()); 21 | } 22 | 23 | // trim from both ends (in place) 24 | static inline void trim(std::string &s) { 25 | l_trim(s); 26 | r_trim(s); 27 | } 28 | 29 | #endif //ZYGISK_MODULE_MIPUSHFAKE_UTIL_H 30 | -------------------------------------------------------------------------------- /module/jni/Android-bhook.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir)/bhook/bytehook/src/main/cpp 2 | 3 | # libcxx defines 4 | bhook_export_includes := $(LOCAL_PATH)/include 5 | bhook_includes := $(bhook_export_includes) $(LOCAL_PATH) $(LOCAL_PATH)/third_party/bsd $(LOCAL_PATH)/third_party/lss 6 | 7 | bhook_cxxflags := -Oz -flto -faddrsig -ffunction-sections -fdata-sections 8 | bhook_export_cxxflags := $(bhook_cxxflags) 9 | bhook_export_ldflags := -Oz -flto 10 | 11 | include $(CLEAR_VARS) 12 | 13 | LOCAL_MODULE := libbhook 14 | 15 | bhook_c_files := $(wildcard $(LOCAL_PATH)/*.c) 16 | bhook_c_files := $(bhook_c_files:$(LOCAL_PATH)/%=%) 17 | 18 | LOCAL_SRC_FILES := $(bhook_c_files) 19 | LOCAL_C_INCLUDES := $(bhook_includes) 20 | LOCAL_CPPFLAGS := $(bhook_cxxflags) 21 | LOCAL_EXPORT_C_INCLUDES := $(bhook_export_includes) 22 | LOCAL_EXPORT_CPPFLAGS := $(bhook_export_cxxflags) 23 | LOCAL_EXPORT_LDFLAGS := $(bhook_export_ldflags) 24 | LOCAL_ARM_NEON := false 25 | 26 | include $(BUILD_STATIC_LIBRARY) 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx3072m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | -------------------------------------------------------------------------------- /module/jni/example.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "zygisk.hpp" 7 | 8 | using zygisk::Api; 9 | using zygisk::AppSpecializeArgs; 10 | using zygisk::ServerSpecializeArgs; 11 | 12 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "Magisk", __VA_ARGS__) 13 | 14 | class MyModule : public zygisk::ModuleBase { 15 | public: 16 | void onLoad(Api *api, JNIEnv *env) override { 17 | this->api = api; 18 | this->env = env; 19 | } 20 | 21 | void preAppSpecialize(AppSpecializeArgs *args) override { 22 | // Use JNI to fetch our process name 23 | const char *process = env->GetStringUTFChars(args->nice_name, nullptr); 24 | preSpecialize(process); 25 | env->ReleaseStringUTFChars(args->nice_name, process); 26 | } 27 | 28 | void preServerSpecialize(ServerSpecializeArgs *args) override { 29 | preSpecialize("system_server"); 30 | } 31 | 32 | private: 33 | Api *api; 34 | JNIEnv *env; 35 | 36 | void preSpecialize(const char *process) { 37 | // Demonstrate connecting to to companion process 38 | // We ask the companion for a random number 39 | unsigned r = 0; 40 | int fd = api->connectCompanion(); 41 | read(fd, &r, sizeof(r)); 42 | close(fd); 43 | LOGD("example: process=[%s], r=[%u]\n", process, r); 44 | 45 | // Since we do not hook any functions, we should let Zygisk dlclose ourselves 46 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 47 | } 48 | 49 | }; 50 | 51 | static int urandom = -1; 52 | 53 | static void companion_handler(int i) { 54 | if (urandom < 0) { 55 | urandom = open("/dev/urandom", O_RDONLY); 56 | } 57 | unsigned r; 58 | read(urandom, &r, sizeof(r)); 59 | LOGD("example: companion r=[%u]\n", r); 60 | write(i, &r, sizeof(r)); 61 | } 62 | 63 | REGISTER_ZYGISK_MODULE(MyModule) 64 | REGISTER_ZYGISK_COMPANION(companion_handler) 65 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developing Zygisk Modules 2 | 3 | This repository hosts a template zygisk module for developers to start developing Zygisk modules. Before developing Zygisk modules, you should first check out the official documentation for [Magisk Modules](https://topjohnwu.github.io/Magisk/guides.html). Do not fork this repository for your new module; either manually clone this repository, or press the "Use this template" button in the GitHub UI. 4 | 5 | This repository is archived because it is meant to be read-only; the project is not abandoned. For any issues, please report them to the main Magisk repository. 6 | 7 | ## API 8 | 9 | - The canonical URL of the latest public Zygisk API is [module/jni/zygisk.hpp](https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp). 10 | - The header file is self documented; directly refer to the header source code for all Zygisk API details. 11 | - Magisk is committed to maintain backwards compatibility forever. That is, whenever there is an API update for Zygisk in a newer Magisk version, Magisk can always load Zygisk modules built for an older Zygisk API. If you do not need the new features introduced in newer API versions, feel free to stay on the older API version to maintain maximum compatibility. 12 | 13 | | Zygisk API | Minimal Magisk | 14 | | :--------: | :------------: | 15 | | 2 | 24000 | 16 | | 3 | 24300 | 17 | 18 | ## Notes 19 | 20 | - This repository can be opened with Android Studio. 21 | - Developing Zygisk modules requires a modern C++ compiler. Please use NDK r21 or higher. 22 | - All the C++ code is in the [module/jni](https://github.com/topjohnwu/zygisk-module-sample/tree/master/module/jni) folder. 23 | - DO NOT modify the default configurations in `Application.mk` unless you know what you are doing. 24 | 25 | ## C++ STL 26 | 27 | - The `APP_STL` variable in `Application.mk` is set to `none`. **DO NOT** use any C++ STL included in NDK. 28 | - If you'd like to use C++ STL, you **have to** use the `libcxx` included as a git submodule in this repository. Zygisk modules' code are injected into Zygote, and the included `libc++` is setup to be lightweight and fully self contained that prevents conflicts with the hosting program. 29 | - If you do not need STL, link to the system `libstdc++` so that you can at least call the `new` operator. 30 | - Both configurations are demonstrated in the example `Android.mk`. 31 | 32 | ## Building 33 | 34 | - In the `module` folder, call [`ndk-build`](https://developer.android.com/ndk/guides/ndk-build) to compile your modules. 35 | - Your module libraries will be in `libs//lib.so`. 36 | - Copy the libraries into your module's `zygisk` folder, with the ABI as it's file name: 37 | 38 | ``` 39 | module_id 40 | ├── module.prop 41 | └── zygisk 42 | ├── arm64-v8a.so 43 | ├── armeabi-v7a.so 44 | ├── x86.so 45 | └── x86_64.so 46 | ``` 47 | 48 | ## License 49 | 50 | Although the main Magisk project is licensed under GPLv3, the Zygisk API and its headers are not. Every file in this repository is released to the public domain, so you don't have to worry about any licensing issues while developing Zygisk modules. 51 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /module/jni/mipushfake.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "zygisk.hpp" 18 | #include "util.h" 19 | #include "bytehook.h" 20 | 21 | using zygisk::Api; 22 | using zygisk::AppSpecializeArgs; 23 | using zygisk::ServerSpecializeArgs; 24 | 25 | #define LOG_DEBUG_MPF(...) __android_log_print(ANDROID_LOG_DEBUG, "MiPushFake", __VA_ARGS__) 26 | #define LOG_INFO_MPF(...) __android_log_print(ANDROID_LOG_INFO, "MiPushFake", __VA_ARGS__) 27 | #define LOG_WARN_MPF(...) __android_log_print(ANDROID_LOG_WARN, "MiPushFake", __VA_ARGS__) 28 | #define LOG_ERROR_MPF(...) __android_log_print(ANDROID_LOG_ERROR, "MiPushFake", __VA_ARGS__) 29 | #define LOG_BYTE_HOOK(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, "ByteHook", fmt, ##__VA_ARGS__) 30 | 31 | static std::vector packages_2_work; 32 | static long time_next_read_packages_2_work = 0; 33 | const static std::string conf_file_path = "/data/adb/mi-push-fake/packages.txt"; 34 | // properties to hack. 35 | const static char *brand_hack = "Xiaomi"; 36 | const static char *internal_storage_hack = "/sdcard/"; 37 | const static char *fingerprint_hack = "Xiaomi/missi/missi:12/SP1A.210812.016/V12.0.9.0.SLKCNXM:user/release-keys"; 38 | const static char *version_name_hack = "V12"; 39 | const static char *version_code_hack = "10"; 40 | const static char *version_code_time_hack = "1592409600"; 41 | const static char *notch_hack = "1"; 42 | const static std::map props_4_hack{ 43 | {"ro.product.manufacturer", brand_hack}, 44 | {"ro.product.system.manufacturer", brand_hack}, 45 | {"ro.product.vendor.manufacturer", brand_hack}, 46 | {"ro.product.brand", brand_hack}, 47 | {"ro.product.system.brand", brand_hack}, 48 | {"ro.product.vendor.brand", brand_hack}, 49 | {"ro.fota.oem", brand_hack}, 50 | {"ro.system.build.fingerprint", fingerprint_hack}, 51 | {"ro.miui.internal.storage", internal_storage_hack}, 52 | {"ro.miui.ui.version.name", version_name_hack}, 53 | {"ro.miui.ui.version.code", version_code_hack}, 54 | {"ro.miui.version.code_time", version_code_time_hack}, 55 | {"ro.miui.notch", notch_hack} 56 | }; 57 | 58 | // callback func for __system_property_read_callback 59 | typedef void (*spr_param_cb_t)(void *cookie, 60 | const char *name, 61 | const char *value, 62 | uint32_t serial); 63 | 64 | // __system_property_read_callback 65 | typedef void (*__system_property_read_callback_t)(const prop_info *pi, 66 | spr_param_cb_t *callback, 67 | void *cookie); 68 | 69 | // __system_property_get 70 | typedef int (*__system_property_get_t)(const char *name, char *value); 71 | 72 | #define MPF_BHOOK_DEF(fn) \ 73 | static fn##_t fn##_prev = nullptr; \ 74 | static bytehook_stub_t fn##_stub = nullptr; \ 75 | static void fn##_hooked_callback(bytehook_stub_t task_stub, int status_code, const char *caller_path_name, \ 76 | const char *sym_name, void *new_func, void *prev_func, void *arg) { \ 77 | if (BYTEHOOK_STATUS_CODE_ORIG_ADDR == status_code) { \ 78 | fn##_prev = (fn##_t)prev_func; \ 79 | LOG_BYTE_HOOK(">>>>> save original address: %lu", (unsigned long)prev_func); \ 80 | } else { \ 81 | LOG_BYTE_HOOK(">>>>> hooked. stub: %lu, status: %d, caller_path_name: %s, sym_name: %s, new_func: %lu, prev_func: %lu, arg: %lu", \ 82 | (unsigned long)task_stub, status_code, caller_path_name, sym_name, (unsigned long)new_func, \ 83 | (unsigned long)prev_func, (unsigned long)arg); \ 84 | } \ 85 | } 86 | 87 | MPF_BHOOK_DEF(__system_property_read_callback) 88 | 89 | MPF_BHOOK_DEF(__system_property_get) 90 | 91 | thread_local spr_param_cb_t spr_param_cb_prev = nullptr; 92 | 93 | static void spr_param_cb_new(void *cookie, const char *name, const char *value, uint32_t serial) { 94 | if (spr_param_cb_prev == nullptr) { 95 | LOG_ERROR_MPF("spr_param_cb_new previous function is null"); 96 | } 97 | auto search_ = props_4_hack.find(name); 98 | if (search_ != props_4_hack.end()) { 99 | LOG_DEBUG_MPF("spr_param_cb_new: [%s] -> [%s]", name, 100 | search_->second.c_str()); 101 | return spr_param_cb_prev(cookie, name, search_->second.c_str(), serial); 102 | } 103 | LOG_DEBUG_MPF("spr_param_cb_new call prev: [%s] -> [%s]", name, 104 | search_->second.c_str()); 105 | spr_param_cb_prev(cookie, name, value, serial); 106 | } 107 | 108 | static void system_property_read_callback_new(const prop_info *pi, 109 | spr_param_cb_t callback, 110 | void *cookie) { 111 | BYTEHOOK_STACK_SCOPE(); 112 | if (pi == nullptr) { 113 | LOG_DEBUG_MPF("system_property_read_callback_new prop_info is null"); 114 | return; 115 | } 116 | spr_param_cb_prev = callback; 117 | LOG_DEBUG_MPF("system_property_read_callback_new call prev: prop_info[%lu] cookie[%lu]", 118 | (unsigned long) pi, (unsigned long) cookie); 119 | BYTEHOOK_CALL_PREV(system_property_read_callback_new, pi, spr_param_cb_new, cookie); 120 | } 121 | 122 | static int system_property_get_new(const char *name, char *value) { 123 | BYTEHOOK_STACK_SCOPE(); 124 | auto search_ = props_4_hack.find(name); 125 | if (search_ != props_4_hack.end()) { 126 | strcpy(value, search_->second.c_str()); 127 | LOG_DEBUG_MPF("system_property_get_new: [%s] -> [%s]", name, value); 128 | return 1; 129 | } 130 | LOG_DEBUG_MPF("system_property_get_new call prev: [%s] -> [%s]", name, value); 131 | return BYTEHOOK_CALL_PREV(system_property_get_new, name, value); 132 | } 133 | 134 | static void read_packages_2_work(const std::string &file) { 135 | packages_2_work.clear(); 136 | std::ifstream i_file_(file); 137 | std::string line_; 138 | while (std::getline(i_file_, line_)) { 139 | trim(line_); 140 | if (line_.empty()) { 141 | continue; 142 | } 143 | packages_2_work.push_back(line_); 144 | } 145 | for (const auto &p: packages_2_work) { 146 | LOG_INFO_MPF("mi push work package: [%s]", p.c_str()); 147 | } 148 | } 149 | 150 | class MiPushFakeModule : public zygisk::ModuleBase { 151 | public: 152 | void onLoad(Api *pApi, JNIEnv *pEnv) override { 153 | this->api = pApi; 154 | this->env = pEnv; 155 | LOG_INFO_MPF("module loaded"); 156 | } 157 | 158 | void preAppSpecialize(AppSpecializeArgs *args) override { 159 | // Use JNI to fetch our process name 160 | const char *process = env->GetStringUTFChars(args->nice_name, nullptr); 161 | pre_specialize(process); 162 | env->ReleaseStringUTFChars(args->nice_name, process); 163 | } 164 | 165 | void preServerSpecialize(ServerSpecializeArgs *args) override { 166 | pre_specialize("system_server"); 167 | } 168 | 169 | void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) override { 170 | const char *process = env->GetStringUTFChars(args->nice_name, nullptr); 171 | if (!this->IfManipulate) { 172 | LOG_DEBUG_MPF("skip build info hijack for: [%s]", process); 173 | env->ReleaseStringUTFChars(args->nice_name, process); 174 | return; 175 | } 176 | LOG_INFO_MPF("inject android.os.Build for %s ", process); 177 | 178 | jclass build_class = env->FindClass("android/os/Build"); 179 | if (build_class == nullptr) { 180 | LOG_WARN_MPF("failed to inject android.os.Build for %s due to build is null", process); 181 | return; 182 | } 183 | 184 | jstring new_brand = env->NewStringUTF("Xiaomi"); 185 | jstring new_product = env->NewStringUTF("Xiaomi"); 186 | jstring new_model = env->NewStringUTF("Mi 10"); 187 | 188 | jfieldID brand_id = env->GetStaticFieldID(build_class, "BRAND", "Ljava/lang/String;"); 189 | if (brand_id != nullptr) { 190 | env->SetStaticObjectField(build_class, brand_id, new_brand); 191 | } 192 | 193 | jfieldID manufacturer_id = env->GetStaticFieldID(build_class, "MANUFACTURER", "Ljava/lang/String;"); 194 | if (manufacturer_id != nullptr) { 195 | env->SetStaticObjectField(build_class, manufacturer_id, new_brand); 196 | } 197 | 198 | jfieldID product_id = env->GetStaticFieldID(build_class, "PRODUCT", "Ljava/lang/String;"); 199 | if (product_id != nullptr) { 200 | env->SetStaticObjectField(build_class, product_id, new_product); 201 | } 202 | 203 | jfieldID device_id = env->GetStaticFieldID(build_class, "DEVICE", "Ljava/lang/String;"); 204 | if (device_id != nullptr) { 205 | env->SetStaticObjectField(build_class, device_id, new_product); 206 | } 207 | 208 | jfieldID model_id = env->GetStaticFieldID(build_class, "MODEL", "Ljava/lang/String;"); 209 | if (model_id != nullptr) { 210 | env->SetStaticObjectField(build_class, model_id, new_model); 211 | } 212 | 213 | if(env->ExceptionCheck()) 214 | { 215 | env->ExceptionClear(); 216 | } 217 | 218 | env->DeleteLocalRef(new_brand); 219 | env->DeleteLocalRef(new_product); 220 | env->DeleteLocalRef(new_model); 221 | 222 | env->ReleaseStringUTFChars(args->nice_name, process); 223 | } 224 | 225 | private: 226 | Api *api{}; 227 | JNIEnv *env{}; 228 | bool IfManipulate = false; 229 | 230 | bool is_process_2_work(const char *process) { 231 | int match_ = 0; 232 | LOG_DEBUG_MPF("process=[%s]", process); 233 | int fd = api->connectCompanion(); 234 | if (write(fd, process, strlen(process) + 1) <= 0) { 235 | LOG_WARN_MPF("write socket failed"); 236 | } 237 | if (read(fd, &match_, sizeof(match_)) <= 0) { 238 | LOG_WARN_MPF("read socket failed"); 239 | } 240 | close(fd); 241 | LOG_DEBUG_MPF("process=[%s] to manipulate: %d", process, match_); 242 | return match_ > 0; 243 | } 244 | 245 | void pre_specialize(const char *process) { 246 | LOG_INFO_MPF("Zygisk preSpecialize within [%s]", process); 247 | bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false); 248 | if (is_process_2_work(process)) { 249 | this->IfManipulate = true; 250 | LOG_INFO_MPF("Will hook property functions"); 251 | auto hook_stub_ = bytehook_hook_all(nullptr, "__system_property_get", 252 | (void *) system_property_get_new, 253 | __system_property_get_hooked_callback, nullptr); 254 | LOG_DEBUG_MPF("hook result __system_property_get stub: %ld", 255 | (unsigned long) hook_stub_); 256 | hook_stub_ = bytehook_hook_all(nullptr, "__system_property_read_callback", 257 | (void *) system_property_read_callback_new, 258 | __system_property_read_callback_hooked_callback, 259 | nullptr); 260 | LOG_DEBUG_MPF("hook result __system_property_read_callback stub: %ld", 261 | (unsigned long) hook_stub_); 262 | 263 | } 264 | } 265 | }; 266 | 267 | static std::mutex conf_mutex; 268 | 269 | static void companion_handler(int fd) { 270 | if (access(conf_file_path.c_str(), F_OK) != 0) { 271 | LOG_ERROR_MPF("can't access config file"); 272 | return; 273 | } 274 | std::lock_guard lock(conf_mutex); 275 | auto now_ts_ = std::time(nullptr); 276 | if (now_ts_ > time_next_read_packages_2_work) { 277 | read_packages_2_work(conf_file_path); 278 | time_next_read_packages_2_work = now_ts_ + 60; // reread after seconds. 279 | } 280 | char buff_[BUFSIZ]; 281 | memset(buff_, 0, BUFSIZ); 282 | if (read(fd, buff_, BUFSIZ) <= 0) { 283 | return; 284 | } 285 | auto package_ = std::string(buff_); 286 | LOG_DEBUG_MPF("package [%s] for matching", package_.c_str()); 287 | int match_ = 0; 288 | for (auto &p: packages_2_work) { 289 | trim(p); 290 | trim(package_); 291 | std::transform(p.begin(), p.end(), p.begin(), 292 | [](unsigned char c){ return std::tolower(c); }); 293 | std::transform(package_.begin(), package_.end(), package_.begin(), 294 | [](unsigned char c){ return std::tolower(c); }); 295 | if (strncmp(p.c_str(), package_.c_str(), strlen(p.c_str())) == 0) { 296 | match_ = 1; 297 | LOG_DEBUG_MPF("package [%s] match %s", package_.c_str(), p.c_str()); 298 | } 299 | } 300 | LOG_DEBUG_MPF("package [%s] match result: %d", package_.c_str(), match_); 301 | if (write(fd, &match_, sizeof(match_)) < sizeof(match_)) { 302 | LOG_ERROR_MPF("partial/failed write"); 303 | } 304 | } 305 | 306 | REGISTER_ZYGISK_MODULE(MiPushFakeModule) 307 | 308 | REGISTER_ZYGISK_COMPANION(companion_handler) 309 | -------------------------------------------------------------------------------- /module/jni/zygisk.hpp: -------------------------------------------------------------------------------- 1 | // This is the public API for Zygisk modules. 2 | // DO NOT MODIFY ANY CODE IN THIS HEADER. 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | #define ZYGISK_API_VERSION 3 9 | 10 | /* 11 | 12 | Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. 13 | Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. 14 | 15 | Please note that modules will only be loaded after zygote has forked the child process. 16 | THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON! 17 | 18 | Example code: 19 | 20 | static jint (*orig_logger_entry_max)(JNIEnv *env); 21 | static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } 22 | 23 | static void example_handler(int socket) { ... } 24 | 25 | class ExampleModule : public zygisk::ModuleBase { 26 | public: 27 | void onLoad(zygisk::Api *api, JNIEnv *env) override { 28 | this->api = api; 29 | this->env = env; 30 | } 31 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { 32 | JNINativeMethod methods[] = { 33 | { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, 34 | }; 35 | api->hookJniNativeMethods(env, "android/util/Log", methods, 1); 36 | *(void **) &orig_logger_entry_max = methods[0].fnPtr; 37 | } 38 | private: 39 | zygisk::Api *api; 40 | JNIEnv *env; 41 | }; 42 | 43 | REGISTER_ZYGISK_MODULE(ExampleModule) 44 | 45 | REGISTER_ZYGISK_COMPANION(example_handler) 46 | 47 | */ 48 | 49 | namespace zygisk { 50 | 51 | struct Api; 52 | struct AppSpecializeArgs; 53 | struct ServerSpecializeArgs; 54 | 55 | class ModuleBase { 56 | public: 57 | 58 | // This function is called when the module is loaded into the target process. 59 | // A Zygisk API handle will be sent as an argument; call utility functions or interface 60 | // with Zygisk through this handle. 61 | virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} 62 | 63 | // This function is called before the app process is specialized. 64 | // At this point, the process just got forked from zygote, but no app specific specialization 65 | // is applied. This means that the process does not have any sandbox restrictions and 66 | // still runs with the same privilege of zygote. 67 | // 68 | // All the arguments that will be sent and used for app specialization is passed as a single 69 | // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app 70 | // process will be specialized. 71 | // 72 | // If you need to run some operations as superuser, you can call Api::connectCompanion() to 73 | // get a socket to do IPC calls with a root companion process. 74 | // See Api::connectCompanion() for more info. 75 | virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} 76 | 77 | // This function is called after the app process is specialized. 78 | // At this point, the process has all sandbox restrictions enabled for this application. 79 | // This means that this function runs as the same privilege of the app's own code. 80 | virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} 81 | 82 | // This function is called before the system server process is specialized. 83 | // See preAppSpecialize(args) for more info. 84 | virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} 85 | 86 | // This function is called after the system server process is specialized. 87 | // At this point, the process runs with the privilege of system_server. 88 | virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} 89 | }; 90 | 91 | struct AppSpecializeArgs { 92 | // Required arguments. These arguments are guaranteed to exist on all Android versions. 93 | jint &uid; 94 | jint &gid; 95 | jintArray &gids; 96 | jint &runtime_flags; 97 | jobjectArray &rlimits; 98 | jint &mount_external; 99 | jstring &se_info; 100 | jstring &nice_name; 101 | jstring &instruction_set; 102 | jstring &app_data_dir; 103 | 104 | // Optional arguments. Please check whether the pointer is null before de-referencing 105 | jintArray *const fds_to_ignore; 106 | jboolean *const is_child_zygote; 107 | jboolean *const is_top_app; 108 | jobjectArray *const pkg_data_info_list; 109 | jobjectArray *const whitelisted_data_info_list; 110 | jboolean *const mount_data_dirs; 111 | jboolean *const mount_storage_dirs; 112 | 113 | AppSpecializeArgs() = delete; 114 | }; 115 | 116 | struct ServerSpecializeArgs { 117 | jint &uid; 118 | jint &gid; 119 | jintArray &gids; 120 | jint &runtime_flags; 121 | jlong &permitted_capabilities; 122 | jlong &effective_capabilities; 123 | 124 | ServerSpecializeArgs() = delete; 125 | }; 126 | 127 | namespace internal { 128 | struct api_table; 129 | template void entry_impl(api_table *, JNIEnv *); 130 | } 131 | 132 | // These values are used in Api::setOption(Option) 133 | enum Option : int { 134 | // Force Magisk's denylist unmount routines to run on this process. 135 | // 136 | // Setting this option only makes sense in preAppSpecialize. 137 | // The actual unmounting happens during app process specialization. 138 | // 139 | // Set this option to force all Magisk and modules' files to be unmounted from the 140 | // mount namespace of the process, regardless of the denylist enforcement status. 141 | FORCE_DENYLIST_UNMOUNT = 0, 142 | 143 | // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. 144 | // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. 145 | // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. 146 | DLCLOSE_MODULE_LIBRARY = 1, 147 | }; 148 | 149 | // Bit masks of the return value of Api::getFlags() 150 | enum StateFlag : uint32_t { 151 | // The user has granted root access to the current process 152 | PROCESS_GRANTED_ROOT = (1u << 0), 153 | 154 | // The current process was added on the denylist 155 | PROCESS_ON_DENYLIST = (1u << 1), 156 | }; 157 | 158 | // All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded 159 | // from the specialized process afterwards. 160 | struct Api { 161 | 162 | // Connect to a root companion process and get a Unix domain socket for IPC. 163 | // 164 | // This API only works in the pre[XXX]Specialize functions due to SELinux restrictions. 165 | // 166 | // The pre[XXX]Specialize functions run with the same privilege of zygote. 167 | // If you would like to do some operations with superuser permissions, register a handler 168 | // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). 169 | // Another good use case for a companion process is that if you want to share some resources 170 | // across multiple processes, hold the resources in the companion process and pass it over. 171 | // 172 | // The root companion process is ABI aware; that is, when calling this function from a 32-bit 173 | // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. 174 | // 175 | // Returns a file descriptor to a socket that is connected to the socket passed to your 176 | // module's companion request handler. Returns -1 if the connection attempt failed. 177 | int connectCompanion(); 178 | 179 | // Get the file descriptor of the root folder of the current module. 180 | // 181 | // This API only works in the pre[XXX]Specialize functions. 182 | // Accessing the directory returned is only possible in the pre[XXX]Specialize functions 183 | // or in the root companion process (assuming that you sent the fd over the socket). 184 | // Both restrictions are due to SELinux and UID. 185 | // 186 | // Returns -1 if errors occurred. 187 | int getModuleDir(); 188 | 189 | // Set various options for your module. 190 | // Please note that this function accepts one single option at a time. 191 | // Check zygisk::Option for the full list of options available. 192 | void setOption(Option opt); 193 | 194 | // Get information about the current process. 195 | // Returns bitwise-or'd zygisk::StateFlag values. 196 | uint32_t getFlags(); 197 | 198 | // Hook JNI native methods for a class 199 | // 200 | // Lookup all registered JNI native methods and replace it with your own functions. 201 | // The original function pointer will be saved in each JNINativeMethod's fnPtr. 202 | // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr 203 | // will be set to nullptr. 204 | void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); 205 | 206 | // For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`. 207 | // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. 208 | void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc); 209 | 210 | // For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`. 211 | // If `symbol` is nullptr, then all symbols will be excluded. 212 | void pltHookExclude(const char *regex, const char *symbol); 213 | 214 | // Commit all the hooks that was previously registered. 215 | // Returns false if an error occurred. 216 | bool pltHookCommit(); 217 | 218 | private: 219 | internal::api_table *impl; 220 | template friend void internal::entry_impl(internal::api_table *, JNIEnv *); 221 | }; 222 | 223 | // Register a class as a Zygisk module 224 | 225 | #define REGISTER_ZYGISK_MODULE(clazz) \ 226 | void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ 227 | zygisk::internal::entry_impl(table, env); \ 228 | } 229 | 230 | // Register a root companion request handler function for your module 231 | // 232 | // The function runs in a superuser daemon process and handles a root companion request from 233 | // your module running in a target process. The function has to accept an integer value, 234 | // which is a socket that is connected to the target process. 235 | // See Api::connectCompanion() for more info. 236 | // 237 | // NOTE: the function can run concurrently on multiple threads. 238 | // Be aware of race conditions if you have a globally shared resource. 239 | 240 | #define REGISTER_ZYGISK_COMPANION(func) \ 241 | void zygisk_companion_entry(int client) { func(client); } 242 | 243 | /************************************************************************************ 244 | * All the code after this point is internal code used to interface with Zygisk 245 | * and guarantee ABI stability. You do not have to understand what it is doing. 246 | ************************************************************************************/ 247 | 248 | namespace internal { 249 | 250 | struct module_abi { 251 | long api_version; 252 | ModuleBase *_this; 253 | 254 | void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); 255 | void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); 256 | void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); 257 | void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); 258 | 259 | module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) { 260 | preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); }; 261 | postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); }; 262 | preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); }; 263 | postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); }; 264 | } 265 | }; 266 | 267 | struct api_table { 268 | // These first 2 entries are permanent, shall never change 269 | void *_this; 270 | bool (*registerModule)(api_table *, module_abi *); 271 | 272 | // Utility functions 273 | void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); 274 | void (*pltHookRegister)(const char *, const char *, void *, void **); 275 | void (*pltHookExclude)(const char *, const char *); 276 | bool (*pltHookCommit)(); 277 | 278 | // Zygisk functions 279 | int (*connectCompanion)(void * /* _this */); 280 | void (*setOption)(void * /* _this */, Option); 281 | int (*getModuleDir)(void * /* _this */); 282 | uint32_t (*getFlags)(void * /* _this */); 283 | }; 284 | 285 | template 286 | void entry_impl(api_table *table, JNIEnv *env) { 287 | ModuleBase *module = new T(); 288 | if (!table->registerModule(table, new module_abi(module))) 289 | return; 290 | auto api = new Api(); 291 | api->impl = table; 292 | module->onLoad(api, env); 293 | } 294 | 295 | } // namespace internal 296 | 297 | inline int Api::connectCompanion() { 298 | return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1; 299 | } 300 | inline int Api::getModuleDir() { 301 | return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1; 302 | } 303 | inline void Api::setOption(Option opt) { 304 | if (impl->setOption) impl->setOption(impl->_this, opt); 305 | } 306 | inline uint32_t Api::getFlags() { 307 | return impl->getFlags ? impl->getFlags(impl->_this) : 0; 308 | } 309 | inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { 310 | if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods); 311 | } 312 | inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) { 313 | if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc); 314 | } 315 | inline void Api::pltHookExclude(const char *regex, const char *symbol) { 316 | if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol); 317 | } 318 | inline bool Api::pltHookCommit() { 319 | return impl->pltHookCommit != nullptr && impl->pltHookCommit(); 320 | } 321 | 322 | } // namespace zygisk 323 | 324 | [[gnu::visibility("default")]] [[gnu::used]] 325 | extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); 326 | 327 | [[gnu::visibility("default")]] [[gnu::used]] 328 | extern "C" void zygisk_companion_entry(int); 329 | --------------------------------------------------------------------------------