├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── module ├── .gitignore ├── build.gradle.kts ├── jni │ ├── Android.mk │ ├── Application.mk │ ├── fd_reopener.cpp │ ├── include │ │ ├── android_filesystem_config.h │ │ ├── fd_reopener.hpp │ │ ├── logging.hpp │ │ ├── map_parser.hpp │ │ ├── modules.hpp │ │ ├── mountinfo_parser.hpp │ │ ├── utils.hpp │ │ └── zygisk.hpp │ ├── main.cpp │ ├── map_parser.cpp │ ├── modules.cpp │ ├── mountinfo_parser.cpp │ └── utils.cpp └── template │ ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── update-binary │ │ └── updater-script │ ├── common_func.sh │ ├── module.prop │ ├── post-fs-data.sh │ └── service.sh ├── settings.gradle.kts └── update_metadata ├── CHANGELOG.md └── update.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | gradlew.bat text eol=crlf 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **Describe the bug** 13 | 14 | 15 | **Steps To Reproduce** 16 | 17 | 1. 18 | 2. 19 | 3. 20 | 21 | **Context** 22 | 23 | - Device: 24 | - OS: 25 | - Version of Magisk, KSU or APatch: 26 | - Other Root Module(s): None 27 | - LSPosed Module(s): None 28 | 29 | **Logcat** 30 | 31 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | permissions: 4 | contents: write 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v4 12 | with: 13 | submodules: "recursive" 14 | 15 | - name: Setup Java 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: "temurin" 19 | java-version: 17 20 | 21 | - name: Setup Gradle 22 | uses: gradle/actions/setup-gradle@v3 23 | 24 | - name: Build with Gradle 25 | run: ./gradlew moduleZipRelease moduleZipDebug 26 | 27 | - name: Set CI Variables 28 | if: success() 29 | id: prepareArtifact 30 | run: | 31 | echo "releaseName=$(basename module/build/outputs/zip/*-release.zip .zip)" >> $GITHUB_OUTPUT 32 | echo "debugName=$(basename module/build/outputs/zip/*-debug.zip .zip)" >> $GITHUB_OUTPUT 33 | 34 | - name: Upload Release Artifact 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: ${{ steps.prepareArtifact.outputs.releaseName }} 38 | path: "module/build/outputs/zip/release" 39 | 40 | - name: Upload Debug Artifact 41 | uses: actions/upload-artifact@v4 42 | with: 43 | name: ${{ steps.prepareArtifact.outputs.debugName }} 44 | path: "module/build/outputs/zip/debug" 45 | 46 | - name: Create a release 47 | if: startsWith(github.ref, 'refs/tags/v') 48 | uses: softprops/action-gh-release@v2 49 | with: 50 | files: module/build/outputs/zip/*.zip 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle.gitignore 2 | .gradle 3 | **/build/ 4 | !src/**/build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Avoid ignore Gradle wrappper properties 13 | !gradle-wrapper.properties 14 | 15 | # Cache of project 16 | .gradletasknamecache 17 | 18 | # Eclipse Gradle plugin generated files 19 | # Eclipse Core 20 | .project 21 | # JDT-specific (Eclipse Java Development Tools) 22 | .classpath 23 | 24 | ### VisualStudioCode.gitignore 25 | .vscode/* 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | !.vscode/*.code-snippets 30 | 31 | # Local History for Visual Studio Code 32 | .history/ 33 | 34 | # Built Visual Studio Code Extensions 35 | *.vsix 36 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "module/jni/elfio"] 2 | path = module/jni/elfio 3 | url = https://github.com/serge1/ELFIO.git 4 | [submodule "module/jni/libcxx"] 5 | path = module/jni/libcxx 6 | url = https://github.com/topjohnwu/libcxx.git 7 | [submodule "module/jni/aosp_fd_utils"] 8 | path = module/jni/aosp_fd_utils 9 | url = https://github.com/snake-4/aosp_fd_utils.git 10 | [submodule "module/jni/aosp_system_properties"] 11 | path = module/jni/aosp_system_properties 12 | url = https://github.com/topjohnwu/system_properties 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 snake-4 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Zygisk Assistant

2 | 3 |

4 | A Zygisk module that aims to hide the existence root and Zygisk. 5 |
6 |
7 | Report Bug 8 | · 9 | Request Feature 10 | · 11 | Latest Release 12 |

