├── module
├── .gitignore
├── src
│ └── main
│ │ └── AndroidManifest.xml
├── jni
│ ├── server.h
│ ├── Application.mk
│ ├── hook.h
│ ├── util.h
│ ├── Android.mk
│ ├── logging.h
│ ├── server.cpp
│ ├── hook.cpp
│ ├── main.cpp
│ └── zygisk.hpp
└── build.gradle
├── magisk
├── META-INF
│ └── com
│ │ └── google
│ │ └── android
│ │ ├── updater-script
│ │ └── update-binary
├── module.prop
└── customize.sh
├── .gitmodules
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── settings.gradle
├── README.md
├── .github
└── workflows
│ └── build.yml
├── gradle.properties
├── gradlew.bat
└── gradlew
/module/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | /libs
3 | /obj
4 |
--------------------------------------------------------------------------------
/magisk/META-INF/com/google/android/updater-script:
--------------------------------------------------------------------------------
1 | #MAGISK
2 |
--------------------------------------------------------------------------------
/module/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "libcxx"]
2 | path = module/jni/libcxx
3 | url = https://github.com/topjohnwu/libcxx.git
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fei-ke/HmsPushZygisk/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/magisk/module.prop:
--------------------------------------------------------------------------------
1 | id=zygisk_hmspush
2 | name=Zygisk - HMSPush
3 | version=v0.3
4 | versionCode=3
5 | author=Yufz
6 | description=为应用伪装华为设备,以便使用 HMSPush
--------------------------------------------------------------------------------
/.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 | magisk/zygisk/*.so
--------------------------------------------------------------------------------
/module/jni/server.h:
--------------------------------------------------------------------------------
1 | #ifndef HMSPUSHZYGISK_SERVER_H
2 | #define HMSPUSHZYGISK_SERVER_H
3 |
4 | namespace Server {
5 | void companion_handler(int remote_fd);
6 | };
7 | #endif //HMSPUSHZYGISK_SERVER_H
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | include ':module'
9 |
--------------------------------------------------------------------------------
/module/jni/Application.mk:
--------------------------------------------------------------------------------
1 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
2 | APP_CPPFLAGS := -std=c++17 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden
3 | APP_STL := none
4 | APP_PLATFORM := android-21
5 |
--------------------------------------------------------------------------------
/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 | ndkVersion '25.2.9519653'
12 | }
13 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/magisk/customize.sh:
--------------------------------------------------------------------------------
1 | #Some apps will get stuck when getting 'oaid',
2 | #But on non-huawei devices, we don't have one, so we provider a default one.
3 | #You can delete these by execute the following command:
4 | #adb shell settings delete global pps_oaid
5 | #adb shell settings delete global pps_track_limit
6 | settings put global pps_oaid 00000000-0000-0000-0000-000000000000
7 | settings put global pps_track_limit true
--------------------------------------------------------------------------------
/module/jni/hook.h:
--------------------------------------------------------------------------------
1 | #ifndef HMSPUSHZYGISK_HOOK_H
2 | #define HMSPUSHZYGISK_HOOK_H
3 |
4 |
5 | #include
6 | #include
7 | #include "zygisk.hpp"
8 |
9 | using zygisk::Api;
10 |
11 | class Hook {
12 | public:
13 | Hook(Api *api, JNIEnv *env) {
14 | this->api = api;
15 | this->env = env;
16 | }
17 |
18 | void hook();
19 |
20 | private:
21 | Api *api;
22 | JNIEnv *env;
23 | };
24 |
25 | #endif //HMSPUSHZYGISK_HOOK_H
--------------------------------------------------------------------------------
/module/jni/util.h:
--------------------------------------------------------------------------------
1 | #ifndef HMSPUSHZYGISK_UTIL_H
2 | #define HMSPUSHZYGISK_UTIL_H
3 |
4 | #include
5 |
6 | static std::string jstringToStdString(JNIEnv *env, jstring &jstr) {
7 | if (env == nullptr || jstr == nullptr || !jstr) {
8 | return "";
9 | }
10 |
11 | const char *chars = env->GetStringUTFChars(jstr, nullptr);
12 | if (chars && *chars) {
13 | std::string ret(chars);
14 | env->ReleaseStringUTFChars(jstr, chars);
15 | return ret;
16 | }
17 | return "";
18 | }
19 |
20 | #endif //HMSPUSHZYGISK_UTIL_H
21 |
--------------------------------------------------------------------------------
/module/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH := $(call my-dir)
2 |
3 | include $(CLEAR_VARS)
4 | LOCAL_MODULE := hmspush
5 | LOCAL_SRC_FILES := main.cpp server.cpp hook.cpp
6 | LOCAL_STATIC_LIBRARIES := libcxx
7 | LOCAL_LDLIBS := -llog
8 | include $(BUILD_SHARED_LIBRARY)
9 |
10 | include jni/libcxx/Android.mk
11 |
12 | # If you do not want to use libc++, link to system stdc++
13 | # so that you can at least call the new operator in your code
14 |
15 | # include $(CLEAR_VARS)
16 | # LOCAL_MODULE := example
17 | # LOCAL_SRC_FILES := example.cpp
18 | # LOCAL_LDLIBS := -llog -lstdc++
19 | # include $(BUILD_SHARED_LIBRARY)
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HMSPush Zygisk
2 |
3 | 一个用于为应用伪装华为设备的 Magisk/Zygisk 模块,以便于使用 [HMSPush](https://github.com/fei-ke/HmsPushZygisk.git)
4 |
5 | ### 下载
6 | 前往 [Release](https://github.com/fei-ke/HmsPushZygisk/releases) 下载
7 |
8 | ### 配置
9 | 手动编辑 `/data/misc/hmspush/app.conf` 文件,或者使用 [HMSPush](https://github.com/fei-ke/HmsPushZygisk.git) 应用进行配置
10 |
11 | 配置示例:
12 | ```
13 | #对该应用所有进程进行伪装
14 | com.example.app
15 |
16 | #对该应用指定进程进行伪装,格式:包名|进程名
17 | com.example.app|com.example.app:push
18 | ```
19 |
20 |
21 | ### 构建
22 | 配置 ndk 路径后在项目根目录执行,构建产物在 `build` 目录下
23 | ```shell
24 | ./build.sh
25 | ```
26 |
27 | ### License
28 | [GNU General Public License v3 (GPL-3)](http://www.gnu.org/copyleft/gpl.html).
29 |
--------------------------------------------------------------------------------
/magisk/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/logging.h:
--------------------------------------------------------------------------------
1 | #ifndef _LOGGING_H
2 | #define _LOGGING_H
3 |
4 | #include
5 | #include
6 |
7 | #ifndef LOG_TAG
8 | #define LOG_TAG "HmsPushZygisk"
9 | #endif
10 |
11 | #ifdef DEBUG
12 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
13 | #else
14 | #define LOGD(...)
15 | #endif
16 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
17 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
18 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
19 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
20 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
21 |
22 | #endif // _LOGGING_H
23 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches: [ "master" ]
7 | pull_request:
8 | branches: [ "master" ]
9 |
10 | jobs:
11 | build:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v3
17 | with:
18 | submodules: 'recursive'
19 |
20 | - name: Setup Android NDK
21 | uses: nttld/setup-ndk@v1.2.0
22 | with:
23 | ndk-version: r25b
24 | add-to-path: true
25 | local-cache: true
26 |
27 | - name: Build
28 | run: ./build.sh
29 |
30 | - name: Collect artifcat name
31 | run: |
32 | echo "release=$(basename -s .zip build/*.zip)" >> $GITHUB_ENV
33 |
34 | - name: Upload Release
35 | uses: actions/upload-artifact@v3.1.2
36 | with:
37 | name: ${{ env.release }}
38 | path: magisk
39 |
--------------------------------------------------------------------------------
/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=-Xmx2048m -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/server.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include "server.h"
7 | #include "logging.h"
8 |
9 | using namespace std;
10 |
11 | static constexpr auto CONFIG_PATH = "/data/misc/hmspush/app.conf";
12 |
13 | off_t sendFile(int remote_fd, const string &path) {
14 | auto in_fd = open(path.c_str(), O_RDONLY);
15 | if (in_fd < 0) {
16 | LOGD("Failed to open file %s: %d (%s)", path.c_str(), errno, strerror(errno));
17 | return -1;
18 | }
19 |
20 | auto size = lseek(in_fd, 0, SEEK_END);
21 | if (size < 0) {
22 | LOGE("Failed to get file size");
23 | close(in_fd);
24 | return -1;
25 | }
26 | lseek(in_fd, 0, SEEK_SET);
27 |
28 | // Send size first for buffer allocation
29 | int ret = write(remote_fd, &size, sizeof(size));
30 | if (ret < 0) {
31 | LOGI("Failed to send size");
32 | close(in_fd);
33 | return -1;
34 | }
35 |
36 | ret = sendfile(remote_fd, in_fd, nullptr, size);
37 | if (ret < 0) {
38 | LOGI("Failed to send data");
39 | close(in_fd);
40 | return -1;
41 | }
42 |
43 | close(in_fd);
44 | return size;
45 | }
46 |
47 | void Server::companion_handler(int remote_fd) {
48 | auto size = sendFile(remote_fd, (const string) CONFIG_PATH);
49 | LOGD("Sent module payload: %ld bytes", size);
50 | }
--------------------------------------------------------------------------------
/module/jni/hook.cpp:
--------------------------------------------------------------------------------
1 | #include "hook.h"
2 | #include "logging.h"
3 | #include
4 |
5 | using namespace std;
6 |
7 | jstring (*orig_native_get)(JNIEnv *env, jclass clazz, jstring keyJ, jstring defJ);
8 |
9 | jstring my_native_get(JNIEnv *env, jclass clazz, jstring keyJ, jstring defJ) {
10 | const char *key = env->GetStringUTFChars(keyJ, nullptr);
11 | const char *def = env->GetStringUTFChars(defJ, nullptr);
12 |
13 | jstring hooked_result = nullptr;
14 |
15 | if (strcmp(key, "ro.build.version.emui") == 0) {
16 | hooked_result = env->NewStringUTF("EmotionUI_8.0.0");
17 | } else if (strcmp(key, "ro.build.hw_emui_api_level") == 0) {
18 | hooked_result = env->NewStringUTF("21");
19 | }
20 |
21 | env->ReleaseStringUTFChars(keyJ, key);
22 | env->ReleaseStringUTFChars(defJ, def);
23 |
24 | if (hooked_result != nullptr) {
25 | return hooked_result;
26 | } else {
27 | return orig_native_get(env, clazz, keyJ, defJ);
28 | }
29 | }
30 |
31 | void hookBuild(JNIEnv *env) {
32 | LOGD("hook Build\n");
33 | jclass build_class = env->FindClass("android/os/Build");
34 | jstring new_brand = env->NewStringUTF("Huawei");
35 | jstring new_manufacturer = env->NewStringUTF("HUAWEI");
36 |
37 | jfieldID brand_id = env->GetStaticFieldID(build_class, "BRAND", "Ljava/lang/String;");
38 | if (brand_id != nullptr) {
39 | env->SetStaticObjectField(build_class, brand_id, new_brand);
40 | }
41 |
42 | jfieldID manufacturer_id = env->GetStaticFieldID(build_class, "MANUFACTURER", "Ljava/lang/String;");
43 | if (manufacturer_id != nullptr) {
44 | env->SetStaticObjectField(build_class, manufacturer_id, new_manufacturer);
45 | }
46 |
47 | env->DeleteLocalRef(new_brand);
48 | env->DeleteLocalRef(new_manufacturer);
49 |
50 | LOGD("hook Build done");
51 | }
52 |
53 | void hookSystemProperties(JNIEnv *env, zygisk::Api *api) {
54 | LOGD("hook SystemProperties\n");
55 |
56 | JNINativeMethod targetHookMethods[] = {
57 | {"native_get", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
58 | (void *) my_native_get},
59 | };
60 |
61 | api->hookJniNativeMethods(env, "android/os/SystemProperties", targetHookMethods, 1);
62 |
63 | *(void **) &orig_native_get = targetHookMethods[0].fnPtr;
64 |
65 | LOGD("hook SystemProperties done: %p\n", orig_native_get);
66 | }
67 |
68 | void Hook::hook() {
69 | hookBuild(env);
70 | hookSystemProperties(env, api);
71 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/main.cpp:
--------------------------------------------------------------------------------
1 | /* Copyright 2022-2023 John "topjohnwu" Wu
2 | *
3 | * Permission to use, copy, modify, and/or distribute this software for any
4 | * purpose with or without fee is hereby granted.
5 | *
6 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12 | * PERFORMANCE OF THIS SOFTWARE.
13 | */
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 |
20 | #include "zygisk.hpp"
21 | #include "logging.h"
22 | #include "server.h"
23 | #include "hook.h"
24 | #include "util.h"
25 |
26 | using zygisk::Api;
27 | using zygisk::AppSpecializeArgs;
28 | using zygisk::ServerSpecializeArgs;
29 |
30 | using namespace std;
31 |
32 | class HmsPushZygisk : public zygisk::ModuleBase {
33 | public:
34 | void onLoad(Api *api, JNIEnv *env) override {
35 | this->api = api;
36 | this->env = env;
37 | }
38 |
39 | void preAppSpecialize(AppSpecializeArgs *args) override {
40 | string process_name = jstringToStdString(env, args->nice_name);
41 | string app_data_dir = jstringToStdString(env, args->app_data_dir);
42 |
43 | if (process_name.empty() || app_data_dir.empty()) {
44 | // Since we do not hook any functions, we should let Zygisk dlclose ourselves
45 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY);
46 | return;
47 | }
48 |
49 | string package_name = parsePackageName(app_data_dir.c_str());
50 |
51 | LOGD("preAppSpecialize, packageName = %s, process = %s\n", package_name.c_str(), process_name.c_str());
52 |
53 | preSpecialize(package_name, process_name);
54 | }
55 |
56 | void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
57 | // Never tamper with system_server
58 | api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
59 | }
60 |
61 | private:
62 | Api *api;
63 | JNIEnv *env;
64 |
65 | static string parsePackageName(const char *app_data_dir) {
66 | if (app_data_dir && *app_data_dir) {
67 | char package_name[256] = {0};
68 | // /data/user//
69 | if (sscanf(app_data_dir, "/data/%*[^/]/%*[^/]/%s", package_name) == 1) {
70 | return package_name;
71 | }
72 |
73 | // /mnt/expand//user//
74 | if (sscanf(app_data_dir, "/mnt/expand/%*[^/]/%*[^/]/%*[^/]/%s", package_name) == 1) {
75 | return package_name;
76 | }
77 |
78 | // /data/data/
79 | if (sscanf(app_data_dir, "/data/%*[^/]/%s", package_name) == 1) {
80 | return package_name;
81 | }
82 | }
83 | return "";
84 | }
85 |
86 | void preSpecialize(const string &packageName, const string &process) {
87 | vector processList = requestRemoteConfig(packageName);
88 | if (!processList.empty()) {
89 | bool shouldHook = false;
90 | for (const auto &item: processList) {
91 | if (item.empty() || item == process) {
92 | shouldHook = true;
93 | break;
94 | }
95 | }
96 |
97 | if (shouldHook) {
98 | LOGI("hook package = [%s], process = [%s]\n", packageName.c_str(), process.c_str());
99 | Hook(api, env).hook();
100 | return;
101 | }
102 | }
103 |
104 | // Since we do not hook any functions, we should let Zygisk dlclose ourselves
105 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY);
106 | }
107 |
108 | /**
109 | * Request remote config from companion
110 | * @param packageName
111 | * @return list of processes to hook
112 | */
113 | vector requestRemoteConfig(const string &packageName) {
114 | LOGD("requestRemoteConfig for %s", packageName.c_str());
115 | auto fd = api->connectCompanion();
116 | LOGD("connect to companion fd = %d", fd);
117 | vector content;
118 |
119 | auto size = receiveConfig(fd, content);
120 | auto configs = parseConfig(content, packageName);
121 | LOGD("Loaded module payload: %d bytes, config size:%lu ", size, configs.size());
122 |
123 | close(fd);
124 |
125 | return configs;
126 | }
127 |
128 | static int receiveConfig(int remote_fd, vector &buf) {
129 | LOGD("start receiving config");
130 |
131 | off_t size;
132 | int ret = read(remote_fd, &size, sizeof(size));
133 | if (ret == 0) {
134 | LOGD("receive empty config");
135 | return 0;
136 | }
137 | if (ret < 0) {
138 | LOGE("Failed to read size");
139 | return -1;
140 | }
141 |
142 | buf.resize(size);
143 |
144 | int bytesReceived = 0;
145 | while (bytesReceived < size) {
146 | ret = read(remote_fd, buf.data() + bytesReceived, size - bytesReceived);
147 | if (ret < 0) {
148 | LOGE("Failed to read data");
149 | return -1;
150 | }
151 | bytesReceived += ret;
152 | }
153 |
154 | // Ensure the last byte is '\n'
155 | if (!buf.empty() && buf[buf.size() - 1] != '\n') {
156 | buf.push_back('\n');
157 | }
158 | return bytesReceived;
159 | }
160 |
161 | static vector parseConfig(const vector &content, const string &packageName) {
162 | vector result;
163 |
164 | if (content.empty()) return result;
165 |
166 | string line;
167 | for (char c: content) {
168 | if (c == '\n') {
169 | if (!line.empty() || line[0] != '#') {
170 | size_t delimiterPos = line.find('|');
171 | bool found = delimiterPos != string::npos;
172 | auto pkg = line.substr(0, found ? delimiterPos : line.size());
173 | if (pkg == packageName) {
174 | if (found) {
175 | result.push_back(line.substr(delimiterPos + 1, line.size()));
176 | } else {
177 | result.push_back("");
178 | }
179 | }
180 | }
181 | line.clear();
182 | } else {
183 | line.push_back(c);
184 | }
185 | }
186 | return result;
187 | }
188 | };
189 |
190 | // Register our module class and the companion handler function
191 | REGISTER_ZYGISK_MODULE(HmsPushZygisk)
192 |
193 | REGISTER_ZYGISK_COMPANION(Server::companion_handler)
194 |
--------------------------------------------------------------------------------
/module/jni/zygisk.hpp:
--------------------------------------------------------------------------------
1 | /* Copyright 2022-2023 John "topjohnwu" Wu
2 | *
3 | * Permission to use, copy, modify, and/or distribute this software for any
4 | * purpose with or without fee is hereby granted.
5 | *
6 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
7 | * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
8 | * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
9 | * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
10 | * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
11 | * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
12 | * PERFORMANCE OF THIS SOFTWARE.
13 | */
14 |
15 | // This is the public API for Zygisk modules.
16 | // DO NOT MODIFY ANY CODE IN THIS HEADER.
17 |
18 | #pragma once
19 |
20 | #include
21 |
22 | #define ZYGISK_API_VERSION 4
23 |
24 | /*
25 |
26 | ***************
27 | * Introduction
28 | ***************
29 |
30 | On Android, all app processes are forked from a special daemon called "Zygote".
31 | For each new app process, zygote will fork a new process and perform "specialization".
32 | This specialization operation enforces the Android security sandbox on the newly forked
33 | process to make sure that 3rd party application code is only loaded after it is being
34 | restricted within a sandbox.
35 |
36 | On Android, there is also this special process called "system_server". This single
37 | process hosts a significant portion of system services, which controls how the
38 | Android operating system and apps interact with each other.
39 |
40 | The Zygisk framework provides a way to allow developers to build modules and run custom
41 | code before and after system_server and any app processes' specialization.
42 | This enable developers to inject code and alter the behavior of system_server and app processes.
43 |
44 | Please note that modules will only be loaded after zygote has forked the child process.
45 | THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
46 |
47 | *********************
48 | * Development Guide
49 | *********************
50 |
51 | Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
52 | Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
53 |
54 | Example code:
55 |
56 | static jint (*orig_logger_entry_max)(JNIEnv *env);
57 | static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
58 |
59 | class ExampleModule : public zygisk::ModuleBase {
60 | public:
61 | void onLoad(zygisk::Api *api, JNIEnv *env) override {
62 | this->api = api;
63 | this->env = env;
64 | }
65 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
66 | JNINativeMethod methods[] = {
67 | { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
68 | };
69 | api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
70 | *(void **) &orig_logger_entry_max = methods[0].fnPtr;
71 | }
72 | private:
73 | zygisk::Api *api;
74 | JNIEnv *env;
75 | };
76 |
77 | REGISTER_ZYGISK_MODULE(ExampleModule)
78 |
79 | -----------------------------------------------------------------------------------------
80 |
81 | Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
82 | or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
83 | never runs in a true superuser environment.
84 |
85 | If your module require access to superuser permissions, you can create and register
86 | a root companion handler function. This function runs in a separate root companion
87 | daemon process, and an Unix domain socket is provided to allow you to perform IPC between
88 | your target process and the root companion process.
89 |
90 | Example code:
91 |
92 | static void example_handler(int socket) { ... }
93 |
94 | REGISTER_ZYGISK_COMPANION(example_handler)
95 |
96 | */
97 |
98 | namespace zygisk {
99 |
100 | struct Api;
101 | struct AppSpecializeArgs;
102 | struct ServerSpecializeArgs;
103 |
104 | class ModuleBase {
105 | public:
106 |
107 | // This method is called as soon as the module is loaded into the target process.
108 | // A Zygisk API handle will be passed as an argument.
109 | virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
110 |
111 | // This method is called before the app process is specialized.
112 | // At this point, the process just got forked from zygote, but no app specific specialization
113 | // is applied. This means that the process does not have any sandbox restrictions and
114 | // still runs with the same privilege of zygote.
115 | //
116 | // All the arguments that will be sent and used for app specialization is passed as a single
117 | // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
118 | // process will be specialized.
119 | //
120 | // If you need to run some operations as superuser, you can call Api::connectCompanion() to
121 | // get a socket to do IPC calls with a root companion process.
122 | // See Api::connectCompanion() for more info.
123 | virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
124 |
125 | // This method is called after the app process is specialized.
126 | // At this point, the process has all sandbox restrictions enabled for this application.
127 | // This means that this method runs with the same privilege of the app's own code.
128 | virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
129 |
130 | // This method is called before the system server process is specialized.
131 | // See preAppSpecialize(args) for more info.
132 | virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
133 |
134 | // This method is called after the system server process is specialized.
135 | // At this point, the process runs with the privilege of system_server.
136 | virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
137 | };
138 |
139 | struct AppSpecializeArgs {
140 | // Required arguments. These arguments are guaranteed to exist on all Android versions.
141 | jint &uid;
142 | jint &gid;
143 | jintArray &gids;
144 | jint &runtime_flags;
145 | jobjectArray &rlimits;
146 | jint &mount_external;
147 | jstring &se_info;
148 | jstring &nice_name;
149 | jstring &instruction_set;
150 | jstring &app_data_dir;
151 |
152 | // Optional arguments. Please check whether the pointer is null before de-referencing
153 | jintArray *const fds_to_ignore;
154 | jboolean *const is_child_zygote;
155 | jboolean *const is_top_app;
156 | jobjectArray *const pkg_data_info_list;
157 | jobjectArray *const whitelisted_data_info_list;
158 | jboolean *const mount_data_dirs;
159 | jboolean *const mount_storage_dirs;
160 |
161 | AppSpecializeArgs() = delete;
162 | };
163 |
164 | struct ServerSpecializeArgs {
165 | jint &uid;
166 | jint &gid;
167 | jintArray &gids;
168 | jint &runtime_flags;
169 | jlong &permitted_capabilities;
170 | jlong &effective_capabilities;
171 |
172 | ServerSpecializeArgs() = delete;
173 | };
174 |
175 | namespace internal {
176 | struct api_table;
177 | template void entry_impl(api_table *, JNIEnv *);
178 | }
179 |
180 | // These values are used in Api::setOption(Option)
181 | enum Option : int {
182 | // Force Magisk's denylist unmount routines to run on this process.
183 | //
184 | // Setting this option only makes sense in preAppSpecialize.
185 | // The actual unmounting happens during app process specialization.
186 | //
187 | // Set this option to force all Magisk and modules' files to be unmounted from the
188 | // mount namespace of the process, regardless of the denylist enforcement status.
189 | FORCE_DENYLIST_UNMOUNT = 0,
190 |
191 | // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
192 | // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
193 | // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
194 | DLCLOSE_MODULE_LIBRARY = 1,
195 | };
196 |
197 | // Bit masks of the return value of Api::getFlags()
198 | enum StateFlag : uint32_t {
199 | // The user has granted root access to the current process
200 | PROCESS_GRANTED_ROOT = (1u << 0),
201 |
202 | // The current process was added on the denylist
203 | PROCESS_ON_DENYLIST = (1u << 1),
204 | };
205 |
206 | // All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
207 | // from the specialized process afterwards.
208 | struct Api {
209 |
210 | // Connect to a root companion process and get a Unix domain socket for IPC.
211 | //
212 | // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
213 | //
214 | // The pre[XXX]Specialize methods run with the same privilege of zygote.
215 | // If you would like to do some operations with superuser permissions, register a handler
216 | // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
217 | // Another good use case for a companion process is that if you want to share some resources
218 | // across multiple processes, hold the resources in the companion process and pass it over.
219 | //
220 | // The root companion process is ABI aware; that is, when calling this method from a 32-bit
221 | // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
222 | //
223 | // Returns a file descriptor to a socket that is connected to the socket passed to your
224 | // module's companion request handler. Returns -1 if the connection attempt failed.
225 | int connectCompanion();
226 |
227 | // Get the file descriptor of the root folder of the current module.
228 | //
229 | // This API only works in the pre[XXX]Specialize methods.
230 | // Accessing the directory returned is only possible in the pre[XXX]Specialize methods
231 | // or in the root companion process (assuming that you sent the fd over the socket).
232 | // Both restrictions are due to SELinux and UID.
233 | //
234 | // Returns -1 if errors occurred.
235 | int getModuleDir();
236 |
237 | // Set various options for your module.
238 | // Please note that this method accepts one single option at a time.
239 | // Check zygisk::Option for the full list of options available.
240 | void setOption(Option opt);
241 |
242 | // Get information about the current process.
243 | // Returns bitwise-or'd zygisk::StateFlag values.
244 | uint32_t getFlags();
245 |
246 | // Exempt the provided file descriptor from being automatically closed.
247 | //
248 | // This API only make sense in preAppSpecialize; calling this method in any other situation
249 | // is either a no-op (returns true) or an error (returns false).
250 | //
251 | // When false is returned, the provided file descriptor will eventually be closed by zygote.
252 | bool exemptFd(int fd);
253 |
254 | // Hook JNI native methods for a class
255 | //
256 | // Lookup all registered JNI native methods and replace it with your own methods.
257 | // The original function pointer will be saved in each JNINativeMethod's fnPtr.
258 | // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
259 | // will be set to nullptr.
260 | void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
261 |
262 | // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
263 | //
264 | // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
265 | //
266 | //
267 | // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
268 | // (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
269 | //
270 | // The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
271 | // For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
272 | // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
273 | void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
274 |
275 | // Commit all the hooks that was previously registered.
276 | // Returns false if an error occurred.
277 | bool pltHookCommit();
278 |
279 | private:
280 | internal::api_table *tbl;
281 | template friend void internal::entry_impl(internal::api_table *, JNIEnv *);
282 | };
283 |
284 | // Register a class as a Zygisk module
285 |
286 | #define REGISTER_ZYGISK_MODULE(clazz) \
287 | void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
288 | zygisk::internal::entry_impl(table, env); \
289 | }
290 |
291 | // Register a root companion request handler function for your module
292 | //
293 | // The function runs in a superuser daemon process and handles a root companion request from
294 | // your module running in a target process. The function has to accept an integer value,
295 | // which is a Unix domain socket that is connected to the target process.
296 | // See Api::connectCompanion() for more info.
297 | //
298 | // NOTE: the function can run concurrently on multiple threads.
299 | // Be aware of race conditions if you have globally shared resources.
300 |
301 | #define REGISTER_ZYGISK_COMPANION(func) \
302 | void zygisk_companion_entry(int client) { func(client); }
303 |
304 | /*********************************************************
305 | * The following is internal ABI implementation detail.
306 | * You do not have to understand what it is doing.
307 | *********************************************************/
308 |
309 | namespace internal {
310 |
311 | struct module_abi {
312 | long api_version;
313 | ModuleBase *impl;
314 |
315 | void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
316 | void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
317 | void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
318 | void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
319 |
320 | module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
321 | preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
322 | postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
323 | preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
324 | postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
325 | }
326 | };
327 |
328 | struct api_table {
329 | // Base
330 | void *impl;
331 | bool (*registerModule)(api_table *, module_abi *);
332 |
333 | void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
334 | void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
335 | bool (*exemptFd)(int);
336 | bool (*pltHookCommit)();
337 | int (*connectCompanion)(void * /* impl */);
338 | void (*setOption)(void * /* impl */, Option);
339 | int (*getModuleDir)(void * /* impl */);
340 | uint32_t (*getFlags)(void * /* impl */);
341 | };
342 |
343 | template
344 | void entry_impl(api_table *table, JNIEnv *env) {
345 | static Api api;
346 | api.tbl = table;
347 | static T module;
348 | ModuleBase *m = &module;
349 | static module_abi abi(m);
350 | if (!table->registerModule(table, &abi)) return;
351 | m->onLoad(&api, env);
352 | }
353 |
354 | } // namespace internal
355 |
356 | inline int Api::connectCompanion() {
357 | return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
358 | }
359 | inline int Api::getModuleDir() {
360 | return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
361 | }
362 | inline void Api::setOption(Option opt) {
363 | if (tbl->setOption) tbl->setOption(tbl->impl, opt);
364 | }
365 | inline uint32_t Api::getFlags() {
366 | return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
367 | }
368 | inline bool Api::exemptFd(int fd) {
369 | return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
370 | }
371 | inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
372 | if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
373 | }
374 | inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
375 | if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
376 | }
377 | inline bool Api::pltHookCommit() {
378 | return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
379 | }
380 |
381 | } // namespace zygisk
382 |
383 | extern "C" {
384 |
385 | [[gnu::visibility("default"), maybe_unused]]
386 | void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
387 |
388 | [[gnu::visibility("default"), maybe_unused]]
389 | void zygisk_companion_entry(int);
390 |
391 | } // extern "C"
392 |
--------------------------------------------------------------------------------