├── .gitignore ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── module ├── .gitignore ├── build.gradle.kts ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── cpp │ │ ├── CMakeLists.txt │ │ ├── example.cpp │ │ └── zygisk_next_api.h └── template │ ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── update-binary │ │ └── updater-script │ ├── customize.sh │ ├── module.prop │ ├── post-fs-data.sh │ ├── sepolicy.rule │ ├── service.sh │ ├── verify.sh │ └── zn_modules.txt └── settings.gradle.kts /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zygisk Next Module Sample 2 | 3 | [中文](#中文版本) [English](#english-version) 4 | 5 | ## 中文版本 6 | 7 | 本仓库包含一个可构建的 Zygisk Next 模块示例项目。 8 | 9 | 运行 gradle task `zipDebug` 生成 debug 版本的 zip 或运行 `installMagisk|KsuDebug` 以安装模块。构建完成的模块 zip 包将输出到 module/release 目录下。 10 | 11 | ### 模块声明 12 | 13 | 一个 Zygisk Next 模块是一个合法的 [Magisk 模块](https://topjohnwu.github.io/Magisk/guides.html#magisk-modules), 14 | 且具有 Zygisk Next 特有的声明文件,该文件必须被放置在 `$MODDIR/zn_modules.txt` 文件中, 其中 `$MODDIR` 是模块目录。 15 | 16 | 文件的每一行是一个 Zygisk Next 模块库和作用域声明,如下所示: 17 | 18 | ```txt 19 | path=/path/of/program/to/inject path/to/znmodule_lib.so 20 | name=name_of_program_to_inject path/to/znmodule_lib.so 21 | ``` 22 | 23 | 其中,path 表示使用绝对路径的匹配方式,即绝对路径为给定值的程序将被注入; 24 | name 表示名字匹配,即名字为给定值的程序将被注入。 25 | 26 | 被注入的程序会被加载由路径 path/to/znmodule_lib.so 指向的动态链接库。如果动态库的路径是一个相对路径,则会被相对模块目录寻找,如果该路径是一个绝对路径, 27 | 则根据绝对路径寻找,无论是相对路径还是绝对路径,动态库的最终路径必须是自身模块目录下的文件,否则动态库不会被加载。 28 | 29 | 例如,如果需要注入模块目录下的 libmodule.so 到 adbd 进程中,则使用程序名字匹配的方式可以写作: 30 | 31 | ``` 32 | name=adbd libmodule.so 33 | ``` 34 | 35 | 或者使用绝对路径匹配方式,其中 adbd 绝对路径为 `/apex/com.android.adbd/bin/adbd` : 36 | 37 | ``` 38 | path=/apex/com.android.adbd/bin/adbd libmodule.so 39 | ``` 40 | 41 | ### 模块作用范围 42 | 43 | 目前,Zygisk Next 模块可注入到满足以下要求的进程中: 44 | 45 | - 由 init 进程产生(fork-execve)的服务进程。 46 | - 程序是 ELF 格式的动态链接的可执行程序(即,不是 shell 脚本或静态链接程序)。 47 | - 启动晚于 post-fs-data 阶段(早于该阶段启动的无法被 Zygisk Next 拦截)。 48 | - 不是 zygote 。 49 | 50 | 可以使用 `zygisk-ctl dump-zn -sa` 命令观察 Zygisk Next 所知道的服务进程,这些服务进程一般可以注入。 51 | 52 | ### 模块 API (草案) 53 | 54 | Zygisk Next 模块 API 目前仍在设计当中,具体内容请参见 [zygisk_next_api.h](module/src/main/cpp/zygisk_next_api.h) 。 55 | 56 | 当前 API 版本为 1 ,提供 plt hook 和 inline hook 能力。 57 | 58 | 请注意: 59 | 60 | - 如需使用 inline hook ,请确保目标进程的 SELinux 域拥有 execmem 权限。可以借助 sepolicy.rule 来允许该权限。 61 | - 在目前的实现中,每个进程对一个地址的 inline hook 只能进行一次,因此多个模块可能无法 inline hook 同一个地址。 62 | 63 | 由于模块 API 仍处于草案阶段,在未来可能发生变化。我们期待模块开发者提出 API 的改进建议。 64 | 65 | ## English version 66 | 67 | This repository contains a buildable example project for Zygisk Next modules. 68 | 69 | Run the Gradle task `zipDebug` to generate a debug version of the zip file, or run `installMagisk|KsuDebug` to install the module. The built module zip package will be output to the module/release directory. 70 | 71 | ### Module Declaration 72 | 73 | A Zygisk Next module is a valid [Magisk module](https://topjohnwu.github.io/Magisk/guides.html#magisk-modules) with a specific declaration file for Zygisk Next. This file must be placed in the `$MODDIR/zn_modules.txt` file, where `$MODDIR` represents the module directory. 74 | 75 | Each line in the file represents a Zygisk Next module library and scope declaration, as shown below: 76 | 77 | ```txt 78 | path=/path/of/program/to/inject path/to/znmodule_lib.so 79 | name=name_of_program_to_inject path/to/znmodule_lib.so 80 | ``` 81 | 82 | Here, `path` represents matching by absolute path, meaning programs with the given absolute path will be injected, and `name` represents name matching, indicating programs with the given name will be injected. 83 | 84 | The injected program will load the dynamic link library pointed to by the path `path/to/znmodule_lib.so`. If the path to the dynamic library is a relative path, it will be searched relative to the module directory. If the path is an absolute path, it will be searched according to the absolute path. In either case, the dynamic library must ultimately reside in the module directory for it to be loaded. 85 | 86 | For example, if you need to inject the `libmodule.so` from the module directory into the `adbd` process, you can use name matching as follows: 87 | 88 | ``` 89 | name=adbd libmodule.so 90 | ``` 91 | 92 | Or you can use absolute path matching, where the absolute path for `adbd` is `/apex/com.android.adbd/bin/adbd`: 93 | 94 | ``` 95 | path=/apex/com.android.adbd/bin/adbd libmodule.so 96 | ``` 97 | 98 | ### Module Scope 99 | 100 | Currently, Zygisk Next modules can be injected into processes that meet the following criteria: 101 | 102 | - Services processes generated by the init process (fork-execve). 103 | - Programs that are ELF format dynamically linked executables (i.e., not shell scripts or statically linked programs). 104 | - Started later than the post-fs-data stage (programs started before this stage cannot be intercepted by Zygisk Next). 105 | - Not zygote. 106 | 107 | You can use the command `zygisk-ctl dump-zn -sa` to observe the service processes known to Zygisk Next that can generally be injected. 108 | 109 | ### Module API (Draft) 110 | 111 | The Zygisk Next module API is still under design. For specific details, please refer to [zygisk_next_api.h](module/src/main/cpp/zygisk_next_api.h). 112 | 113 | The current API version is 1, providing the ability for PLT hook and inline hook. 114 | 115 | Please note: 116 | 117 | - If you intend to use inline hook, ensure that the target process's SELinux domain has `execmem` permission. You can use `sepolicy.rule` to allow this permission. 118 | - In the current implementation, each process can only perform an inline hook once on an address, so multiple modules may not be able to inline hook the same address. 119 | 120 | As the module API is still in the draft stage, it may undergo changes in the future. We welcome suggestions from module developers for improving the API. 121 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.AppExtension 2 | import java.io.ByteArrayOutputStream 3 | 4 | plugins { 5 | alias(libs.plugins.agp.app) apply false 6 | } 7 | 8 | fun String.execute(currentWorkingDir: File = file("./")): String { 9 | val byteOut = ByteArrayOutputStream() 10 | project.exec { 11 | workingDir = currentWorkingDir 12 | commandLine = split("\\s".toRegex()) 13 | standardOutput = byteOut 14 | } 15 | return String(byteOut.toByteArray()).trim() 16 | } 17 | 18 | val gitCommitCount = "git rev-list HEAD --count".execute().toInt() 19 | val gitCommitHash = "git rev-parse --verify --short HEAD".execute() 20 | 21 | // also the soname 22 | val moduleId by extra("zn_adb_root") 23 | val moduleName by extra("ZN-ADBRoot") 24 | val verName by extra("v1") 25 | val verCode by extra(gitCommitCount) 26 | val commitHash by extra(gitCommitHash) 27 | val abiList by extra(listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64")) 28 | 29 | val androidMinSdkVersion by extra(30) 30 | val androidTargetSdkVersion by extra(34) 31 | val androidCompileSdkVersion by extra(34) 32 | val androidBuildToolsVersion by extra("34.0.0") 33 | val androidCompileNdkVersion by extra("27.0.12077973") 34 | val androidSourceCompatibility by extra(JavaVersion.VERSION_17) 35 | val androidTargetCompatibility by extra(JavaVersion.VERSION_17) 36 | 37 | tasks.register("Delete", Delete::class) { 38 | delete(rootProject.buildDir) 39 | } 40 | 41 | fun Project.configureBaseExtension() { 42 | extensions.findByType(AppExtension::class)?.run { 43 | namespace = "io.github.a13e300.zygisk_next.module.adb_root" 44 | compileSdkVersion(androidCompileSdkVersion) 45 | ndkVersion = androidCompileNdkVersion 46 | buildToolsVersion = androidBuildToolsVersion 47 | 48 | defaultConfig { 49 | minSdk = androidMinSdkVersion 50 | } 51 | 52 | compileOptions { 53 | sourceCompatibility = androidSourceCompatibility 54 | targetCompatibility = androidTargetCompatibility 55 | } 56 | } 57 | 58 | } 59 | 60 | subprojects { 61 | plugins.withId("com.android.application") { 62 | configureBaseExtension() 63 | } 64 | plugins.withType(JavaPlugin::class.java) { 65 | extensions.configure(JavaPluginExtension::class.java) { 66 | sourceCompatibility = androidSourceCompatibility 67 | targetCompatibility = androidTargetCompatibility 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.5.2" 3 | 4 | [plugins] 5 | agp-app = { id = "com.android.application", version.ref = "agp" } 6 | 7 | [libraries] 8 | cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5ec1cff/ZN-adbroot/fbfcf369bd5510e3adc704cc4605a39d5adbd9c0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Dec 31 12:28:57 CST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /obj 4 | /release 5 | -------------------------------------------------------------------------------- /module/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import android.databinding.tool.ext.capitalizeUS 2 | import org.apache.tools.ant.filters.FixCrLfFilter 3 | import org.apache.tools.ant.filters.ReplaceTokens 4 | import java.security.MessageDigest 5 | 6 | plugins { 7 | alias(libs.plugins.agp.app) 8 | } 9 | 10 | val moduleId: String by rootProject.extra 11 | val moduleName: String by rootProject.extra 12 | val verCode: Int by rootProject.extra 13 | val verName: String by rootProject.extra 14 | val commitHash: String by rootProject.extra 15 | val abiList: List by rootProject.extra 16 | 17 | android { 18 | buildFeatures { 19 | prefab = true 20 | } 21 | defaultConfig { 22 | ndk { 23 | abiFilters.addAll(abiList) 24 | } 25 | externalNativeBuild { 26 | cmake { 27 | cppFlags("-std=c++20") 28 | arguments( 29 | "-DANDROID_STL=none", 30 | "-DMODULE_NAME=$moduleId" 31 | ) 32 | } 33 | } 34 | } 35 | externalNativeBuild { 36 | /* 37 | ndkBuild { 38 | path("src/main/cpp/Android.mk") 39 | } 40 | */ 41 | cmake { 42 | path("src/main/cpp/CMakeLists.txt") 43 | } 44 | } 45 | } 46 | 47 | val abiMap = mapOf( 48 | "arm64-v8a" to "arm64", 49 | "armeabi-v7a" to "arm", 50 | "x86" to "x86", 51 | "x86_64" to "x64" 52 | ) 53 | 54 | androidComponents.onVariants { variant -> 55 | afterEvaluate { 56 | val variantLowered = variant.name.lowercase() 57 | val variantCapped = variant.name.capitalizeUS() 58 | val buildTypeLowered = variant.buildType?.lowercase() 59 | val supportedAbis = abiList.map { 60 | abiMap[it] ?: error("unsupported abi $it") 61 | }.joinToString(" ") 62 | 63 | val moduleDir = layout.buildDirectory.file("outputs/module/$variantLowered") 64 | val zipFileName = 65 | "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-') 66 | 67 | val prepareModuleFilesTask = task("prepareModuleFiles$variantCapped") { 68 | group = "module" 69 | dependsOn("assemble$variantCapped") 70 | into(moduleDir) 71 | from(rootProject.layout.projectDirectory.file("README.md")) 72 | from(layout.projectDirectory.file("template")) { 73 | exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "zn_modules.txt") 74 | filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 75 | } 76 | from(layout.projectDirectory.file("template")) { 77 | include("module.prop", "zn_modules.txt") 78 | expand( 79 | "moduleId" to moduleId, 80 | "moduleName" to moduleName, 81 | "versionName" to "$verName ($verCode-$commitHash-$variantLowered)", 82 | "versionCode" to verCode 83 | ) 84 | } 85 | from(layout.projectDirectory.file("template")) { 86 | include("customize.sh", "post-fs-data.sh", "service.sh") 87 | val tokens = mapOf( 88 | "DEBUG" to if (buildTypeLowered == "debug") "true" else "false", 89 | "SONAME" to moduleId, 90 | "SUPPORTED_ABIS" to supportedAbis 91 | ) 92 | filter("tokens" to tokens) 93 | filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 94 | } 95 | abiList.forEach { abi -> 96 | val arch = abiMap[abi] 97 | from(layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib/$abi")) { 98 | into("lib/$arch") 99 | } 100 | } 101 | 102 | doLast { 103 | fileTree(moduleDir).visit { 104 | if (isDirectory) return@visit 105 | val md = MessageDigest.getInstance("SHA-256") 106 | file.forEachBlock(4096) { bytes, size -> 107 | md.update(bytes, 0, size) 108 | } 109 | file(file.path + ".sha256").writeText( 110 | org.apache.commons.codec.binary.Hex.encodeHexString( 111 | md.digest() 112 | ) 113 | ) 114 | } 115 | } 116 | } 117 | 118 | val zipTask = task("zip$variantCapped") { 119 | group = "module" 120 | dependsOn(prepareModuleFilesTask) 121 | archiveFileName.set(zipFileName) 122 | destinationDirectory.set(layout.projectDirectory.file("release").asFile) 123 | from(moduleDir) 124 | } 125 | 126 | val pushTask = task("push$variantCapped") { 127 | group = "module" 128 | dependsOn(zipTask) 129 | commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp") 130 | } 131 | 132 | val installKsuTask = task("installKsu$variantCapped") { 133 | group = "module" 134 | dependsOn(pushTask) 135 | commandLine( 136 | "adb", "shell", "su", "-c", 137 | "/data/adb/ksud module install /data/local/tmp/$zipFileName" 138 | ) 139 | } 140 | 141 | val installMagiskTask = task("installMagisk$variantCapped") { 142 | group = "module" 143 | dependsOn(pushTask) 144 | commandLine( 145 | "adb", 146 | "shell", 147 | "su", 148 | "-M", 149 | "-c", 150 | "magisk --install-module /data/local/tmp/$zipFileName" 151 | ) 152 | } 153 | 154 | task("installKsuAndReboot$variantCapped") { 155 | group = "module" 156 | dependsOn(installKsuTask) 157 | commandLine("adb", "reboot") 158 | } 159 | 160 | task("installMagiskAndReboot$variantCapped") { 161 | group = "module" 162 | dependsOn(installMagiskTask) 163 | commandLine("adb", "reboot") 164 | } 165 | } 166 | } 167 | 168 | dependencies { 169 | implementation(libs.cxx) 170 | } 171 | -------------------------------------------------------------------------------- /module/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /module/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | project(sample) 3 | 4 | set(CXX_FLAGS "${CXX_FLAGS} -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden") 5 | 6 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CXX_FLAGS}") 7 | 8 | find_package(cxx REQUIRED CONFIG) 9 | link_libraries(cxx::cxx) 10 | 11 | add_library(${MODULE_NAME} SHARED example.cpp) 12 | target_link_libraries(${MODULE_NAME} log) 13 | -------------------------------------------------------------------------------- /module/src/main/cpp/example.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 | 15 | #include 16 | 17 | #include "zygisk_next_api.h" 18 | 19 | // An example module which inject to adbd and hook some functions 20 | 21 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ZnAdbRoot", __VA_ARGS__) 22 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, "ZnAdbRoot", __VA_ARGS__) 23 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, "ZnAdbRoot", __VA_ARGS__) 24 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "ZnAdbRoot", __VA_ARGS__) 25 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 26 | 27 | static int my_log_debuggable() { 28 | return 1; 29 | } 30 | 31 | // this function will be called only if --root-seclabel is specified 32 | // https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/adb/daemon/main.cpp;l=169;drc=8b9705698014252b6d42b9c2d469fe92f6ffc56e 33 | static int (*orig_setcon)(const char* c); 34 | static int my_setcon(const char *c) { 35 | if (getuid() != 0) return orig_setcon(c); 36 | int r = orig_setcon("u:r:magisk:s0"); 37 | if (r == -1) { 38 | PLOGE("set magisk"); 39 | // retry with ksu 40 | errno = 0; 41 | r = orig_setcon("u:r:su:s0"); 42 | if (r == -1) { 43 | PLOGE("set ksu"); 44 | errno = 0; 45 | r = orig_setcon(c); 46 | } 47 | } 48 | 49 | int e = errno; 50 | 51 | int fd = open("/proc/self/attr/sockcreate", O_RDWR | O_CLOEXEC); 52 | if (fd >= 0) { 53 | if (write(fd, "u:r:adbd:s0", sizeof("u:r:adbd:s0") - 1) == -1) { 54 | PLOGE("set sock con"); 55 | } 56 | close(fd); 57 | } 58 | 59 | errno = e; 60 | return r; 61 | } 62 | 63 | void onModuleLoaded(void* self_handle, const struct ZygiskNextAPI* api) { 64 | LOGI("module loaded"); 65 | 66 | // get base address of adbd 67 | void* base = nullptr; 68 | dl_iterate_phdr([](struct dl_phdr_info* info, size_t sz, void* data) -> int { 69 | auto linker_base = (uintptr_t) getauxval(AT_BASE); 70 | if (linker_base == info->dlpi_addr) 71 | return 0; 72 | *reinterpret_cast(data) = (void*) info->dlpi_addr; 73 | return 1; 74 | }, &base); 75 | 76 | LOGI("adbd base %p", base); 77 | 78 | // plt hook adbd 79 | if (api->pltHook(base, "__android_log_is_debuggable", (void*) my_log_debuggable, nullptr) == ZN_SUCCESS 80 | && api->pltHook(base, "selinux_android_setcon", (void*) my_setcon, (void**) &orig_setcon) == ZN_SUCCESS) { 81 | LOGI("plt hook success"); 82 | } else { 83 | LOGI("plt hook failed"); 84 | } 85 | } 86 | 87 | // declaration of the zygisk next module 88 | __attribute__((visibility("default"), unused)) 89 | struct ZygiskNextModule zn_module = { 90 | .target_api_version = ZYGISK_NEXT_API_VERSION_1, 91 | .onModuleLoaded = onModuleLoaded 92 | }; 93 | -------------------------------------------------------------------------------- /module/src/main/cpp/zygisk_next_api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #define ZYGISK_NEXT_API_VERSION_1 3 10 | 11 | #define ZN_SUCCESS 0 12 | #define ZN_FAILED 1 13 | 14 | struct ZnSymbolResolver; 15 | 16 | struct ZygiskNextAPI { 17 | // Hook API 18 | 19 | // Do plt hook at symbol specified by the param `symbol` of library specified by the param `base_addr` 20 | // The plt address of `symbol` in the library will be replaced with hook_handler, and 21 | // its original value will be put to the address specified by `origianl` (can be null). 22 | // You can use this api to do caller-oriented hook 23 | // If you want to unhook, please call this function with hook_handler = original 24 | // If hook succeed, returns ZN_SUCCESS, otherwise ZN_FAILED 25 | int (*pltHook)(void* base_addr, const char* symbol, void* hook_handler, void** original); 26 | 27 | // Do inline hook at the address specified by `target`, replace it with a new function specified 28 | // by `addr`, and the param `original` receives the address of original function. 29 | // You can use this api to achieve a global hook in current process. 30 | // In the current implementation , an address can only hook once, so the module can't hook an 31 | // address which is already hooked by an another module, except that the module unhooked it. 32 | // If hooking succeed, returns ZN_SUCCESS, otherwise ZN_FAILED 33 | int (*inlineHook)(void* target, void* addr, void** original); 34 | 35 | // Unhook the address which is formerly hooked. 36 | // If hook succeed, returns ZN_SUCCESS, otherwise ZN_FAILED 37 | int (*inlineUnhook)(void* target); 38 | 39 | // Symbol Resolver API 40 | 41 | // Obtain a new ZnSymbolResolver object 42 | // `path` is required, which specifies the path of library to resolve. It can be an absolute path 43 | // or just the file name of library, e.g. /system/lib64/libc.so or libc.so . 44 | // If `base_addr` is non-zero, it will be used as the base address of the library. 45 | // Otherwise, Zygisk Next will try to find out the base address of the specified library in this process. 46 | // If succeed, it returns a valid pointer to the symbol resolver, otherwise nullptr is returned. 47 | struct ZnSymbolResolver* (*newSymbolResolver)(const char* path, void* base_addr); 48 | 49 | // Release the ZnSymbolResolver object pointed by `resolver`. 50 | void (*freeSymbolResolver)(struct ZnSymbolResolver* resolver); 51 | 52 | // Retrieve the base address of the library of the resolver image in the process. 53 | void* (*getBaseAddress)(struct ZnSymbolResolver* resolver); 54 | 55 | // Lookup the address of symbol by name or prefix (if `prefix` is true) 56 | // If the symbol exists, the function returns its address, otherwise returns nullptr. 57 | // If `size` is not nullptr, the size of the symbol will be put to *size . 58 | // In the current implementation, gnu_debugdata resolution is supported. 59 | void* (*symbolLookup)(struct ZnSymbolResolver* resolver, const char* name, bool prefix, size_t* size); 60 | 61 | // Walk through the symbol table of the library, the callback will receive the name, the address, 62 | // and the size of each symbol. Returning false in the callback means stop the walking. 63 | void (*forEachSymbols)(struct ZnSymbolResolver* resolver, 64 | bool (*callback)(const char* name, void* addr, size_t size, void* data), 65 | void* data); 66 | 67 | // Companion API 68 | 69 | // Create a unix sock stream connection to your declared companion process. 70 | // The value of `handle` is the `self_handle` which you've received from onModuleLoaded. 71 | // On success, it returns the file descriptor refer to the socket, otherwise -1 is returned. 72 | // Please close this file descriptor by yourself. 73 | int (*connectCompanion)(void* handle); 74 | }; 75 | 76 | // Callbacks of an injected library 77 | struct ZygiskNextModule { 78 | // Please fill this with the target version of your module, e.g. ZYGISK_NEXT_API_VERSION_1 79 | int target_api_version; 80 | 81 | // This callback will be called after all needed library of the main executable are loaded, 82 | // and before the entry (i.e. `main`) of the main executable is called. 83 | void (*onModuleLoaded)(void* self_handle, const struct ZygiskNextAPI* api); 84 | }; 85 | 86 | // Callbacks of a companion library 87 | struct ZygiskNextCompanionModule { 88 | int target_api_version; 89 | 90 | void (*onCompanionLoaded)(); 91 | 92 | // This callback will be called when your Zygisk Next module is trying to establish a connection 93 | // with your companion module, i.e. `connectCompanion` is called. 94 | // The `fd` param will be a unix sock stream file descriptor. 95 | // Please close this file descriptor after use by yourself. 96 | void (*onModuleConnected)(int fd); 97 | }; 98 | 99 | // Please define your `zn_module` in your source file. 100 | extern __attribute__((visibility("default"), unused)) struct ZygiskNextModule zn_module; 101 | extern __attribute__((visibility("default"), unused)) struct ZygiskNextCompanionModule zn_companion_module; 102 | 103 | #ifdef __cplusplus 104 | } 105 | #endif 106 | -------------------------------------------------------------------------------- /module/template/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 34 | -------------------------------------------------------------------------------- /module/template/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /module/template/customize.sh: -------------------------------------------------------------------------------- 1 | # shellcheck disable=SC2034 2 | SKIPUNZIP=1 3 | 4 | DEBUG=@DEBUG@ 5 | SONAME=@SONAME@ 6 | SUPPORTED_ABIS="@SUPPORTED_ABIS@" 7 | 8 | if [ "$BOOTMODE" ] && [ "$KSU" ]; then 9 | ui_print "- Installing from KernelSU app" 10 | ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)" 11 | if [ "$(which magisk)" ]; then 12 | ui_print "*********************************************************" 13 | ui_print "! Multiple root implementation is NOT supported!" 14 | ui_print "! Please uninstall Magisk before installing $SONAME" 15 | abort "*********************************************************" 16 | fi 17 | elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then 18 | ui_print "- Installing from Magisk app" 19 | else 20 | ui_print "*********************************************************" 21 | ui_print "! Install from recovery is not supported" 22 | ui_print "! Please install from KernelSU or Magisk app" 23 | abort "*********************************************************" 24 | fi 25 | 26 | VERSION=$(grep_prop version "${TMPDIR}/module.prop") 27 | ui_print "- Installing $SONAME $VERSION" 28 | 29 | # check architecture 30 | support=false 31 | for abi in $SUPPORTED_ABIS 32 | do 33 | if [ "$ARCH" == "$abi" ]; then 34 | support=true 35 | fi 36 | done 37 | if [ "$support" == "false" ]; then 38 | abort "! Unsupported platform: $ARCH" 39 | else 40 | ui_print "- Device platform: $ARCH" 41 | fi 42 | 43 | ui_print "- Extracting verify.sh" 44 | unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2 45 | if [ ! -f "$TMPDIR/verify.sh" ]; then 46 | ui_print "*********************************************************" 47 | ui_print "! Unable to extract verify.sh!" 48 | ui_print "! This zip may be corrupted, please try downloading again" 49 | abort "*********************************************************" 50 | fi 51 | . "$TMPDIR/verify.sh" 52 | extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip" 53 | extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip" 54 | extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR" 55 | 56 | ui_print "- Extracting module files" 57 | extract "$ZIPFILE" 'module.prop' "$MODPATH" 58 | extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" 59 | extract "$ZIPFILE" 'service.sh' "$MODPATH" 60 | extract "$ZIPFILE" 'zn_modules.txt' "$MODPATH" 61 | mv "$TMPDIR/sepolicy.rule" "$MODPATH" 62 | 63 | mkdir "$MODPATH/lib" 64 | 65 | ui_print "- Extracting $ARCH libraries" 66 | extract "$ZIPFILE" "lib/$ARCH/lib$SONAME.so" "$MODPATH/lib" true 67 | -------------------------------------------------------------------------------- /module/template/module.prop: -------------------------------------------------------------------------------- 1 | id=${moduleId} 2 | name=${moduleName} 3 | version=${versionName} 4 | versionCode=${versionCode} 5 | author=5ec1cff 6 | description=Enable adbd root 7 | #updateJson= 8 | -------------------------------------------------------------------------------- /module/template/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | MODDIR=${0%/*} 2 | -------------------------------------------------------------------------------- /module/template/sepolicy.rule: -------------------------------------------------------------------------------- 1 | allow adbd adbd process setcurrent 2 | allow adbd su process dyntransition 3 | allow adbd magisk process dyntransition 4 | -------------------------------------------------------------------------------- /module/template/service.sh: -------------------------------------------------------------------------------- 1 | DEBUG=@DEBUG@ 2 | 3 | MODDIR=${0%/*} 4 | -------------------------------------------------------------------------------- /module/template/verify.sh: -------------------------------------------------------------------------------- 1 | TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip" 2 | mkdir "$TMPDIR_FOR_VERIFY" 3 | 4 | abort_verify() { 5 | ui_print "*********************************************************" 6 | ui_print "! $1" 7 | ui_print "! This zip may be corrupted, please try downloading again" 8 | abort "*********************************************************" 9 | } 10 | 11 | # extract 12 | extract() { 13 | zip=$1 14 | file=$2 15 | dir=$3 16 | junk_paths=$4 17 | [ -z "$junk_paths" ] && junk_paths=false 18 | opts="-o" 19 | [ $junk_paths = true ] && opts="-oj" 20 | 21 | file_path="" 22 | hash_path="" 23 | if [ $junk_paths = true ]; then 24 | file_path="$dir/$(basename "$file")" 25 | hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256" 26 | else 27 | file_path="$dir/$file" 28 | hash_path="$TMPDIR_FOR_VERIFY/$file.sha256" 29 | fi 30 | 31 | unzip $opts "$zip" "$file" -d "$dir" >&2 32 | [ -f "$file_path" ] || abort_verify "$file not exists" 33 | 34 | unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2 35 | [ -f "$hash_path" ] || abort_verify "$file.sha256 not exists" 36 | 37 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 38 | ui_print "- Verified $file" >&1 39 | } 40 | 41 | file="META-INF/com/google/android/update-binary" 42 | file_path="$TMPDIR_FOR_VERIFY/$file" 43 | hash_path="$file_path.sha256" 44 | unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2 45 | [ -f "$file_path" ] || abort_verify "$file not exists" 46 | if [ -f "$hash_path" ]; then 47 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 48 | ui_print "- Verified $file" >&1 49 | else 50 | ui_print "- Download from Magisk app" 51 | fi 52 | -------------------------------------------------------------------------------- /module/template/zn_modules.txt: -------------------------------------------------------------------------------- 1 | name=adbd lib/lib${moduleId}.so 2 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | rootProject.name = "ZNAdbRoot" 18 | include( 19 | ":module" 20 | ) 21 | --------------------------------------------------------------------------------