13 | 14 | 15 | 16 | 17 | ## About The Project 18 | 19 | Using the **release** build is recommended over the debug build. Only use debug builds if you are going to make a bug report. 20 | 21 | ### KernelSU & APatch users: 22 | 1. Install ZygiskNext. 23 | 1. Make sure the unmount setting is enabled for the target app in the KernelSU/APatch Manager. 24 | 1. Disable `Enforce DenyList` in ZygiskNext settings if there is one. 25 | 26 | ### Magisk users: 27 | 1. Update your Magisk to 27.0 or newer for better hiding capabilities. (optional) 28 | 1. Turn on Zygisk in Magisk settings. 29 | 1. Turn off `Enforce DenyList` in Magisk settings. 30 | 1. Add the target app to the deny list unless you're using a Magisk fork with a white list instead. 31 | 32 | 33 | 34 | ## Contributing 35 | 36 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 37 | 38 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 39 | Don't forget to give the project a star! Thanks again! 40 | 41 | 1. Fork the Project 42 | 2. Create your Feature Branch (`git checkout -b feature/FeatureName`) 43 | 3. Commit your Changes (`git commit -m 'Add some FeatureName'`) 44 | 4. Push to the Branch (`git push origin feature/FeatureName`) 45 | 5. Open a Pull Request 46 | 47 | 48 | 49 | ## License 50 | 51 | Distributed under the MIT License. See `LICENSE` for more information. -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.io.ByteArrayOutputStream 2 | 3 | plugins { 4 | id("com.android.library") version "8.8.1" apply false 5 | } 6 | 7 | val commitHash: String by extra { 8 | val result = providers.exec { 9 | commandLine("git", "rev-parse", "--verify", "--short", "HEAD") 10 | } 11 | result.standardOutput.asText.get().trim() 12 | } 13 | 14 | val moduleId by extra("zygisk-assistant") 15 | val moduleName by extra("Zygisk Assistant") 16 | val verName by extra("v2.1.4") 17 | val verCode by extra(214) 18 | -------------------------------------------------------------------------------- /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/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snake-4/Zygisk-Assistant/847d7fb255dfc5c33ae888d22318a53fa63fcd8b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /obj 4 | -------------------------------------------------------------------------------- /module/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import android.databinding.tool.ext.capitalizeUS 2 | 3 | plugins { 4 | id("com.android.library") 5 | } 6 | 7 | val moduleId: String by rootProject.extra 8 | val moduleName: String by rootProject.extra 9 | val verCode: Int by rootProject.extra 10 | val commitHash: String by rootProject.extra 11 | val verName: String by rootProject.extra 12 | val abiList: List by rootProject.extra 13 | 14 | android { 15 | namespace = "com.example.library" 16 | compileSdkVersion = "android-34" 17 | ndkVersion = "28.0.13004108" 18 | defaultConfig { 19 | minSdk = 21 20 | externalNativeBuild { 21 | ndkBuild { 22 | arguments("-j${Runtime.getRuntime().availableProcessors()}") 23 | } 24 | } 25 | } 26 | externalNativeBuild { 27 | ndkBuild { 28 | path("jni/Android.mk") 29 | } 30 | } 31 | } 32 | 33 | androidComponents.onVariants { variant -> 34 | val variantLowered = variant.name.lowercase() 35 | val variantCapped = variant.name.capitalizeUS() 36 | val buildTypeLowered = variant.buildType?.lowercase() 37 | 38 | val libOutDir = layout.buildDirectory.dir("intermediates/stripped_native_libs/$variantLowered/strip${variantCapped}DebugSymbols/out/lib").get() 39 | val moduleDir = layout.buildDirectory.dir("outputs/zip/$variantLowered").get() 40 | val zipOutDir = layout.buildDirectory.dir("outputs/zip/").get() 41 | val zipFileName = "$moduleName-$verName-$commitHash-$buildTypeLowered.zip".replace(' ', '-') 42 | 43 | val moduleFilesTask = task("moduleFiles$variantCapped") { 44 | group = "module" 45 | dependsOn("assemble$variantCapped") 46 | into(moduleDir) 47 | from("$projectDir/template") { 48 | include("module.prop") 49 | expand( 50 | "moduleId" to moduleId, 51 | "moduleName" to moduleName, 52 | "versionName" to "$verName ($commitHash-$variantLowered)", 53 | "versionCode" to verCode 54 | ) 55 | } 56 | from("$projectDir/template") { 57 | exclude("module.prop") 58 | } 59 | from(libOutDir) { 60 | into("zygisk") 61 | } 62 | doLast { 63 | moduleDir.dir("zygisk").asFile.listFiles { f -> f.isDirectory }?.forEach { sourceDir -> 64 | val srcFile = file("$sourceDir/libzygisk.so") 65 | val dstFile = moduleDir.file("zygisk/${sourceDir.name}.so") 66 | srcFile.copyTo(dstFile.asFile, overwrite=true) 67 | delete(sourceDir) 68 | } 69 | } 70 | } 71 | 72 | task("moduleZip$variantCapped") { 73 | group = "module" 74 | dependsOn(moduleFilesTask) 75 | archiveFileName.set(zipFileName) 76 | destinationDirectory.set(zipOutDir) 77 | from(moduleDir) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /module/jni/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | include $(CLEAR_VARS) 4 | LOCAL_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/elfio 5 | LOCAL_MODULE := zygisk 6 | LOCAL_SRC_FILES := fd_reopener.cpp utils.cpp map_parser.cpp mountinfo_parser.cpp modules.cpp main.cpp 7 | LOCAL_STATIC_LIBRARIES := libcxx libsystemproperties libfdutils 8 | LOCAL_LDLIBS := -llog 9 | include $(BUILD_SHARED_LIBRARY) 10 | 11 | include jni/libcxx/Android.mk 12 | include jni/aosp_system_properties/Android.mk 13 | include jni/aosp_fd_utils/Android.mk 14 | -------------------------------------------------------------------------------- /module/jni/Application.mk: -------------------------------------------------------------------------------- 1 | APP_ABI := armeabi-v7a arm64-v8a x86 x86_64 2 | APP_CPPFLAGS := -std=c++20 -fno-exceptions -fno-rtti -fvisibility=hidden -fvisibility-inlines-hidden 3 | APP_STL := none 4 | APP_PLATFORM := android-34 5 | -------------------------------------------------------------------------------- /module/jni/fd_reopener.cpp: -------------------------------------------------------------------------------- 1 | #include "fd_reopener.hpp" 2 | #include 3 | #include 4 | #include 5 | #include "logging.hpp" 6 | 7 | FDReopener::ScopedRegularReopener::ScopedRegularReopener() 8 | { 9 | auto pFdSet = GetOpenFds([](const std::string &error) 10 | { LOGE("GetOpenFds: %s", error.c_str()); }); 11 | if (pFdSet) 12 | { 13 | for (const auto &fd : *pFdSet) 14 | { 15 | auto pFDI = FileDescriptorInfo::CreateFromFd(fd, [fd](const std::string &error) 16 | { LOGE("CreateFromFd(%d): %s", fd, error.c_str()); }); 17 | 18 | // Only process regular files that are not memfds 19 | if (pFDI && !pFDI->is_sock && !pFDI->file_path.starts_with("/memfd:")) 20 | fdi_vector.emplace_back(std::move(pFDI)); 21 | } 22 | } 23 | } 24 | 25 | FDReopener::ScopedRegularReopener::~ScopedRegularReopener() 26 | { 27 | for (const auto &pFDI : fdi_vector) 28 | { 29 | LOGD("Reopening FD %d with %s", pFDI->fd, pFDI->file_path.c_str()); 30 | pFDI->ReopenOrDetach([fd = pFDI->fd](const std::string &error) 31 | { LOGE("ReopenOrDetach(%d): %s", fd, error.c_str()); }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /module/jni/include/android_filesystem_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2007 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /* 17 | * This file is consumed by build/tools/fs_config and is used 18 | * for generating various files. Anything #define AID_ 19 | * becomes the mapping for getpwnam/getpwuid, etc. The 20 | * field is lowercased. 21 | * For example: 22 | * #define AID_FOO_BAR 6666 becomes a friendly name of "foo_bar" 23 | * 24 | * The above holds true with the exception of: 25 | * mediacodec 26 | * mediaex 27 | * mediadrm 28 | * Whose friendly names do not match the #define statements. 29 | * 30 | * This file must only be used for platform (Google managed, and submitted through AOSP), AIDs. 3rd 31 | * party AIDs must be added via config.fs, which will place them in the corresponding partition's 32 | * passwd and group files. There are ranges in this file reserved for AIDs for each 3rd party 33 | * partition, from which the system reads passwd and group files. 34 | */ 35 | #pragma once 36 | /* This is the main Users and Groups config for the platform. 37 | * DO NOT EVER RENUMBER 38 | */ 39 | #define AID_ROOT 0 /* traditional unix root user */ 40 | /* The following are for tests like LTP and should only be used for testing. */ 41 | #define AID_DAEMON 1 /* Traditional unix daemon owner. */ 42 | #define AID_BIN 2 /* Traditional unix binaries owner. */ 43 | #define AID_SYS 3 /* A group with the same gid on Linux/macOS/Android. */ 44 | #define AID_SYSTEM 1000 /* system server */ 45 | #define AID_RADIO 1001 /* telephony subsystem, RIL */ 46 | #define AID_BLUETOOTH 1002 /* bluetooth subsystem */ 47 | #define AID_GRAPHICS 1003 /* graphics devices */ 48 | #define AID_INPUT 1004 /* input devices */ 49 | #define AID_AUDIO 1005 /* audio devices */ 50 | #define AID_CAMERA 1006 /* camera devices */ 51 | #define AID_LOG 1007 /* log devices */ 52 | #define AID_COMPASS 1008 /* compass device */ 53 | #define AID_MOUNT 1009 /* mountd socket */ 54 | #define AID_WIFI 1010 /* wifi subsystem */ 55 | #define AID_ADB 1011 /* android debug bridge (adbd) */ 56 | #define AID_INSTALL 1012 /* group for installing packages */ 57 | #define AID_MEDIA 1013 /* mediaserver process */ 58 | #define AID_DHCP 1014 /* dhcp client */ 59 | #define AID_SDCARD_RW 1015 /* external storage write access */ 60 | #define AID_VPN 1016 /* vpn system */ 61 | #define AID_KEYSTORE 1017 /* keystore subsystem */ 62 | #define AID_USB 1018 /* USB devices */ 63 | #define AID_DRM 1019 /* DRM server */ 64 | #define AID_MDNSR 1020 /* MulticastDNSResponder (service discovery) */ 65 | #define AID_GPS 1021 /* GPS daemon */ 66 | #define AID_UNUSED1 1022 /* deprecated, DO NOT USE */ 67 | #define AID_MEDIA_RW 1023 /* internal media storage write access */ 68 | #define AID_MTP 1024 /* MTP USB driver access */ 69 | #define AID_UNUSED2 1025 /* deprecated, DO NOT USE */ 70 | #define AID_DRMRPC 1026 /* group for drm rpc */ 71 | #define AID_NFC 1027 /* nfc subsystem */ 72 | #define AID_SDCARD_R 1028 /* external storage read access */ 73 | #define AID_CLAT 1029 /* clat part of nat464 */ 74 | #define AID_LOOP_RADIO 1030 /* loop radio devices */ 75 | #define AID_MEDIA_DRM 1031 /* MediaDrm plugins */ 76 | #define AID_PACKAGE_INFO 1032 /* access to installed package details */ 77 | #define AID_SDCARD_PICS 1033 /* external storage photos access */ 78 | #define AID_SDCARD_AV 1034 /* external storage audio/video access */ 79 | #define AID_SDCARD_ALL 1035 /* access all users external storage */ 80 | #define AID_LOGD 1036 /* log daemon */ 81 | #define AID_SHARED_RELRO 1037 /* creator of shared GNU RELRO files */ 82 | #define AID_DBUS 1038 /* dbus-daemon IPC broker process */ 83 | #define AID_TLSDATE 1039 /* tlsdate unprivileged user */ 84 | #define AID_MEDIA_EX 1040 /* mediaextractor process */ 85 | #define AID_AUDIOSERVER 1041 /* audioserver process */ 86 | #define AID_METRICS_COLL 1042 /* metrics_collector process */ 87 | #define AID_METRICSD 1043 /* metricsd process */ 88 | #define AID_WEBSERV 1044 /* webservd process */ 89 | #define AID_DEBUGGERD 1045 /* debuggerd unprivileged user */ 90 | #define AID_MEDIA_CODEC 1046 /* mediacodec process */ 91 | #define AID_CAMERASERVER 1047 /* cameraserver process */ 92 | #define AID_FIREWALL 1048 /* firewalld process */ 93 | #define AID_TRUNKS 1049 /* trunksd process (TPM daemon) */ 94 | #define AID_NVRAM 1050 /* Access-controlled NVRAM */ 95 | #define AID_DNS 1051 /* DNS resolution daemon (system: netd) */ 96 | #define AID_DNS_TETHER 1052 /* DNS resolution daemon (tether: dnsmasq) */ 97 | #define AID_WEBVIEW_ZYGOTE 1053 /* WebView zygote process */ 98 | #define AID_VEHICLE_NETWORK 1054 /* Vehicle network service */ 99 | #define AID_MEDIA_AUDIO 1055 /* GID for audio files on internal media storage */ 100 | #define AID_MEDIA_VIDEO 1056 /* GID for video files on internal media storage */ 101 | #define AID_MEDIA_IMAGE 1057 /* GID for image files on internal media storage */ 102 | #define AID_TOMBSTONED 1058 /* tombstoned user */ 103 | #define AID_MEDIA_OBB 1059 /* GID for OBB files on internal media storage */ 104 | #define AID_ESE 1060 /* embedded secure element (eSE) subsystem */ 105 | #define AID_OTA_UPDATE 1061 /* resource tracking UID for OTA updates */ 106 | #define AID_AUTOMOTIVE_EVS 1062 /* Automotive rear and surround view system */ 107 | #define AID_LOWPAN 1063 /* LoWPAN subsystem */ 108 | #define AID_HSM 1064 /* hardware security module subsystem */ 109 | #define AID_RESERVED_DISK 1065 /* GID that has access to reserved disk space */ 110 | #define AID_STATSD 1066 /* statsd daemon */ 111 | #define AID_INCIDENTD 1067 /* incidentd daemon */ 112 | #define AID_SECURE_ELEMENT 1068 /* secure element subsystem */ 113 | #define AID_LMKD 1069 /* low memory killer daemon */ 114 | #define AID_LLKD 1070 /* live lock daemon */ 115 | #define AID_IORAPD 1071 /* input/output readahead and pin daemon */ 116 | #define AID_GPU_SERVICE 1072 /* GPU service daemon */ 117 | #define AID_NETWORK_STACK 1073 /* network stack service */ 118 | #define AID_GSID 1074 /* GSI service daemon */ 119 | #define AID_FSVERITY_CERT 1075 /* fs-verity key ownership in keystore */ 120 | #define AID_CREDSTORE 1076 /* identity credential manager service */ 121 | #define AID_EXTERNAL_STORAGE 1077 /* Full external storage access including USB OTG volumes */ 122 | #define AID_EXT_DATA_RW 1078 /* GID for app-private data directories on external storage */ 123 | #define AID_EXT_OBB_RW 1079 /* GID for OBB directories on external storage */ 124 | #define AID_CONTEXT_HUB 1080 /* GID for access to the Context Hub */ 125 | #define AID_VIRTUALIZATIONSERVICE 1081 /* VirtualizationService daemon */ 126 | #define AID_ARTD 1082 /* ART Service daemon */ 127 | #define AID_UWB 1083 /* UWB subsystem */ 128 | #define AID_THREAD_NETWORK 1084 /* Thread Network subsystem */ 129 | #define AID_DICED 1085 /* Android's DICE daemon */ 130 | #define AID_DMESGD 1086 /* dmesg parsing daemon for kernel report collection */ 131 | #define AID_JC_WEAVER 1087 /* Javacard Weaver HAL - to manage omapi ARA rules */ 132 | #define AID_JC_STRONGBOX 1088 /* Javacard Strongbox HAL - to manage omapi ARA rules */ 133 | #define AID_JC_IDENTITYCRED 1089 /* Javacard Identity Cred HAL - to manage omapi ARA rules */ 134 | #define AID_SDK_SANDBOX 1090 /* SDK sandbox virtual UID */ 135 | #define AID_SECURITY_LOG_WRITER 1091 /* write to security log */ 136 | #define AID_PRNG_SEEDER 1092 /* PRNG seeder daemon */ 137 | /* Changes to this file must be made in AOSP, *not* in internal branches. */ 138 | #define AID_SHELL 2000 /* adb and debug shell user */ 139 | #define AID_CACHE 2001 /* cache access */ 140 | #define AID_DIAG 2002 /* access to diagnostic resources */ 141 | /* The range 2900-2999 is reserved for the vendor partition */ 142 | /* Note that the two 'OEM' ranges pre-dated the vendor partition, so they take the legacy 'OEM' 143 | * name. Additionally, they pre-dated passwd/group files, so there are users and groups named oem_# 144 | * created automatically for all values in these ranges. If there is a user/group in a passwd/group 145 | * file corresponding to this range, both the oem_# and user/group names will resolve to the same 146 | * value. */ 147 | #define AID_OEM_RESERVED_START 2900 148 | #define AID_OEM_RESERVED_END 2999 149 | /* The 3000 series are intended for use as supplemental group id's only. 150 | * They indicate special Android capabilities that the kernel is aware of. */ 151 | #define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */ 152 | #define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */ 153 | #define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */ 154 | #define AID_NET_RAW 3004 /* can create raw INET sockets */ 155 | #define AID_NET_ADMIN 3005 /* can configure interfaces and routing tables. */ 156 | #define AID_NET_BW_STATS 3006 /* read bandwidth statistics */ 157 | #define AID_NET_BW_ACCT 3007 /* change bandwidth statistics accounting */ 158 | #define AID_READPROC 3009 /* Allow /proc read access */ 159 | #define AID_WAKELOCK 3010 /* Allow system wakelock read/write access */ 160 | #define AID_UHID 3011 /* Allow read/write to /dev/uhid node */ 161 | #define AID_READTRACEFS 3012 /* Allow tracefs read */ 162 | /* The range 5000-5999 is also reserved for vendor partition. */ 163 | #define AID_OEM_RESERVED_2_START 5000 164 | #define AID_OEM_RESERVED_2_END 5999 165 | /* The range 6000-6499 is reserved for the system partition. */ 166 | #define AID_SYSTEM_RESERVED_START 6000 167 | #define AID_SYSTEM_RESERVED_END 6499 168 | /* The range 6500-6999 is reserved for the odm partition. */ 169 | #define AID_ODM_RESERVED_START 6500 170 | #define AID_ODM_RESERVED_END 6999 171 | /* The range 7000-7499 is reserved for the product partition. */ 172 | #define AID_PRODUCT_RESERVED_START 7000 173 | #define AID_PRODUCT_RESERVED_END 7499 174 | /* The range 7500-7999 is reserved for the system_ext partition. */ 175 | #define AID_SYSTEM_EXT_RESERVED_START 7500 176 | #define AID_SYSTEM_EXT_RESERVED_END 7999 177 | #define AID_EVERYBODY 9997 /* shared between all apps in the same profile */ 178 | #define AID_MISC 9998 /* access to misc storage */ 179 | #define AID_NOBODY 9999 180 | #define AID_APP 10000 /* TODO: switch users over to AID_APP_START */ 181 | #define AID_APP_START 10000 /* first app user */ 182 | #define AID_APP_END 19999 /* last app user */ 183 | #define AID_CACHE_GID_START 20000 /* start of gids for apps to mark cached data */ 184 | #define AID_CACHE_GID_END 29999 /* end of gids for apps to mark cached data */ 185 | #define AID_EXT_GID_START 30000 /* start of gids for apps to mark external data */ 186 | #define AID_EXT_GID_END 39999 /* end of gids for apps to mark external data */ 187 | #define AID_EXT_CACHE_GID_START 40000 /* start of gids for apps to mark external cached data */ 188 | #define AID_EXT_CACHE_GID_END 49999 /* end of gids for apps to mark external cached data */ 189 | #define AID_SHARED_GID_START 50000 /* start of gids for apps in each user to share */ 190 | #define AID_SHARED_GID_END 59999 /* end of gids for apps in each user to share */ 191 | /* 192 | * This is a magic number in the kernel and not something that was picked 193 | * arbitrarily. This value is returned whenever a uid that has no mapping in the 194 | * user namespace is returned to userspace: 195 | * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/highuid.h?h=v4.4#n40 196 | */ 197 | #define AID_OVERFLOWUID 65534 /* unmapped user in the user namespace */ 198 | /* use the ranges below to determine whether a process is sdk sandbox */ 199 | #define AID_SDK_SANDBOX_PROCESS_START 20000 /* start of uids allocated to sdk sandbox processes */ 200 | #define AID_SDK_SANDBOX_PROCESS_END 29999 /* end of uids allocated to sdk sandbox processes */ 201 | /* use the ranges below to determine whether a process is isolated */ 202 | #define AID_ISOLATED_START 90000 /* start of uids for fully isolated sandboxed processes */ 203 | #define AID_ISOLATED_END 99999 /* end of uids for fully isolated sandboxed processes */ 204 | #define AID_USER 100000 /* TODO: switch users over to AID_USER_OFFSET */ 205 | #define AID_USER_OFFSET 100000 /* offset for uid ranges for each user */ 206 | /* 207 | * android_ids has moved to pwd/grp functionality. 208 | * If you need to add one, the structure is now 209 | * auto-generated based on the AID_ constraints 210 | * documented at the top of this header file. 211 | * Also see build/tools/fs_config for more details. 212 | */ -------------------------------------------------------------------------------- /module/jni/include/fd_reopener.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "fd_utils.h" 3 | #include 4 | #include 5 | 6 | namespace FDReopener 7 | { 8 | class ScopedRegularReopener 9 | { 10 | public: 11 | ScopedRegularReopener(); 12 | ~ScopedRegularReopener(); 13 | 14 | private: 15 | ScopedRegularReopener(const ScopedRegularReopener &); 16 | void operator=(const ScopedRegularReopener &); 17 | std::vector> fdi_vector; 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /module/jni/include/logging.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef NDEBUG 8 | static constexpr auto TAG = "ZygiskAssistant/JNI"; 9 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) 10 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) 11 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) 12 | #else 13 | #define LOGD(...) 14 | #define LOGW(...) 15 | #define LOGE(...) 16 | #endif 17 | -------------------------------------------------------------------------------- /module/jni/include/map_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Parsers 8 | { 9 | class map_entry 10 | { 11 | public: 12 | map_entry(uintptr_t address_start, uintptr_t address_end, uintptr_t offset, 13 | const std::string &perms, const std::string &pathname, dev_t device, ino_t inode); 14 | 15 | uintptr_t getAddressStart() const; 16 | uintptr_t getAddressEnd() const; 17 | const std::string &getPerms() const; 18 | uintptr_t getOffset() const; 19 | dev_t getDevice() const; 20 | ino_t getInode() const; 21 | const std::string &getPathname() const; 22 | 23 | private: 24 | uintptr_t address_start, address_end, offset; 25 | std::string perms, pathname; 26 | dev_t device; 27 | ino_t inode; 28 | }; 29 | 30 | const std::vector &parseSelfMaps(bool cached = true); 31 | } 32 | -------------------------------------------------------------------------------- /module/jni/include/modules.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void doUnmount(); 4 | void doRemount(); 5 | void doHideZygisk(); 6 | void doMrProp(); 7 | -------------------------------------------------------------------------------- /module/jni/include/mountinfo_parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace Parsers 8 | { 9 | class mountinfo_entry 10 | { 11 | public: 12 | mountinfo_entry(int mount_id, int parent_id, dev_t device, 13 | const std::string &root, const std::string &mount_point, 14 | const std::string &mount_options, const std::string &optional_fields, 15 | const std::string &filesystem_type, const std::string &mount_source, 16 | const std::string &super_options); 17 | 18 | int getMountId() const; 19 | int getParentId() const; 20 | dev_t getDevice() const; 21 | const std::string &getRoot() const; 22 | const std::string &getMountPoint() const; 23 | const std::unordered_map &getMountOptions() const; 24 | const std::string &getOptionalFields() const; 25 | const std::string &getFilesystemType() const; 26 | const std::string &getMountSource() const; 27 | const std::unordered_map &getSuperOptions() const; 28 | 29 | private: 30 | dev_t device; 31 | int mount_id, parent_id; 32 | std::string root, mount_point, optional_fields, filesystem_type, mount_source; 33 | std::unordered_map mount_options, super_options; 34 | }; 35 | 36 | const std::vector &parseSelfMountinfo(bool cached = true); 37 | 38 | class mountinfo_root_resolver 39 | { 40 | public: 41 | mountinfo_root_resolver(const std::vector &mount_infos); 42 | std::string resolveRootOf(const mountinfo_entry &mount_info) const; 43 | 44 | private: 45 | std::unordered_map device_mount_map; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /module/jni/include/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "logging.hpp" 6 | #include "zygisk.hpp" 7 | #include "mountinfo_parser.hpp" 8 | 9 | #define DCL_HOOK_FUNC(ret, func, ...) \ 10 | ret (*old_##func)(__VA_ARGS__) = nullptr; \ 11 | ret new_##func(__VA_ARGS__) 12 | 13 | #define ASSERT_LOG(tag, expr) \ 14 | if (!(expr)) \ 15 | { \ 16 | LOGE("%s:%d Assertion %s failed. %d:%s", #tag, __LINE__, #expr, errno, std::strerror(errno)); \ 17 | } 18 | 19 | #define ASSERT_DO(tag, expr, ret) \ 20 | if (!(expr)) \ 21 | { \ 22 | LOGE("%s:%d Assertion %s failed. %d:%s", #tag, __LINE__, #expr, errno, std::strerror(errno)); \ 23 | ret; \ 24 | } 25 | 26 | namespace Utils 27 | { 28 | /* 29 | * Always null terminates dest if dest_size is at least 1. 30 | * Writes at most dest_size bytes to dest including null terminator. 31 | * Reads at most dest_size bytes from src. 32 | * Returns strlen(dest) 33 | */ 34 | size_t safeStringCopy(char *dest, const char *src, size_t dest_size); 35 | 36 | bool switchMountNS(int pid); 37 | int isUserAppUID(int uid); 38 | bool hookPLTByName(zygisk::Api *api, const std::string &libName, const std::string &symbolName, void *hookFunc, void **origFunc); 39 | int forkAndInvoke(const std::function &lambda); 40 | const char *getExtErrorsBehavior(const Parsers::mountinfo_entry &entry); 41 | } 42 | -------------------------------------------------------------------------------- /module/jni/include/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 | #include 22 | 23 | #define ZYGISK_API_VERSION 4 24 | 25 | /* 26 | 27 | *************** 28 | * Introduction 29 | *************** 30 | 31 | On Android, all app processes are forked from a special daemon called "Zygote". 32 | For each new app process, zygote will fork a new process and perform "specialization". 33 | This specialization operation enforces the Android security sandbox on the newly forked 34 | process to make sure that 3rd party application code is only loaded after it is being 35 | restricted within a sandbox. 36 | 37 | On Android, there is also this special process called "system_server". This single 38 | process hosts a significant portion of system services, which controls how the 39 | Android operating system and apps interact with each other. 40 | 41 | The Zygisk framework provides a way to allow developers to build modules and run custom 42 | code before and after system_server and any app processes' specialization. 43 | This enable developers to inject code and alter the behavior of system_server and app processes. 44 | 45 | Please note that modules will only be loaded after zygote has forked the child process. 46 | THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON! 47 | 48 | ********************* 49 | * Development Guide 50 | ********************* 51 | 52 | Define a class and inherit zygisk::ModuleBase to implement the functionality of your module. 53 | Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk. 54 | 55 | Example code: 56 | 57 | static jint (*orig_logger_entry_max)(JNIEnv *env); 58 | static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); } 59 | 60 | class ExampleModule : public zygisk::ModuleBase { 61 | public: 62 | void onLoad(zygisk::Api *api, JNIEnv *env) override { 63 | this->api = api; 64 | this->env = env; 65 | } 66 | void preAppSpecialize(zygisk::AppSpecializeArgs *args) override { 67 | JNINativeMethod methods[] = { 68 | { "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max }, 69 | }; 70 | api->hookJniNativeMethods(env, "android/util/Log", methods, 1); 71 | *(void **) &orig_logger_entry_max = methods[0].fnPtr; 72 | } 73 | private: 74 | zygisk::Api *api; 75 | JNIEnv *env; 76 | }; 77 | 78 | REGISTER_ZYGISK_MODULE(ExampleModule) 79 | 80 | ----------------------------------------------------------------------------------------- 81 | 82 | Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize, 83 | or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class 84 | never runs in a true superuser environment. 85 | 86 | If your module require access to superuser permissions, you can create and register 87 | a root companion handler function. This function runs in a separate root companion 88 | daemon process, and an Unix domain socket is provided to allow you to perform IPC between 89 | your target process and the root companion process. 90 | 91 | Example code: 92 | 93 | static void example_handler(int socket) { ... } 94 | 95 | REGISTER_ZYGISK_COMPANION(example_handler) 96 | 97 | */ 98 | 99 | namespace zygisk { 100 | 101 | struct Api; 102 | struct AppSpecializeArgs; 103 | struct ServerSpecializeArgs; 104 | 105 | class ModuleBase { 106 | public: 107 | 108 | // This method is called as soon as the module is loaded into the target process. 109 | // A Zygisk API handle will be passed as an argument. 110 | virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {} 111 | 112 | // This method is called before the app process is specialized. 113 | // At this point, the process just got forked from zygote, but no app specific specialization 114 | // is applied. This means that the process does not have any sandbox restrictions and 115 | // still runs with the same privilege of zygote. 116 | // 117 | // All the arguments that will be sent and used for app specialization is passed as a single 118 | // AppSpecializeArgs object. You can read and overwrite these arguments to change how the app 119 | // process will be specialized. 120 | // 121 | // If you need to run some operations as superuser, you can call Api::connectCompanion() to 122 | // get a socket to do IPC calls with a root companion process. 123 | // See Api::connectCompanion() for more info. 124 | virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {} 125 | 126 | // This method is called after the app process is specialized. 127 | // At this point, the process has all sandbox restrictions enabled for this application. 128 | // This means that this method runs with the same privilege of the app's own code. 129 | virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {} 130 | 131 | // This method is called before the system server process is specialized. 132 | // See preAppSpecialize(args) for more info. 133 | virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {} 134 | 135 | // This method is called after the system server process is specialized. 136 | // At this point, the process runs with the privilege of system_server. 137 | virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {} 138 | }; 139 | 140 | struct AppSpecializeArgs { 141 | // Required arguments. These arguments are guaranteed to exist on all Android versions. 142 | jint &uid; 143 | jint &gid; 144 | jintArray &gids; 145 | jint &runtime_flags; 146 | jobjectArray &rlimits; 147 | jint &mount_external; 148 | jstring &se_info; 149 | jstring &nice_name; 150 | jstring &instruction_set; 151 | jstring &app_data_dir; 152 | 153 | // Optional arguments. Please check whether the pointer is null before de-referencing 154 | jintArray *const fds_to_ignore; 155 | jboolean *const is_child_zygote; 156 | jboolean *const is_top_app; 157 | jobjectArray *const pkg_data_info_list; 158 | jobjectArray *const whitelisted_data_info_list; 159 | jboolean *const mount_data_dirs; 160 | jboolean *const mount_storage_dirs; 161 | 162 | AppSpecializeArgs() = delete; 163 | }; 164 | 165 | struct ServerSpecializeArgs { 166 | jint &uid; 167 | jint &gid; 168 | jintArray &gids; 169 | jint &runtime_flags; 170 | jlong &permitted_capabilities; 171 | jlong &effective_capabilities; 172 | 173 | ServerSpecializeArgs() = delete; 174 | }; 175 | 176 | namespace internal { 177 | struct api_table; 178 | template void entry_impl(api_table *, JNIEnv *); 179 | } 180 | 181 | // These values are used in Api::setOption(Option) 182 | enum Option : int { 183 | // Force Magisk's denylist unmount routines to run on this process. 184 | // 185 | // Setting this option only makes sense in preAppSpecialize. 186 | // The actual unmounting happens during app process specialization. 187 | // 188 | // Set this option to force all Magisk and modules' files to be unmounted from the 189 | // mount namespace of the process, regardless of the denylist enforcement status. 190 | FORCE_DENYLIST_UNMOUNT = 0, 191 | 192 | // When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize. 193 | // Be aware that after dlclose-ing your module, all of your code will be unmapped from memory. 194 | // YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS. 195 | DLCLOSE_MODULE_LIBRARY = 1, 196 | }; 197 | 198 | // Bit masks of the return value of Api::getFlags() 199 | enum StateFlag : uint32_t { 200 | // The user has granted root access to the current process 201 | PROCESS_GRANTED_ROOT = (1u << 0), 202 | 203 | // The current process was added on the denylist 204 | PROCESS_ON_DENYLIST = (1u << 1), 205 | }; 206 | 207 | // All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded 208 | // from the specialized process afterwards. 209 | struct Api { 210 | 211 | // Connect to a root companion process and get a Unix domain socket for IPC. 212 | // 213 | // This API only works in the pre[XXX]Specialize methods due to SELinux restrictions. 214 | // 215 | // The pre[XXX]Specialize methods run with the same privilege of zygote. 216 | // If you would like to do some operations with superuser permissions, register a handler 217 | // function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func). 218 | // Another good use case for a companion process is that if you want to share some resources 219 | // across multiple processes, hold the resources in the companion process and pass it over. 220 | // 221 | // The root companion process is ABI aware; that is, when calling this method from a 32-bit 222 | // process, you will be connected to a 32-bit companion process, and vice versa for 64-bit. 223 | // 224 | // Returns a file descriptor to a socket that is connected to the socket passed to your 225 | // module's companion request handler. Returns -1 if the connection attempt failed. 226 | int connectCompanion(); 227 | 228 | // Get the file descriptor of the root folder of the current module. 229 | // 230 | // This API only works in the pre[XXX]Specialize methods. 231 | // Accessing the directory returned is only possible in the pre[XXX]Specialize methods 232 | // or in the root companion process (assuming that you sent the fd over the socket). 233 | // Both restrictions are due to SELinux and UID. 234 | // 235 | // Returns -1 if errors occurred. 236 | int getModuleDir(); 237 | 238 | // Set various options for your module. 239 | // Please note that this method accepts one single option at a time. 240 | // Check zygisk::Option for the full list of options available. 241 | void setOption(Option opt); 242 | 243 | // Get information about the current process. 244 | // Returns bitwise-or'd zygisk::StateFlag values. 245 | uint32_t getFlags(); 246 | 247 | // Exempt the provided file descriptor from being automatically closed. 248 | // 249 | // This API only make sense in preAppSpecialize; calling this method in any other situation 250 | // is either a no-op (returns true) or an error (returns false). 251 | // 252 | // When false is returned, the provided file descriptor will eventually be closed by zygote. 253 | bool exemptFd(int fd); 254 | 255 | // Hook JNI native methods for a class 256 | // 257 | // Lookup all registered JNI native methods and replace it with your own methods. 258 | // The original function pointer will be saved in each JNINativeMethod's fnPtr. 259 | // If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr 260 | // will be set to nullptr. 261 | void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods); 262 | 263 | // Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory. 264 | // 265 | // Parsing /proc/[PID]/maps will give you the memory map of a process. As an example: 266 | // 267 | //
268 | // 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64 269 | // (More details: https://man7.org/linux/man-pages/man5/proc.5.html) 270 | // 271 | // The `dev` and `inode` pair uniquely identifies a file being mapped into memory. 272 | // For matching ELFs loaded in memory, replace function `symbol` with `newFunc`. 273 | // If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`. 274 | void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc); 275 | 276 | // Commit all the hooks that was previously registered. 277 | // Returns false if an error occurred. 278 | bool pltHookCommit(); 279 | 280 | private: 281 | internal::api_table *tbl; 282 | template friend void internal::entry_impl(internal::api_table *, JNIEnv *); 283 | }; 284 | 285 | // Register a class as a Zygisk module 286 | 287 | #define REGISTER_ZYGISK_MODULE(clazz) \ 288 | void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \ 289 | zygisk::internal::entry_impl(table, env); \ 290 | } 291 | 292 | // Register a root companion request handler function for your module 293 | // 294 | // The function runs in a superuser daemon process and handles a root companion request from 295 | // your module running in a target process. The function has to accept an integer value, 296 | // which is a Unix domain socket that is connected to the target process. 297 | // See Api::connectCompanion() for more info. 298 | // 299 | // NOTE: the function can run concurrently on multiple threads. 300 | // Be aware of race conditions if you have globally shared resources. 301 | 302 | #define REGISTER_ZYGISK_COMPANION(func) \ 303 | void zygisk_companion_entry(int client) { func(client); } 304 | 305 | /********************************************************* 306 | * The following is internal ABI implementation detail. 307 | * You do not have to understand what it is doing. 308 | *********************************************************/ 309 | 310 | namespace internal { 311 | 312 | struct module_abi { 313 | long api_version; 314 | ModuleBase *impl; 315 | 316 | void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *); 317 | void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *); 318 | void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *); 319 | void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *); 320 | 321 | module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) { 322 | preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); }; 323 | postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); }; 324 | preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); }; 325 | postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); }; 326 | } 327 | }; 328 | 329 | struct api_table { 330 | // Base 331 | void *impl; 332 | bool (*registerModule)(api_table *, module_abi *); 333 | 334 | void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); 335 | void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **); 336 | bool (*exemptFd)(int); 337 | bool (*pltHookCommit)(); 338 | int (*connectCompanion)(void * /* impl */); 339 | void (*setOption)(void * /* impl */, Option); 340 | int (*getModuleDir)(void * /* impl */); 341 | uint32_t (*getFlags)(void * /* impl */); 342 | }; 343 | 344 | template 345 | void entry_impl(api_table *table, JNIEnv *env) { 346 | static Api api; 347 | api.tbl = table; 348 | static T module; 349 | ModuleBase *m = &module; 350 | static module_abi abi(m); 351 | if (!table->registerModule(table, &abi)) return; 352 | m->onLoad(&api, env); 353 | } 354 | 355 | } // namespace internal 356 | 357 | inline int Api::connectCompanion() { 358 | return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1; 359 | } 360 | inline int Api::getModuleDir() { 361 | return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1; 362 | } 363 | inline void Api::setOption(Option opt) { 364 | if (tbl->setOption) tbl->setOption(tbl->impl, opt); 365 | } 366 | inline uint32_t Api::getFlags() { 367 | return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0; 368 | } 369 | inline bool Api::exemptFd(int fd) { 370 | return tbl->exemptFd != nullptr && tbl->exemptFd(fd); 371 | } 372 | inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { 373 | if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods); 374 | } 375 | inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) { 376 | if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc); 377 | } 378 | inline bool Api::pltHookCommit() { 379 | return tbl->pltHookCommit != nullptr && tbl->pltHookCommit(); 380 | } 381 | 382 | } // namespace zygisk 383 | 384 | extern "C" { 385 | 386 | [[gnu::visibility("default"), maybe_unused]] 387 | void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *); 388 | 389 | [[gnu::visibility("default"), maybe_unused]] 390 | void zygisk_companion_entry(int); 391 | 392 | } // extern "C" 393 | -------------------------------------------------------------------------------- /module/jni/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include "zygisk.hpp" 9 | #include "logging.hpp" 10 | #include "utils.hpp" 11 | #include "modules.hpp" 12 | #include "fd_reopener.hpp" 13 | 14 | using zygisk::Api; 15 | using zygisk::AppSpecializeArgs; 16 | using zygisk::ServerSpecializeArgs; 17 | 18 | static std::function callbackFunction = []() {}; 19 | 20 | /* 21 | * [What's the purpose of this hook?] 22 | * Hooking unshare is necessary to stop Zygote from calling unshare a second time, 23 | * because that breaks the FDs. We handle this by reopening FDs, 24 | * allowing us to call unshare twice safely in our callback. 25 | * 26 | * [Doesn't Android already call unshare?] 27 | * Android's use of unshare changes with each major version, so we always call unshare 28 | * in preAppSpecialize. 29 | * > Android 5: Sometimes calls unshare 30 | * > Android 6: Always calls unshare 31 | * > Android 7-11: Sometimes calls unshare 32 | * > Android 12-14: Always calls unshare 33 | */ 34 | DCL_HOOK_FUNC(static int, unshare, int flags) 35 | { 36 | callbackFunction(); 37 | // Do not allow CLONE_NEWNS. 38 | flags &= ~(CLONE_NEWNS); 39 | if (!flags) 40 | { 41 | // If CLONE_NEWNS was the only flag, skip the call. 42 | errno = 0; 43 | return 0; 44 | } 45 | return old_unshare(flags); 46 | } 47 | 48 | /* 49 | * [What's the purpose of this hook?] 50 | * Hooking setresuid ensures we can execute code while we still have CAP_SYS_ADMIN, 51 | * which is necessary for some operations. 52 | * This hook is necessary because setresuid is called unconditionally, 53 | * and we need to perform actions before this syscall. 54 | */ 55 | DCL_HOOK_FUNC(static int, setresuid, uid_t ruid, uid_t euid, uid_t suid) 56 | { 57 | callbackFunction(); 58 | return old_setresuid(ruid, euid, suid); 59 | } 60 | 61 | /* 62 | * [Why is this function needed?] 63 | * This function unconditionally calls unshare to create a new mount namespace. 64 | * It ensures that the new namespace is isolated but still allows propagation of mount 65 | * events from the parent namespace by setting the root as MS_SLAVE. 66 | */ 67 | static bool new_mount_ns() 68 | { 69 | /* 70 | * Unconditional unshare. 71 | */ 72 | ASSERT_DO(new_mount_ns, old_unshare(CLONE_NEWNS) != -1, return false); 73 | 74 | /* 75 | * Mount the app mount namespace's root as MS_SLAVE, so every mount/umount from 76 | * Zygote shared pre-specialization namespace is propagated to this one. 77 | */ 78 | ASSERT_DO(new_mount_ns, mount("rootfs", "/", NULL, (MS_SLAVE | MS_REC), NULL) != -1, return false); 79 | return true; 80 | } 81 | 82 | class ZygiskModule : public zygisk::ModuleBase 83 | { 84 | public: 85 | void onLoad(Api *api, JNIEnv *env) override 86 | { 87 | this->api = api; 88 | this->env = env; 89 | } 90 | 91 | void preAppSpecialize(AppSpecializeArgs *args) override 92 | { 93 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 94 | 95 | uint32_t flags = api->getFlags(); 96 | bool isRoot = (flags & zygisk::StateFlag::PROCESS_GRANTED_ROOT) != 0; 97 | bool isOnDenylist = (flags & zygisk::StateFlag::PROCESS_ON_DENYLIST) != 0; 98 | bool isChildZygote = args->is_child_zygote != NULL && *args->is_child_zygote; 99 | if (isRoot || !isOnDenylist || !Utils::isUserAppUID(args->uid)) 100 | { 101 | LOGD("Skipping ppid=%d uid=%d isChildZygote=%d", getppid(), args->uid, isChildZygote); 102 | return; 103 | } 104 | LOGD("Processing ppid=%d uid=%d isChildZygote=%d", getppid(), args->uid, isChildZygote); 105 | 106 | ASSERT_DO(preAppSpecialize, hookPLTByName("libandroid_runtime.so", "unshare", new_unshare, &old_unshare), return); 107 | ASSERT_DO(preAppSpecialize, hookPLTByName("libandroid_runtime.so", "setresuid", new_setresuid, &old_setresuid), return); 108 | 109 | int companionFd = -1; 110 | ASSERT_LOG(preAppSpecialize, (companionFd = api->connectCompanion()) != -1); 111 | ASSERT_LOG(preAppSpecialize, companionFd != -1 && api->exemptFd(companionFd)); 112 | 113 | callbackFunction = [fd = companionFd]() 114 | { 115 | // Call only once per process. 116 | callbackFunction = []() {}; 117 | FDReopener::ScopedRegularReopener srr; 118 | 119 | if (!new_mount_ns()) 120 | return; 121 | 122 | bool result = false; 123 | if (fd != -1) 124 | { 125 | do 126 | { 127 | pid_t pid = getpid(); 128 | ASSERT_DO(callbackFunction, write(fd, &pid, sizeof(pid)) == sizeof(pid), break); 129 | ASSERT_DO(callbackFunction, read(fd, &result, sizeof(result)) == sizeof(result), break); 130 | } while (false); 131 | close(fd); 132 | } 133 | 134 | if (result) 135 | LOGD("Invoking the companion was successful."); 136 | else 137 | { 138 | LOGW("Invoking the companion failed. Functionality will be limited in Zygote context!"); 139 | doUnmount(); 140 | } 141 | 142 | doHideZygisk(); 143 | }; 144 | } 145 | 146 | void preServerSpecialize(ServerSpecializeArgs *args) override 147 | { 148 | api->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 149 | } 150 | 151 | void postAppSpecialize(const AppSpecializeArgs *args) override 152 | { 153 | if (old_unshare != nullptr) 154 | ASSERT_LOG(postAppSpecialize, hookPLTByName("libandroid_runtime.so", "unshare", old_unshare)); 155 | if (old_setresuid != nullptr) 156 | ASSERT_LOG(postAppSpecialize, hookPLTByName("libandroid_runtime.so", "setresuid", old_setresuid)); 157 | } 158 | 159 | template 160 | bool hookPLTByName(const std::string &libName, const std::string &symbolName, T *hookFunction, T **originalFunction = nullptr) 161 | { 162 | return Utils::hookPLTByName(api, libName, symbolName, (void *)hookFunction, (void **)originalFunction) && api->pltHookCommit(); 163 | } 164 | 165 | private: 166 | Api *api; 167 | JNIEnv *env; 168 | }; 169 | 170 | void zygisk_companion_handler(int fd) 171 | { 172 | pid_t pid; 173 | ASSERT_DO(zygisk_companion_handler, read(fd, &pid, sizeof(pid)) == sizeof(pid), return); 174 | LOGD("zygisk_companion_handler processing namespace of pid=%d", pid); 175 | 176 | // setns requires the caller to be single-threaded 177 | bool result = WIFEXITED(Utils::forkAndInvoke( 178 | [pid]() 179 | { 180 | ASSERT_DO(zygisk_companion_handler, Utils::switchMountNS(pid), return 1); 181 | doUnmount(); 182 | doRemount(); 183 | doMrProp(); 184 | return 0; 185 | })); 186 | 187 | ASSERT_LOG(zygisk_companion_handler, write(fd, &result, sizeof(result)) == sizeof(result)); 188 | } 189 | 190 | REGISTER_ZYGISK_MODULE(ZygiskModule) 191 | REGISTER_ZYGISK_COMPANION(zygisk_companion_handler) 192 | -------------------------------------------------------------------------------- /module/jni/map_parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include // For makedev 6 | 7 | #include "map_parser.hpp" 8 | #include "logging.hpp" 9 | 10 | using namespace Parsers; 11 | 12 | map_entry::map_entry(uintptr_t address_start, uintptr_t address_end, uintptr_t offset, const std::string &perms, const std::string &pathname, dev_t device, ino_t inode) 13 | : address_start(address_start), address_end(address_end), perms(perms), 14 | offset(offset), device(device), inode(inode), pathname(pathname) {} 15 | 16 | uintptr_t map_entry::getAddressStart() const { return address_start; } 17 | uintptr_t map_entry::getAddressEnd() const { return address_end; } 18 | const std::string &map_entry::getPerms() const { return perms; } 19 | uintptr_t map_entry::getOffset() const { return offset; } 20 | dev_t map_entry::getDevice() const { return device; } 21 | ino_t map_entry::getInode() const { return inode; } 22 | const std::string &map_entry::getPathname() const { return pathname; } 23 | 24 | const std::vector &Parsers::parseSelfMaps(bool cached) 25 | { 26 | static std::vector parser_cache; 27 | if (cached && !parser_cache.empty()) 28 | { 29 | return parser_cache; 30 | } 31 | parser_cache.clear(); 32 | 33 | std::ifstream ifs("/proc/self/maps", std::ifstream::in); 34 | if (!ifs) 35 | { 36 | LOGE("parseSelfMaps could not open /proc/self/maps"); 37 | return parser_cache; 38 | } 39 | 40 | for (std::string line; std::getline(ifs, line);) 41 | { 42 | std::istringstream iss(line); 43 | 44 | uintptr_t address_start, address_end, offset; 45 | std::string perms; 46 | std::string pathname; 47 | ino_t inode; 48 | int dev_major, dev_minor; 49 | char dummy_char; 50 | 51 | iss >> std::hex >> address_start >> dummy_char >> address_end >> perms >> offset >> dev_major >> dummy_char >> dev_minor >> std::dec >> inode; 52 | 53 | if (iss.fail()) 54 | { 55 | LOGE("parseSelfMaps failed to parse line: %s", line.c_str()); 56 | continue; 57 | } 58 | 59 | // This operation can fail, it doesn't matter as it's an optional field. 60 | std::getline(iss >> std::ws, pathname); 61 | 62 | parser_cache.emplace_back(map_entry(address_start, address_end, offset, perms, pathname, makedev(dev_major, dev_minor), inode)); 63 | } 64 | 65 | return parser_cache; 66 | } 67 | -------------------------------------------------------------------------------- /module/jni/modules.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // These includes are from the system_properties submodule, not NDK! 12 | #define _REALLY_INCLUDE_SYS__SYSTEM_PROPERTIES_H_ 13 | #include 14 | #include 15 | #include 16 | 17 | #include "zygisk.hpp" 18 | #include "logging.hpp" 19 | #include "map_parser.hpp" 20 | #include "mountinfo_parser.hpp" 21 | #include "utils.hpp" 22 | 23 | using namespace Parsers; 24 | 25 | static const std::set fsname_list = {"KSU", "APatch", "magisk", "worker"}; 26 | static const std::unordered_map mount_flags_procfs = { 27 | {"nosuid", MS_NOSUID}, 28 | {"nodev", MS_NODEV}, 29 | {"noexec", MS_NOEXEC}, 30 | {"noatime", MS_NOATIME}, 31 | {"nodiratime", MS_NODIRATIME}, 32 | {"relatime", MS_RELATIME}, 33 | {"nosymfollow", MS_NOSYMFOLLOW}}; 34 | 35 | static bool shouldUnmount(const mountinfo_entry &mount, const mountinfo_root_resolver &root_resolver) 36 | { 37 | const auto true_root = root_resolver.resolveRootOf(mount); 38 | const auto &mount_point = mount.getMountPoint(); 39 | const auto &type = mount.getFilesystemType(); 40 | 41 | // Mount is from /data/adb 42 | if (true_root.starts_with("/data/adb")) 43 | return true; 44 | 45 | // Mount is to /data/adb 46 | if (mount_point.starts_with("/data/adb")) 47 | return true; 48 | 49 | // Unmount all module overlayfs and tmpfs 50 | if ((type == "overlay" || type == "tmpfs") && fsname_list.contains(mount.getMountSource())) 51 | return true; 52 | 53 | // Unmount all overlayfs with lowerdir/upperdir/workdir starting with /data/adb 54 | if (type == "overlay") 55 | { 56 | const auto &options = mount.getSuperOptions(); 57 | 58 | if (options.contains("lowerdir") && options.at("lowerdir").starts_with("/data/adb")) 59 | return true; 60 | 61 | if (options.contains("upperdir") && options.at("upperdir").starts_with("/data/adb")) 62 | return true; 63 | 64 | if (options.contains("workdir") && options.at("workdir").starts_with("/data/adb")) 65 | return true; 66 | } 67 | 68 | return false; 69 | } 70 | 71 | void doUnmount() 72 | { 73 | const auto &mount_infos = parseSelfMountinfo(false); 74 | auto root_resolver = mountinfo_root_resolver(mount_infos); 75 | 76 | for (auto it = mount_infos.rbegin(); it != mount_infos.rend(); it++) 77 | { 78 | if (shouldUnmount(*it, root_resolver)) 79 | { 80 | const auto &mount_point_cstr = it->getMountPoint().c_str(); 81 | if (umount2(mount_point_cstr, MNT_DETACH) == 0) 82 | LOGD("umount2(\"%s\", MNT_DETACH) returned 0", mount_point_cstr); 83 | else 84 | LOGW("umount2(\"%s\", MNT_DETACH) returned -1: %d (%s)", mount_point_cstr, errno, strerror(errno)); 85 | } 86 | } 87 | } 88 | 89 | void doRemount() 90 | { 91 | for (const auto &mount : parseSelfMountinfo(false)) 92 | { 93 | if (mount.getMountPoint() == "/data") 94 | { 95 | const auto &superOptions = mount.getSuperOptions(); 96 | if (!superOptions.contains("errors")) 97 | break; 98 | 99 | // Remount /data only if errors behavior is not the same as superblock's 100 | const char *sb_errors = Utils::getExtErrorsBehavior(mount); 101 | if (!sb_errors || superOptions.at("errors") == sb_errors) 102 | break; 103 | 104 | const auto &mountOptions = mount.getMountOptions(); 105 | unsigned long flags = MS_REMOUNT; 106 | for (const auto &flagName : mount_flags_procfs) 107 | { 108 | if (mountOptions.contains(flagName.first)) 109 | flags |= flagName.second; 110 | } 111 | 112 | if (::mount(NULL, "/data", NULL, flags, (std::string("errors=") + sb_errors).c_str()) == 0) 113 | LOGD("mount(NULL, \"/data\", NULL, 0x%lx, ...) returned 0", flags); 114 | else 115 | LOGW("mount(NULL, \"/data\", NULL, 0x%lx, ...) returned -1: %d (%s)", flags, errno, strerror(errno)); 116 | break; 117 | } 118 | } 119 | } 120 | 121 | /* 122 | * Is it guaranteed to work? No. 123 | * At least it has lots of error checking so if something goes wrong 124 | * the state should remain relatively safe. 125 | */ 126 | void doHideZygisk() 127 | { 128 | using namespace ELFIO; 129 | 130 | elfio reader; 131 | std::string filePath; 132 | uintptr_t startAddress = 0, bssAddress = 0; 133 | 134 | for (const auto &map : parseSelfMaps()) 135 | { 136 | if (map.getPathname().ends_with("/libnativebridge.so") && map.getPerms() == "r--p") 137 | { 138 | // First ro page should be the ELF header 139 | filePath = map.getPathname(); 140 | startAddress = map.getAddressStart(); 141 | break; 142 | } 143 | } 144 | 145 | ASSERT_DO(doHideZygisk, startAddress != 0, return); 146 | ASSERT_DO(doHideZygisk, reader.load(filePath), return); 147 | 148 | size_t bssSize = 0; 149 | for (const auto &sec : reader.sections) 150 | { 151 | if (sec->get_name() == ".bss") 152 | { 153 | bssAddress = startAddress + sec->get_address(); 154 | bssSize = static_cast(sec->get_size()); 155 | break; 156 | } 157 | } 158 | 159 | ASSERT_DO(doHideZygisk, bssAddress != 0, return); 160 | LOGD("Found .bss for \"%s\" at 0x%" PRIxPTR " sized %" PRIuPTR " bytes.", filePath.c_str(), bssAddress, bssSize); 161 | 162 | uint8_t *pHadError = reinterpret_cast(memchr(reinterpret_cast(bssAddress), 0x01, bssSize)); 163 | if (pHadError != nullptr) 164 | { 165 | *pHadError = 0; 166 | LOGD("libnativebridge.so had_error was reset."); 167 | } 168 | } 169 | 170 | static bool shouldResetProperty(const prop_info *pi) 171 | { 172 | // Read-write properties or properties with long values should not be reset 173 | if (strncmp(pi->name, "ro.", 3) != 0 || pi->is_long()) 174 | return false; 175 | 176 | // Check if the serial indicates that it was modified 177 | auto serial = std::atomic_load_explicit(&pi->serial, std::memory_order_relaxed); 178 | if ((serial & 0xFFFFFF) != 0) 179 | return true; 180 | 181 | // Check if any characters exist beyond the null-terminated string 182 | size_t length = strnlen(pi->value, PROP_VALUE_MAX); 183 | for (size_t i = length; i < PROP_VALUE_MAX; i++) 184 | { 185 | if (pi->value[i] != '\0') 186 | return true; 187 | } 188 | 189 | return false; 190 | } 191 | 192 | void doMrProp() 193 | { 194 | static bool isInitialized = false; 195 | static int resetCount = 0; 196 | if (!isInitialized) 197 | { 198 | if (__system_properties_init() == -1) 199 | { 200 | LOGE("Could not initialize system_properties!"); 201 | return; 202 | } 203 | isInitialized = true; 204 | } 205 | 206 | int ret = __system_property_foreach( 207 | [](const prop_info *pi, void *) 208 | { 209 | if (shouldResetProperty(pi)) 210 | { 211 | // Overlapping pointers in strncpy is undefined behavior so make a copy. 212 | char buffer[PROP_VALUE_MAX]; 213 | size_t length = Utils::safeStringCopy(buffer, pi->value, PROP_VALUE_MAX); 214 | 215 | __system_property_update(const_cast(pi), buffer, length); 216 | resetCount++; 217 | } 218 | }, 219 | nullptr); 220 | 221 | LOGD("__system_property_foreach returned %d. resetCount=%d", ret, resetCount); 222 | } -------------------------------------------------------------------------------- /module/jni/mountinfo_parser.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "mountinfo_parser.hpp" 10 | #include "logging.hpp" 11 | 12 | using namespace Parsers; 13 | 14 | static std::unordered_map parseMountOptions(const std::string &input) 15 | { 16 | std::unordered_map ret; 17 | std::istringstream iss(input); 18 | std::string token; 19 | while (std::getline(iss, token, ',')) 20 | { 21 | std::istringstream tokenStream(token); 22 | std::string key, value; 23 | 24 | if (std::getline(tokenStream, key, '=')) 25 | { 26 | std::getline(tokenStream, value); // Put what's left in the stream to value, could be empty 27 | ret[key] = value; 28 | } 29 | } 30 | return ret; 31 | } 32 | 33 | mountinfo_entry::mountinfo_entry(int mount_id, int parent_id, dev_t device, 34 | const std::string &root, const std::string &mount_point, 35 | const std::string &mount_options, const std::string &optional_fields, 36 | const std::string &filesystem_type, const std::string &mount_source, 37 | const std::string &super_options) 38 | : mount_id(mount_id), parent_id(parent_id), device(device), 39 | root(root), mount_point(mount_point), 40 | optional_fields(optional_fields), filesystem_type(filesystem_type), 41 | mount_source(mount_source) 42 | { 43 | this->mount_options = parseMountOptions(mount_options); 44 | this->super_options = parseMountOptions(super_options); 45 | } 46 | 47 | int mountinfo_entry::getMountId() const { return mount_id; } 48 | int mountinfo_entry::getParentId() const { return parent_id; } 49 | dev_t mountinfo_entry::getDevice() const { return device; } 50 | const std::string &mountinfo_entry::getRoot() const { return root; } 51 | const std::string &mountinfo_entry::getMountPoint() const { return mount_point; } 52 | const std::unordered_map &mountinfo_entry::getMountOptions() const { return mount_options; } 53 | const std::string &mountinfo_entry::getOptionalFields() const { return optional_fields; } 54 | const std::string &mountinfo_entry::getFilesystemType() const { return filesystem_type; } 55 | const std::string &mountinfo_entry::getMountSource() const { return mount_source; } 56 | const std::unordered_map &mountinfo_entry::getSuperOptions() const { return super_options; } 57 | 58 | const std::vector &Parsers::parseSelfMountinfo(bool cached) 59 | { 60 | static std::vector parser_cache; 61 | if (cached && !parser_cache.empty()) 62 | { 63 | return parser_cache; 64 | } 65 | parser_cache.clear(); 66 | 67 | std::ifstream ifs("/proc/self/mountinfo", std::ifstream::in); 68 | if (!ifs) 69 | { 70 | LOGE("parseSelfMountinfo could not open /proc/self/mountinfo"); 71 | return parser_cache; 72 | } 73 | 74 | for (std::string line; std::getline(ifs, line);) 75 | { 76 | std::istringstream iss(line); 77 | 78 | int mount_id, parent_id, _major, _minor; 79 | std::string root, mount_point, mount_options, optional_fields, filesystem_type, mount_source, super_options; 80 | char colon; 81 | 82 | // Read the first 6 fields (major, colon and minor are the same field) 83 | iss >> mount_id >> parent_id >> _major >> colon >> _minor >> root >> mount_point >> mount_options; 84 | if (iss.fail()) 85 | { 86 | LOGE("parseSelfMountinfo failed to parse the first 6 fields of line: %s", line.c_str()); 87 | continue; 88 | } 89 | 90 | std::string field; 91 | while (iss >> field && field != "-") 92 | { 93 | optional_fields += " " + field; 94 | } 95 | 96 | if (iss.fail()) 97 | { 98 | LOGE("parseSelfMountinfo failed to parse the optional fields of line: %s", line.c_str()); 99 | continue; 100 | } 101 | 102 | iss >> filesystem_type >> mount_source >> super_options; 103 | if (iss.fail()) 104 | { 105 | LOGE("parseSelfMountinfo failed to parse the last 3 fields of line: %s", line.c_str()); 106 | continue; 107 | } 108 | 109 | parser_cache.emplace_back(mountinfo_entry(mount_id, parent_id, makedev(_major, _minor), 110 | root, mount_point, mount_options, 111 | optional_fields, filesystem_type, mount_source, 112 | super_options)); 113 | } 114 | 115 | return parser_cache; 116 | } 117 | 118 | mountinfo_root_resolver::mountinfo_root_resolver(const std::vector &mount_infos) 119 | { 120 | for (const auto &mount_info : mount_infos) 121 | { 122 | if (mount_info.getRoot() == "/") 123 | { 124 | device_mount_map[mount_info.getDevice()] = mount_info.getMountPoint(); 125 | } 126 | } 127 | } 128 | 129 | std::string mountinfo_root_resolver::resolveRootOf(const mountinfo_entry &mount_info) const 130 | { 131 | auto dev = mount_info.getDevice(); 132 | if (device_mount_map.contains(dev)) 133 | { 134 | const auto &mount_root = device_mount_map.at(dev); 135 | 136 | // If mount root is /, mount_info root will already be the true root 137 | if (mount_root != "/") 138 | return mount_root + mount_info.getRoot(); 139 | } 140 | 141 | return mount_info.getRoot(); 142 | } 143 | -------------------------------------------------------------------------------- /module/jni/utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "map_parser.hpp" 13 | #include "utils.hpp" 14 | #include "android_filesystem_config.h" 15 | #include "zygisk.hpp" 16 | #include "logging.hpp" 17 | 18 | using namespace Utils; 19 | 20 | size_t Utils::safeStringCopy(char *dest, const char *src, size_t dest_size) 21 | { 22 | if (dest_size < 1) 23 | return 0; 24 | 25 | *dest = 0; 26 | // strncpy does not null terminate, strlcpy fails on non-terminated src 27 | // strncat is relatively safe but may write count+1 because of the null terminator 28 | strncat(dest, src, dest_size - 1); 29 | 30 | // strlen is safe here because dest is now null-terminated 31 | return strlen(dest); 32 | } 33 | 34 | bool Utils::hookPLTByName(zygisk::Api *api, const std::string &libName, const std::string &symbolName, void *hookFunc, void **origFunc) 35 | { 36 | for (const auto &map : Parsers::parseSelfMaps()) 37 | { 38 | if (map.getPathname().ends_with("/" + libName)) 39 | { 40 | api->pltHookRegister(map.getDevice(), map.getInode(), symbolName.c_str(), hookFunc, origFunc); 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | 47 | int Utils::isUserAppUID(int uid) 48 | { 49 | int appid = uid % AID_USER_OFFSET; 50 | if (appid >= AID_APP_START && appid <= AID_APP_END) 51 | return true; 52 | if (appid >= AID_ISOLATED_START && appid <= AID_ISOLATED_END) 53 | return true; 54 | return false; 55 | } 56 | 57 | bool Utils::switchMountNS(int pid) 58 | { 59 | std::string path = "/proc/" + std::to_string(pid) + "/ns/mnt"; 60 | int ret, fd; 61 | if ((fd = open(path.c_str(), O_RDONLY | O_CLOEXEC)) < 0) 62 | { 63 | return false; 64 | } 65 | 66 | ret = setns(fd, 0); 67 | close(fd); 68 | return ret == 0; 69 | } 70 | 71 | int Utils::forkAndInvoke(const std::function &lambda) 72 | { 73 | pid_t pid = fork(); 74 | if (pid == -1) 75 | return -1; 76 | 77 | if (pid == 0) // Child process 78 | exit(lambda()); 79 | 80 | // Parent process 81 | int status = -1; 82 | waitpid(pid, &status, 0); 83 | return status; 84 | } 85 | 86 | constexpr off_t EXT_SUPERBLOCK_OFFSET = 0x400; 87 | constexpr off_t EXT_MAGIC_OFFSET = 0x38; 88 | constexpr off_t EXT_ERRORS_OFFSET = 0x3C; 89 | constexpr uint16_t EXT_MAGIC = 0xEF53; 90 | 91 | const char *Utils::getExtErrorsBehavior(const Parsers::mountinfo_entry &entry) 92 | { 93 | auto fs_type = entry.getFilesystemType(); 94 | if (fs_type != "ext2" && fs_type != "ext3" && fs_type != "ext4") 95 | return nullptr; 96 | 97 | std::ifstream file(entry.getMountSource(), std::ios::binary); 98 | if (!file) 99 | return nullptr; 100 | 101 | uint16_t magic; 102 | file.seekg(EXT_SUPERBLOCK_OFFSET + EXT_MAGIC_OFFSET, std::ios::beg); 103 | file.read(reinterpret_cast(&magic), sizeof(magic)); 104 | if (!file || file.gcount() != sizeof(magic)) 105 | return nullptr; 106 | magic = le16toh(magic); 107 | 108 | if (magic != EXT_MAGIC) 109 | return nullptr; 110 | 111 | uint16_t errors; 112 | file.seekg(EXT_SUPERBLOCK_OFFSET + EXT_ERRORS_OFFSET, std::ios::beg); 113 | file.read(reinterpret_cast(&errors), sizeof(errors)); 114 | if (!file || file.gcount() != sizeof(errors)) 115 | return nullptr; 116 | errors = le16toh(errors); 117 | 118 | switch (errors) 119 | { 120 | case 1: 121 | return "continue"; 122 | case 2: 123 | return "remount-ro"; 124 | case 3: 125 | return "panic"; 126 | default: 127 | return nullptr; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /module/template/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK -------------------------------------------------------------------------------- /module/template/common_func.sh: -------------------------------------------------------------------------------- 1 | SKIPDELPROP=false 2 | [ -f "$MODPATH/skipdelprop" ] && SKIPDELPROP=true 3 | 4 | # resetprop_if_diff 5 | resetprop_if_diff() { 6 | local NAME="$1" 7 | local EXPECTED="$2" 8 | local CURRENT="$(resetprop "$NAME")" 9 | 10 | [ -z "$CURRENT" ] || [ "$CURRENT" = "$EXPECTED" ] || resetprop -n "$NAME" "$EXPECTED" 11 | } 12 | 13 | # resetprop_if_match 14 | resetprop_if_match() { 15 | local NAME="$1" 16 | local CONTAINS="$2" 17 | local VALUE="$3" 18 | 19 | [[ "$(resetprop "$NAME")" = *"$CONTAINS"* ]] && resetprop -n "$NAME" "$VALUE" 20 | } 21 | 22 | # delprop_if_exist 23 | delprop_if_exist() { 24 | local NAME="$1" 25 | 26 | [ -n "$(resetprop "$NAME")" ] && resetprop --delete "$NAME" 27 | } 28 | -------------------------------------------------------------------------------- /module/template/module.prop: -------------------------------------------------------------------------------- 1 | id=${moduleId} 2 | name=${moduleName} 3 | version=${versionName} 4 | versionCode=${versionCode} 5 | author=snake-4 6 | description=A Zygisk module to hide root. 7 | updateJson=https://raw.githubusercontent.com/snake-4/Zygisk-Assistant/main/update_metadata/update.json 8 | -------------------------------------------------------------------------------- /module/template/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | MODPATH="${0%/*}" 2 | . $MODPATH/common_func.sh 3 | 4 | # Conditional early sensitive properties 5 | 6 | # Samsung 7 | resetprop_if_diff ro.boot.warranty_bit 0 8 | resetprop_if_diff ro.vendor.boot.warranty_bit 0 9 | resetprop_if_diff ro.vendor.warranty_bit 0 10 | resetprop_if_diff ro.warranty_bit 0 11 | 12 | # Realme 13 | resetprop_if_diff ro.boot.realmebootstate green 14 | 15 | # OnePlus 16 | resetprop_if_diff ro.is_ever_orange 0 17 | 18 | # Microsoft 19 | for PROP in $(resetprop | grep -oE 'ro.*.build.tags'); do 20 | resetprop_if_diff $PROP release-keys 21 | done 22 | 23 | # Other 24 | for PROP in $(resetprop | grep -oE 'ro.*.build.type'); do 25 | resetprop_if_diff $PROP user 26 | done 27 | resetprop_if_diff ro.adb.secure 1 28 | if ! $SKIPDELPROP; then 29 | delprop_if_exist ro.boot.verifiedbooterror 30 | delprop_if_exist ro.boot.verifyerrorpart 31 | fi 32 | resetprop_if_diff ro.boot.veritymode.managed yes 33 | resetprop_if_diff ro.debuggable 0 34 | resetprop_if_diff ro.force.debuggable 0 35 | resetprop_if_diff ro.secure 1 -------------------------------------------------------------------------------- /module/template/service.sh: -------------------------------------------------------------------------------- 1 | MODPATH="${0%/*}" 2 | . $MODPATH/common_func.sh 3 | 4 | # Conditional sensitive properties 5 | 6 | # Magisk Recovery Mode 7 | resetprop_if_match ro.boot.mode recovery unknown 8 | resetprop_if_match ro.bootmode recovery unknown 9 | resetprop_if_match vendor.boot.mode recovery unknown 10 | 11 | # SELinux 12 | resetprop_if_diff ro.boot.selinux enforcing 13 | # use delete since it can be 0 or 1 for enforcing depending on OEM 14 | if ! $SKIPDELPROP; then 15 | delprop_if_exist ro.build.selinux 16 | fi 17 | # use toybox to protect stat access time reading 18 | if [ "$(toybox cat /sys/fs/selinux/enforce)" = "0" ]; then 19 | chmod 640 /sys/fs/selinux/enforce 20 | chmod 440 /sys/fs/selinux/policy 21 | fi 22 | 23 | # Conditional late sensitive properties 24 | 25 | # must be set after boot_completed for various OEMs 26 | { 27 | until [ "$(getprop sys.boot_completed)" = "1" ]; do 28 | sleep 1 29 | done 30 | 31 | # SafetyNet/Play Integrity + OEM 32 | # avoid bootloop on some Xiaomi devices 33 | resetprop_if_diff ro.secureboot.lockstate locked 34 | # avoid breaking Realme fingerprint scanners 35 | resetprop_if_diff ro.boot.flash.locked 1 36 | resetprop_if_diff ro.boot.realme.lockstate 1 37 | # avoid breaking Oppo fingerprint scanners 38 | resetprop_if_diff ro.boot.vbmeta.device_state locked 39 | # avoid breaking OnePlus display modes/fingerprint scanners 40 | resetprop_if_diff vendor.boot.verifiedbootstate green 41 | # avoid breaking OnePlus/Oppo fingerprint scanners on OOS/ColorOS 12+ 42 | resetprop_if_diff ro.boot.verifiedbootstate green 43 | resetprop_if_diff ro.boot.veritymode enforcing 44 | resetprop_if_diff vendor.boot.vbmeta.device_state locked 45 | 46 | # Other 47 | resetprop_if_diff sys.oem_unlock_allowed 0 48 | 49 | }& -------------------------------------------------------------------------------- /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 | include(":module") 18 | -------------------------------------------------------------------------------- /update_metadata/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.4 2 | * Fixed a problem causing Zygisk Assistant to be detectable. 3 | * Updated prop scripts. 4 | * Compiled with a newer compiler. 5 | 6 | ## 2.1.3 7 | * Restored Shamiko v1.1.1 compatibility. 8 | * Fixed bootloop on some Xiaomi devices. 9 | - Removed unnecessary mount ID regeneration. 10 | 11 | ## 2.1.2 12 | + Added scripts to reset sensitive props. 13 | -------------------------------------------------------------------------------- /update_metadata/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "v2.1.4", 3 | "versionCode": 214, 4 | "zipUrl": "https://github.com/snake-4/Zygisk-Assistant/releases/download/v2.1.4/Zygisk-Assistant-v2.1.4-1013f8a-release.zip", 5 | "changelog": "https://raw.githubusercontent.com/snake-4/Zygisk-Assistant/main/update_metadata/CHANGELOG.md" 6 | } 7 | --------------------------------------------------------------------------------