├── .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 |
--------------------------------------------------------------------------------