├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ ├── issue.yml │ └── main.yml ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── riru ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── cpp │ ├── .clang-tidy │ ├── CMakeLists.txt │ ├── entry.cpp │ ├── entry.h │ ├── hide │ └── hide.cpp │ ├── hide_utils.cpp │ ├── hide_utils.h │ ├── include │ ├── android_prop.h │ ├── buff_string.h │ ├── config.h │ ├── dl.h │ ├── elf_util.h │ ├── finally.h │ ├── logging.h │ ├── module.h │ ├── pmparser.h │ ├── rirud.h │ ├── selinux.h │ └── socket.h │ ├── include_riru │ └── riru.h │ ├── jni_hooks.cpp │ ├── jni_hooks.h │ ├── loader │ ├── loader.cpp │ └── native_bridge_callbacks.h │ ├── magisk.cpp │ ├── magisk.h │ ├── module.cpp │ ├── template │ └── config.cpp │ └── util │ ├── android_prop.cpp │ ├── dl.cpp │ ├── elf_util.cpp │ └── rirud.cpp ├── rirud ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── riru │ │ ├── Daemon.java │ │ ├── DaemonSocketServerThread.java │ │ ├── DaemonUtils.java │ │ └── Installer.java │ └── res │ ├── strings.xml │ ├── values-zh-rCN │ └── strings.xml │ ├── values-zh-rTW │ └── strings.xml │ └── values │ └── strings.xml ├── settings.gradle ├── stub ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── android │ └── os │ ├── IBinder.java │ ├── SELinux.java │ ├── ServiceManager.java │ └── SystemProperties.java └── template ├── aar ├── riru-javadoc.jar │ └── README ├── riru-sources.jar │ └── README ├── riru.aar │ ├── AndroidManifest.xml │ ├── META-INF │ │ └── com │ │ │ └── android │ │ │ └── build │ │ │ └── gradle │ │ │ └── aar-metadata.properties │ └── prefab │ │ ├── modules │ │ └── riru │ │ │ └── module.json │ │ └── prefab.json └── riru.pom └── magisk_module ├── .gitattributes ├── META-INF └── com │ └── google │ └── android │ ├── update-binary │ └── updater-script ├── README.md ├── customize.sh ├── module.prop ├── post-fs-data.sh ├── service.sh ├── system.prop ├── uninstall.sh ├── util_functions.sh └── verify.sh /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.jar binary 5 | *.aar binary -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug of Riru 3 | title: "[Bug] " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | 11 | Please use the latest debug version of Riru, and make sure any other modules are disabled. 12 | 13 | Logs are required, no one can fix the problem without the log. For example, if you have a "Riru module not loaded" problem, a full boot log will be helpful. 14 | 15 | Reports not meet the requirements will be closed. 16 | - type: checkboxes 17 | id: bug-of-riru 18 | attributes: 19 | label: This is a bug of Riru itself 20 | options: 21 | - label: This is a bug of Riru, not a bug of a Riru module or something else 22 | required: true 23 | - type: checkboxes 24 | id: modules-disabled 25 | attributes: 26 | label: Other modules are disabled 27 | options: 28 | - label: Except Riru itself, all other Magisk modules are disabled 29 | required: true 30 | - type: checkboxes 31 | id: zygisk-disabled 32 | attributes: 33 | label: Zygisk are disabled 34 | options: 35 | - label: I know that Zygisk and Riru are not compatible and Zygisk has been disabled 36 | required: true 37 | - type: input 38 | id: riru-version 39 | attributes: 40 | label: Riru version 41 | validations: 42 | required: true 43 | - type: input 44 | id: android-version 45 | attributes: 46 | label: Android version 47 | validations: 48 | required: true 49 | - type: input 50 | id: device 51 | attributes: 52 | label: Device 53 | validations: 54 | required: false 55 | - type: textarea 56 | id: description 57 | attributes: 58 | label: Describe the bug 59 | placeholder: | 60 | A clear and concise description of what the bug is. 61 | validations: 62 | required: true 63 | - type: textarea 64 | id: logs 65 | attributes: 66 | label: Relevant logs 67 | description: | 68 | Please copy and paste any relevant log output. 69 | 70 | If the log is too long, upload the file as the attachment. 71 | render: shell 72 | validations: 73 | required: true 74 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/workflows/issue.yml: -------------------------------------------------------------------------------- 1 | name: Check Issues 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | jobs: 7 | check: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - if: contains(github.event.issue.body, 'edxposed') == true || contains(github.event.issue.title, 'edxposed') == true 11 | id: close 12 | uses: peter-evans/close-issue@v1 13 | with: 14 | comment: Automatically closing this issue since it may be EdXposed relative. EdXposed is out of maintenance. If you want to use Xposed Framework, use the original Xposed, [LSPosed](https://lsposed.org), [Taichi](https://taichi.cool) or [Dreamland](https://github.com/canyie/Dreamland) instead. 15 | - if: contains(github.event.issue.body, 'exposed') == true || contains(github.event.issue.title, 'exposed') == true 16 | id: close2 17 | uses: peter-evans/close-issue@v1 18 | with: 19 | comment: Automatically closing this issue since Exposed (aka Taichi) does not require Riru. 20 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Core 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '**/README.md' 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-20.04 14 | if: ${{ !startsWith(github.event.head_commit.message, '[skip ci]') }} 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: 'recursive' 21 | fetch-depth: 0 22 | - name: set up JDK 11 23 | uses: actions/setup-java@v1 24 | with: 25 | java-version: 11 26 | - name: Cache Gradle Dependencies 27 | uses: actions/cache@v2 28 | with: 29 | path: | 30 | ~/.gradle/caches 31 | ~/.gradle/wrapper 32 | !~/.gradle/caches/build-cache-* 33 | key: gradle-deps-core-${{ hashFiles('**/build.gradle') }} 34 | restore-keys: | 35 | gradle-deps 36 | - name: Cache Gradle Build 37 | uses: actions/cache@v2 38 | with: 39 | path: | 40 | ~/.gradle/caches/build-cache-* 41 | key: gradle-builds-core-${{ github.sha }} 42 | restore-keys: | 43 | gradle-builds 44 | - name: Cache Ccache 45 | uses: actions/cache@v2 46 | with: 47 | path: ~/.ccache 48 | key: ccache-cache-${{ github.sha }} 49 | restore-keys: ccache-cache- 50 | - name: Install ccache 51 | run: | 52 | sudo apt-get install -y ccache 53 | ccache -o max_size=2G 54 | ccache -o hash_dir=false 55 | - name: Build with Gradle 56 | run: | 57 | mkdir -p ~/.gradle/wrapper 58 | mkdir -p ~/.gradle/caches 59 | [ $(du -s ~/.gradle/wrapper | awk '{ print $1 }') -gt 250000 ] && rm -rf ~/.gradle/wrapper/* || true 60 | find ~/.gradle/caches -exec touch -d "2 days ago" {} + || true 61 | echo 'org.gradle.caching=true' >> gradle.properties 62 | echo 'org.gradle.parallel=true' >> gradle.properties 63 | echo 'org.gradle.vfs.watch=true' >> gradle.properties 64 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 65 | ./gradlew zipDebug zipRelease 66 | - name: Prepare artifact 67 | if: success() 68 | id: prepareArtifact 69 | run: | 70 | releaseName=`ls out/riru-*-release.zip | awk -F '(/|.zip)' '{print $2}'` && echo "::set-output name=releaseName::$releaseName" 71 | debugName=`ls out/riru-*-debug.zip | awk -F '(/|.zip)' '{print $2}'` && echo "::set-output name=debugName::$debugName" 72 | unzip out/riru-*-release.zip -d riru-release 73 | unzip out/riru-*-debug.zip -d riru-debug 74 | - name: Upload release 75 | uses: actions/upload-artifact@v2 76 | with: 77 | name: ${{ steps.prepareArtifact.outputs.releaseName }} 78 | path: './riru-release/*' 79 | - name: Upload debug 80 | # if: ${{ github.event_name == 'pull_request' && success() }} 81 | uses: actions/upload-artifact@v2 82 | with: 83 | name: ${{ steps.prepareArtifact.outputs.debugName }} 84 | path: './riru-debug/*' 85 | - name: Upload mappings 86 | uses: actions/upload-artifact@v2 87 | with: 88 | name: mappings 89 | path: "riru/build/outputs/mapping/release" 90 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | /.idea/caches/build_file_checksums.ser 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | .DS_Store 10 | /build 11 | /captures 12 | /out 13 | .externalNativeBuild 14 | .cxx 15 | elf-cleaner.sh 16 | settings.gradle 17 | /template/aar/riru.aar/prefab/modules/riru/include 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | All Riru users and Riru modules should migrate to Zygisk. 4 | 5 | # Riru 6 | 7 | Riru only does one thing, inject into zygote in order to allow modules to run their codes in apps or the system server. 8 | 9 | > The name, Riru, comes from a character. (https://www.pixiv.net/member_illust.php?mode=medium&illust_id=74128856) 10 | 11 | ## Requirements 12 | 13 | Android 6.0+ devices rooted with [Magisk](https://github.com/topjohnwu/Magisk) 14 | 15 | ## Guide 16 | 17 | ### Install 18 | 19 | * From Magisk Manager 20 | 21 | 1. Search "Riru" in Magisk Manager 22 | 2. Install the module named "Riru" 23 | 24 | > The Magisk version requirement is enforced by Magisk Manager. You can check [Magisk's module installer script](https://github.com/topjohnwu/Magisk/blob/master/scripts/module_installer.sh). 25 | 26 | * Manually 27 | 28 | 1. Download the zip from the [GitHub release](https://github.com/RikkaApps/Riru/releases) 29 | 2. Install in Magisk Manager (Modules - Install from storage - Select downloaded zip) 30 | 31 | ### Common problems 32 | 33 | * Third-party ROMs have incorrect SELinux rule 34 | 35 | 36 | 37 | * Have low quality module that changes `ro.dalvik.vm.native.bridge` installed 38 | 39 | **If you are using other modules that change `ro.dalvik.vm.native.bridge`, Riru will not work.** (Riru will automatically set it back) 40 | 41 | A typical example is, some "optimize" modules change this property. Since changing this property is meaningless for "optimization", their quality is very questionable. In fact, changing properties for optimization is a joke. 42 | 43 | ## How Riru works? 44 | 45 | * How to inject into the zygote process? 46 | 47 | Before v22.0, we use the method of replacing a system library (libmemtrack) that will be loaded by zygote. However, it seems to cause some weird problems. Maybe because libmemtrack is used by something else. 48 | 49 | Then we found a super easy way, the "native bridge" (`ro.dalvik.vm.native.bridge`). The specific "so" file will be automatically "dlopen-ed" and "dlclose-ed" by the system. This way is from [here](https://github.com/canyie/NbInjection). 50 | 51 | * How to know if we are in an app process or a system server process? 52 | 53 | Some JNI functions (`com.android.internal.os.Zygote#nativeForkAndSpecialize` & `com.android.internal.os.Zygote#nativeForkSystemServer`) is to fork the app process or the system server process. 54 | So we need to replace these functions with ours. This part is simple, hook `jniRegisterNativeMethods` since all Java native methods in `libandroid_runtime.so` is registered through this function. 55 | Then we can call the original `jniRegisterNativeMethods` again to replace them. 56 | 57 | ## How does Hide works? 58 | 59 | From v22.0, Riru provides a hidden mechanism (idea from [Haruue Icymoon](https://github.com/haruue)), make the memory of Riru and module to anonymous memory to hide from "`/proc/maps` string scanning". 60 | 61 | ## Build 62 | 63 | Gradle tasks: 64 | 65 | * `:riru:assembleDebug/Release` 66 | 67 | Generate Magisk module zip to `out`. 68 | 69 | * `:riru:pushDebug/Release` 70 | 71 | Push the zip with adb to `/data/local/tmp`. 72 | 73 | * `:riru:flashDebug/Release` 74 | 75 | Flash the zip with `adb shell su -c magisk --install-module`. 76 | 77 | * `:riru:flashAndRebootDebug/Release` 78 | 79 | Flash the zip and reboot the device. 80 | 81 | ## Module template 82 | 83 | https://github.com/RikkaApps/Riru-ModuleTemplate 84 | 85 | ## Module API changes 86 | 87 | https://github.com/RikkaApps/Riru-ModuleTemplate/blob/master/README.md#api-changes 88 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.FixCrLfFilter 2 | import org.eclipse.jgit.api.Git 3 | import org.eclipse.jgit.internal.storage.file.FileRepository 4 | 5 | import java.util.stream.StreamSupport 6 | 7 | buildscript { 8 | repositories { 9 | mavenCentral() 10 | } 11 | dependencies { 12 | classpath 'org.eclipse.jgit:org.eclipse.jgit:5.10.0.202012080955-r' 13 | } 14 | } 15 | 16 | plugins { 17 | id 'idea' 18 | } 19 | 20 | idea.module { 21 | excludeDirs += file('out') 22 | resourceDirs += file('template') 23 | excludeDirs += file('template/aar') 24 | resourceDirs += file('scripts') 25 | resourceDirs += file('docs') 26 | } 27 | 28 | ext { 29 | minSdkVersion = 23 30 | targetSdkVersion = 33 31 | buildToolsVersion = "33.0.0" 32 | 33 | riruApiVersion = 26 34 | riruMinApiVersion = 24 35 | 36 | repo = new FileRepository(rootProject.file(".git")) 37 | gitObjectId = repo.refDatabase.exactRef("refs/remotes/origin/master").objectId 38 | gitCommitId = gitObjectId.abbreviate(10).name() 39 | gitCommitCount = StreamSupport.stream(new Git(repo).log().add(gitObjectId).call().spliterator(), false).count() 40 | 41 | versionNameMinor = 1 42 | versionNamePatch = 7 43 | 44 | outDir = file("$rootDir/out") 45 | } 46 | 47 | task clean(type: Delete) { 48 | delete rootProject.buildDir, outDir 49 | } 50 | 51 | def aarVersion = "${riruApiVersion}.0.0" 52 | def aarDir = "out/aar/dev/rikka/ndk/riru/${aarVersion}" 53 | 54 | task generateLibraryAar(type: Zip) { 55 | copy { 56 | from 'riru/src/main/cpp/include_riru/riru.h' 57 | into 'template/aar/riru.aar/prefab/modules/riru/include' 58 | } 59 | 60 | from 'template/aar/riru.aar' 61 | archiveName "riru-${aarVersion}.aar" 62 | destinationDir file(aarDir) 63 | } 64 | 65 | task signLibraryAar(type: Exec) { 66 | commandLine "gpg", 67 | "--armor", 68 | "--detach-sign", 69 | "--passphrase=${findProperty("signing.password")}", 70 | "--batch", 71 | "--yes", 72 | "riru-${aarVersion}.aar" 73 | workingDir aarDir 74 | } 75 | 76 | task generateLibraryPom(type: Copy) { 77 | from 'template/aar/riru.pom' 78 | into file(aarDir) 79 | filter { line -> line.replaceAll('%%%VERSION%%%', "$aarVersion") } 80 | filter(FixCrLfFilter.class, 81 | eol: FixCrLfFilter.CrLf.newInstance("lf")) 82 | rename { "riru-${aarVersion}.pom" } 83 | } 84 | 85 | task signLibraryPom(type: Exec) { 86 | commandLine "gpg", 87 | "--armor", 88 | "--detach-sign", 89 | "--passphrase=${findProperty("signing.password")}", 90 | "--batch", 91 | "--yes", 92 | "riru-${aarVersion}.pom" 93 | workingDir aarDir 94 | } 95 | 96 | task generateLibrarySourceJar(type: Zip) { 97 | from 'template/aar/riru-sources.jar' 98 | archiveName "riru-${aarVersion}-sources.jar" 99 | destinationDir file(aarDir) 100 | } 101 | 102 | task signLibrarySourceJar(type: Exec) { 103 | commandLine "gpg", 104 | "--armor", 105 | "--detach-sign", 106 | "--passphrase=${findProperty("signing.password")}", 107 | "--batch", 108 | "--yes", 109 | "riru-${aarVersion}-sources.jar" 110 | workingDir aarDir 111 | } 112 | 113 | task generateLibraryJavaDocJar(type: Zip) { 114 | from 'template/aar/riru-javadoc.jar' 115 | archiveName "riru-${aarVersion}-javadoc.jar" 116 | destinationDir file(aarDir) 117 | } 118 | 119 | task signLibraryJavaDocJar(type: Exec) { 120 | commandLine "gpg", 121 | "--armor", 122 | "--detach-sign", 123 | "--passphrase=${findProperty("signing.password")}", 124 | "--batch", 125 | "--yes", 126 | "riru-${aarVersion}-javadoc.jar" 127 | workingDir aarDir 128 | } 129 | 130 | task generateLibrary(type: GradleBuild) { 131 | tasks = [':generateLibraryAar', ':generateLibraryPom', ':generateLibrarySourceJar', ':generateLibraryJavaDocJar', 132 | ':signLibraryAar', ':signLibraryPom', ':signLibrarySourceJar', ':signLibraryJavaDocJar'] 133 | } 134 | -------------------------------------------------------------------------------- /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=-Xmx1536m 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 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Riru/cfeb4a70ab829fe65f6e834a5c9a457cff79d998/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-7.5.1-bin.zip 4 | zipStorePath=wrapper/dists 5 | -------------------------------------------------------------------------------- /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/master/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 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || 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 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /riru/.gitignore: -------------------------------------------------------------------------------- 1 | /.externalNativeBuild 2 | /build 3 | /release -------------------------------------------------------------------------------- /riru/build.gradle: -------------------------------------------------------------------------------- 1 | import org.apache.tools.ant.filters.FixCrLfFilter 2 | import org.apache.tools.ant.filters.ReplaceTokens 3 | 4 | import java.security.MessageDigest 5 | 6 | plugins { 7 | id 'com.android.application' 8 | } 9 | 10 | def versionNameShort = "v${riruApiVersion}.${versionNameMinor}.${versionNamePatch}" 11 | def versionName = "${versionNameShort}.r${gitCommitCount}.${gitCommitId}" 12 | def versionCode = gitCommitCount 13 | 14 | def apiVersion = rootProject.ext.riruApiVersion 15 | def minApiVersion = rootProject.ext.riruMinApiVersion 16 | def moduleProps = [ 17 | id : "riru-core", 18 | name : "Riru", 19 | version : "$versionName", 20 | versionCode: "$versionCode", 21 | author : "Rikka, yujincheng08", 22 | description: 'Inject into zygote and run codes from "Riru" modules in apps or the system server. Support modules which use Riru API v24+.', 23 | riruApi : "$apiVersion", 24 | riruMinApi : "$minApiVersion" 25 | ] 26 | 27 | android { 28 | compileSdkVersion rootProject.ext.targetSdkVersion 29 | buildToolsVersion rootProject.ext.buildToolsVersion 30 | defaultConfig { 31 | minSdkVersion rootProject.ext.minSdkVersion 32 | targetSdkVersion rootProject.ext.targetSdkVersion 33 | ndkVersion '25.1.8937393' 34 | externalNativeBuild { 35 | cmake { 36 | arguments "-DRIRU_VERSION_NAME:STRING=$versionNameShort", 37 | "-DRIRU_VERSION_CODE:STRING=$versionCode", 38 | "-DRIRU_API_VERSION=$apiVersion", 39 | "-DRIRU_MIN_API_VERSION=$minApiVersion", 40 | "-DANDROID_STL=none" 41 | cppFlags "-std=c++20" 42 | } 43 | } 44 | } 45 | compileOptions { 46 | sourceCompatibility JavaVersion.VERSION_11 47 | targetCompatibility JavaVersion.VERSION_11 48 | } 49 | buildTypes { 50 | release { 51 | minifyEnabled true 52 | shrinkResources true 53 | proguardFiles 'proguard-rules.pro' 54 | } 55 | } 56 | buildFeatures { 57 | prefab true 58 | } 59 | externalNativeBuild { 60 | cmake { 61 | path "src/main/cpp/CMakeLists.txt" 62 | version "3.22.1+" 63 | } 64 | } 65 | dependenciesInfo.includeInApk false 66 | lint { 67 | checkReleaseBuilds false 68 | } 69 | } 70 | 71 | dependencies { 72 | implementation 'dev.rikka.ndk.thirdparty:xhook:1.2.0' 73 | implementation 'dev.rikka.ndk.thirdparty:cxx:1.2.0' 74 | implementation 'dev.rikka.ndk.thirdparty:proc-maps-parser:1.0.0' 75 | implementation 'dev.rikka.rikkax.io:little-endian-data-stream:1.0.2' 76 | compileOnly project(':stub') 77 | } 78 | 79 | android.applicationVariants.all { variant -> 80 | variant.outputs.all { 81 | outputFileName = "riru.apk" 82 | } 83 | } 84 | 85 | afterEvaluate { 86 | android.applicationVariants.forEach { variant -> 87 | def variantCapped = variant.name.capitalize() 88 | def variantLowered = variant.name.toLowerCase() 89 | 90 | def zipName = "riru-${versionName}-${variantLowered}.zip" 91 | def magiskDir = file("$outDir/magisk_module_$variantLowered") 92 | 93 | task("prepareMagiskFiles${variantCapped}", type: Sync) { 94 | dependsOn(":rirud:assemble$variantCapped") 95 | dependsOn("assemble$variantCapped") 96 | 97 | def templatePath = "$rootDir/template/magisk_module" 98 | 99 | into magiskDir 100 | from(templatePath) { 101 | exclude 'customize.sh', 'util_functions.sh', 'module.prop' 102 | } 103 | from(templatePath) { 104 | include 'customize.sh', 'util_functions.sh' 105 | filter(ReplaceTokens.class, tokens: [ 106 | "RIRU_API" : apiVersion.toString(), 107 | "RIRU_VERSION_CODE": versionCode.toString(), 108 | "RIRU_VERSION_NAME": versionName.toString() 109 | ]) 110 | filter(FixCrLfFilter.class, 111 | eol: FixCrLfFilter.CrLf.newInstance("lf")) 112 | } 113 | from(templatePath) { 114 | include 'module.prop' 115 | expand moduleProps 116 | filter(FixCrLfFilter.class, 117 | eol: FixCrLfFilter.CrLf.newInstance("lf")) 118 | } 119 | from("$buildDir/intermediates/stripped_native_libs/$variantLowered/out/lib") { 120 | into 'lib' 121 | } 122 | from(project(":rirud").buildDir.absolutePath + "/outputs/apk/$variantLowered/rirud.apk") { 123 | include 'rirud.apk' 124 | } 125 | doLast { 126 | fileTree("$magiskDir").visit { f -> 127 | if (f.directory) return 128 | if (f.file.name == '.gitattributes') return 129 | 130 | def md = MessageDigest.getInstance("SHA-256") 131 | f.file.eachByte 4096, { bytes, size -> 132 | md.update(bytes, 0, size) 133 | } 134 | file(f.file.path + ".sha256sum").text = md.digest().encodeHex() 135 | } 136 | } 137 | } 138 | 139 | task("zip${variantCapped}", type: Zip) { 140 | dependsOn("prepareMagiskFiles${variantCapped}") 141 | from magiskDir 142 | archiveName zipName 143 | destinationDir outDir 144 | } 145 | 146 | task("push${variantCapped}", type: Exec) { 147 | dependsOn("zip${variantCapped}") 148 | workingDir outDir 149 | commandLine android.adbExecutable, "push", zipName, "/data/local/tmp/" 150 | } 151 | 152 | task("flash${variantCapped}", type: Exec) { 153 | dependsOn("push${variantCapped}") 154 | commandLine android.adbExecutable, "shell", "su", "-c", "magisk --install-module /data/local/tmp/${zipName}" 155 | } 156 | 157 | task("flashAndReboot${variantCapped}", type: Exec) { 158 | dependsOn("flash${variantCapped}") 159 | commandLine android.adbExecutable, "shell", "reboot" 160 | } 161 | 162 | variant.assembleProvider.get().finalizedBy("zip${variantCapped}") 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /riru/proguard-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Riru/cfeb4a70ab829fe65f6e834a5c9a457cff79d998/riru/proguard-rules.pro -------------------------------------------------------------------------------- /riru/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /riru/src/main/cpp/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | google-*, 6 | misc-*, 7 | modernize-*, 8 | performance-*, 9 | portability-*, 10 | readability-*, 11 | clang-analyzer-*, 12 | llvm-include-order, 13 | -modernize-use-trailing-return-type, 14 | -readability-implicit-bool-conversion, 15 | CheckOptions: 16 | - key: readability-identifier-naming.ClassCase 17 | value: CamelCase 18 | - key: readability-identifier-naming.ClassMemberCase 19 | value: lower_case 20 | - key: readability-identifier-naming.EnumCase 21 | value: CamelCase 22 | - key: readability-identifier-naming.EnumConstantCase 23 | value: CamelCase 24 | - key: readability-identifier-naming.EnumConstantPrefix 25 | value: k 26 | - key: readability-identifier-naming.FunctionCase 27 | value: CamelCase 28 | - key: readability-identifier-naming.GlobalConstantCase 29 | value: CamelCase 30 | - key: readability-identifier-naming.GlobalConstantPrefix 31 | value: k 32 | - key: readability-identifier-naming.StaticConstantCase 33 | value: CamelCase 34 | - key: readability-identifier-naming.StaticConstantPrefix 35 | value: k 36 | - key: readability-identifier-naming.StaticVariableCase 37 | value: CamelCase 38 | - key: readability-identifier-naming.StaticVariablePrefix 39 | value: k 40 | - key: readability-identifier-naming.MacroDefinitionCase 41 | value: UPPER_CASE 42 | - key: readability-identifier-naming.MemberCase 43 | value: lower_case 44 | - key: readability-identifier-naming.MemberSuffix 45 | value: _ 46 | - key: readability-identifier-naming.NamespaceCase 47 | value: lower_case 48 | - key: readability-identifier-naming.ParameterCase 49 | value: lower_case 50 | - key: readability-identifier-naming.TypeAliasCase 51 | value: CamelCase 52 | - key: readability-identifier-naming.TypedefCase 53 | value: CamelCase 54 | - key: readability-identifier-naming.VariableCase 55 | value: lower_case 56 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 57 | value: 1 58 | - key: readability-braces-around-statements.ShortStatementLines 59 | value: 1 60 | -------------------------------------------------------------------------------- /riru/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project("riru") 4 | 5 | find_program(CCACHE ccache) 6 | 7 | if (CCACHE) 8 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 9 | set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) 10 | endif () 11 | 12 | message("Build type: ${CMAKE_BUILD_TYPE}") 13 | 14 | 15 | if (NOT DEFINED RIRU_VERSION_NAME) 16 | message(FATAL_ERROR "RIRU_VERSION_NAME is not set") 17 | endif () 18 | 19 | if (NOT DEFINED RIRU_VERSION_CODE) 20 | message(FATAL_ERROR "RIRU_VERSION_CODE is not set") 21 | endif () 22 | 23 | if (NOT DEFINED RIRU_API_VERSION) 24 | message(FATAL_ERROR "RIRU_API_VERSION is not set") 25 | endif () 26 | 27 | if (NOT DEFINED RIRU_MIN_API_VERSION) 28 | message(FATAL_ERROR "RIRU_MIN_API_VERSION is not set") 29 | endif () 30 | 31 | configure_file(template/config.cpp config.cpp) 32 | 33 | #add_definitions(-DRIRU_VERSION_NAME="${RIRU_VERSION_NAME}") 34 | #add_definitions(-DRIRU_VERSION_CODE=${RIRU_VERSION_CODE}) 35 | #add_definitions(-DRIRU_API_VERSION=${RIRU_API_VERSION}) 36 | #add_definitions(-DRIRU_MIN_API_VERSION=${RIRU_MIN_API_VERSION}) 37 | 38 | set(LINKER_FLAGS "-ffixed-x18 -Wl,--hash-style=both") 39 | set(C_FLAGS "-Werror=format -fdata-sections -ffunction-sections -fno-exceptions -fno-rtti -fno-threadsafe-statics -D__FILE__=__FILE_NAME__ -Wno-builtin-macro-redefined") 40 | 41 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 42 | set(C_FLAGS "${C_FLAGS} -Os -fvisibility=hidden -fvisibility-inlines-hidden -fno-asynchronous-unwind-tables -fno-unwind-tables") 43 | set(LINKER_FLAGS "${LINKER_FLAGS} -Wl,-exclude-libs,ALL -Wl,--gc-sections") 44 | else () 45 | set(C_FLAGS "${C_FLAGS} -Og") 46 | endif () 47 | 48 | string(TOUPPER CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE} CXX_FLAGS_WITH_BUILD_TYPE) 49 | string(TOUPPER CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE} C_FLAGS_WITH_BUILD_TYPE) 50 | 51 | set(${CXX_FLAGS_WITH_BUILD_TYPE} "${${CXX_FLAGS_WITH_BUILD_TYPE}} ${C_FLAGS}") 52 | set(${C_FLAGS_WITH_BUILD_TYPE} "${${CXX_FLAGS_WITH_BUILD_TYPE}} ${C_FLAGS}") 53 | 54 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") 55 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") 56 | 57 | find_package(xhook REQUIRED CONFIG) 58 | find_package(cxx REQUIRED CONFIG) 59 | find_package(proc-maps-parser REQUIRED CONFIG) 60 | 61 | include_directories(include) 62 | include_directories(include_riru) 63 | 64 | add_library(utils STATIC 65 | util/android_prop.cpp 66 | util/elf_util.cpp 67 | util/dl.cpp 68 | util/rirud.cpp) 69 | target_link_libraries(utils cxx::cxx) 70 | 71 | add_library(riru SHARED 72 | entry.cpp 73 | jni_hooks.cpp 74 | hide_utils.cpp 75 | module.cpp 76 | magisk.cpp 77 | ${CMAKE_CURRENT_BINARY_DIR}/config.cpp) 78 | target_include_directories(riru PRIVATE ${CMAKE_SOURCE_DIR}) 79 | target_link_libraries(riru log utils xhook::xhook cxx::cxx proc-maps-parser::proc-maps-parser) 80 | 81 | add_library(riruhide SHARED hide/hide.cpp) 82 | target_include_directories(riruhide PRIVATE ${CMAKE_SOURCE_DIR}) 83 | target_link_libraries(riruhide log utils cxx::cxx proc-maps-parser::proc-maps-parser) 84 | 85 | if ("${ANDROID_ABI}" STREQUAL "x86" OR "${ANDROID_ABI}" STREQUAL "x86_64") 86 | add_definitions(-DHAS_NATIVE_BRIDGE) 87 | endif () 88 | 89 | add_library(riruloader SHARED loader/loader.cpp ${CMAKE_CURRENT_BINARY_DIR}/config.cpp) 90 | target_link_libraries(riruloader log utils cxx::cxx) 91 | 92 | if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") 93 | add_custom_command(TARGET riru POST_BUILD 94 | COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libriru.so") 95 | add_custom_command(TARGET riruhide POST_BUILD 96 | COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libriruhide.so") 97 | add_custom_command(TARGET riruloader POST_BUILD 98 | COMMAND ${CMAKE_STRIP} --strip-all --remove-section=.comment "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/libriruloader.so") 99 | endif () 100 | -------------------------------------------------------------------------------- /riru/src/main/cpp/entry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "jni_hooks.h" 6 | #include "logging.h" 7 | #include "module.h" 8 | #include "hide_utils.h" 9 | #include "magisk.h" 10 | #include "entry.h" 11 | 12 | static void *self_handle; 13 | static bool self_unload_allowed; 14 | 15 | struct SelfUnloadGuard { 16 | 17 | SelfUnloadGuard() { 18 | pthread_mutex_init(&mutex_, nullptr); 19 | } 20 | 21 | ~SelfUnloadGuard() { 22 | LOGD("self unload lock (destructor)"); 23 | pthread_mutex_lock(&mutex_); 24 | 25 | LOGD("self unload"); 26 | 27 | timespec ts = {.tv_sec = 0, .tv_nsec = 1000000L}; 28 | nanosleep(&ts, nullptr); 29 | } 30 | 31 | struct Holder { 32 | explicit Holder(pthread_mutex_t *mutex) : mutex_(mutex) { 33 | LOGD("self unload lock (holder constructor)"); 34 | pthread_mutex_lock(mutex_); 35 | } 36 | 37 | Holder(Holder &&other) noexcept: mutex_(other.mutex_) { 38 | other.mutex_ = nullptr; 39 | } 40 | 41 | ~Holder() { 42 | if (mutex_) { 43 | pthread_mutex_unlock(mutex_); 44 | LOGD("self unload unlock (holder destructor)"); 45 | } 46 | } 47 | 48 | private: 49 | pthread_mutex_t *mutex_; 50 | 51 | public: 52 | Holder(const Holder &) = delete; 53 | 54 | void operator=(const Holder &) = delete; 55 | }; 56 | 57 | auto hold() { return Holder(&mutex_); }; 58 | 59 | private: 60 | pthread_mutex_t mutex_{}; 61 | } self_unload_guard; 62 | 63 | static void SelfUnload() { 64 | LOGD("attempt to self unload"); 65 | 66 | [[maybe_unused]] auto holder = self_unload_guard.hold(); 67 | 68 | pthread_t thread; 69 | pthread_create(&thread, nullptr, (void *(*)(void *)) &dlclose, self_handle); 70 | pthread_detach(thread); 71 | } 72 | 73 | bool Entry::IsSelfUnloadAllowed() { 74 | return self_unload_allowed; 75 | } 76 | 77 | void Entry::Unload(jboolean is_child_zygote) { 78 | self_unload_allowed = true; 79 | 80 | for (auto &module : modules::Get()) { 81 | if (module.allowUnload()) { 82 | LOGD("%s: unload", module.id.data()); 83 | module.unload(); 84 | } else { 85 | if (module.apiVersion >= 25) 86 | LOGD("%s: unload is not allow for this process", module.id.data()); 87 | else { 88 | LOGD("%s: unload is not supported by module (API < 25), self unload is also disabled", 89 | module.id.data()); 90 | self_unload_allowed = false; 91 | } 92 | } 93 | } 94 | 95 | hide::HideFromSoList(); 96 | 97 | // Child zygote (webview zyote or app zygote) has no "execmem" permission 98 | if (android_prop::GetApiLevel() < 29 && !is_child_zygote) { 99 | hide::HideFromMaps(); 100 | } 101 | 102 | if (self_unload_allowed) { 103 | SelfUnload(); 104 | } 105 | } 106 | 107 | extern "C" [[gnu::visibility("default")]] [[maybe_unused]] void 108 | // NOLINTNEXTLINE 109 | init(void *handle, const char* magisk_path, const RirudSocket& rirud) { 110 | self_handle = handle; 111 | 112 | magisk::SetPath(magisk_path); 113 | hide::PrepareMapsHideLibrary(); 114 | jni::InstallHooks(); 115 | modules::Load(rirud); 116 | } 117 | -------------------------------------------------------------------------------- /riru/src/main/cpp/entry.h: -------------------------------------------------------------------------------- 1 | #ifndef _MAIN_H 2 | #define _MAIN_H 3 | 4 | namespace Entry { 5 | 6 | void Unload(jboolean is_child_zygote); 7 | 8 | bool IsSelfUnloadAllowed(); 9 | 10 | } 11 | #endif // _MAIN_H 12 | -------------------------------------------------------------------------------- /riru/src/main/cpp/hide/hide.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "logging.h" 7 | 8 | /** 9 | * Magic to hide from /proc/###/maps, the idea is from Haruue Icymoon (https://github.com/haruue) 10 | */ 11 | 12 | #define EXPORT __attribute__((visibility("default"))) __attribute__((used)) 13 | 14 | extern "C" { 15 | int riru_hide(const std::set &) EXPORT; 16 | } 17 | 18 | #ifdef __LP64__ 19 | #define LIB_PATH "/system/lib64/" 20 | #else 21 | #define LIB_PATH "/system/lib/" 22 | #endif 23 | 24 | struct hide_struct { 25 | procmaps_struct *original; 26 | uintptr_t backup_address; 27 | }; 28 | 29 | static int get_prot(const procmaps_struct *procstruct) { 30 | int prot = 0; 31 | if (procstruct->is_r) { 32 | prot |= PROT_READ; 33 | } 34 | if (procstruct->is_w) { 35 | prot |= PROT_WRITE; 36 | } 37 | if (procstruct->is_x) { 38 | prot |= PROT_EXEC; 39 | } 40 | return prot; 41 | } 42 | 43 | #define FAILURE_RETURN(exp, failure_value) ({ \ 44 | __typeof__(exp) _rc; \ 45 | _rc = (exp); \ 46 | if (_rc == failure_value) { \ 47 | PLOGE(#exp); \ 48 | return 1; \ 49 | } \ 50 | _rc; }) 51 | 52 | static int do_hide(hide_struct *data) { 53 | auto procstruct = data->original; 54 | auto start = (uintptr_t) procstruct->addr_start; 55 | auto end = (uintptr_t) procstruct->addr_end; 56 | auto length = end - start; 57 | int prot = get_prot(procstruct); 58 | 59 | // backup 60 | data->backup_address = (uintptr_t) FAILURE_RETURN( 61 | mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0), 62 | MAP_FAILED); 63 | LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s is backup to %" PRIxPTR, start, end, procstruct->perm, 64 | procstruct->offset, 65 | procstruct->pathname, data->backup_address); 66 | 67 | if (!procstruct->is_r) { 68 | LOGD("mprotect +r"); 69 | FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_READ), -1); 70 | } 71 | LOGD("memcpy -> backup"); 72 | memcpy((void *) data->backup_address, (void *) start, length); 73 | 74 | // munmap original 75 | LOGD("munmap original"); 76 | FAILURE_RETURN(munmap((void *) start, length), -1); 77 | 78 | // restore 79 | LOGD("mmap original"); 80 | FAILURE_RETURN(mmap((void *) start, length, prot, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0), 81 | MAP_FAILED); 82 | LOGD("mprotect +w"); 83 | FAILURE_RETURN(mprotect((void *) start, length, prot | PROT_WRITE), -1); 84 | LOGD("memcpy -> original"); 85 | memcpy((void *) start, (void *) data->backup_address, length); 86 | if (!procstruct->is_w) { 87 | LOGD("mprotect -w"); 88 | FAILURE_RETURN(mprotect((void *) start, length, prot), -1); 89 | } 90 | return 0; 91 | } 92 | 93 | int riru_hide(const std::set &names) { 94 | procmaps_iterator *maps = pmparser_parse(-1); 95 | if (maps == nullptr) { 96 | LOGE("cannot parse the memory map"); 97 | return false; 98 | } 99 | 100 | char buf[PATH_MAX]; 101 | hide_struct *data = nullptr; 102 | size_t data_count = 0; 103 | procmaps_struct *maps_tmp; 104 | while ((maps_tmp = pmparser_next(maps)) != nullptr) { 105 | bool matched = false; 106 | #ifdef DEBUG_APP 107 | matched = strstr(maps_tmp->pathname, "libriru.so"); 108 | #endif 109 | matched = names.count(maps_tmp->pathname); 110 | 111 | if (!matched) continue; 112 | 113 | auto start = (uintptr_t) maps_tmp->addr_start; 114 | auto end = (uintptr_t) maps_tmp->addr_end; 115 | if (maps_tmp->is_r) { 116 | if (data) { 117 | data = (hide_struct *) realloc(data, sizeof(hide_struct) * (data_count + 1)); 118 | } else { 119 | data = (hide_struct *) malloc(sizeof(hide_struct)); 120 | } 121 | data[data_count].original = maps_tmp; 122 | data_count += 1; 123 | } 124 | LOGD("%" PRIxPTR"-%" PRIxPTR" %s %ld %s", start, end, maps_tmp->perm, maps_tmp->offset, 125 | maps_tmp->pathname); 126 | } 127 | 128 | for (int i = 0; i < data_count; ++i) { 129 | do_hide(&data[i]); 130 | } 131 | 132 | if (data) free(data); 133 | pmparser_free(maps); 134 | return 0; 135 | } 136 | -------------------------------------------------------------------------------- /riru/src/main/cpp/hide_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "hide_utils.h" 9 | #include "logging.h" 10 | #include "module.h" 11 | #include "entry.h" 12 | #include 13 | #include 14 | #include 15 | 16 | namespace hide { 17 | namespace { 18 | class ProtectedDataGuard { 19 | 20 | public: 21 | ProtectedDataGuard() { 22 | if (ctor != nullptr) 23 | (this->*ctor)(); 24 | } 25 | 26 | ~ProtectedDataGuard() { 27 | if (dtor != nullptr) 28 | (this->*dtor)(); 29 | } 30 | 31 | static bool setup(const SandHook::ElfImg &linker) { 32 | ctor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( 33 | "__dl__ZN18ProtectedDataGuardC2Ev")), 34 | .adj = 0}} 35 | .f; 36 | dtor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( 37 | "__dl__ZN18ProtectedDataGuardD2Ev")), 38 | .adj = 0}} 39 | .f; 40 | return ctor != nullptr && dtor != nullptr; 41 | } 42 | 43 | ProtectedDataGuard(const ProtectedDataGuard &) = delete; 44 | 45 | void operator=(const ProtectedDataGuard &) = delete; 46 | 47 | private: 48 | using FuncType = void (ProtectedDataGuard::*)(); 49 | 50 | static FuncType ctor; 51 | static FuncType dtor; 52 | 53 | union MemFunc { 54 | FuncType f; 55 | 56 | struct { 57 | void *p; 58 | std::ptrdiff_t adj; 59 | } data; 60 | }; 61 | }; 62 | 63 | ProtectedDataGuard::FuncType ProtectedDataGuard::ctor = nullptr; 64 | ProtectedDataGuard::FuncType ProtectedDataGuard::dtor = nullptr; 65 | 66 | struct soinfo; 67 | 68 | soinfo *solist = nullptr; 69 | soinfo **sonext = nullptr; 70 | soinfo *somain = nullptr; 71 | 72 | template 73 | inline T *getStaticVariable(const SandHook::ElfImg &linker, std::string_view name) { 74 | auto *addr = reinterpret_cast(linker.getSymbAddress(name.data())); 75 | return addr == nullptr ? nullptr : *addr; 76 | } 77 | 78 | struct soinfo { 79 | soinfo *next() { 80 | return *(soinfo **) ((uintptr_t) this + solist_next_offset); 81 | } 82 | 83 | void next(soinfo *si) { 84 | *(soinfo **) ((uintptr_t) this + solist_next_offset) = si; 85 | } 86 | 87 | const char *get_realpath() { 88 | return get_realpath_sym ? get_realpath_sym(this) : ((std::string *) ( 89 | (uintptr_t) this + solist_realpath_offset))->c_str(); 90 | 91 | } 92 | 93 | static bool setup(const SandHook::ElfImg &linker) { 94 | get_realpath_sym = reinterpret_cast(linker.getSymbAddress( 95 | "__dl__ZNK6soinfo12get_realpathEv")); 96 | auto vsdo = getStaticVariable(linker, "__dl__ZL4vdso"); 97 | for (size_t i = 0; i < 1024 / sizeof(void *); i++) { 98 | auto *possible_next = *(void **) ((uintptr_t) solist + i * sizeof(void *)); 99 | if (possible_next == somain || (vsdo != nullptr && possible_next == vsdo)) { 100 | solist_next_offset = i * sizeof(void *); 101 | return android_prop::GetApiLevel() < 26 || get_realpath_sym != nullptr; 102 | } 103 | } 104 | LOGW("failed to search next offset"); 105 | // shortcut 106 | return android_prop::GetApiLevel() < 26 || get_realpath_sym != nullptr; 107 | } 108 | 109 | #ifdef __LP64__ 110 | constexpr static size_t solist_realpath_offset = 0x1a8; 111 | inline static size_t solist_next_offset = 0x30; 112 | #else 113 | constexpr static size_t solist_realpath_offset = 0x174; 114 | inline static size_t solist_next_offset = 0xa4; 115 | #endif 116 | 117 | // since Android 8 118 | inline static const char *(*get_realpath_sym)(soinfo *); 119 | }; 120 | 121 | bool solist_remove_soinfo(soinfo *si) { 122 | soinfo *prev = nullptr, *trav; 123 | for (trav = solist; trav != nullptr; trav = trav->next()) { 124 | if (trav == si) { 125 | break; 126 | } 127 | prev = trav; 128 | } 129 | 130 | if (trav == nullptr) { 131 | // si was not in solist 132 | LOGE("name \"%s\"@%p is not in solist!", si->get_realpath(), si); 133 | return false; 134 | } 135 | 136 | // prev will never be null, because the first entry in solist is 137 | // always the static libdl_info. 138 | prev->next(si->next()); 139 | if (si == *sonext) { 140 | *sonext = prev; 141 | } 142 | 143 | LOGD("removed soinfo: %s", si->get_realpath()); 144 | 145 | return true; 146 | } 147 | 148 | const auto initialized = []() { 149 | SandHook::ElfImg linker("/linker"); 150 | return ProtectedDataGuard::setup(linker) && 151 | (solist = getStaticVariable(linker, "__dl__ZL6solist")) != nullptr && 152 | (sonext = linker.getSymbAddress("__dl__ZL6sonext")) != nullptr && 153 | (somain = getStaticVariable(linker, "__dl__ZL6somain")) != nullptr && 154 | soinfo::setup(linker); 155 | }(); 156 | 157 | std::list linker_get_solist() { 158 | std::list linker_solist{}; 159 | for (auto *iter = solist; iter; iter = iter->next()) { 160 | linker_solist.push_back(iter); 161 | } 162 | return linker_solist; 163 | } 164 | 165 | void RemovePathsFromSolist(const std::set &names) { 166 | if (!initialized) { 167 | LOGW("not initialized"); 168 | return; 169 | } 170 | ProtectedDataGuard g; 171 | for (const auto &soinfo : linker_get_solist()) { 172 | const auto &real_path = soinfo->get_realpath(); 173 | if (real_path && names.count(real_path)) { 174 | solist_remove_soinfo(soinfo); 175 | } 176 | } 177 | } 178 | 179 | using riru_hide_t = int(const std::set &names); 180 | 181 | void *riru_hide_handle; 182 | riru_hide_t *riru_hide_func; 183 | 184 | void HidePathsFromMaps(const std::set &names) { 185 | if (!riru_hide_func) return; 186 | 187 | LOGD("do hide"); 188 | riru_hide_func(names); 189 | 190 | // cleanup riruhide.so 191 | LOGD("dlclose"); 192 | if (dlclose(riru_hide_handle) != 0) { 193 | LOGE("dlclose failed: %s", dlerror()); 194 | return; 195 | } 196 | } 197 | } // namespace 198 | 199 | void HideFromMaps() { 200 | auto self_path = magisk::GetPathForSelfLib("libriru.so"); 201 | std::set names{self_path}; 202 | for (const auto &module : modules::Get()) { 203 | if (module.supportHide) { 204 | if (!module.isLoaded()) { 205 | LOGD("%s is unloaded", module.id.data()); 206 | } else { 207 | names.emplace(module.path); 208 | } 209 | } else { 210 | LOGD("module %s does not support hide", module.id.data()); 211 | } 212 | } 213 | if (!names.empty()) hide::HidePathsFromMaps(names); 214 | } 215 | 216 | static void RemoveFromSoList(const std::set &names) { 217 | hide::RemovePathsFromSolist(names); 218 | } 219 | 220 | void HideFromSoList() { 221 | auto self_path = magisk::GetPathForSelfLib("libriru.so"); 222 | std::set names_to_remove{}; 223 | if (Entry::IsSelfUnloadAllowed()) { 224 | LOGD("don't hide self since it will be unloaded"); 225 | } else { 226 | names_to_remove.emplace(self_path); 227 | } 228 | for (const auto &module : modules::Get()) { 229 | if (module.supportHide) { 230 | if (!module.isLoaded()) { 231 | LOGD("%s is unloaded", module.id.data()); 232 | continue; 233 | } 234 | if (module.apiVersion < 24) { 235 | LOGW("%s is too old to hide so", module.id.data()); 236 | } else { 237 | names_to_remove.emplace(module.path); 238 | } 239 | } else { 240 | LOGD("module %s does not support hide", module.id.data()); 241 | } 242 | } 243 | 244 | if (android_prop::GetApiLevel() >= 23 && !names_to_remove.empty()) { 245 | RemoveFromSoList(names_to_remove); 246 | } 247 | } 248 | 249 | void PrepareMapsHideLibrary() { 250 | auto hide_lib_path = magisk::GetPathForSelfLib("libriruhide.so"); 251 | 252 | // load riruhide.so and run the hide 253 | LOGD("dlopen libriruhide"); 254 | riru_hide_handle = DlopenExt(hide_lib_path.c_str(), 0); 255 | if (!riru_hide_handle) { 256 | LOGE("dlopen %s failed: %s", hide_lib_path.c_str(), dlerror()); 257 | return; 258 | } 259 | riru_hide_func = reinterpret_cast(dlsym(riru_hide_handle, "riru_hide")); 260 | if (!riru_hide_func) { 261 | LOGE("dlsym failed: %s", dlerror()); 262 | dlclose(riru_hide_handle); 263 | return; 264 | } 265 | } 266 | } // namespace Hide 267 | -------------------------------------------------------------------------------- /riru/src/main/cpp/hide_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RIRU_HIDE_UTILS_H 2 | #define RIRU_HIDE_UTILS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace hide { 8 | 9 | void PrepareMapsHideLibrary(); 10 | 11 | void HideFromMaps(); 12 | 13 | void HideFromSoList(); 14 | } 15 | #endif //RIRU_HIDE_UTILS_H 16 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/android_prop.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace android_prop { 4 | 5 | const char* GetRelease(); 6 | 7 | int GetApiLevel(); 8 | 9 | int GetPreviewApiLevel(); 10 | 11 | bool CheckZTE(); 12 | } 13 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/buff_string.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by loves on 7/14/2021. 3 | // 4 | 5 | #ifndef RIRU_BUFF_STRING_H 6 | #define RIRU_BUFF_STRING_H 7 | 8 | #include 9 | #include 10 | 11 | template 12 | class BuffString { 13 | std::array data_{'\0'}; 14 | size_t size_{0u}; 15 | public: 16 | BuffString &operator+=(std::string_view str) { 17 | memcpy(data_.data() + size_, str.data(), str.size()); 18 | size_ += str.size(); 19 | data_[size_] = '\0'; 20 | return *this; 21 | } 22 | 23 | void size(const size_t &size) { 24 | size_ = size; 25 | data_[size] = '\0'; 26 | } 27 | 28 | constexpr auto data() const { 29 | return data_.data(); 30 | } 31 | 32 | constexpr auto size() const { 33 | return size_; 34 | } 35 | 36 | operator std::string_view() const { 37 | return {data_.data(), size_}; 38 | } 39 | 40 | operator const CHAR *() const { 41 | return data_.data(); 42 | } 43 | }; 44 | 45 | #endif //RIRU_BUFF_STRING_H 46 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SOCKET_ADDRESS "rirud" 4 | 5 | namespace riru { 6 | extern const int versionCode; 7 | extern const char* const versionName; 8 | extern const int apiVersion; 9 | extern const int minApiVersion; 10 | } 11 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/dl.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_H 2 | #define DL_H 3 | 4 | void *DlopenExt(const char *path, int flags); 5 | 6 | #endif //DL_H 7 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/elf_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of LSPosed. 3 | * 4 | * LSPosed is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * LSPosed is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with LSPosed. If not, see . 16 | * 17 | * Copyright (C) 2019 Swift Gan 18 | * Copyright (C) 2021 LSPosed Contributors 19 | */ 20 | #ifndef SANDHOOK_ELF_UTIL_H 21 | #define SANDHOOK_ELF_UTIL_H 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include "config.h" 30 | 31 | #define SHT_GNU_HASH 0x6ffffff6 32 | 33 | namespace SandHook { 34 | class ElfImg { 35 | public: 36 | 37 | ElfImg(std::string_view elf); 38 | 39 | constexpr ElfW(Addr) getSymbOffset(std::string_view name) const { 40 | return getSymbOffset(name, GnuHash(name), ElfHash(name)); 41 | } 42 | 43 | constexpr ElfW(Addr) getSymbAddress(std::string_view name) const { 44 | ElfW(Addr) offset = getSymbOffset(name); 45 | if (offset > 0 && base != nullptr) { 46 | return static_cast((uintptr_t) base + offset - bias); 47 | } else { 48 | return 0; 49 | } 50 | } 51 | 52 | template 53 | requires(std::is_pointer_v) 54 | constexpr T getSymbAddress(std::string_view name) const { 55 | return reinterpret_cast(getSymbAddress(name)); 56 | } 57 | 58 | bool isValid() const { 59 | return base != nullptr; 60 | } 61 | 62 | const std::string name() const { 63 | return elf; 64 | } 65 | 66 | ~ElfImg(); 67 | 68 | private: 69 | ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; 70 | 71 | ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const; 72 | 73 | ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const; 74 | 75 | ElfW(Addr) LinearLookup(std::string_view name) const; 76 | 77 | constexpr static uint32_t ElfHash(std::string_view name); 78 | 79 | constexpr static uint32_t GnuHash(std::string_view name); 80 | 81 | bool findModuleBase(); 82 | 83 | std::string elf; 84 | void *base = nullptr; 85 | char *buffer = nullptr; 86 | off_t size = 0; 87 | off_t bias = -4396; 88 | ElfW(Ehdr) *header = nullptr; 89 | ElfW(Shdr) *section_header = nullptr; 90 | ElfW(Shdr) *symtab = nullptr; 91 | ElfW(Shdr) *strtab = nullptr; 92 | ElfW(Shdr) *dynsym = nullptr; 93 | ElfW(Sym) *symtab_start = nullptr; 94 | ElfW(Sym) *dynsym_start = nullptr; 95 | ElfW(Sym) *strtab_start = nullptr; 96 | ElfW(Off) symtab_count = 0; 97 | ElfW(Off) symstr_offset = 0; 98 | ElfW(Off) symstr_offset_for_symtab = 0; 99 | ElfW(Off) symtab_offset = 0; 100 | ElfW(Off) dynsym_offset = 0; 101 | ElfW(Off) symtab_size = 0; 102 | 103 | uint32_t nbucket_{}; 104 | uint32_t *bucket_ = nullptr; 105 | uint32_t *chain_ = nullptr; 106 | 107 | uint32_t gnu_nbucket_{}; 108 | uint32_t gnu_symndx_{}; 109 | uint32_t gnu_bloom_size_; 110 | uint32_t gnu_shift2_; 111 | uintptr_t *gnu_bloom_filter_; 112 | uint32_t *gnu_bucket_; 113 | uint32_t *gnu_chain_; 114 | 115 | mutable std::unordered_map symtabs_; 116 | }; 117 | 118 | constexpr uint32_t ElfImg::ElfHash(std::string_view name) { 119 | uint32_t h = 0, g; 120 | for (unsigned char p: name) { 121 | h = (h << 4) + p; 122 | g = h & 0xf0000000; 123 | h ^= g; 124 | h ^= g >> 24; 125 | } 126 | return h; 127 | } 128 | 129 | constexpr uint32_t ElfImg::GnuHash(std::string_view name) { 130 | uint32_t h = 5381; 131 | for (unsigned char p: name) { 132 | h += (h << 5) + p; 133 | } 134 | return h; 135 | } 136 | } 137 | 138 | #endif //SANDHOOK_ELF_UTIL_H 139 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/finally.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by loves on 7/14/2021. 3 | // 4 | 5 | #ifndef RIRU_FINALLY_H 6 | #define RIRU_FINALLY_H 7 | 8 | #include 9 | 10 | struct finally { 11 | std::function body; 12 | 13 | finally(const std::function &body) : body(body) {} 14 | 15 | finally(std::function &&body) : body(std::move(body)) {}; 16 | 17 | ~finally() { 18 | body(); 19 | } 20 | }; 21 | 22 | #endif //RIRU_FINALLY_H 23 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/logging.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGGING_H 2 | #define LOGGING_H 3 | 4 | #include 5 | #include 6 | #include "android/log.h" 7 | 8 | #ifndef LOG_TAG 9 | #ifdef __LP64__ 10 | #define LOG_TAG "Riru64" 11 | #else 12 | #define LOG_TAG "Riru" 13 | #endif 14 | #endif 15 | 16 | #ifndef NDEBUG 17 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 18 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 19 | #else 20 | #define LOGV(...) 21 | #define LOGD(...) 22 | #endif 23 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 24 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 25 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 26 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 27 | 28 | #endif // LOGGING_H 29 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/module.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "rirud.h" 10 | 11 | struct RiruModule : public RiruModuleInfo { 12 | 13 | public: 14 | std::string id; 15 | std::string path; 16 | std::string magisk_module_path; 17 | int apiVersion; 18 | 19 | 20 | private: 21 | void *handle_{}; 22 | std::unique_ptr _allowUnload; 23 | 24 | public: 25 | RiruModule(RiruModule &&other) = default; 26 | 27 | explicit RiruModule(std::string_view id, std::string_view path, 28 | std::string_view magisk_module_path, 29 | int apiVersion, const RiruModuleInfo &info, void *handle = nullptr, 30 | std::unique_ptr allowUnload = nullptr) : RiruModuleInfo(info), 31 | id(id), path(path), 32 | magisk_module_path( 33 | magisk_module_path), 34 | apiVersion(apiVersion), 35 | handle_(handle), _allowUnload( 36 | std::move(allowUnload)) { 37 | } 38 | 39 | void unload() { 40 | if (!handle_) return; 41 | 42 | if (dlclose(handle_) == 0) { 43 | handle_ = nullptr; 44 | } 45 | } 46 | 47 | bool isLoaded() const { 48 | return handle_ != nullptr; 49 | } 50 | 51 | bool allowUnload() const { 52 | return apiVersion >= 25 53 | && ((_allowUnload && *_allowUnload != 0) || !hasAppFunctions()); 54 | } 55 | 56 | void resetAllowUnload() const { 57 | if (_allowUnload) *_allowUnload = 0; 58 | } 59 | 60 | bool hasOnModuleLoaded() const { 61 | return onModuleLoaded; 62 | } 63 | 64 | bool hasShouldSkipUid() const { 65 | return shouldSkipUid; 66 | } 67 | 68 | bool hasForkAndSpecializePre() const { 69 | return forkAndSpecializePre; 70 | } 71 | 72 | bool hasForkAndSpecializePost() const { 73 | return forkAndSpecializePost; 74 | } 75 | 76 | bool hasForkSystemServerPre() const { 77 | return forkSystemServerPre; 78 | } 79 | 80 | bool hasForkSystemServerPost() const { 81 | return forkSystemServerPost; 82 | } 83 | 84 | bool hasSpecializeAppProcessPre() const { 85 | return specializeAppProcessPre; 86 | } 87 | 88 | bool hasSpecializeAppProcessPost() const { 89 | return specializeAppProcessPost; 90 | } 91 | 92 | bool hasAppFunctions() const { 93 | return hasForkAndSpecializePre() 94 | || hasForkAndSpecializePost() 95 | || hasSpecializeAppProcessPre() 96 | || hasSpecializeAppProcessPost(); 97 | } 98 | }; 99 | 100 | namespace modules { 101 | 102 | std::list &Get(); 103 | 104 | void Load(const RirudSocket &rirud); 105 | } 106 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/pmparser.h: -------------------------------------------------------------------------------- 1 | /* 2 | @Author : ouadimjamal@gmail.com 3 | @date : December 2015 4 | 5 | Permission to use, copy, modify, distribute, and sell this software and its 6 | documentation for any purpose is hereby granted without fee, provided that 7 | the above copyright notice appear in all copies and that both that 8 | copyright notice and this permission notice appear in supporting 9 | documentation. No representations are made about the suitability of this 10 | software for any purpose. It is provided "as is" without express or 11 | implied warranty. 12 | 13 | */ 14 | 15 | #ifndef H_PMPARSER 16 | #define H_PMPARSER 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | //maximum line length in a procmaps file 33 | #define PROCMAPS_LINE_MAX_LENGTH (PATH_MAX + 100) 34 | /** 35 | * procmaps_struct 36 | * @desc hold all the information about an area in the process's VM 37 | */ 38 | typedef struct procmaps_struct{ 39 | void* addr_start; //< start address of the area 40 | void* addr_end; //< end address 41 | unsigned long length; //< size of the range 42 | 43 | char perm[5]; //< permissions rwxp 44 | short is_r; //< rewrote of perm with short flags 45 | short is_w; 46 | short is_x; 47 | short is_p; 48 | 49 | long offset; //< offset 50 | char dev[12]; //< dev major:minor 51 | int inode; //< inode of the file that backs the area 52 | 53 | char pathname[PATH_MAX]; //< the path of the file that backs the area 54 | //chained list 55 | struct procmaps_struct* next; // 5 | #include 6 | #include 7 | 8 | #include 9 | #include "buff_string.h" 10 | 11 | class RirudSocket { 12 | public: 13 | constexpr static std::string_view RIRUD = "rirud"; 14 | 15 | enum class Action : uint32_t { 16 | READ_FILE = 4, 17 | READ_DIR = 5, 18 | 19 | // used by riru itself only, could be removed in the future 20 | WRITE_STATUS = 2, 21 | READ_NATIVE_BRIDGE = 3, 22 | READ_MAGISK_TMPFS_PATH = 6, 23 | 24 | READ_MODULES = 7, 25 | }; 26 | 27 | enum class CODE : uint8_t { 28 | OK = 0, 29 | FAILED = 1, 30 | }; 31 | 32 | class DirIter { 33 | constexpr static uint8_t continue_read = true; 34 | constexpr static size_t MAX_PATH_SIZE = 256u; 35 | private: 36 | DirIter(std::string_view path, const RirudSocket &socket) : socket_(socket), path({'\0'}) { 37 | int32_t reply; 38 | if (socket_.Write(Action::READ_DIR) && socket_.Write(path) && socket_.Read(reply) && reply == 0) { 39 | ContinueRead(); 40 | } 41 | } 42 | 43 | void ContinueRead(); 44 | 45 | DirIter(const DirIter &) = delete; 46 | 47 | DirIter operator=(const DirIter &) = delete; 48 | 49 | 50 | const RirudSocket &socket_; 51 | std::array path; 52 | 53 | friend class RirudSocket; 54 | 55 | public: 56 | operator bool() { 57 | return path[0]; 58 | } 59 | 60 | DirIter &operator++() { 61 | ContinueRead(); 62 | return *this; 63 | } 64 | 65 | std::string_view operator*() { 66 | return {path.data()}; 67 | } 68 | }; 69 | 70 | friend class RirudSocket::DirIter; 71 | 72 | bool valid() const { 73 | return fd_ != -1; 74 | } 75 | 76 | RirudSocket(unsigned retries = 1); 77 | 78 | std::string ReadFile(std::string_view path); 79 | 80 | std::string ReadMagiskTmpfsPath() const; 81 | 82 | std::string ReadNativeBridge() const; 83 | 84 | DirIter ReadDir(std::string_view path) const { 85 | return {path, *this}; 86 | } 87 | 88 | template 89 | std::enable_if_t || std::is_enum_v, bool> 90 | Read(T &obj) const { 91 | return Read(reinterpret_cast(&obj), sizeof(T)); 92 | } 93 | 94 | bool Read(std::string &str) const; 95 | 96 | template 97 | std::enable_if_t || std::is_enum_v, bool> 98 | Write(const T &obj) const { 99 | return Write(&obj, sizeof(T)); 100 | } 101 | 102 | bool Write(std::string_view str) const; 103 | 104 | ~RirudSocket(); 105 | 106 | private: 107 | RirudSocket(const RirudSocket &) = delete; 108 | 109 | RirudSocket operator=(const RirudSocket &) = delete; 110 | 111 | bool Write(const void *buf, size_t len) const; 112 | 113 | bool Read(void *buf, size_t len) const; 114 | 115 | int fd_ = -1; 116 | }; 117 | 118 | #endif //RIRUD_H 119 | -------------------------------------------------------------------------------- /riru/src/main/cpp/include/selinux.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void dload_selinux(); 4 | 5 | extern int (*setsockcreatecon)(const char *con); 6 | extern int (*setfilecon)(const char *, const char *); 7 | extern int (*selinux_check_access)(const char *, const char *, const char *, const char *, void *); -------------------------------------------------------------------------------- /riru/src/main/cpp/include/socket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | socklen_t setup_sockaddr(struct sockaddr_un *sun, const char *name); 7 | int get_client_cred(int fd, struct ucred *cred); -------------------------------------------------------------------------------- /riru/src/main/cpp/include_riru/riru.h: -------------------------------------------------------------------------------- 1 | #ifndef RIRU_H 2 | #define RIRU_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // --------------------------------------------------------- 14 | 15 | typedef void(onModuleLoaded_v9)(); 16 | 17 | #ifndef RIRU_MODULE 18 | typedef int(shouldSkipUid_v9)(int uid); 19 | #endif 20 | 21 | typedef void(nativeForkAndSpecializePre_v9)( 22 | JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags, 23 | jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName, 24 | jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote, 25 | jstring *instructionSet, jstring *appDataDir, jboolean *isTopApp, 26 | jobjectArray *pkgDataInfoList, 27 | jobjectArray *whitelistedDataInfoList, jboolean *bindMountAppDataDirs, 28 | jboolean *bindMountAppStorageDirs); 29 | 30 | typedef void(nativeForkAndSpecializePost_v9)(JNIEnv *env, jclass cls, jint res); 31 | 32 | typedef void(nativeForkSystemServerPre_v9)( 33 | JNIEnv *env, jclass cls, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags, 34 | jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities); 35 | 36 | typedef void(nativeForkSystemServerPost_v9)(JNIEnv *env, jclass cls, jint res); 37 | 38 | typedef void(nativeSpecializeAppProcessPre_v9)( 39 | JNIEnv *env, jclass cls, jint *uid, jint *gid, jintArray *gids, jint *runtimeFlags, 40 | jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName, 41 | jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir, 42 | jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList, 43 | jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs); 44 | 45 | typedef void(nativeSpecializeAppProcessPost_v9)(JNIEnv *env, jclass cls); 46 | 47 | typedef struct { 48 | int supportHide; 49 | int version; 50 | const char *versionName; 51 | onModuleLoaded_v9 *onModuleLoaded; 52 | #ifndef RIRU_MODULE 53 | shouldSkipUid_v9 *shouldSkipUid; 54 | #else 55 | void *unused; 56 | #endif 57 | nativeForkAndSpecializePre_v9 *forkAndSpecializePre; 58 | nativeForkAndSpecializePost_v9 *forkAndSpecializePost; 59 | nativeForkSystemServerPre_v9 *forkSystemServerPre; 60 | nativeForkSystemServerPost_v9 *forkSystemServerPost; 61 | nativeSpecializeAppProcessPre_v9 *specializeAppProcessPre; 62 | nativeSpecializeAppProcessPost_v9 *specializeAppProcessPost; 63 | } RiruModuleInfo; 64 | 65 | typedef struct { 66 | int moduleApiVersion; 67 | RiruModuleInfo moduleInfo; 68 | } RiruVersionedModuleInfo; 69 | 70 | // --------------------------------------------------------- 71 | 72 | typedef struct { 73 | int riruApiVersion; 74 | void *unused; 75 | const char *magiskModulePath; 76 | int *allowUnload; 77 | } Riru; 78 | 79 | typedef RiruVersionedModuleInfo *(RiruInit_t)(Riru *); 80 | 81 | #ifdef RIRU_MODULE 82 | #define RIRUD_ADDRESS "rirud" 83 | 84 | 85 | #if __cplusplus < 201103L 86 | #define RIRU_EXPORT __attribute__((visibility("default"))) __attribute__((used)) 87 | #else 88 | #define RIRU_EXPORT [[gnu::visibility("default")]] [[gnu::used]] 89 | #endif 90 | 91 | RIRU_EXPORT RiruVersionedModuleInfo *init(Riru *riru) ; 92 | 93 | extern int riru_api_version; 94 | extern const char *riru_magisk_module_path; 95 | extern int *riru_allow_unload; 96 | 97 | #if !__cplusplus && __STDC_VERSION__ < 199409L 98 | #define RIRU_INLINE __attribute__((weak)) __inline__ 99 | #elif !__cplusplus 100 | #define RIRU_INLINE __attribute__((weak)) inline extern 101 | #else 102 | #define RIRU_INLINE inline 103 | #endif 104 | 105 | RIRU_INLINE const char *riru_get_magisk_module_path() { 106 | if (riru_api_version >= 24) { 107 | return riru_magisk_module_path; 108 | } 109 | return NULL; 110 | } 111 | 112 | RIRU_INLINE void riru_set_unload_allowed(int allowed) { 113 | if (riru_api_version >= 25 && riru_allow_unload) { 114 | *riru_allow_unload = allowed; 115 | } 116 | } 117 | #undef RIRU_INLINE 118 | 119 | #endif 120 | 121 | #ifdef __cplusplus 122 | } 123 | #endif 124 | 125 | #endif //RIRU_H 126 | -------------------------------------------------------------------------------- /riru/src/main/cpp/jni_hooks.h: -------------------------------------------------------------------------------- 1 | #ifndef _JNI_NATIVE_METHOD_H 2 | #define _JNI_NATIVE_METHOD_H 3 | 4 | #include 5 | #include 6 | 7 | namespace jni { 8 | 9 | namespace zygote { 10 | extern const char* classname; 11 | extern JNINativeMethod *nativeForkAndSpecialize; 12 | extern JNINativeMethod *nativeSpecializeAppProcess; 13 | extern JNINativeMethod *nativeForkSystemServer; 14 | } 15 | 16 | void InstallHooks(); 17 | 18 | void RestoreHooks(JNIEnv *env); 19 | } 20 | 21 | const static char *nativeForkAndSpecialize_marshmallow_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I"; 22 | 23 | using nativeForkAndSpecialize_marshmallow_t = jint( 24 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 25 | jintArray, jstring, jstring); 26 | 27 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_marshmallow( 28 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint debug_flags, 29 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 30 | jintArray fdsToClose, jstring instructionSet, jstring appDataDir); 31 | 32 | const static char *nativeForkAndSpecialize_oreo_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I"; 33 | 34 | using nativeForkAndSpecialize_oreo_t = jint( 35 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 36 | jintArray, jintArray, jstring, jstring); 37 | 38 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_oreo( 39 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint debug_flags, 40 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 41 | jintArray fdsToClose, jintArray fdsToIgnore, jstring instructionSet, jstring appDataDir); 42 | 43 | const static char *nativeForkAndSpecialize_p_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I"; 44 | 45 | using nativeForkAndSpecialize_p_t = jint( 46 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 47 | jintArray, jintArray, jboolean, jstring, jstring); 48 | 49 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_p( 50 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 51 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 52 | jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 53 | jstring instructionSet, jstring appDataDir); 54 | 55 | const static char *nativeForkAndSpecialize_q_alternative_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I"; 56 | 57 | using nativeForkAndSpecialize_q_alternative_t = jint( 58 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 59 | jintArray, jintArray, jboolean, jstring, jstring, jboolean); 60 | 61 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_q_alternative( 62 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 63 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 64 | jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 65 | jstring instructionSet, jstring appDataDir, jboolean isTopApp); 66 | 67 | const static char *nativeForkAndSpecialize_r_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I"; 68 | 69 | using nativeForkAndSpecialize_r_t = jint( 70 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 71 | jintArray, jintArray, jboolean, jstring, jstring, jboolean, jobjectArray, jobjectArray, 72 | jboolean, jboolean); 73 | 74 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_r( 75 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 76 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 77 | jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 78 | jstring instructionSet, jstring appDataDir, jboolean isTopApp, jobjectArray pkgDataInfoList, 79 | jobjectArray whitelistedDataInfoList, jboolean bindMountAppDataDirs, jboolean bindMountAppStorageDirs); 80 | 81 | const static char *nativeForkAndSpecialize_r_dp2_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)I"; 82 | 83 | using nativeForkAndSpecialize_r_dp2_t = jint( 84 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 85 | jintArray, jintArray, jboolean, jstring, jstring, jboolean, jobjectArray); 86 | 87 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_r_dp2( 88 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 89 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 90 | jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 91 | jstring instructionSet, jstring appDataDir, jboolean isTopApp, jobjectArray pkgDataInfoList); 92 | 93 | const static char *nativeForkAndSpecialize_r_dp3_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)I"; 94 | 95 | using nativeForkAndSpecialize_r_dp3_t = jint( 96 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 97 | jintArray, jintArray, jboolean, jstring, jstring, jboolean, jobjectArray, jboolean); 98 | 99 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_r_dp3( 100 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 101 | jobjectArray rlimits, jint mount_external, jstring se_info, jstring se_name, 102 | jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 103 | jstring instructionSet, jstring appDataDir, jboolean isTopApp, jobjectArray pkgDataInfoList, 104 | jboolean bindMountAppStorageDirs); 105 | 106 | const static char *nativeForkAndSpecialize_samsung_p_sig = "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I"; 107 | 108 | using nativeForkAndSpecialize_samsung_p_t = jint( 109 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jint, jint, 110 | jstring, jintArray, jintArray, jboolean, jstring, jstring); 111 | 112 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_p( 113 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, 114 | jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo, 115 | jstring se_name, jintArray fdsToClose, jintArray fdsToIgnore, jboolean is_child_zygote, 116 | jstring instructionSet, jstring appDataDir); 117 | 118 | const static char *nativeForkAndSpecialize_samsung_o_sig = "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I"; 119 | 120 | using nativeForkAndSpecialize_samsung_o_t = jint( 121 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jint, jint, 122 | jstring, jintArray, jintArray, jstring, jstring); 123 | 124 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_o( 125 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint debug_flags, 126 | jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo, 127 | jstring se_name, jintArray fdsToClose, jintArray fdsToIgnore, jstring instructionSet, 128 | jstring appDataDir); 129 | 130 | const static char *nativeForkAndSpecialize_samsung_n_sig = "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I"; 131 | 132 | using nativeForkAndSpecialize_samsung_n_t = jint( 133 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jint, jint, 134 | jstring, jintArray, jstring, jstring, jint); 135 | 136 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_n( 137 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint debug_flags, 138 | jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo, 139 | jstring se_name, jintArray fdsToClose, jstring instructionSet, jstring appDataDir, jint); 140 | 141 | const static char *nativeForkAndSpecialize_samsung_m_sig = "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I"; 142 | 143 | using nativeForkAndSpecialize_samsung_m_t = jint( 144 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jint, jint, 145 | jstring, jintArray, jstring, jstring); 146 | 147 | [[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_m( 148 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint debug_flags, 149 | jobjectArray rlimits, jint mount_external, jstring se_info, jint category, jint accessInfo, 150 | jstring se_name, jintArray fdsToClose, jstring instructionSet, jstring appDataDir); 151 | 152 | // ----------------------------------------------------------------- 153 | 154 | const static char *nativeSpecializeAppProcess_q_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V"; 155 | 156 | using nativeSpecializeAppProcess_q_t = void( 157 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 158 | jboolean, jstring, jstring); 159 | 160 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_q( 161 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 162 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jstring niceName, 163 | jboolean startChildZygote, jstring instructionSet, jstring appDataDir); 164 | 165 | const static char *nativeSpecializeAppProcess_r_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V"; 166 | 167 | using nativeSpecializeAppProcess_r_t = void( 168 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 169 | jboolean, jstring, jstring, jboolean, jobjectArray, jobjectArray, jboolean, jboolean); 170 | 171 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_r( 172 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 173 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jstring niceName, 174 | jboolean startChildZygote, jstring instructionSet, jstring appDataDir, 175 | jboolean isTopApp, jobjectArray pkgDataInfoList, jobjectArray whitelistedDataInfoList, 176 | jboolean bindMountAppDataDirs, jboolean bindMountAppStorageDirs); 177 | 178 | const static char *nativeSpecializeAppProcess_r_dp2_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;)V"; 179 | 180 | using nativeSpecializeAppProcess_r_dp2_t = void( 181 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 182 | jboolean, jstring, jstring, jboolean, jobjectArray); 183 | 184 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_r_dp2( 185 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 186 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jstring niceName, 187 | jboolean startChildZygote, jstring instructionSet, jstring appDataDir, 188 | jboolean isTopApp, jobjectArray pkgDataInfoList); 189 | 190 | const static char *nativeSpecializeAppProcess_r_dp3_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;Z)V"; 191 | 192 | using nativeSpecializeAppProcess_r_dp3_t = void( 193 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 194 | jboolean, jstring, jstring, jboolean, jobjectArray, jboolean); 195 | 196 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_r_dp3( 197 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 198 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jstring niceName, 199 | jboolean startChildZygote, jstring instructionSet, jstring appDataDir, 200 | jboolean isTopApp, jobjectArray pkgDataInfoList, jboolean bindMountAppStorageDirs); 201 | 202 | const static char *nativeSpecializeAppProcess_q_alternative_sig = "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V"; 203 | 204 | using nativeSpecializeAppProcess_q_alternative_t = void( 205 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jstring, 206 | jboolean, jstring, jstring, jboolean); 207 | 208 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_q_alternative( 209 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 210 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jstring niceName, 211 | jboolean startChildZygote, jstring instructionSet, jstring appDataDir, 212 | jboolean isTopApp); 213 | 214 | const static char *nativeSpecializeAppProcess_sig_samsung_q = "(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V"; 215 | 216 | using nativeSpecializeAppProcess_samsung_t = void( 217 | JNIEnv *, jclass, jint, jint, jintArray, jint, jobjectArray, jint, jstring, jint, 218 | jint, jstring, jboolean, jstring, jstring); 219 | 220 | [[clang::no_stack_protector]] void nativeSpecializeAppProcess_samsung_q( 221 | JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtimeFlags, 222 | jobjectArray rlimits, jint mountExternal, jstring seInfo, jint space, jint accessInfo, 223 | jstring niceName, jboolean startChildZygote, jstring instructionSet, jstring appDataDir); 224 | 225 | // ----------------------------------------------------------------- 226 | 227 | const static char *nativeForkSystemServer_sig = "(II[II[[IJJ)I"; 228 | 229 | using nativeForkSystemServer_t = jint( 230 | JNIEnv *, jclass, uid_t, gid_t, jintArray, jint, jobjectArray, jlong, jlong); 231 | 232 | [[clang::no_stack_protector]] jint nativeForkSystemServer( 233 | JNIEnv *env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtimeFlags, 234 | jobjectArray rlimits, jlong permittedCapabilities, jlong effectiveCapabilities); 235 | 236 | const static char *nativeForkSystemServer_samsung_q_sig = "(II[IIII[[IJJ)I"; 237 | 238 | using nativeForkSystemServer_samsung_q_t = jint( 239 | JNIEnv *, jclass, uid_t, gid_t, jintArray, jint, jint, jint, jobjectArray, jlong, jlong); 240 | 241 | [[clang::no_stack_protector]] jint nativeForkSystemServer_samsung_q( 242 | JNIEnv *env, jclass, uid_t uid, gid_t gid, jintArray gids, jint runtimeFlags, 243 | jint space, jint accessInfo, jobjectArray rlimits, jlong permittedCapabilities, 244 | jlong effectiveCapabilities); 245 | 246 | #endif // _JNI_NATIVE_METHOD_H 247 | -------------------------------------------------------------------------------- /riru/src/main/cpp/loader/loader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "rirud.h" 14 | #include "config.h" 15 | #include "logging.h" 16 | #include 17 | #include "buff_string.h" 18 | 19 | #ifndef NDEBUG 20 | #ifndef HAS_NATIVE_BRIDGE 21 | #define HAS_NATIVE_BRIDGE 22 | #endif 23 | #endif 24 | 25 | #ifdef HAS_NATIVE_BRIDGE 26 | 27 | #include "native_bridge_callbacks.h" 28 | 29 | //NOLINTNEXTLINE 30 | extern "C" [[gnu::visibility("default")]] uint8_t NativeBridgeItf[ 31 | sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0}; 32 | 33 | static void *original_bridge = nullptr; 34 | 35 | __used __attribute__((destructor)) void Destructor() { 36 | if (original_bridge) dlclose(original_bridge); 37 | } 38 | 39 | #endif 40 | 41 | __used __attribute__((constructor)) void Constructor() { 42 | if (getuid() != 0) { 43 | return; 44 | } 45 | 46 | std::string_view cmdline = getprogname(); 47 | 48 | if (cmdline != "zygote" && 49 | cmdline != "zygote32" && 50 | cmdline != "zygote64" && 51 | cmdline != "usap32" && 52 | cmdline != "usap64") { 53 | LOGW("not zygote (cmdline=%s)", cmdline.data()); 54 | return; 55 | } 56 | 57 | LOGI("Riru %s (%d) in %s", riru::versionName, riru::versionCode, cmdline.data()); 58 | LOGI("Android %s (api %d, preview_api %d)", android_prop::GetRelease(), 59 | android_prop::GetApiLevel(), 60 | android_prop::GetPreviewApiLevel()); 61 | 62 | constexpr auto retries = 5U; 63 | RirudSocket rirud{retries}; 64 | 65 | if (!rirud.valid()) { 66 | LOGE("rirud connect fails"); 67 | return; 68 | } 69 | 70 | std::string magisk_path = rirud.ReadMagiskTmpfsPath(); 71 | if (magisk_path.empty()) { 72 | LOGE("failed to obtain magisk path"); 73 | return; 74 | } 75 | 76 | BuffString riru_path; 77 | riru_path += magisk_path; 78 | riru_path += "/.magisk/modules/riru-core/lib"; 79 | #ifdef __LP64__ 80 | riru_path += "64"; 81 | #endif 82 | riru_path += "/libriru.so"; 83 | 84 | auto *handle = DlopenExt(riru_path, 0); 85 | if (handle) { 86 | auto init = reinterpret_cast(dlsym( 87 | handle, "init")); 88 | if (init) { 89 | init(handle, magisk_path.data(), rirud); 90 | } else { 91 | LOGE("dlsym init %s", dlerror()); 92 | } 93 | } else { 94 | LOGE("dlopen riru.so %s", dlerror()); 95 | } 96 | 97 | #ifdef HAS_NATIVE_BRIDGE 98 | 99 | auto native_bridge = rirud.ReadNativeBridge(); 100 | if (native_bridge.empty()) { 101 | LOGW("Failed to read original native bridge from socket"); 102 | return; 103 | } 104 | 105 | LOGI("original native bridge: %s", native_bridge.data()); 106 | 107 | if (native_bridge == "0") { 108 | return; 109 | } 110 | 111 | original_bridge = dlopen(native_bridge.data(), RTLD_NOW); 112 | if (original_bridge == nullptr) { 113 | LOGE("dlopen failed: %s", dlerror()); 114 | return; 115 | } 116 | 117 | auto *original_native_bridge_itf = dlsym(original_bridge, "NativeBridgeItf"); 118 | if (original_native_bridge_itf == nullptr) { 119 | LOGE("dlsym failed: %s", dlerror()); 120 | return; 121 | } 122 | 123 | int sdk = 0; 124 | std::array value; 125 | if (__system_property_get("ro.build.version.sdk", value.data()) > 0) { 126 | sdk = atoi(value.data()); 127 | } 128 | 129 | auto callbacks_size = 0; 130 | if (sdk >= __ANDROID_API_R__) { 131 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>); 132 | } else if (sdk == __ANDROID_API_Q__) { 133 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_Q__>); 134 | } else if (sdk == __ANDROID_API_P__) { 135 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_P__>); 136 | } else if (sdk == __ANDROID_API_O_MR1__) { 137 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_O_MR1__>); 138 | } else if (sdk == __ANDROID_API_O__) { 139 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_O__>); 140 | } else if (sdk == __ANDROID_API_N_MR1__) { 141 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_N_MR1__>); 142 | } else if (sdk == __ANDROID_API_N__) { 143 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_N__>); 144 | } else if (sdk == __ANDROID_API_M__) { 145 | callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_M__>); 146 | } 147 | 148 | memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size); 149 | #endif 150 | } 151 | -------------------------------------------------------------------------------- /riru/src/main/cpp/loader/native_bridge_callbacks.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by loves on 7/25/2021. 3 | // 4 | 5 | #ifndef RIRU_NATIVE_BRIDGE_CALLBACKS_H 6 | #define RIRU_NATIVE_BRIDGE_CALLBACKS_H 7 | 8 | #include 9 | 10 | template 11 | struct NativeBridgeCallbacks; 12 | 13 | template<> 14 | struct NativeBridgeCallbacks<__ANDROID_API_M__> { 15 | [[maybe_unused]] uint32_t version; 16 | [[maybe_unused]] void *initialize; 17 | [[maybe_unused]] void *loadLibrary; 18 | [[maybe_unused]] void *getTrampoline; 19 | [[maybe_unused]] void *isSupported; 20 | [[maybe_unused]] void *getAppEnv; 21 | [[maybe_unused]] void *isCompatibleWith; 22 | [[maybe_unused]] void *getSignalHandler; 23 | }; 24 | 25 | template<> 26 | struct NativeBridgeCallbacks<__ANDROID_API_N__> : NativeBridgeCallbacks<__ANDROID_API_M__> { 27 | }; 28 | 29 | template<> 30 | struct NativeBridgeCallbacks<__ANDROID_API_N_MR1__> : NativeBridgeCallbacks<__ANDROID_API_N__> { 31 | }; 32 | 33 | template<> 34 | struct NativeBridgeCallbacks<__ANDROID_API_O__> : NativeBridgeCallbacks<__ANDROID_API_N_MR1__> { 35 | [[maybe_unused]] void *unloadLibrary; 36 | [[maybe_unused]] void *getError; 37 | [[maybe_unused]] void *isPathSupported; 38 | [[maybe_unused]] void *initAnonymousNamespace; 39 | [[maybe_unused]] void *createNamespace; 40 | [[maybe_unused]] void *linkNamespaces; 41 | [[maybe_unused]] void *loadLibraryExt; 42 | [[maybe_unused]] void *getVendorNamespace; 43 | }; 44 | 45 | template<> 46 | struct NativeBridgeCallbacks<__ANDROID_API_O_MR1__> : NativeBridgeCallbacks<__ANDROID_API_O__> { 47 | }; 48 | 49 | template<> 50 | struct NativeBridgeCallbacks<__ANDROID_API_P__> : NativeBridgeCallbacks<__ANDROID_API_O_MR1__> { 51 | }; 52 | 53 | template<> 54 | struct NativeBridgeCallbacks<__ANDROID_API_Q__> : NativeBridgeCallbacks<__ANDROID_API_P__> { 55 | [[maybe_unused]] void *getExportedNamespace; 56 | }; 57 | 58 | template<> 59 | struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> { 60 | [[maybe_unused]] void *preZygoteFork; 61 | }; 62 | 63 | #endif //RIRU_NATIVE_BRIDGE_CALLBACKS_H 64 | -------------------------------------------------------------------------------- /riru/src/main/cpp/magisk.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "buff_string.h" 13 | 14 | using namespace std::string_literals; 15 | 16 | namespace magisk { 17 | 18 | static std::string path; 19 | 20 | const auto &GetPath() { 21 | return path; 22 | } 23 | 24 | void SetPath(const char *p) { 25 | if (p) path = p; 26 | } 27 | 28 | std::string GetPathForSelf(const char *name) { 29 | return GetPath() + "/.magisk/modules/riru-core/"s + name; 30 | } 31 | 32 | std::string GetPathForSelfLib(const char *name) { 33 | #ifdef __LP64__ 34 | return GetPath() + "/.magisk/modules/riru-core/lib64/"s + name; 35 | #else 36 | return GetPath() + "/.magisk/modules/riru-core/lib/"s + name; 37 | #endif 38 | } 39 | } // namespace magisk 40 | -------------------------------------------------------------------------------- /riru/src/main/cpp/magisk.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace magisk { 6 | 7 | const char* GetPath(); 8 | 9 | void SetPath(const char *p); 10 | 11 | std::string GetPathForSelf(const char *name); 12 | 13 | std::string GetPathForSelfLib(const char *name); 14 | } 15 | -------------------------------------------------------------------------------- /riru/src/main/cpp/module.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "buff_string.h" 8 | #include 9 | #include "module.h" 10 | #include "logging.h" 11 | #include "config.h" 12 | #include "hide_utils.h" 13 | #include "magisk.h" 14 | #include "dl.h" 15 | 16 | using namespace std::string_literals; 17 | using namespace std::string_view_literals; 18 | 19 | constexpr uint8_t is64bit = sizeof(void *) == 8; 20 | 21 | std::list &modules::Get() { 22 | static std::list kModules; 23 | return kModules; 24 | } 25 | 26 | static void Cleanup(void *handle) { 27 | if (dlclose(handle) != 0) { 28 | LOGE("dlclose failed: %s", dlerror()); 29 | return; 30 | } 31 | } 32 | 33 | static void LoadModule(std::string_view id, std::string_view path, std::string_view magisk_module_path) { 34 | if (access(path.data(), F_OK) != 0) { 35 | PLOGE("access %s", path.data()); 36 | return; 37 | } 38 | 39 | auto *handle = DlopenExt(path.data(), 0); 40 | if (!handle) { 41 | LOGE("dlopen %s failed: %s", path.data(), dlerror()); 42 | return; 43 | } 44 | 45 | auto init = reinterpret_cast(dlsym(handle, "init")); 46 | if (!init) { 47 | LOGW("%s does not export init", path.data()); 48 | Cleanup(handle); 49 | return; 50 | } 51 | 52 | auto allow_unload = std::make_unique(); 53 | auto riru = std::make_unique(Riru{ 54 | .riruApiVersion = riru::apiVersion, 55 | .unused = nullptr, 56 | .magiskModulePath = magisk_module_path.data(), 57 | .allowUnload = allow_unload.get() 58 | }); 59 | 60 | auto *module_info = init(riru.get()); 61 | if (module_info == nullptr) { 62 | LOGE("%s requires higher Riru version (or its broken)", path.data()); 63 | Cleanup(handle); 64 | return; 65 | } 66 | 67 | auto api_version = module_info->moduleApiVersion; 68 | if (api_version < riru::minApiVersion || api_version > riru::apiVersion) { 69 | LOGW("unsupported API %s: %d", id.data(), api_version); 70 | Cleanup(handle); 71 | return; 72 | } 73 | 74 | LOGI("module loaded: %s (api %d)", id.data(), api_version); 75 | 76 | modules::Get().emplace_back(id, path, magisk_module_path, api_version, module_info->moduleInfo, 77 | handle, 78 | std::move(allow_unload)); 79 | } 80 | 81 | static void WriteModules(const RirudSocket &rirud) { 82 | auto &modules = modules::Get(); 83 | uint32_t count = modules.size(); 84 | if (!rirud.Write(RirudSocket::Action::WRITE_STATUS) || !rirud.Write(is64bit) || 85 | !rirud.Write(count)) { 86 | PLOGE("write %s", SOCKET_ADDRESS); 87 | return; 88 | } 89 | 90 | for (const auto &module : modules) { 91 | rirud.Write(module.id); 92 | rirud.Write(module.apiVersion); 93 | rirud.Write(module.version); 94 | rirud.Write(module.versionName); 95 | rirud.Write(module.supportHide); 96 | } 97 | } 98 | 99 | void modules::Load(const RirudSocket &rirud) { 100 | uint32_t num_modules; 101 | auto &modules = modules::Get(); 102 | if (!rirud.Write(RirudSocket::Action::READ_MODULES) || 103 | !rirud.Write(is64bit) || !rirud.Read(num_modules)) { 104 | LOGE("Faild to load modules"); 105 | return; 106 | } 107 | std::string magisk_module_path; 108 | std::string path; 109 | std::string id; 110 | uint32_t num_libs; 111 | while (num_modules-- > 0) { 112 | if (!rirud.Read(magisk_module_path) || !rirud.Read(num_libs)) { 113 | LOGE("Faild to read module's magisk path"); 114 | return; 115 | } 116 | while (num_libs-- > 0) { 117 | if (!rirud.Read(id) || !rirud.Read(path)) { 118 | LOGE("Faild to read module's lib path"); 119 | return; 120 | } 121 | LoadModule(id, path, magisk_module_path); 122 | } 123 | } 124 | 125 | // On Android 10+, zygote has "execmem" permission, we can use "riru hide" here 126 | if (android_prop::GetApiLevel() >= __ANDROID_API_Q__) { 127 | hide::HideFromMaps(); 128 | } 129 | 130 | for (const auto &module : modules::Get()) { 131 | if (module.hasOnModuleLoaded()) { 132 | LOGV("%s: onModuleLoaded", module.id.data()); 133 | module.onModuleLoaded(); 134 | } 135 | } 136 | 137 | WriteModules(rirud); 138 | } 139 | -------------------------------------------------------------------------------- /riru/src/main/cpp/template/config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | 3 | namespace riru { 4 | const int versionCode = ${RIRU_VERSION_CODE}; 5 | const char* const versionName = "${RIRU_VERSION_NAME}"; 6 | const int apiVersion = ${RIRU_API_VERSION}; 7 | const int minApiVersion = ${RIRU_MIN_API_VERSION}; 8 | } 9 | -------------------------------------------------------------------------------- /riru/src/main/cpp/util/android_prop.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace android_prop { 4 | 5 | const char *GetRelease() { 6 | static char release[PROP_VALUE_MAX + 1]{0}; 7 | if (release[0] != 0) return release; 8 | __system_property_get("ro.build.version.release", release); 9 | return release; 10 | } 11 | 12 | auto GetApiLevel() { 13 | static auto kApiLevel = (({ 14 | int result = 0; 15 | if (char buf[PROP_VALUE_MAX + 1]; 16 | __system_property_get("ro.build.version.sdk", buf) > 0) { 17 | result = atoi(buf); 18 | } 19 | result; 20 | })); 21 | return kApiLevel; 22 | } 23 | 24 | auto GetPreviewApiLevel() { 25 | static auto kPreviewApiLevel = (({ 26 | int result = 0; 27 | if (char buf[PROP_VALUE_MAX + 1]; 28 | __system_property_get("ro.build.version.preview_sdk", buf) > 0) { 29 | result = atoi(buf); 30 | } 31 | result; 32 | })); 33 | return kPreviewApiLevel; 34 | } 35 | 36 | auto CheckZTE() { 37 | static bool kZTE = __system_property_find("ro.vendor.product.ztename"); 38 | return kZTE; 39 | } 40 | } // namespace android_prop 41 | -------------------------------------------------------------------------------- /riru/src/main/cpp/util/dl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | extern "C" [[gnu::weak]] struct android_namespace_t * 11 | //NOLINTNEXTLINE 12 | __loader_android_create_namespace([[maybe_unused]] const char *name, 13 | [[maybe_unused]] const char *ld_library_path, 14 | [[maybe_unused]] const char *default_library_path, 15 | [[maybe_unused]] uint64_t type, 16 | [[maybe_unused]] const char *permitted_when_isolated_path, 17 | [[maybe_unused]] android_namespace_t *parent, 18 | [[maybe_unused]] const void *caller_addr); 19 | 20 | void *DlopenExt(const char *path, int flags) { 21 | auto info = android_dlextinfo{}; 22 | 23 | if (android_prop::GetApiLevel() >= __ANDROID_API_O__) { 24 | auto *dir = dirname(path); 25 | auto *ns = &__loader_android_create_namespace == nullptr ? nullptr : 26 | __loader_android_create_namespace(path, dir, nullptr, 27 | 2/*ANDROID_NAMESPACE_TYPE_SHARED*/, 28 | nullptr, nullptr, 29 | reinterpret_cast(&DlopenExt)); 30 | if (ns) { 31 | info.flags = ANDROID_DLEXT_USE_NAMESPACE; 32 | info.library_namespace = ns; 33 | 34 | LOGD("open %s with namespace %p", path, ns); 35 | } else { 36 | LOGD("cannot create namespace for %s", path); 37 | } 38 | } 39 | 40 | auto *handle = android_dlopen_ext(path, flags, &info); 41 | if (handle) { 42 | LOGD("dlopen %s: %p", path, handle); 43 | } else { 44 | LOGE("dlopen %s: %s", path, dlerror()); 45 | } 46 | return handle; 47 | } 48 | -------------------------------------------------------------------------------- /riru/src/main/cpp/util/elf_util.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of LSPosed. 3 | * 4 | * LSPosed is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * LSPosed is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with LSPosed. If not, see . 16 | * 17 | * Copyright (C) 2019 Swift Gan 18 | * Copyright (C) 2021 LSPosed Contributors 19 | */ 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "logging.h" 28 | #include "elf_util.h" 29 | 30 | using namespace SandHook; 31 | 32 | template 33 | inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) { 34 | return reinterpret_cast, T, T *>>( 35 | reinterpret_cast(head) + off); 36 | } 37 | 38 | ElfImg::ElfImg(std::string_view base_name) : elf(base_name) { 39 | if (!findModuleBase()) { 40 | base = nullptr; 41 | return; 42 | } 43 | 44 | //load elf 45 | int fd = open(elf.data(), O_RDONLY); 46 | if (fd < 0) { 47 | LOGE("failed to open %s", elf.data()); 48 | return; 49 | } 50 | 51 | size = lseek(fd, 0, SEEK_END); 52 | if (size <= 0) { 53 | LOGE("lseek() failed for %s", elf.data()); 54 | } 55 | 56 | header = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); 57 | 58 | close(fd); 59 | 60 | section_header = offsetOf(header, header->e_shoff); 61 | 62 | auto shoff = reinterpret_cast(section_header); 63 | char *section_str = offsetOf(header, section_header[header->e_shstrndx].sh_offset); 64 | 65 | for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) { 66 | auto *section_h = (ElfW(Shdr) *) shoff; 67 | char *sname = section_h->sh_name + section_str; 68 | auto entsize = section_h->sh_entsize; 69 | switch (section_h->sh_type) { 70 | case SHT_DYNSYM: { 71 | if (bias == -4396) { 72 | dynsym = section_h; 73 | dynsym_offset = section_h->sh_offset; 74 | dynsym_start = offsetOf(header, dynsym_offset); 75 | } 76 | break; 77 | } 78 | case SHT_SYMTAB: { 79 | if (strcmp(sname, ".symtab") == 0) { 80 | symtab = section_h; 81 | symtab_offset = section_h->sh_offset; 82 | symtab_size = section_h->sh_size; 83 | symtab_count = symtab_size / entsize; 84 | symtab_start = offsetOf(header, symtab_offset); 85 | } 86 | break; 87 | } 88 | case SHT_STRTAB: { 89 | if (bias == -4396) { 90 | strtab = section_h; 91 | symstr_offset = section_h->sh_offset; 92 | strtab_start = offsetOf(header, symstr_offset); 93 | } 94 | if (strcmp(sname, ".strtab") == 0) { 95 | symstr_offset_for_symtab = section_h->sh_offset; 96 | } 97 | break; 98 | } 99 | case SHT_PROGBITS: { 100 | if (strtab == nullptr || dynsym == nullptr) break; 101 | if (bias == -4396) { 102 | bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset; 103 | } 104 | break; 105 | } 106 | case SHT_HASH: { 107 | auto *d_un = offsetOf(header, section_h->sh_offset); 108 | nbucket_ = d_un[0]; 109 | bucket_ = d_un + 2; 110 | chain_ = bucket_ + nbucket_; 111 | break; 112 | } 113 | case SHT_GNU_HASH: { 114 | auto *d_buf = reinterpret_cast(((size_t) header) + 115 | section_h->sh_offset); 116 | gnu_nbucket_ = d_buf[0]; 117 | gnu_symndx_ = d_buf[1]; 118 | gnu_bloom_size_ = d_buf[2]; 119 | gnu_shift2_ = d_buf[3]; 120 | gnu_bloom_filter_ = reinterpret_cast(d_buf + 4); 121 | gnu_bucket_ = reinterpret_cast(gnu_bloom_filter_ + 122 | gnu_bloom_size_); 123 | gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_; 124 | break; 125 | } 126 | } 127 | } 128 | } 129 | 130 | ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const { 131 | if (nbucket_ == 0) return 0; 132 | 133 | char *strings = (char *) strtab_start; 134 | 135 | for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { 136 | auto *sym = dynsym_start + n; 137 | if (name == strings + sym->st_name) { 138 | return sym->st_value; 139 | } 140 | } 141 | return 0; 142 | } 143 | 144 | ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const { 145 | static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8; 146 | 147 | if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0; 148 | 149 | auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_]; 150 | uintptr_t mask = 0 151 | | (uintptr_t) 1 << (hash % bloom_mask_bits) 152 | | (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits); 153 | if ((mask & bloom_word) == mask) { 154 | auto sym_index = gnu_bucket_[hash % gnu_nbucket_]; 155 | if (sym_index >= gnu_symndx_) { 156 | char *strings = (char *) strtab_start; 157 | do { 158 | auto *sym = dynsym_start + sym_index; 159 | if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0 160 | && name == strings + sym->st_name) { 161 | return sym->st_value; 162 | } 163 | } while ((gnu_chain_[sym_index++] & 1) == 0); 164 | } 165 | } 166 | return 0; 167 | } 168 | 169 | ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const { 170 | if (symtabs_.empty()) { 171 | symtabs_.reserve(symtab_count); 172 | if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { 173 | for (ElfW(Off) i = 0; i < symtab_count; i++) { 174 | unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); 175 | const char *st_name = offsetOf(header, symstr_offset_for_symtab + 176 | symtab_start[i].st_name); 177 | if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { 178 | symtabs_.emplace(st_name, &symtab_start[i]); 179 | } 180 | } 181 | } 182 | } 183 | if (auto i = symtabs_.find(name); i != symtabs_.end()) { 184 | return i->second->st_value; 185 | } else { 186 | return 0; 187 | } 188 | } 189 | 190 | 191 | ElfImg::~ElfImg() { 192 | //open elf file local 193 | if (buffer) { 194 | free(buffer); 195 | buffer = nullptr; 196 | } 197 | //use mmap 198 | if (header) { 199 | munmap(header, size); 200 | } 201 | } 202 | 203 | ElfW(Addr) 204 | ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const { 205 | if (auto offset = GnuLookup(name, gnu_hash); offset > 0) { 206 | LOGD("found %s %p in %s in dynsym by gnuhash", name.data(), 207 | reinterpret_cast(offset), elf.data()); 208 | return offset; 209 | } else if (offset = ElfLookup(name, elf_hash); offset > 0) { 210 | LOGD("found %s %p in %s in dynsym by elfhash", name.data(), 211 | reinterpret_cast(offset), elf.data()); 212 | return offset; 213 | } else if (offset = LinearLookup(name); offset > 0) { 214 | LOGD("found %s %p in %s in symtab by linear lookup", name.data(), 215 | reinterpret_cast(offset), elf.data()); 216 | return offset; 217 | } else { 218 | return 0; 219 | } 220 | 221 | } 222 | 223 | bool ElfImg::findModuleBase() { 224 | char buff[256]; 225 | off_t load_addr; 226 | bool found = false; 227 | FILE *maps = fopen("/proc/self/maps", "r"); 228 | 229 | 230 | while (fgets(buff, sizeof(buff), maps)) { 231 | if ((strstr(buff, "r-xp") || strstr(buff, "r--p")) && strstr(buff, elf.data())) { 232 | LOGD("found: %s", buff); 233 | std::string_view b = buff; 234 | if (auto begin = b.find_last_of(' '); begin != std::string_view::npos && b[++begin] == '/') { 235 | found = true; 236 | elf = b.substr(begin); 237 | if (elf.back() == '\n') elf.pop_back(); 238 | LOGD("update path: %s", elf.data()); 239 | break; 240 | } 241 | } 242 | } 243 | 244 | if (!found) { 245 | LOGE("failed to read load address for %s", elf.data()); 246 | fclose(maps); 247 | return false; 248 | } 249 | 250 | if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) { 251 | LOGE("failed to read load address for %s", elf.data()); 252 | } 253 | 254 | fclose(maps); 255 | 256 | LOGD("get module base %s: %lx", elf.data(), load_addr); 257 | 258 | base = reinterpret_cast(load_addr); 259 | return true; 260 | } 261 | -------------------------------------------------------------------------------- /riru/src/main/cpp/util/rirud.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "rirud.h" 13 | #include "module.h" 14 | #include "finally.h" 15 | 16 | bool RirudSocket::Write(std::string_view str) const { 17 | auto count = str.size(); 18 | const auto *buf = str.data(); 19 | return Write(str.size()) && Write(buf, count); 20 | } 21 | 22 | bool RirudSocket::Read(std::string &str) const { 23 | uint32_t size; 24 | if (!Read(size) || size < 0) return false; 25 | str.resize(size); 26 | return Read(str.data(), size); 27 | } 28 | 29 | 30 | RirudSocket::RirudSocket(unsigned retries) { 31 | if ((fd_ = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) < 0) { 32 | return; 33 | } 34 | 35 | struct sockaddr_un addr{ 36 | .sun_family = AF_UNIX, 37 | .sun_path={0} 38 | }; 39 | strncpy(addr.sun_path + 1, RIRUD.data(), RIRUD.size()); 40 | socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1; 41 | 42 | while (retries-- > 0) { 43 | if (connect(fd_, reinterpret_cast(&addr), socklen) != -1) return; 44 | LOGW("retrying to connect rirud in 1s"); 45 | sleep(1); 46 | } 47 | close(fd_); 48 | fd_ = -1; 49 | } 50 | 51 | RirudSocket::~RirudSocket() { 52 | if (fd_ != -1) { 53 | close(fd_); 54 | } 55 | } 56 | 57 | void RirudSocket::DirIter::ContinueRead() { 58 | int32_t reply; 59 | unsigned char type; 60 | if (!socket_.Write(continue_read) || !socket_.Read(reply) || reply != 0 || 61 | !socket_.Read(type) || !socket_.Read(path.data(), MAX_PATH_SIZE)) { 62 | path[0] = '\0'; 63 | } 64 | } 65 | 66 | std::string RirudSocket::ReadMagiskTmpfsPath() const { 67 | std::string result; 68 | if (Write(Action::READ_MAGISK_TMPFS_PATH)) { 69 | Read(result); 70 | } 71 | return result; 72 | } 73 | 74 | std::string RirudSocket::ReadNativeBridge() const { 75 | std::string result; 76 | if (Write(Action::READ_NATIVE_BRIDGE)) { 77 | Read(result); 78 | } 79 | return result; 80 | } 81 | 82 | std::string RirudSocket::ReadFile(std::string_view path) { 83 | Write(Action::READ_FILE); 84 | Write(path); 85 | int32_t rirud_errno; 86 | Read(rirud_errno); 87 | if (rirud_errno != 0) { 88 | return {}; 89 | } 90 | std::string content; 91 | Read(content); 92 | return content; 93 | } 94 | 95 | bool RirudSocket::Write(const void *buf, size_t len) const { 96 | auto count = len; 97 | while (count > 0) { 98 | ssize_t size = write(fd_, buf, count < SSIZE_MAX ? count : SSIZE_MAX); 99 | if (size == -1) { 100 | if (errno == EINTR) continue; 101 | else return false; 102 | } 103 | buf = static_cast(buf) + size; 104 | count -= size; 105 | } 106 | return true; 107 | } 108 | 109 | bool RirudSocket::Read(void *out, size_t len) const { 110 | while (len > 0) { 111 | ssize_t ret = read(fd_, out, len); 112 | if (ret <= 0) { 113 | if (errno == EINTR) continue; 114 | else return false; 115 | } 116 | out = static_cast(out) + ret; 117 | len -= ret; 118 | } 119 | return true; 120 | } 121 | -------------------------------------------------------------------------------- /rirud/.gitignore: -------------------------------------------------------------------------------- 1 | /.externalNativeBuild 2 | /build 3 | /release -------------------------------------------------------------------------------- /rirud/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | def versionNameShort = "v${riruApiVersion}.${versionNameMinor}.${versionNamePatch}" 6 | def versionCode = gitCommitCount 7 | 8 | def apiVersion = rootProject.ext.riruApiVersion 9 | 10 | android { 11 | compileSdkVersion rootProject.ext.targetSdkVersion 12 | buildToolsVersion rootProject.ext.buildToolsVersion 13 | defaultConfig { 14 | minSdkVersion rootProject.ext.minSdkVersion 15 | targetSdkVersion rootProject.ext.targetSdkVersion 16 | buildConfigField "int", "RIRU_API_VERSION", "$apiVersion" 17 | buildConfigField "int", "RIRU_VERSION_CODE", "$versionCode" 18 | buildConfigField "String", "RIRU_VERSION_NAME", "\"$versionNameShort\"" 19 | } 20 | compileOptions { 21 | sourceCompatibility JavaVersion.VERSION_11 22 | targetCompatibility JavaVersion.VERSION_11 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled true 27 | shrinkResources true 28 | proguardFiles 'proguard-rules.pro' 29 | } 30 | } 31 | dependenciesInfo.includeInApk false 32 | lint { 33 | checkReleaseBuilds false 34 | } 35 | } 36 | 37 | dependencies { 38 | implementation 'dev.rikka.rikkax.io:little-endian-data-stream:1.0.2' 39 | compileOnly project(':stub') 40 | } 41 | 42 | android.applicationVariants.all { variant -> 43 | variant.outputs.all { 44 | outputFileName = "rirud.apk" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /rirud/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class riru.Daemon { 2 | public static void main(java.lang.String[]); 3 | } 4 | -keep class riru.Installer { 5 | public static void main(java.lang.String[]); 6 | } 7 | -------------------------------------------------------------------------------- /rirud/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rirud/src/main/java/riru/Daemon.java: -------------------------------------------------------------------------------- 1 | package riru; 2 | 3 | import android.os.Handler; 4 | import android.os.IBinder; 5 | import android.os.Looper; 6 | import android.os.RemoteException; 7 | import android.os.SELinux; 8 | import android.os.SystemProperties; 9 | import android.util.Log; 10 | 11 | import java.io.File; 12 | 13 | import riru.rirud.R; 14 | 15 | public class Daemon implements IBinder.DeathRecipient { 16 | 17 | public static final String TAG = "RiruDaemon"; 18 | 19 | private static final String SERVICE_FOR_TEST = "activity"; 20 | private static final String RIRU_LOADER = "libriruloader.so"; 21 | 22 | private final Handler handler = new Handler(Looper.myLooper()); 23 | private static final DaemonSocketServerThread serverThread = new DaemonSocketServerThread(); 24 | 25 | private boolean allowRestart = true; 26 | 27 | private IBinder systemServerBinder; 28 | 29 | static { 30 | serverThread.start(); 31 | } 32 | 33 | public Daemon() { 34 | // prevent zygote died after system server starts but before onRiruLoaded called 35 | synchronized (serverThread) { 36 | handler.post(() -> startWait(true)); 37 | } 38 | } 39 | 40 | @Override 41 | public void binderDied() { 42 | systemServerBinder.unlinkToDeath(this, 0); 43 | systemServerBinder = null; 44 | 45 | DaemonUtils.clearLoadedProcess(); 46 | DaemonUtils.getLoadedModules().clear(); 47 | 48 | DaemonUtils.writeStatus(R.string.zygote_dead); 49 | 50 | Log.i(TAG, "Zygote is probably dead, restart rirud socket..."); 51 | 52 | Log.i(TAG, "Zygote is probably dead, reset native bridge to " + RIRU_LOADER + "..."); 53 | DaemonUtils.resetNativeBridgeProp(RIRU_LOADER); 54 | 55 | Log.i(TAG, "Zygote is probably dead, delete existing /dev/riru folders..."); 56 | DaemonUtils.deleteDevFolder(); 57 | 58 | synchronized (serverThread) { 59 | serverThread.restartServer(); 60 | handler.post(() -> startWait(false)); 61 | } 62 | } 63 | 64 | private void onRiruNotLoaded(boolean isFirst) { 65 | Log.w(TAG, "Riru is not loaded."); 66 | 67 | if (allowRestart) { 68 | allowRestart = false; 69 | handler.post(() -> { 70 | Log.w(TAG, "Restarting zygote..."); 71 | if (DaemonUtils.has64Bit() && DaemonUtils.has32Bit()) { 72 | // Only devices with both 32-bit and 64-bit support have zygote_secondary 73 | SystemProperties.set("ctl.restart", "zygote_secondary"); 74 | } else { 75 | SystemProperties.set("ctl.restart", "zygote"); 76 | } 77 | }); 78 | return; 79 | } else { 80 | Log.w(TAG, "Restarting zygote does not help"); 81 | } 82 | 83 | if (DaemonUtils.hasIncorrectFileContext()) { 84 | DaemonUtils.writeStatus(R.string.bad_file_context); 85 | return; 86 | } 87 | 88 | if ((DaemonUtils.has32Bit() && !new File("/proc/1/root/system/lib/libriruloader.so").exists()) || 89 | (DaemonUtils.has64Bit() && !new File("/proc/1/root/system/lib64/libriruloader.so").exists())) { 90 | DaemonUtils.writeStatus(R.string.files_not_mounted); 91 | return; 92 | } 93 | 94 | if (DaemonUtils.hasSELinux() && SELinux.isSELinuxEnabled() && SELinux.isSELinuxEnforced() 95 | && (SELinux.checkSELinuxAccess("u:r:init:s0", "u:object_r:system_file:s0", "file", "relabelfrom") 96 | || SELinux.checkSELinuxAccess("u:r:init:s0", "u:object_r:system_file:s0", "dir", "relabelfrom"))) { 97 | DaemonUtils.writeStatus(R.string.bad_selinux_rule); 98 | return; 99 | } 100 | 101 | if (!"libriruloader.so".equals(SystemProperties.get("ro.dalvik.vm.native.bridge"))) { 102 | DaemonUtils.writeStatus(R.string.bad_prop); 103 | return; 104 | } 105 | 106 | DaemonUtils.writeStatus(R.string.not_loaded); 107 | if (isFirst) { 108 | Log.w(TAG, "Magisk post-fs-data slow?"); 109 | } 110 | } 111 | 112 | private void onRiruLoad() { 113 | allowRestart = true; 114 | Log.i(TAG, "Riru loaded, reset native bridge to " + DaemonUtils.getOriginalNativeBridge() + "..."); 115 | DaemonUtils.resetNativeBridgeProp(DaemonUtils.getOriginalNativeBridge()); 116 | 117 | Log.i(TAG, "Riru loaded, stop rirud socket..."); 118 | serverThread.stopServer(); 119 | 120 | var loadedModules = DaemonUtils.getLoadedModules().toArray(); 121 | 122 | StringBuilder sb = new StringBuilder(); 123 | if (loadedModules.length == 0) { 124 | sb.append(DaemonUtils.res.getString(R.string.empty)); 125 | } else { 126 | sb.append(loadedModules[0]); 127 | for (int i = 1; i < loadedModules.length; ++i) { 128 | sb.append(", "); 129 | sb.append(loadedModules[i]); 130 | } 131 | } 132 | 133 | if (DaemonUtils.hasIncorrectFileContext()) { 134 | DaemonUtils.writeStatus(R.string.bad_file_context_loaded, loadedModules.length, sb); 135 | } else { 136 | DaemonUtils.writeStatus(R.string.loaded, loadedModules.length, sb); 137 | } 138 | } 139 | 140 | private void startWait(boolean isFirst) { 141 | systemServerBinder = DaemonUtils.waitForSystemService(SERVICE_FOR_TEST); 142 | 143 | DaemonUtils.reloadLocale(); 144 | 145 | try { 146 | systemServerBinder.linkToDeath(this, 0); 147 | } catch (RemoteException e) { 148 | Log.w(TAG, "linkToDeath", e); 149 | } 150 | 151 | synchronized (serverThread) { 152 | if (!DaemonUtils.isLoaded()) { 153 | onRiruNotLoaded(isFirst); 154 | } else { 155 | onRiruLoad(); 156 | } 157 | } 158 | } 159 | 160 | public static void main(String[] args) { 161 | DaemonUtils.init(args); 162 | DaemonUtils.writeStatus(R.string.app_process); 163 | int magiskVersionCode = DaemonUtils.getMagiskVersionCode(); 164 | String magiskTmpfsPath = DaemonUtils.getMagiskTmpfsPath(); 165 | 166 | Log.i(TAG, "Magisk version is " + magiskVersionCode); 167 | Log.i(TAG, "Magisk tmpfs path is " + magiskTmpfsPath); 168 | Log.i(TAG, "Original native bridge is " + DaemonUtils.getOriginalNativeBridge()); 169 | Log.i(TAG, "Dev random is " + DaemonUtils.getDevRandom()); 170 | 171 | Looper.prepare(); 172 | new Daemon(); 173 | Looper.loop(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /rirud/src/main/java/riru/DaemonSocketServerThread.java: -------------------------------------------------------------------------------- 1 | package riru; 2 | 3 | import static android.system.OsConstants.EINVAL; 4 | import static riru.Daemon.TAG; 5 | 6 | import android.net.Credentials; 7 | import android.net.LocalServerSocket; 8 | import android.net.LocalSocket; 9 | import android.os.SELinux; 10 | import android.system.ErrnoException; 11 | import android.system.Os; 12 | import android.system.OsConstants; 13 | import android.util.Log; 14 | import android.util.Pair; 15 | 16 | import java.io.EOFException; 17 | import java.io.File; 18 | import java.io.FileDescriptor; 19 | import java.io.FileInputStream; 20 | import java.io.FileOutputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.net.SocketException; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Arrays; 26 | import java.util.Collections; 27 | import java.util.HashSet; 28 | import java.util.List; 29 | import java.util.Locale; 30 | import java.util.Map; 31 | import java.util.Objects; 32 | import java.util.Set; 33 | import java.util.concurrent.ConcurrentHashMap; 34 | import java.util.concurrent.CountDownLatch; 35 | import java.util.concurrent.Executor; 36 | import java.util.concurrent.Executors; 37 | 38 | import rikka.io.LittleEndianDataInputStream; 39 | import rikka.io.LittleEndianDataOutputStream; 40 | import riru.rirud.BuildConfig; 41 | 42 | public class DaemonSocketServerThread extends Thread { 43 | 44 | private static final int ACTION_READ_FILE = 4; 45 | private static final int ACTION_READ_DIR = 5; 46 | 47 | // used by riru itself only, could be removed in the future 48 | private static final int ACTION_WRITE_STATUS = 2; 49 | private static final int ACTION_READ_NATIVE_BRIDGE = 3; 50 | private static final int ACTION_READ_MAGISK_TMPFS_PATH = 6; 51 | private static final int ACTION_READ_MODULES = 7; 52 | 53 | private static final HashSet privateActions = new HashSet<>(Arrays.asList(ACTION_WRITE_STATUS, ACTION_READ_NATIVE_BRIDGE, ACTION_READ_MAGISK_TMPFS_PATH, ACTION_READ_MODULES)); 54 | 55 | private static final Executor EXECUTOR = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() / 4 + 1); 56 | 57 | private LocalServerSocket serverSocket; 58 | private CountDownLatch countDownLatch; 59 | 60 | private final Set zygotePid = Collections.newSetFromMap(new ConcurrentHashMap<>()); 61 | 62 | private void handleReadOriginalNativeBridge(LittleEndianDataOutputStream out) throws IOException { 63 | String s = DaemonUtils.getOriginalNativeBridge(); 64 | out.writeInt(s.length()); 65 | out.write(s.getBytes()); 66 | } 67 | 68 | private void handleMagiskTmpfsPath(LittleEndianDataOutputStream out) throws IOException { 69 | String s = DaemonUtils.getMagiskTmpfsPath(); 70 | out.writeInt(s.length()); 71 | out.write(s.getBytes()); 72 | } 73 | 74 | private void writeToFile(File parent, String name, String content) { 75 | if (!parent.exists()) { 76 | //noinspection ResultOfMethodCallIgnored 77 | parent.mkdirs(); 78 | } 79 | File file = new File(parent, name); 80 | 81 | try (FileOutputStream out = new FileOutputStream(file)) { 82 | out.write(content.getBytes()); 83 | } catch (IOException ignored) { 84 | } 85 | } 86 | 87 | private void handleWriteStatus(LittleEndianDataInputStream in, Credentials credentials) throws IOException { 88 | boolean is64Bit = in.readBoolean(); 89 | int count = in.readInt(); 90 | 91 | DaemonUtils.recordLoadedProcess(credentials.getPid()); 92 | 93 | File parent = new File("/dev/riru" + (is64Bit ? "64" : "") + "_" + DaemonUtils.getDevRandom()); 94 | File modules = new File(parent, "modules"); 95 | 96 | writeToFile(parent, "api", Integer.toString(BuildConfig.RIRU_API_VERSION)); 97 | writeToFile(parent, "version", Integer.toString(BuildConfig.RIRU_VERSION_CODE)); 98 | writeToFile(parent, "version_name", BuildConfig.RIRU_VERSION_NAME); 99 | 100 | byte[] buffer = new byte[256]; 101 | int size; 102 | String id; 103 | int apiVersion; 104 | int version; 105 | String versionName; 106 | boolean supportHide; 107 | 108 | for (int i = 0; i < count; i++) { 109 | size = in.readInt(); 110 | in.readFully(buffer, 0, size); 111 | id = new String(buffer, 0, size); 112 | Arrays.fill(buffer, (byte) 0); 113 | 114 | apiVersion = in.readInt(); 115 | version = in.readInt(); 116 | 117 | size = in.readInt(); 118 | in.readFully(buffer, 0, size); 119 | versionName = new String(buffer, 0, size); 120 | Arrays.fill(buffer, (byte) 0); 121 | 122 | supportHide = in.readBoolean(); 123 | 124 | File module = new File(modules, id); 125 | 126 | writeToFile(module, "hide", Boolean.toString(supportHide)); 127 | writeToFile(module, "api", Integer.toString(apiVersion)); 128 | writeToFile(module, "version", Integer.toString(version)); 129 | writeToFile(module, "version_name", versionName); 130 | 131 | int index = id.indexOf('@'); 132 | if (index != -1 && index < id.length()) { 133 | DaemonUtils.getLoadedModules().add(id.substring(index + 1)); 134 | } 135 | } 136 | } 137 | 138 | private void handleReadFile(LittleEndianDataInputStream in, LittleEndianDataOutputStream out) throws IOException { 139 | int path_size = in.readInt(); 140 | byte[] path_bytes = new byte[path_size]; 141 | in.readFully(path_bytes); 142 | String path = new String(path_bytes); 143 | 144 | Log.i(TAG, "Read file " + path); 145 | 146 | FileDescriptor fd; 147 | try { 148 | fd = Os.open(path, OsConstants.O_RDONLY, 0); 149 | out.writeInt(0); 150 | } catch (ErrnoException e) { 151 | Log.w(TAG, "Open file " + path + " failed with " + e.errno, e); 152 | out.writeInt(e.errno); 153 | return; 154 | } 155 | 156 | int file_size; 157 | try { 158 | file_size = (int) Os.fstat(fd).st_size; 159 | } catch (ErrnoException e) { 160 | file_size = -1; 161 | } 162 | out.writeInt(file_size); 163 | 164 | byte[] buffer = new byte[8192]; 165 | try (InputStream fin = new FileInputStream(fd)) { 166 | if (file_size > 0) { 167 | int bytes_remaining = file_size; 168 | 169 | do { 170 | int count = fin.read(buffer, 0, 8192); 171 | if (count == -1) { 172 | return; 173 | } else { 174 | out.write(buffer, 0, count); 175 | bytes_remaining -= count; 176 | } 177 | } while (bytes_remaining > 0); 178 | } else if (file_size == -1) { 179 | int count; 180 | while ((count = fin.read(buffer, 0, 8192)) > 0) { 181 | out.write(buffer, 0, count); 182 | } 183 | } 184 | } 185 | } 186 | 187 | static void writeString(LittleEndianDataOutputStream out, String s) throws IOException { 188 | out.writeInt(s.length()); 189 | out.writeBytes(s); 190 | } 191 | 192 | private void handleReadDir(LittleEndianDataInputStream in, LittleEndianDataOutputStream out) throws IOException { 193 | int path_size = in.readInt(); 194 | byte[] path_bytes = new byte[path_size]; 195 | in.readFully(path_bytes); 196 | String path = new String(path_bytes); 197 | 198 | Log.i(TAG, "Read dir " + path); 199 | 200 | FileDescriptor fd; 201 | try { 202 | fd = Os.open(path, OsConstants.O_RDONLY | 0x00200000 /*O_DIRECTORY*/, 0); 203 | out.writeInt(0); 204 | } catch (ErrnoException e) { 205 | Log.w(TAG, "Open file " + path + " failed with " + e.errno, e); 206 | out.writeInt(e.errno); 207 | return; 208 | } 209 | try { 210 | Os.close(fd); 211 | } catch (ErrnoException ignored) { 212 | } 213 | 214 | File[] files = new File(path).listFiles(); 215 | int index = 0; 216 | int size = files != null ? files.length : -1; 217 | while (true) { 218 | boolean continue_read = in.readBoolean(); 219 | if (!continue_read) { 220 | break; 221 | } 222 | if (files == null || index >= size) { 223 | out.writeInt(-1); 224 | break; 225 | } else { 226 | out.writeInt(0); 227 | } 228 | 229 | File f = files[index]; 230 | index++; 231 | 232 | boolean isFile = f.isFile(); 233 | boolean isDir = f.isDirectory(); 234 | if (isFile) 235 | out.writeByte(/*DT_REG*/ 8); 236 | else if (isDir) 237 | out.writeByte(/*DT_DIR*/ 4); 238 | else 239 | out.writeByte(/*DT_UNKNOWN*/0); 240 | 241 | byte[] name = Arrays.copyOf(f.getName().getBytes(StandardCharsets.UTF_8), 256); 242 | name[255] = 0; // null-terminated 243 | out.write(name); 244 | } 245 | } 246 | 247 | private void handleReadModules(LittleEndianDataInputStream in, LittleEndianDataOutputStream out) throws IOException { 248 | boolean is64 = in.readBoolean(); 249 | Map>> modules = DaemonUtils.getModules(is64); 250 | 251 | out.writeInt(modules.size()); 252 | 253 | for (Map.Entry>> entry : modules.entrySet()) { 254 | String magiskModulePath = entry.getKey(); 255 | List> libs = entry.getValue(); 256 | 257 | writeString(out, magiskModulePath); 258 | out.writeInt(libs.size()); 259 | 260 | for (Pair pair : libs) { 261 | String id = pair.first; 262 | String lib = pair.second; 263 | 264 | writeString(out, id); 265 | writeString(out, lib); 266 | } 267 | } 268 | } 269 | 270 | private void handleAction(LittleEndianDataInputStream in, LittleEndianDataOutputStream out, int action, Credentials credentials) throws IOException { 271 | Log.i(TAG, "Action " + action); 272 | 273 | switch (action) { 274 | case ACTION_READ_NATIVE_BRIDGE: { 275 | Log.i(TAG, "Action: read original native bridge"); 276 | handleReadOriginalNativeBridge(out); 277 | break; 278 | } 279 | case ACTION_READ_MAGISK_TMPFS_PATH: { 280 | Log.i(TAG, "Action: read Magisk tmpfs path"); 281 | handleMagiskTmpfsPath(out); 282 | break; 283 | } 284 | case ACTION_WRITE_STATUS: { 285 | Log.i(TAG, "Action: write status"); 286 | handleWriteStatus(in, credentials); 287 | break; 288 | } 289 | case ACTION_READ_FILE: { 290 | Log.i(TAG, "Action: read file"); 291 | handleReadFile(in, out); 292 | break; 293 | } 294 | case ACTION_READ_DIR: { 295 | Log.i(TAG, "Action: read dir"); 296 | handleReadDir(in, out); 297 | break; 298 | } 299 | case ACTION_READ_MODULES: { 300 | Log.i(TAG, "Action: read modules"); 301 | handleReadModules(in, out); 302 | break; 303 | } 304 | default: 305 | Log.w(TAG, "unknown action"); 306 | break; 307 | } 308 | 309 | Log.i(TAG, "Handle action " + action + " finished"); 310 | } 311 | 312 | private void handleSocket(LocalSocket socket, boolean privateConnection) throws IOException { 313 | int action; 314 | boolean first = true; 315 | var credentials = socket.getPeerCredentials(); 316 | 317 | try (LittleEndianDataInputStream in = new LittleEndianDataInputStream(socket.getInputStream()); 318 | LittleEndianDataOutputStream out = new LittleEndianDataOutputStream(socket.getOutputStream())) { 319 | 320 | while (true) { 321 | try { 322 | action = in.readInt(); 323 | } catch (EOFException e) { 324 | if (first) { 325 | Log.e(TAG, "Failed to read next action", e); 326 | } else { 327 | Log.i(TAG, "No next action, exiting..."); 328 | } 329 | return; 330 | } 331 | 332 | if (!privateConnection && privateActions.contains(action)) { 333 | Log.e(TAG, "Unauthorized connection using private action"); 334 | return; 335 | } 336 | handleAction(in, out, action, credentials); 337 | first = false; 338 | } 339 | } 340 | } 341 | 342 | private void startServer() throws IOException { 343 | zygotePid.clear(); 344 | serverSocket = new LocalServerSocket("rirud"); 345 | 346 | while (true) { 347 | Log.d(TAG, "Accept"); 348 | LocalSocket socket; 349 | try { 350 | socket = serverSocket.accept(); 351 | } catch (IOException e) { 352 | if ((e.getCause() != null && e.getCause() instanceof ErrnoException && ((ErrnoException) e.getCause()).errno == EINVAL) || 353 | e instanceof SocketException || 354 | (e.getMessage() != null && (e.getMessage().contains("EINVAL") || e.getMessage().contains(String.format(Locale.ROOT, "errno %d", EINVAL))))) { 355 | Log.i(TAG, "Server shutdown."); 356 | return; 357 | } 358 | Log.w(TAG, "Accept failed, server is closed ?", e); 359 | return; 360 | } 361 | 362 | Credentials credentials = socket.getPeerCredentials(); 363 | var uid = credentials.getUid(); 364 | var pid = credentials.getPid(); 365 | var context = SELinux.getPidContext(pid); 366 | if (uid != 0 || !Objects.equals(context, "u:r:zygote:s0")) { 367 | socket.close(); 368 | Log.w(TAG, "Unauthorized peer (" + 369 | "uid=" + uid + ", " + 370 | "pid=" + pid + ", " + 371 | "context=" + context + ")"); 372 | continue; 373 | } 374 | Log.d(TAG, "Accepted " + 375 | "uid=" + uid + ", " + 376 | "pid=" + pid + ", " + 377 | "context=" + context); 378 | 379 | EXECUTOR.execute(() -> { 380 | try (socket) { 381 | handleSocket(socket, zygotePid.add(pid)); 382 | } catch (Throwable e) { 383 | Log.w(TAG, "Handle socket", e); 384 | } 385 | }); 386 | } 387 | } 388 | 389 | public void stopServer() { 390 | Log.i(TAG, "Stop server"); 391 | 392 | if (serverSocket != null) { 393 | try { 394 | Os.shutdown(serverSocket.getFileDescriptor(), OsConstants.SHUT_RD); 395 | serverSocket.close(); 396 | Log.i(TAG, "Server stopped"); 397 | } catch (IOException | ErrnoException e) { 398 | Log.w(TAG, "Close server socket", e); 399 | } 400 | serverSocket = null; 401 | } else { 402 | Log.w(TAG, "Server socket is null while stopping server"); 403 | } 404 | } 405 | 406 | public void restartServer() { 407 | Log.i(TAG, "Restart server"); 408 | 409 | if (serverSocket != null) { 410 | Log.w(TAG, "Socket is not null while restarting server"); 411 | return; 412 | } 413 | 414 | if (countDownLatch != null) { 415 | zygotePid.clear(); 416 | countDownLatch.countDown(); 417 | } 418 | } 419 | 420 | @Override 421 | public void run() { 422 | if (DaemonUtils.hasSELinux()) { 423 | if (DaemonUtils.setSocketCreateContext("u:r:zygote:s0")) { 424 | Log.i(TAG, "Set socket context to u:r:zygote:s0"); 425 | } else { 426 | Log.w(TAG, "Failed to set socket context"); 427 | } 428 | } 429 | 430 | //noinspection InfiniteLoopStatement 431 | while (true) { 432 | Log.d(TAG, "Start server"); 433 | 434 | try { 435 | startServer(); 436 | } catch (Throwable e) { 437 | Log.w(TAG, "Start server", e); 438 | } 439 | 440 | countDownLatch = new CountDownLatch(1); 441 | Log.i(TAG, "Waiting for restart server..."); 442 | 443 | try { 444 | countDownLatch.await(); 445 | Log.i(TAG, "Restart server received"); 446 | } catch (InterruptedException e) { 447 | Log.w(TAG, Log.getStackTraceString(e)); 448 | } 449 | } 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /rirud/src/main/java/riru/DaemonUtils.java: -------------------------------------------------------------------------------- 1 | package riru; 2 | 3 | import static riru.Daemon.TAG; 4 | 5 | import android.content.res.AssetManager; 6 | import android.content.res.Configuration; 7 | import android.content.res.Resources; 8 | import android.os.Build; 9 | import android.os.IBinder; 10 | import android.os.SELinux; 11 | import android.os.ServiceManager; 12 | import android.os.SystemProperties; 13 | import android.system.ErrnoException; 14 | import android.system.Os; 15 | import android.system.OsConstants; 16 | import android.text.TextUtils; 17 | import android.util.Log; 18 | import android.util.Pair; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.DataOutputStream; 22 | import java.io.File; 23 | import java.io.FileDescriptor; 24 | import java.io.FileInputStream; 25 | import java.io.FileOutputStream; 26 | import java.io.FileReader; 27 | import java.io.IOException; 28 | import java.io.InputStreamReader; 29 | import java.io.InterruptedIOException; 30 | import java.lang.reflect.Field; 31 | import java.lang.reflect.Method; 32 | import java.security.SecureRandom; 33 | import java.util.ArrayList; 34 | import java.util.Collections; 35 | import java.util.List; 36 | import java.util.Locale; 37 | import java.util.Map; 38 | import java.util.Objects; 39 | import java.util.Set; 40 | import java.util.concurrent.ConcurrentHashMap; 41 | import java.util.concurrent.Executors; 42 | import java.util.concurrent.FutureTask; 43 | 44 | public class DaemonUtils { 45 | 46 | private static Boolean has32Bit = null, has64Bit = null; 47 | private static String originalNativeBridge; 48 | private static String devRandom; 49 | private static int magiskVersionCode = -1; 50 | private static String magiskTmpfsPath; 51 | 52 | public static Resources res; 53 | 54 | private static int lastStatusId = 0; 55 | private static Object[] lastStatusArgs = new Object[0]; 56 | 57 | private static final String LIB_PREFIX = "lib"; 58 | private static final String RIRU_PREFIX = "libriru_"; 59 | private static final String SO_SUFFIX = ".so"; 60 | 61 | private static final FutureTask>>> modules = new FutureTask<>(() -> collectModules(false)); 62 | private static final FutureTask>>> modules64 = new FutureTask<>(() -> collectModules(true)); 63 | 64 | private static final Set loadedModules = Collections.newSetFromMap(new ConcurrentHashMap<>()); 65 | 66 | private static boolean isSELinuxEnforcing = false; 67 | private static boolean fileContext = true; 68 | 69 | private static final Set zygotePid = Collections.newSetFromMap(new ConcurrentHashMap<>()); 70 | 71 | public static void init(String[] args) { 72 | magiskVersionCode = Integer.parseInt(args[0]); 73 | magiskTmpfsPath = args[1]; 74 | if (args.length > 2) { 75 | originalNativeBridge = args[2]; 76 | } else { 77 | originalNativeBridge = "0"; 78 | } 79 | try { 80 | isSELinuxEnforcing = hasSELinux() && SELinux.isSELinuxEnabled() && SELinux.isSELinuxEnforced(); 81 | } catch (Throwable e) { 82 | Log.e(TAG, "read is enforcing", e); 83 | } 84 | 85 | try { 86 | AssetManager am = AssetManager.class.newInstance(); 87 | Method addAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); 88 | addAssetPath.setAccessible(true); 89 | // TODO: may use classpath 90 | addAssetPath.invoke(am, new File("rirud.apk").getAbsolutePath()); 91 | res = new Resources(am, null, null); 92 | } catch (Throwable e) { 93 | Log.e(TAG, "load res", e); 94 | } 95 | 96 | var service = Executors.newFixedThreadPool(2); 97 | 98 | if (has64Bit()) { 99 | service.submit(modules64); 100 | } 101 | 102 | if (has32Bit()) { 103 | service.submit(modules); 104 | } 105 | 106 | File magiskDir = new File(DaemonUtils.getMagiskTmpfsPath(), ".magisk/modules/riru-core"); 107 | 108 | if (has64Bit()) { 109 | fileContext &= checkOrResetContextForChildren(new File(magiskDir, "lib64")); 110 | fileContext &= checkOrResetContextForForParent(new File(magiskDir, "lib64"), magiskDir); 111 | fileContext &= checkOrResetContextForChildren(new File(magiskDir, "system/lib64")); 112 | fileContext &= checkOrResetContextForForParent(new File(magiskDir, "system/lib64"), magiskDir); 113 | } 114 | 115 | if (has32Bit()) { 116 | fileContext &= checkOrResetContextForChildren(new File(magiskDir, "lib")); 117 | fileContext &= checkOrResetContextForForParent(new File(magiskDir, "lib"), magiskDir); 118 | fileContext &= checkOrResetContextForChildren(new File(magiskDir, "system/lib")); 119 | fileContext &= checkOrResetContextForForParent(new File(magiskDir, "system/lib"), magiskDir); 120 | } 121 | } 122 | 123 | public static boolean isLoaded() { 124 | var processes = new File("/proc").listFiles((file, s) -> TextUtils.isDigitsOnly(s)); 125 | if (processes == null) { 126 | Log.w(TAG, "Could not list all processes"); 127 | return false; 128 | } 129 | for (var process : processes) { 130 | var pid = Integer.parseInt(process.getName()); 131 | if (Objects.equals(SELinux.getPidContext(pid), "u:r:zygote:s0") && !zygotePid.contains(pid)) { 132 | Log.w(TAG, "Process " + pid + " has zygote context but did not load riru"); 133 | return false; 134 | } 135 | } 136 | return true; 137 | } 138 | 139 | public static void clearServiceManagerCache() { 140 | try { 141 | //noinspection JavaReflectionMemberAccess 142 | Field field = ServiceManager.class.getDeclaredField("sServiceManager"); 143 | field.setAccessible(true); 144 | field.set(null, null); 145 | 146 | //noinspection JavaReflectionMemberAccess 147 | field = ServiceManager.class.getDeclaredField("sCache"); 148 | field.setAccessible(true); 149 | Object sCache = field.get(null); 150 | if (sCache instanceof Map) { 151 | //noinspection rawtypes 152 | ((Map) sCache).clear(); 153 | } 154 | Log.i(TAG, "clear ServiceManager"); 155 | } catch (Throwable e) { 156 | Log.w(TAG, "clear ServiceManager: " + Log.getStackTraceString(e)); 157 | } 158 | } 159 | 160 | public static void clearLoadedProcess() { 161 | zygotePid.clear(); 162 | } 163 | 164 | public static void recordLoadedProcess(int pid) { 165 | zygotePid.add(pid); 166 | } 167 | 168 | public static Set getLoadedModules() { 169 | return loadedModules; 170 | } 171 | 172 | // from AndroidRuntime.cpp 173 | private static String readLocale() { 174 | String locale = SystemProperties.get("persist.sys.locale", ""); 175 | if (!locale.isEmpty()) { 176 | return locale; 177 | } 178 | 179 | String language = SystemProperties.get("persist.sys.language", ""); 180 | if (!language.isEmpty()) { 181 | String country = SystemProperties.get("persist.sys.country", ""); 182 | String variant = SystemProperties.get("persist.sys.localevar", ""); 183 | 184 | String out = language; 185 | if (!country.isEmpty()) { 186 | out = out + "-" + country; 187 | } 188 | 189 | if (!variant.isEmpty()) { 190 | out = out + "-" + variant; 191 | } 192 | 193 | return out; 194 | } 195 | 196 | String productLocale = SystemProperties.get("ro.product.locale", ""); 197 | if (!productLocale.isEmpty()) { 198 | return productLocale; 199 | } 200 | 201 | // If persist.sys.locale and ro.product.locale are missing, 202 | // construct a locale value from the individual locale components. 203 | String productLanguage = SystemProperties.get("ro.product.locale.language", "en"); 204 | String productRegion = SystemProperties.get("ro.product.locale.region", "US"); 205 | 206 | return productLanguage + "-" + productRegion; 207 | } 208 | 209 | public static void reloadLocale() { 210 | Locale locale = Locale.forLanguageTag(readLocale()); 211 | Locale.setDefault(locale); 212 | Configuration conf = res.getConfiguration(); 213 | conf.setLocale(Locale.forLanguageTag(readLocale())); 214 | res.updateConfiguration(conf, res.getDisplayMetrics()); 215 | writeStatus(lastStatusId, lastStatusArgs); 216 | } 217 | 218 | public static void writeStatus(int id, Object... args) { 219 | File prop = new File("module.prop"); 220 | lastStatusId = id; 221 | lastStatusArgs = args; 222 | try (BufferedReader br = new BufferedReader(new FileReader(prop))) { 223 | StringBuilder sb = new StringBuilder(); 224 | String line = br.readLine(); 225 | while (line != null) { 226 | sb.append(line.replaceFirst("^description=(\\[.*]\\s*)?", "description=[ " + res.getString(id, args) + " ] ")); 227 | sb.append(System.lineSeparator()); 228 | line = br.readLine(); 229 | } 230 | String content = sb.toString(); 231 | try (DataOutputStream os = new DataOutputStream(new FileOutputStream(prop, false))) { 232 | os.write(content.getBytes()); 233 | } 234 | } catch (Throwable e) { 235 | Log.e(TAG, "fail to write status", e); 236 | } 237 | } 238 | 239 | public static boolean has32Bit() { 240 | if (has32Bit == null) { 241 | has32Bit = Build.SUPPORTED_32_BIT_ABIS.length > 0; 242 | } 243 | return has32Bit; 244 | } 245 | 246 | public static boolean has64Bit() { 247 | if (has64Bit == null) { 248 | has64Bit = Build.SUPPORTED_64_BIT_ABIS.length > 0; 249 | } 250 | return has64Bit; 251 | } 252 | 253 | public static String getOriginalNativeBridge() { 254 | return originalNativeBridge; 255 | } 256 | 257 | public static void resetNativeBridgeProp(String value) { 258 | resetProperty("ro.dalvik.vm.native.bridge", value); 259 | } 260 | 261 | public static void resetProperty(String key, String value) { 262 | exec("resetprop", key, value); 263 | } 264 | 265 | public static void exec(String... command) { 266 | ProcessBuilder pb = new ProcessBuilder(command); 267 | try { 268 | Process process = pb.start(); 269 | int code = process.waitFor(); 270 | Log.i(TAG, "Exec " + command[0] + " exited with " + code); 271 | } catch (Throwable e) { 272 | Log.w(TAG, "Exec " + command[0], e); 273 | } 274 | } 275 | 276 | public static IBinder waitForSystemService(String name) { 277 | clearServiceManagerCache(); 278 | 279 | IBinder binder = null; 280 | do { 281 | try { 282 | binder = ServiceManager.getService(name); 283 | } catch (Throwable ignored) { 284 | 285 | } 286 | if (binder != null && binder.pingBinder()) { 287 | return binder; 288 | } 289 | 290 | Log.i(TAG, "Service " + name + " not found, wait 1s..."); 291 | try { 292 | //noinspection BusyWait 293 | Thread.sleep(1000); 294 | } catch (InterruptedException ignored) { 295 | } 296 | } while (true); 297 | } 298 | 299 | private static boolean deleteDir(File file) { 300 | boolean res = true; 301 | File[] files = file.listFiles(); 302 | if (files != null) { 303 | for (File f : files) { 304 | res &= deleteDir(f); 305 | } 306 | } 307 | return res & file.delete(); 308 | } 309 | 310 | public static void deleteDevFolder() { 311 | String devRandom = getDevRandom(); 312 | if (devRandom == null) { 313 | return; 314 | } 315 | 316 | File file; 317 | 318 | file = new File("/dev/riru_" + devRandom); 319 | Log.i(TAG, "Attempt to delete " + file + "..."); 320 | if (!deleteDir(file)) { 321 | file.renameTo(new File("/dev/riru_" + devRandom + "_" + System.currentTimeMillis())); 322 | } else { 323 | Log.i(TAG, "Deleted " + file); 324 | } 325 | 326 | file = new File("/dev/riru64_" + devRandom); 327 | Log.i(TAG, "Attempt to delete " + file + "..."); 328 | if (!deleteDir(file)) { 329 | file.renameTo(new File("/dev/riru_" + devRandom + "_" + System.currentTimeMillis())); 330 | } else { 331 | Log.i(TAG, "Deleted " + file + "."); 332 | } 333 | } 334 | 335 | 336 | public static int getMagiskVersionCode() { 337 | if (magiskVersionCode != -1) { 338 | return magiskVersionCode; 339 | } 340 | 341 | try { 342 | ProcessBuilder ps = new ProcessBuilder("magisk", "-V"); 343 | ps.redirectErrorStream(true); 344 | Process pr = ps.start(); 345 | 346 | BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); 347 | String line = in.readLine(); 348 | Log.i(TAG, "Exec magisk -V: " + line); 349 | magiskVersionCode = Integer.parseInt(line); 350 | pr.waitFor(); 351 | in.close(); 352 | return magiskVersionCode; 353 | } catch (Throwable e) { 354 | Log.w(TAG, "Exec magisk -V", e); 355 | return -1; 356 | } 357 | } 358 | 359 | public static String getMagiskTmpfsPath() { 360 | if (magiskTmpfsPath != null) { 361 | return magiskTmpfsPath; 362 | } 363 | 364 | if (getMagiskVersionCode() < 21000) { 365 | return "/sbin"; 366 | } 367 | 368 | try { 369 | ProcessBuilder ps = new ProcessBuilder("magisk", "--path"); 370 | ps.redirectErrorStream(true); 371 | Process pr = ps.start(); 372 | 373 | BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream())); 374 | magiskTmpfsPath = in.readLine(); 375 | Log.i(TAG, "Exec magisk --path: " + magiskTmpfsPath); 376 | pr.waitFor(); 377 | in.close(); 378 | return magiskTmpfsPath; 379 | } catch (Throwable e) { 380 | Log.w(TAG, "Exec magisk --path", e); 381 | return ""; 382 | } 383 | } 384 | 385 | public static boolean hasSELinux() { 386 | return new File("/system/lib/libselinux.so").exists() 387 | || new File("/system/lib64/libselinux.so").exists(); 388 | } 389 | 390 | public static boolean setSocketCreateContext(String context) { 391 | FileDescriptor fd = null; 392 | try { 393 | fd = Os.open("/proc/thread-self/attr/sockcreate", OsConstants.O_RDWR, 0); 394 | } catch (ErrnoException e) { 395 | if (e.errno == OsConstants.ENOENT) { 396 | int tid = Os.gettid(); 397 | try { 398 | fd = Os.open(String.format(Locale.ENGLISH, "/proc/self/task/%d/attr/sockcreate", tid), OsConstants.O_RDWR, 0); 399 | } catch (ErrnoException ignored) { 400 | } 401 | } 402 | } 403 | 404 | if (fd == null) { 405 | return false; 406 | } 407 | 408 | byte[] bytes; 409 | int length; 410 | int remaining; 411 | if (!TextUtils.isEmpty(context)) { 412 | byte[] stringBytes = context.getBytes(); 413 | bytes = new byte[stringBytes.length + 1]; 414 | System.arraycopy(stringBytes, 0, bytes, 0, stringBytes.length); 415 | bytes[stringBytes.length] = '\0'; 416 | 417 | length = bytes.length; 418 | remaining = bytes.length; 419 | } else { 420 | bytes = null; 421 | length = 0; 422 | remaining = 0; 423 | } 424 | 425 | do { 426 | try { 427 | remaining -= Os.write(fd, bytes, length - remaining, remaining); 428 | if (remaining <= 0) { 429 | break; 430 | } 431 | } catch (ErrnoException e) { 432 | break; 433 | } catch (InterruptedIOException e) { 434 | remaining -= e.bytesTransferred; 435 | } 436 | } while (true); 437 | 438 | try { 439 | Os.close(fd); 440 | } catch (ErrnoException e) { 441 | Log.w(TAG, Log.getStackTraceString(e)); 442 | } 443 | return true; 444 | } 445 | 446 | public static String getDevRandom() { 447 | if (devRandom != null) { 448 | return devRandom; 449 | } 450 | 451 | File dir = new File("/data/adb/riru"); 452 | if (!dir.exists()) { 453 | //noinspection ResultOfMethodCallIgnored 454 | dir.mkdir(); 455 | } 456 | 457 | File file = new File(dir, "dev_random"); 458 | 459 | if (file.exists()) { 460 | try (FileInputStream in = new FileInputStream(file)) { 461 | byte[] buffer = new byte[8192]; 462 | if (in.read(buffer, 0, 8192) > 0) { 463 | devRandom = new String(buffer).trim(); 464 | Log.i(TAG, "Read dev random " + devRandom); 465 | return devRandom; 466 | } 467 | } catch (IOException e) { 468 | Log.w(TAG, "Read dev random", e); 469 | } 470 | } 471 | 472 | String charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 473 | SecureRandom rnd = new SecureRandom(); 474 | StringBuilder sb = new StringBuilder(7); 475 | for (int i = 0; i < 7; i++) { 476 | sb.append(charset.charAt(rnd.nextInt(charset.length()))); 477 | } 478 | 479 | devRandom = sb.toString(); 480 | Log.i(TAG, "Generated dev random " + devRandom); 481 | 482 | try (FileOutputStream out = new FileOutputStream(file)) { 483 | out.write(devRandom.getBytes()); 484 | } catch (IOException e) { 485 | Log.w(TAG, "Write dev random", e); 486 | } 487 | 488 | return devRandom; 489 | } 490 | 491 | private static boolean checkAndResetContextForFile(File file) { 492 | if (!isSELinuxEnforcing) return true; 493 | 494 | String path = file.getAbsolutePath(); 495 | try { 496 | String context = SELinux.getFileContext(path); 497 | if (!Objects.equals("u:object_r:system_file:s0", context)) { 498 | Log.w(TAG, "Context for " + path + " is " + context + " rather than u:object_r:system_file:s0"); 499 | 500 | if (SELinux.setFileContext(path, "u:object_r:system_file:s0")) { 501 | Log.i(TAG, path + " -> u:object_r:system_file:s0"); 502 | } else { 503 | Log.w(TAG, "Failed to reset context."); 504 | } 505 | return false; 506 | } else { 507 | Log.d(TAG, context + " " + path); 508 | } 509 | } catch (Throwable ignored) { 510 | } 511 | return true; 512 | } 513 | 514 | private static boolean checkOrResetContextForChildren(File folder) { 515 | if (!isSELinuxEnforcing) return true; 516 | 517 | boolean res = true; 518 | File[] files = folder.listFiles(); 519 | if (files != null) { 520 | for (File f : files) { 521 | res &= checkAndResetContextForFile(f); 522 | } 523 | } 524 | return res; 525 | } 526 | 527 | private static boolean checkOrResetContextForForParent(File from, File to) { 528 | if (!isSELinuxEnforcing) return true; 529 | 530 | boolean res = true; 531 | 532 | do { 533 | res &= checkAndResetContextForFile(from); 534 | from = from.getParentFile(); 535 | } while (from != null && !Objects.equals(from, to)); 536 | 537 | return res & checkAndResetContextForFile(to); 538 | } 539 | 540 | private static Map>> collectModules(boolean is64) { 541 | Map>> m = new ConcurrentHashMap<>(); 542 | 543 | String riruLibPath = "riru/" + (is64 ? "lib64" : "lib"); 544 | File[] magiskDirs = new File(DaemonUtils.getMagiskTmpfsPath(), ".magisk/modules").listFiles(); 545 | if (magiskDirs == null) { 546 | return Collections.emptyMap(); 547 | } 548 | 549 | for (File magiskDir : magiskDirs) { 550 | if (new File(magiskDir, "remove").exists() 551 | || new File(magiskDir, "disable").exists()) 552 | continue; 553 | 554 | File libDir = new File(magiskDir, riruLibPath); 555 | if (!libDir.exists()) 556 | continue; 557 | 558 | File[] libsFiles = libDir.listFiles(); 559 | if (libsFiles == null) { 560 | continue; 561 | } 562 | 563 | List> libs = new ArrayList<>(); 564 | m.put(magiskDir.getAbsolutePath(), libs); 565 | 566 | Log.d(TAG, magiskDir.getAbsolutePath() + " is a Riru module"); 567 | 568 | for (File lib : libsFiles) { 569 | String name = lib.getName(); 570 | String id = name; 571 | if (id.startsWith(RIRU_PREFIX)) id = id.substring(RIRU_PREFIX.length()); 572 | else if (id.startsWith(LIB_PREFIX)) id = id.substring(LIB_PREFIX.length()); 573 | if (id.endsWith(SO_SUFFIX)) id = id.substring(0, id.length() - 3); 574 | id = magiskDir.getName() + "@" + id; 575 | if (!name.endsWith(SO_SUFFIX)) { 576 | var relativeLibPath = "system/" + (is64 ? "lib64" : "lib"); 577 | lib = new File(new File("/", relativeLibPath), name + SO_SUFFIX); 578 | fileContext &= checkAndResetContextForFile(new File(new File(magiskDir, relativeLibPath), name + SO_SUFFIX)); 579 | } else { 580 | fileContext &= checkAndResetContextForFile(lib); 581 | } 582 | 583 | libs.add(new Pair<>(id, lib.getAbsolutePath())); 584 | Log.d(TAG, "Path for " + id + " is " + lib.getAbsolutePath()); 585 | 586 | } 587 | 588 | fileContext &= checkOrResetContextForForParent(libDir, magiskDir); 589 | } 590 | return m; 591 | } 592 | 593 | public static Map>> getModules(boolean is64) { 594 | try { 595 | return is64 ? modules64.get() : modules.get(); 596 | } catch (Throwable e) { 597 | Log.e(TAG, "get modules", e); 598 | return Collections.emptyMap(); 599 | } 600 | } 601 | 602 | public static boolean hasIncorrectFileContext() { 603 | return !fileContext; 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /rirud/src/main/java/riru/Installer.java: -------------------------------------------------------------------------------- 1 | package riru; 2 | 3 | import android.os.SELinux; 4 | 5 | public class Installer { 6 | 7 | private static void checkSELinux() { 8 | if (!DaemonUtils.hasSELinux()) { 9 | System.out.println("- This device does not have SELinux"); 10 | return; 11 | } 12 | if (!SELinux.isSELinuxEnabled()) { 13 | System.out.println("- SELinux is disabled"); 14 | return; 15 | } 16 | if (!SELinux.isSELinuxEnforced()) { 17 | System.out.println("! SELinux is permissive"); 18 | System.out.println("! If your ROM runs Android 10+ and has SELinux permissive (it will disable seccomp), ANY app can escalate itself to uid 0, this is extremely dangerous"); 19 | System.out.println("- Just a kind reminder, continue installation..."); 20 | return; 21 | } 22 | 23 | boolean file = SELinux.checkSELinuxAccess("u:r:init:s0", "u:object_r:system_file:s0", "file", "relabelfrom"); 24 | boolean dir = SELinux.checkSELinuxAccess("u:r:init:s0", "u:object_r:system_file:s0", "dir", "relabelfrom"); 25 | 26 | if (file || dir) { 27 | System.out.println("!!!!!!!!! PLEASE READ !!!!!!!!!!"); 28 | if (file) { 29 | System.out.println("! Your ROM allows init to relabel Magisk module files"); 30 | } 31 | if (dir) { 32 | System.out.println("! Your ROM allows init to relabel Magisk module files"); 33 | } 34 | System.out.println("- Riru will try to reset the context of modules files, but not guaranteed to always work"); 35 | } else { 36 | System.out.println("- No problem found"); 37 | } 38 | } 39 | 40 | public static void main(String[] args) { 41 | for (String arg : args) { 42 | if ("--check-selinux".equals(arg)) { 43 | System.out.println("- Start checks..."); 44 | checkSELinux(); 45 | System.exit(0); 46 | return; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /rirud/src/main/res/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | \uD83E\uDD14 app_process lancé 4 | Zygote est probablement hors service, en attente de redémarrage… 5 | \u26A0\uFE0F Les fichiers ne sont pas montés. Magisk est cassé sur ce périphérique. 6 | \u26A0\uFE0F SELinux incorrect trouvé sur ce périphérique. Ce problème ne devrait pas être résolu par Riru, veuillez vous référer au fichier README du module pour plus d\’informations. 7 | \u26A0\uFE0F La propriété du système est erronée. N\’utilisez pas les modules d\’optimisation car il est très douteux d\’optimiser en changeant les propriétés. 8 | \u26A0\uFE0F Riru n\’est pas chargé et la raison est inconnue. 9 | \uD83D\uDE0B Riru fonctionne normalement. %1$d modules chargés. 10 | \u26A0\uFE0F Le contexte SELinux pour Riru et les fichiers des modules sont incorrects. Riru a essayé de les réinitialiser, mais le fait que vous voyez cette invite signifie que la réinitialisation n\’a pas fonctionné. Cela peut être dû au fait que votre ROM contient des règles SELinux incorrectes. Voir le wiki de Riru sur GitHub pour plus d\’informations. 11 | \u26A0\uFE0F Le contexte SELinux pour Riru et les fichiers des modules sont incorrects. Riru à tenté de les réinitialiser, mais les modules pourraient ne pas fonctionner correctement. Cela peut être dû au fait que votre ROM contient des règles SELinux incorrectes. Voir le wiki de Riru sur GitHub pour plus d\’informations. %1$d modules chargés, %2$s. 12 | aucun 13 | 14 | -------------------------------------------------------------------------------- /rirud/src/main/res/values-zh-rCN/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | \uD83E\uDD14 app_process 已运行 4 | Zygote 似乎死了,等待重新启动…… 5 | \u26A0\uFE0F 文件未被挂载。Magisk 在此设备上损坏。 6 | \u25A0\uFE0F 此设备上有错误的 SELinux 规则。此问题不应该由 Riru 解决,请参阅模块自述文件以了解更多。 7 | \u26A0\uFE0F 系统属性错误。请不要使用“优化”模块,因为通过修改属性来优化非常值得怀疑。 8 | \u26A0\uFE0F Riru 未被加载,原因未知。 9 | \uD83D\uDE0B Riru 正常工作中。已载入 %1$d 个模块 %2$s。 10 | \u26A0\uFE0F Riru 和模块文件的 SELinux 上下文不正确。Riru 已尝试重设它们,但您看到了该提示意味着重设没有起作用。这可能是由于您的 ROM 有不正确的 SELinux 规则。请参阅 Riru 在 GitHub 上的 wiki 以了解更多。 11 | 12 | \u26A0\uFE0F Riru 和模块文件的 SELinux 上下文不正确。Riru 已尝试重设它们,但模块仍可能不能正常工作。这可能是由于您的 ROM 有不正确的 SELinux 规则。请参阅 Riru 在 GitHub 上的 wiki 以了解更多。已载入 %1$d 个模块 %2$s。 13 | 14 | -------------------------------------------------------------------------------- /rirud/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | \uD83E\uDD14 app_process 已執行 4 | Zygote 似乎死了,等待重新啟動…… 5 | \u26A0\uFE0F 檔案未被掛載。Magisk 在此裝置上損壞。 6 | \u26A0\uFE0F 此裝置上有錯誤的 SELinux 規則。此問題不應該由 Riru 解決,請參閱模組自述檔案以瞭解更多。 7 | \u26A0\uFE0F 系統屬性錯誤。請不要使用“最佳化”模組,因為透過修改屬性來最佳化非常值得懷疑。 8 | \u26A0\uFE0F Riru 未被載入,原因未知。 9 | \uD83D\uDE0B Riru 正常工作中。已載入 %1$d 個模組 %2$s。 10 | \u26A0\uFE0F Riru 和模組檔案的 SELinux 上下文不正確。Riru 已嘗試重設它們,但您看到了該提示意味著重設沒有起作用。這可能是由於您的 ROM 有不正確的 SELinux 規則。請參閱 Riru 在 GitHub 上的 wiki 以瞭解更多。 11 | 12 | \u26A0\uFE0F Riru 和模組檔案的 SELinux 上下文不正確。Riru 已嘗試重設它們,但模組仍可能不能正常運作。這可能是由於您的 ROM 有不正確的 SELinux 規則。請參閱 Riru 在 GitHub 上的 wiki 以瞭解更多。已載入 %1$d 個模組 %2$s。 13 | 14 | -------------------------------------------------------------------------------- /rirud/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | \uD83E\uDD14 app_process launched 4 | Zygote is probably dead, waiting for restart… 5 | \u26A0\uFE0F Files are not mounted. Magisk is broken on this device. 6 | \u26A0\uFE0F Incorrect SELinux found on this device. This issue should not be resolved by Riru, please refer to the module README file for more information. 7 | \u26A0\uFE0F System property is wrong. Please don\'t use \"optimize\" modules since it\'s very questionable to optimize by changing properties. 8 | \u26A0\uFE0F Riru is not loaded and the reason in unknown. 9 | \uD83D\uDE0B Riru is working normally. Loaded %1$d modules, %2$s. 10 | \u26A0\uFE0F SELinux context for Riru and modules files are incorrect. Riru has tried to reset them, but you see this prompt means the reset did not work. This is may because your ROM have incorrect SELinux rules. See Riru wiki at GitHub for more. 11 | \u26A0\uFE0F SELinux context for Riru and modules files are incorrect. Riru has tried to reset them, but modules may not work properly. This is may because your ROM have incorrect SELinux rules. See Riru wiki at GitHub for more. Loaded %1$d modules, %2$s. 12 | none 13 | 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | plugins { 8 | id 'com.android.application' version '7.2.2' 9 | id 'com.android.library' version '7.2.2' 10 | } 11 | } 12 | 13 | dependencyResolutionManagement { 14 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 15 | repositories { 16 | google() 17 | mavenCentral() 18 | maven { url "https://s01.oss.sonatype.org/content/repositories/releases/" } 19 | mavenLocal() 20 | } 21 | } 22 | 23 | include ':riru', ':stub', ':rirud' 24 | 25 | import org.apache.tools.ant.DirectoryScanner 26 | 27 | DirectoryScanner.removeDefaultExclude('**/.gitattributes') 28 | -------------------------------------------------------------------------------- /stub/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /stub/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java-library' 3 | } 4 | 5 | java { 6 | sourceCompatibility = JavaVersion.VERSION_11 7 | targetCompatibility = JavaVersion.VERSION_11 8 | } 9 | -------------------------------------------------------------------------------- /stub/src/main/java/android/os/IBinder.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public interface IBinder { 4 | } 5 | -------------------------------------------------------------------------------- /stub/src/main/java/android/os/SELinux.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class SELinux { 4 | 5 | public static final native boolean isSELinuxEnabled(); 6 | 7 | public static final native boolean isSELinuxEnforced(); 8 | 9 | public static final native boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm); 10 | 11 | public static final native String getFileContext(String path); 12 | 13 | public static final native boolean setFileContext(String path, String context); 14 | 15 | public static final native String getPidContext(int pid); 16 | } 17 | -------------------------------------------------------------------------------- /stub/src/main/java/android/os/ServiceManager.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class ServiceManager { 4 | 5 | public static IBinder getService(String name) { 6 | throw new RuntimeException("STUB"); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /stub/src/main/java/android/os/SystemProperties.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | public class SystemProperties { 4 | 5 | public static String get(String key) { 6 | throw new RuntimeException("STUB"); 7 | } 8 | 9 | public static String get(String key, String def) { 10 | throw new RuntimeException("STUB"); 11 | } 12 | 13 | public static void set(String key, String value) { 14 | throw new RuntimeException("STUB"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /template/aar/riru-javadoc.jar/README: -------------------------------------------------------------------------------- 1 | This is a Prefab project which does not have -sources.jar or -javadoc.jar. -------------------------------------------------------------------------------- /template/aar/riru-sources.jar/README: -------------------------------------------------------------------------------- 1 | This is a Prefab project which does not have -sources.jar or -javadoc.jar. -------------------------------------------------------------------------------- /template/aar/riru.aar/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /template/aar/riru.aar/META-INF/com/android/build/gradle/aar-metadata.properties: -------------------------------------------------------------------------------- 1 | aarFormatVersion=1.0 2 | aarMetadataVersion=1.0 3 | -------------------------------------------------------------------------------- /template/aar/riru.aar/prefab/modules/riru/module.json: -------------------------------------------------------------------------------- 1 | { 2 | "export_libraries": [], 3 | "android": {} 4 | } -------------------------------------------------------------------------------- /template/aar/riru.aar/prefab/prefab.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riru", 3 | "schema_version": 1, 4 | "dependencies": [] 5 | } -------------------------------------------------------------------------------- /template/aar/riru.pom: -------------------------------------------------------------------------------- 1 | 2 | 4 | dev.rikka.ndk 5 | riru 6 | %%%VERSION%%% 7 | aar 8 | Riru 9 | Riru module interface. 10 | https://github.com/RikkaApps/Riru 11 | 2018 12 | 13 | 14 | MIT License 15 | https://github.com/RikkaApps/RikkaX/blob/master/LICENSE 16 | repo 17 | 18 | 19 | 20 | 21 | rikka 22 | Rikka 23 | https://github.com/RikkaW 24 | 25 | 26 | 27 | scm:git:https://github.com/RikkaApps/Riru 28 | https://github.com/RikkaApps/Riru 29 | 30 | 31 | -------------------------------------------------------------------------------- /template/magisk_module/.gitattributes: -------------------------------------------------------------------------------- 1 | # Declare files that will always have LF line endings on checkout. 2 | META-INF/** text eol=lf 3 | *.prop text eol=lf 4 | *.sh text eol=lf 5 | *.md text eol=lf 6 | *.rule text eol=lf 7 | 8 | # Denote all files that are truly binary and should not be modified. 9 | system/** binary 10 | system_x86/** binary -------------------------------------------------------------------------------- /template/magisk_module/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 v19.0+! " 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 19000 ] && require_new_magisk 31 | 32 | if [ $MAGISK_VER_CODE -ge 20400 ]; then 33 | # New Magisk have complete installation logic within util_functions.sh 34 | install_module 35 | exit 0 36 | fi 37 | 38 | ################# 39 | # Legacy Support 40 | ################# 41 | 42 | TMPDIR=/dev/tmp 43 | PERSISTDIR=/sbin/.magisk/mirror/persist 44 | 45 | is_legacy_script() { 46 | unzip -l "$ZIPFILE" install.sh | grep -q install.sh 47 | return $? 48 | } 49 | 50 | print_modname() { 51 | local len 52 | len=`echo -n $MODNAME | wc -c` 53 | len=$((len + 2)) 54 | local pounds=`printf "%${len}s" | tr ' ' '*'` 55 | ui_print "$pounds" 56 | ui_print " $MODNAME " 57 | ui_print "$pounds" 58 | ui_print "*******************" 59 | ui_print " Powered by Magisk " 60 | ui_print "*******************" 61 | } 62 | 63 | # Override abort as old scripts have some issues 64 | abort() { 65 | ui_print "$1" 66 | $BOOTMODE || recovery_cleanup 67 | [ -n $MODPATH ] && rm -rf $MODPATH 68 | rm -rf $TMPDIR 69 | exit 1 70 | } 71 | 72 | rm -rf $TMPDIR 2>/dev/null 73 | mkdir -p $TMPDIR 74 | 75 | # Preperation for flashable zips 76 | setup_flashable 77 | 78 | # Mount partitions 79 | mount_partitions 80 | 81 | # Detect version and architecture 82 | api_level_arch_detect 83 | 84 | # Setup busybox and binaries 85 | $BOOTMODE && boot_actions || recovery_actions 86 | 87 | ############## 88 | # Preparation 89 | ############## 90 | 91 | # Extract prop file 92 | unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 93 | [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" 94 | 95 | $BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules 96 | MODULEROOT=$NVBASE/$MODDIRNAME 97 | MODID=`grep_prop id $TMPDIR/module.prop` 98 | MODPATH=$MODULEROOT/$MODID 99 | MODNAME=`grep_prop name $TMPDIR/module.prop` 100 | 101 | # Create mod paths 102 | rm -rf $MODPATH 2>/dev/null 103 | mkdir -p $MODPATH 104 | 105 | ########## 106 | # Install 107 | ########## 108 | 109 | if is_legacy_script; then 110 | unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2 111 | 112 | # Load install script 113 | . $TMPDIR/install.sh 114 | 115 | # Callbacks 116 | print_modname 117 | on_install 118 | 119 | # Custom uninstaller 120 | [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh 121 | 122 | # Skip mount 123 | $SKIPMOUNT && touch $MODPATH/skip_mount 124 | 125 | # prop file 126 | $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop 127 | 128 | # Module info 129 | cp -af $TMPDIR/module.prop $MODPATH/module.prop 130 | 131 | # post-fs-data scripts 132 | $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh 133 | 134 | # service scripts 135 | $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh 136 | 137 | ui_print "- Setting permissions" 138 | set_permissions 139 | else 140 | print_modname 141 | 142 | unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2 143 | 144 | if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then 145 | ui_print "- Extracting module files" 146 | unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 147 | 148 | # Default permissions 149 | set_perm_recursive $MODPATH 0 0 0755 0644 150 | fi 151 | 152 | # Load customization script 153 | [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh 154 | fi 155 | 156 | # Handle replace folders 157 | for TARGET in $REPLACE; do 158 | ui_print "- Replace target: $TARGET" 159 | mktouch $MODPATH$TARGET/.replace 160 | done 161 | 162 | if $BOOTMODE; then 163 | # Update info for Magisk Manager 164 | mktouch $NVBASE/modules/$MODID/update 165 | cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop 166 | fi 167 | 168 | # Copy over custom sepolicy rules 169 | if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then 170 | ui_print "- Installing custom sepolicy patch" 171 | PERSISTMOD=$PERSISTDIR/magisk/$MODID 172 | mkdir -p $PERSISTMOD 173 | cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule 174 | fi 175 | 176 | # Remove stuffs that don't belong to modules 177 | rm -rf \ 178 | $MODPATH/system/placeholder $MODPATH/customize.sh \ 179 | $MODPATH/README.md $MODPATH/.git* 2>/dev/null 180 | 181 | ############# 182 | # Finalizing 183 | ############# 184 | 185 | cd / 186 | $BOOTMODE || recovery_cleanup 187 | rm -rf $TMPDIR 188 | 189 | ui_print "- Done" 190 | exit 0 -------------------------------------------------------------------------------- /template/magisk_module/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /template/magisk_module/README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | All Riru users and Riru modules should migrate to Zygisk. 4 | 5 | # Riru 6 | 7 | Riru only does one thing, inject into zygote in order to allow modules run their codes in apps or the system server. 8 | 9 | All other Riru modules requires Riru. 10 | 11 | See [https://github.com/RikkaApps/Riru](https://github.com/RikkaApps/Riru) for more details. 12 | 13 | ### Pre-24 modules are no longer supported 14 | 15 | > All live modules have upgraded to v24+ for a long time. 16 | 17 | The goal is to eliminate bad designs due to historical reasons. 18 | 19 | If you are module developer, it only takes you less than 1 min to switch to v24+ if your module is already v22. 20 | 21 | For users, if you find you cannot install a module (the installer will say Riru is not installed) or a module stop working, it means that such modules have stop updating for at least 4 months. 22 | 23 | ### Note 24 | 25 | If you are use other modules that changes `ro.dalvik.vm.native.bridge`, Riru will not work. (Riru will automatically set it back) 26 | 27 | A typical example is, some "optimize" modules changes this property. Since changing this property is meaningless for "optimization", their quality is very questionable. In fact, changing properties for optimization is a joke. 28 | 29 | ### Install from recovery is NOT supported 30 | 31 | Many TWRP has broken implementations, which will finally cause Riru and Riru modules "installed" but not working. 32 | 33 | ### Incorrect SELinux rules problem 34 | 35 | 36 | 37 | ## Changelog 38 | 39 | ### v26.1.5 (2022-2-2) 40 | - THIS IS THE LAST RELEASE. MODULES AND USERS SHOULD MIGRATE TO ZYGISK. 41 | - Fix description hint 42 | 43 | ### v26.1.4 (2021-12-15) 44 | - Skip launching Rirud when Zygisk is enabled 45 | - Fix `sonext` update during hiding 46 | 47 | ### v26.1.3 (2021-10-08) 48 | - Call `android_create_namespace` on Android 8.0+ 49 | 50 | ### v26.1.2 (2021-09-04) 51 | - Speedup Rirud launch 52 | - Warning about incorrect SELinux policy 53 | - No warning about Rirud socket closed 54 | - Use `getprogname` to detect zygote 55 | 56 | ### v26.1.1 (2021-08-18) 57 | - Fix reset native bridge 58 | 59 | ### v26.1.0 (2021-08-16) 60 | - Preload modules parallelly to speedup startup 61 | - Fix status showing after a soft reboot 62 | - Fix riru is not loading after a soft reboot 63 | - Prevent modules from using internal interfaces 64 | - Stricter authorization of Rirud 65 | - More accurate loaded status 66 | - Fix status showing on Huawei and Samsung devices 67 | - Unshare Rirud 68 | 69 | ### v26.0.5 (2021-08-01) 70 | - Fix killing parent process 71 | 72 | ### v26.0.4 (2021-07-30) 73 | - Fix flock not working on some devices 74 | 75 | This is a bug from Magisk's busybox and it will be fixed [here](https://github.com/topjohnwu/ndk-busybox/commit/d75558194ae9c9dfaa21a4e514c91ec6127016f9). As a workaround, we set `SHELL` in the script manually. 76 | 77 | ### v26.0.3 (2021-07-27) 78 | 79 | - Reset SELinux context for module files when necessary 80 | 81 | This will not always work since on ROMs with incorrect SELinux rules, the system will reset module file to the incorrect one at the same time 82 | 83 | ### v26.0.2 (2021-07-27) 84 | 85 | - Report if the SELinux context of the module files are incorrect 86 | - Use Resources for i18n 87 | 88 | ### v26.0.1 (2021-07-18) 89 | 90 | - Remove support for pre-v24 modules (it has been more than 4 months and all live modules have upgraded to v24+) 91 | - Display status on module description in Magisk (app is removed because of this) 92 | - Combine `rirud` with `rirud_java` 93 | - Refactor codes 94 | 95 | ### v25.4.4 (2021-05-07) 96 | 97 | - Fix in rare cases "soft boot" causes Riru not working 98 | - Fix keep `allow_install_app` flag (#225) 99 | 100 | ### v25.4.3 (2021-05-05) 101 | 102 | - Exit `service.sh` script 103 | - Use uid 0 to install app 104 | 105 | ### v25.4.2 (2021-04-15) 106 | 107 | - "Fix" system server injection does not work on Huawei devices by setting `ro.maple.enable` to `0` 108 | 109 | ### v25.4.1 (2021-04-10) 110 | 111 | - Report incorrect SELinux rule [1] 112 | - Bundle app with the module (Create file `/data/adb/modules/riru-core/allow_install_app` to allow the module to install the app) 113 | 114 | [1] 115 | 116 | ### v25.3.4 (2021-03-24) 117 | 118 | - Unload API 25+ modules in the app process if the module does not provide related functions 119 | - Don't use temporary buffers when parsing PID maps in pmparser ([#202](https://github.com/RikkaApps/Riru/pull/202)) 120 | - Use self-compiled libcxx (https://github.com/topjohnwu/libcxx) 121 | 122 | ### v25.3.3 (2021-03-22) 123 | 124 | - Fix crash on Android 8.0 again 125 | 126 | ### Important changes from the last stable version (v23.9) 127 | 128 | - Unify the Riru API version and Riru version, Riru 25 stands for API version 25 129 | - For modules that have adapted Riru API 24+, lib files are loaded from the Magisk path directly, they don't need to be mounted to `/system` anymore 130 | - Support unload self and modules, leaving no trace for unrelated processes (requires module changes) 131 | - Support remove self and modules from `dl_iterate_phdr` 132 | - `/data/adb/riru/modules` is no longer used, you can remove it when all modules are updated to Riru API 24+ 133 | 134 | ### v25.3.2 (2021-03-22) 135 | 136 | - New way to get realpath on old systems 137 | - Fix next offset on Android 9 138 | 139 | ### v25.3.1 (2021-03-20) 140 | 141 | - Fix crash on Android 8 142 | 143 | ### v25.3.0 (2021-03-20) 144 | 145 | - Support remove self and modules from `dl_iterate_phdr` now works for all Android versions 146 | 147 | ### v25.2.0 (2021-03-17) 148 | 149 | - Always clear name from `dl_iterate_phdr` 150 | - Fix reset native bridge is broken since v24.0.0 151 | - Continue to reduce the file size (down to less than 200K now) 152 | 153 | ### v25.0.0 (2021-03-16) 154 | 155 | - Support unload self and modules, leaving no trace for unrelated processes (requires module changes) 156 | - Support remove self and modules from `dl_iterate_phdr` (requires Android 8.0+) 157 | - Use a new way to bypass `dlopen` path limitation 158 | 159 | ### v24.1.2 (2021-03-13) 160 | 161 | - Don't attempt to run hide for `webview_zygote` on pre-29 162 | 163 | ### v24.1.1 (2021-03-13) 164 | 165 | - Hide is enabled by default and cannot be disabled 166 | - Hide works on pre-29 without extra SELinux rule 167 | 168 | Since v24 starts to load so files directly from the Magisk path (`/sbin` or `/dev`), it's highly possible to trigger anti-cheat from games, so hide is a must. 169 | 170 | ### v24.1.0 (2021-03-12) 171 | 172 | - Hide names from `dl_iterate_phdr` 173 | 174 | ### v24.0.1 (2021-03-11) 175 | 176 | - Fix pre-v24 modules installation 177 | 178 | ### v24.0.0 (2021-03-11) 179 | 180 | - Unify the Riru API version and Riru version, now the API version is 24 181 | - For modules that have adapted Riru API 24, lib files are loaded from the Magisk path directly, they don't need to be mounted to `/system` anymore 182 | - `/data/adb/riru/modules` is no longer used, you can remove it when all modules are update to Riru API 24 183 | - Use git commit count as version code 184 | - Remove fallback SELinux rules, if rirud is not started, it's highly possible that the booting processes of Magisk is totally broken on your device 185 | 186 | ### v23.9 (59) (2021-03-06) 187 | 188 | - Fix crash when JVM reuses reference index on devices with `libnativehelper_lazy` (`libnativehelper_lazy` may come in Android 12 DP2 or later) (by LSPosed devs) 189 | 190 | ### v23.8 (58) (2021-03-05) 191 | 192 | - Fix a problem that only exists on 32-bit devices 193 | 194 | ### v23.7 (57) (2021-03-01) 195 | 196 | - Prepare for changes brought by `libnativehelper_lazy` (these changes may come in Android 12 DP2 or later) 197 | - Fix symbols are incorrectly exported 198 | 199 | ### v23.6 (56) (2021-02-21) 200 | 201 | - Continue reduce the file size 202 | - Works on devices that have dropped 32-bit support (Android 12 emulator or devices in the future) 203 | 204 | ### v23.5 (55) (2021-02-11) 205 | 206 | - Reduce the file size 207 | 208 | ### v23.4 (54) (2021-01-23) 209 | 210 | - Ensure auto restart works 211 | 212 | ### v23.3 (53) (2021-01-13) 213 | 214 | - Ensure auto restart works 215 | 216 | ### v23.2 (52) (2021-01-02) 217 | 218 | - Add `/data/adb/riru/util_functions.sh` for module installer to use 219 | - Ensure auto restart works 220 | 221 | ### v23.1 (51) (2020-12-18) 222 | 223 | - Restart zygote even for the first time (for "broken environment", such as modules are loaded after than zygote is started) 224 | - Hide should work for pre-Android-10 225 | - Prevent crash caused by hiding failure 226 | 227 | ### v23.0 (49) (2020-12-02) 228 | 229 | - Add read file & read dir function for "rirud". Modules can use this to read files that zygote itself has no permission to access. 230 | 231 | ### v22.4 (46) (2020-11-26) 232 | 233 | Magisk's `sepolicy.rule` does not work on some devices and no one report to Magisk 😒. Versions from 22.1 to 22.4 attempt to workaround it. 234 | 235 | - Add a socket runs under `u:r:zygote:s0 context` to handle all file operations from zygote (Riru) 236 | - For Magisk < v21.1, reboot twice is no longer required 237 | 238 | ### v22.0 (41) (2020-10-09) 239 | 240 | Riru v22 has a new hide mechanism which makes detection "not that easy". 241 | 242 | Because of this, all modules must change. If your module hasn't updated, ask the module developer to make changes. **For 99% modules, this is super easy.** 243 | 244 | **Before Magisk v21.1, you have to manually reboot the device twice.** 245 | 246 | ### v21.3 (36) (2020-07-01) 247 | 248 | - Support custom ROMs with isTopApp changes backported (#106) 249 | 250 | ### v21.2 (35) (2020-05-08) 251 | 252 | - Works on Android R DP4 253 | 254 | ### v21.1 (34) (2020-04-28) 255 | 256 | - Generate a random name for libmemtrack_real to temporarily make SafetyNet happy (this can't work for long) 257 | 258 | ### v21.0 (33) (2020-04-24) 259 | 260 | - Works on Android R DP3 261 | 262 | ### v20.1 (32) (2020-04-21) 263 | 264 | - Works on Android R DP2 265 | 266 | ### v19.8 (30) 267 | 268 | - Fix install script for x86 ([#91](https://github.com/RikkaApps/Riru/pull/91)) 269 | - Allow uid 1000 to access `/data/misc/riru` 270 | 271 | ### v19.7 (29) 272 | 273 | - Support Samsung Q with "usap" enabled (this should happens only on custom ROMs?) 274 | 275 | ### v19.6 (28) 276 | 277 | - Support Samsung Q 278 | - Copy libmemtrack.so in `post-fs-data.sh` 279 | - Upgrade to the latest module format 280 | 281 | ### v19.5 (27) 282 | 283 | - Verify important files on install (2019/10/29) 284 | - Fix [#58](https://github.com/RikkaApps/Riru/issues/58) 285 | 286 | ### v19.4 (26) 287 | 288 | - Fix bug 289 | 290 | ### v19.3 (25) 291 | 292 | - Fix not work on Android Q Beta 5 (if "process pool" enabled) 293 | - Remove jniRegisterNativeMethods hook when entering the app process 294 | 295 | ### v19 (21) 296 | 297 | - Always reset module files SELinux in case 298 | 299 | ### v19 (20) 300 | 301 | - Support Android Q Beta 3 (all modules need to be upgraded) 302 | -------------------------------------------------------------------------------- /template/magisk_module/customize.sh: -------------------------------------------------------------------------------- 1 | SKIPUNZIP=1 2 | 3 | RIRU_API="@RIRU_API@" 4 | RIRU_VERSION_CODE="@RIRU_VERSION_CODE@" 5 | RIRU_VERSION_NAME="@RIRU_VERSION_NAME@" 6 | 7 | if $BOOTMODE; then 8 | ui_print "- Installing from Magisk app" 9 | else 10 | ui_print "*********************************************************" 11 | ui_print "! Install from recovery is NOT supported" 12 | ui_print "! Some recovery has broken implementations, install with such recovery will finally cause Riru or Riru modules not working" 13 | ui_print "! Please install from Magisk app" 14 | abort "*********************************************************" 15 | fi 16 | 17 | ui_print "- Installing Riru $RIRU_VERSION_NAME (Riru API $RIRU_API)" 18 | 19 | # check Magisk 20 | ui_print "- Magisk version: $MAGISK_VER ($MAGISK_VER_CODE)" 21 | 22 | # check android 23 | if [ "$API" -lt 23 ]; then 24 | ui_print "! Unsupported sdk: $API" 25 | abort "! Minimal supported sdk is 23 (Android 6.0)" 26 | else 27 | ui_print "- Device sdk: $API" 28 | fi 29 | 30 | # check architecture 31 | if [ "$ARCH" != "arm" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "x86" ] && [ "$ARCH" != "x64" ]; then 32 | abort "! Unsupported platform: $ARCH" 33 | else 34 | ui_print "- Device platform: $ARCH" 35 | fi 36 | 37 | unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2 38 | if [ ! -f "$TMPDIR/verify.sh" ]; then 39 | ui_print "*********************************************************" 40 | ui_print "! Unable to extract verify.sh!" 41 | ui_print "! This zip may be corrupted, please try downloading again" 42 | abort "*********************************************************" 43 | fi 44 | . $TMPDIR/verify.sh 45 | 46 | extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip" 47 | extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip" 48 | 49 | ui_print "- Extracting Magisk files" 50 | 51 | if [ "$MAGISK_VER_CODE" -ge 21000 ]; then 52 | MAGISK_CURRENT_MODULE_PATH=$(magisk --path)/.magisk/modules/riru-core 53 | else 54 | MAGISK_CURRENT_MODULE_PATH=/sbin/.magisk/modules/riru-core 55 | fi 56 | 57 | extract "$ZIPFILE" 'module.prop' "$MODPATH" 58 | cp "$MODPATH/module.prop" "$MODPATH/module.prop.bk" 59 | extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" 60 | extract "$ZIPFILE" 'service.sh' "$MODPATH" 61 | extract "$ZIPFILE" 'system.prop' "$MODPATH" 62 | extract "$ZIPFILE" 'util_functions.sh' "$MODPATH" 63 | extract "$ZIPFILE" 'uninstall.sh' "$MODPATH" 64 | 65 | mkdir $MAGISK_CURRENT_MODULE_PATH 66 | rm "$MAGISK_CURRENT_MODULE_PATH"/util_functions.sh 67 | cp "$MODPATH"/util_functions.sh "$MAGISK_CURRENT_MODULE_PATH"/util_functions.sh 68 | 69 | mkdir "$MODPATH/lib" 70 | mkdir "$MODPATH/lib64" 71 | mkdir "$MODPATH/system" 72 | mkdir "$MODPATH/system/lib" 73 | [ "$IS64BIT" = true ] && mkdir "$MODPATH/system/lib64" 74 | 75 | if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then 76 | ui_print "- Extracting x86 libraries" 77 | extract "$ZIPFILE" 'lib/x86/libriru.so' "$MODPATH/lib" true 78 | extract "$ZIPFILE" 'lib/x86/libriruhide.so' "$MODPATH/lib" true 79 | extract "$ZIPFILE" 'lib/x86/libriruloader.so' "$MODPATH/system/lib" true 80 | 81 | if [ "$IS64BIT" = true ]; then 82 | ui_print "- Extracting x64 libraries" 83 | extract "$ZIPFILE" 'lib/x86_64/libriru.so' "$MODPATH/lib64" true 84 | extract "$ZIPFILE" 'lib/x86_64/libriruhide.so' "$MODPATH/lib64" true 85 | extract "$ZIPFILE" 'lib/x86_64/libriruloader.so' "$MODPATH/system/lib64" true 86 | fi 87 | else 88 | ui_print "- Extracting arm libraries" 89 | extract "$ZIPFILE" 'lib/armeabi-v7a/libriru.so' "$MODPATH/lib" true 90 | extract "$ZIPFILE" 'lib/armeabi-v7a/libriruhide.so' "$MODPATH/lib" true 91 | extract "$ZIPFILE" 'lib/armeabi-v7a/libriruloader.so' "$MODPATH/system/lib" true 92 | 93 | if [ "$IS64BIT" = true ]; then 94 | ui_print "- Extracting arm64 libraries" 95 | extract "$ZIPFILE" 'lib/arm64-v8a/libriru.so' "$MODPATH/lib64" true 96 | extract "$ZIPFILE" 'lib/arm64-v8a/libriruhide.so' "$MODPATH/lib64" true 97 | extract "$ZIPFILE" 'lib/arm64-v8a/libriruloader.so' "$MODPATH/system/lib64" true 98 | fi 99 | fi 100 | 101 | ui_print "- Setting permissions" 102 | set_perm_recursive "$MODPATH" 0 0 0755 0644 103 | 104 | ui_print "- Extracting rirud" 105 | extract "$ZIPFILE" "rirud.apk" "$MODPATH" 106 | set_perm "$MODPATH/rirud.apk" 0 0 0600 107 | 108 | ui_print "- Checking if your ROM has incorrect SELinux rules" 109 | /system/bin/app_process -Djava.class.path="$MODPATH/rirud.apk" /system/bin --nice-name=riru_installer riru.Installer --check-selinux 110 | 111 | ui_print "- Removing old files" 112 | rm -rf /data/adb/riru/bin 113 | rm /data/adb/riru/native_bridge 114 | rm /data/adb/riru/api_version.new 115 | rm /data/adb/riru/version_code.new 116 | rm /data/adb/riru/version_name.new 117 | rm /data/adb/riru/enable_hide 118 | rm /data/adb/riru/api_version 119 | rm /data/adb/riru/util_functions.sh 120 | rm /data/misc/riru/api_version 121 | rm /data/misc/riru/version_code 122 | rm /data/misc/riru/version_name 123 | 124 | # If Huawei's Maple is enabled, system_server is created with a special way which is out of Riru's control 125 | HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable) 126 | if [ $HUAWEI_MAPLE_ENABLED == "1" ]; then 127 | ui_print "- Add ro.maple.enable=0" 128 | echo "ro.maple.enable=0" >> "$MODPATH/system.prop" 129 | fi 130 | -------------------------------------------------------------------------------- /template/magisk_module/module.prop: -------------------------------------------------------------------------------- 1 | id=${id} 2 | name=${name} 3 | version=${version} 4 | versionCode=${versionCode} 5 | author=${author} 6 | description=${description} 7 | riruApi=${riruApi} 8 | riruMinApi=${riruMinApi} 9 | -------------------------------------------------------------------------------- /template/magisk_module/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | MODDIR=${0%/*} 3 | TMPPROP="$(magisk --path)/riru.prop" 4 | MIRRORPROP="$(magisk --path)/.magisk/modules/riru-core/module.prop" 5 | sh -Cc "cat '$MODDIR/module.prop' > '$TMPPROP'" 6 | if [ $? -ne 0 ]; then 7 | exit 8 | fi 9 | mount --bind "$TMPPROP" "$MIRRORPROP" 10 | if [ "$ZYGISK_ENABLE" = "1" ]; then 11 | sed -Ei 's/^description=(\[.*][[:space:]]*)?/description=[ ⛔ Riru is not loaded because of Zygisk. ] /g' "$MIRRORPROP" 12 | exit 13 | fi 14 | sed -Ei 's/^description=(\[.*][[:space:]]*)?/description=[ ⛔ app_process fails to run. ] /g' "$MIRRORPROP" 15 | cd "$MODDIR" || exit 16 | flock "module.prop" 17 | mount --bind "$TMPPROP" "$MODDIR/module.prop" 18 | unshare -m sh -c "/system/bin/app_process -Djava.class.path=rirud.apk /system/bin --nice-name=rirud riru.Daemon $(magisk -V) $(magisk --path) $(getprop ro.dalvik.vm.native.bridge)&" 19 | umount "$MODDIR/module.prop" 20 | -------------------------------------------------------------------------------- /template/magisk_module/service.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | MODDIR=${0%/*} 3 | TMPPROP="$(magisk --path)/riru.prop" 4 | MIRRORPROP="$(magisk --path)/.magisk/modules/riru-core/module.prop" 5 | sh -Cc "cat '$MODDIR/module.prop' > '$TMPPROP'" 6 | if [ $? -eq 0 ]; then 7 | mount --bind "$TMPPROP" "$MIRRORPROP" 8 | sed -Ei 's/^description=(\[.*][[:space:]]*)?/description=[ ⛔ post-fs-data.sh fails to run. Magisk is broken on this device. ] /g' "$MODDIR/module.prop" 9 | exit 10 | fi 11 | 12 | -------------------------------------------------------------------------------- /template/magisk_module/system.prop: -------------------------------------------------------------------------------- 1 | ro.dalvik.vm.native.bridge=libriruloader.so 2 | -------------------------------------------------------------------------------- /template/magisk_module/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | # /data/adb/riru/modules may be used by old modules, remove /data/adb/riru only if /data/adb/riru/modules does not exists 3 | if [ ! -d /data/adb/riru/modules ]; then 4 | rm -rf /data/adb/riru 5 | fi 6 | -------------------------------------------------------------------------------- /template/magisk_module/util_functions.sh: -------------------------------------------------------------------------------- 1 | RIRU_MIN_COMPATIBLE_API=9 2 | RIRU_API="@RIRU_API@" 3 | RIRU_VERSION_CODE="@RIRU_VERSION_CODE@" 4 | RIRU_VERSION_NAME="@RIRU_VERSION_NAME@" 5 | 6 | abort_for_requires_new_version() { 7 | ui_print "*********************************************************" 8 | ui_print "! This module requires Riru $1 or above" 9 | ui_print "! Please install (upgrade) Riru from Magisk Manager" 10 | ui_print "! Or you can download zip from https://github.com/RikkaApps/Riru/releases" 11 | abort "*********************************************************" 12 | } 13 | 14 | abort_for_old_module() { 15 | ui_print "*********************************************************" 16 | ui_print "! This module haven't support Riru v22.0+" 17 | ui_print "! Please ask the developer of this module to make changes" 18 | abort "*********************************************************" 19 | } 20 | 21 | check_riru_version() { 22 | ui_print "- Riru: $RIRU_VERSION_NAME (API $RIRU_API)" 23 | [ "$RIRU_MODULE_API_VERSION" -lt "$RIRU_MIN_COMPATIBLE_API" ] && abort_for_old_module 24 | [ "$RIRU_MODULE_MIN_API_VERSION" -gt "$RIRU_API" ] && abort_for_requires_new_version "$RIRU_MODULE_MIN_RIRU_VERSION_NAME" 25 | } 26 | 27 | enforce_install_from_magisk_app() { 28 | if $BOOTMODE; then 29 | ui_print "- Installing from Magisk app" 30 | else 31 | ui_print "*********************************************************" 32 | ui_print "! Install from recovery is NOT supported" 33 | ui_print "! Some recovery has broken implementations, install with such recovery will finally cause Riru or Riru modules not working" 34 | ui_print "! Please install from Magisk app" 35 | abort "*********************************************************" 36 | fi 37 | } 38 | -------------------------------------------------------------------------------- /template/magisk_module/verify.sh: -------------------------------------------------------------------------------- 1 | TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip" 2 | mkdir "$TMPDIR_FOR_VERIFY" 3 | 4 | abort_verify() { 5 | ui_print "*********************************************************" 6 | ui_print "! $1" 7 | ui_print "! This zip may be corrupted, please try downloading again" 8 | abort "*********************************************************" 9 | } 10 | 11 | # extract 12 | extract() { 13 | zip=$1 14 | file=$2 15 | dir=$3 16 | junk_paths=$4 17 | [ -z "$junk_paths" ] && junk_paths=false 18 | opts="-o" 19 | [ $junk_paths = true ] && opts="-oj" 20 | 21 | file_path="" 22 | hash_path="" 23 | if [ $junk_paths = true ]; then 24 | file_path="$dir/$(basename "$file")" 25 | hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256sum" 26 | else 27 | file_path="$dir/$file" 28 | hash_path="$TMPDIR_FOR_VERIFY/$file.sha256sum" 29 | fi 30 | 31 | unzip $opts "$zip" "$file" -d "$dir" >&2 32 | [ -f "$file_path" ] || abort_verify "$file not exists" 33 | 34 | unzip $opts "$zip" "$file.sha256sum" -d "$TMPDIR_FOR_VERIFY" >&2 35 | [ -f "$hash_path" ] || abort_verify "$file.sha256sum not exists" 36 | 37 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 38 | ui_print "- Verified $file" >&1 39 | } 40 | 41 | file="META-INF/com/google/android/update-binary" 42 | file_path="$TMPDIR_FOR_VERIFY/$file" 43 | hash_path="$file_path.sha256sum" 44 | unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2 45 | [ -f "$file_path" ] || abort_verify "$file not exists" 46 | if [ -f "$hash_path" ]; then 47 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 48 | ui_print "- Verified $file" >&1 49 | else 50 | ui_print "- Download from Magisk app" 51 | fi 52 | --------------------------------------------------------------------------------