├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── issue_template.yml │ └── pull_request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── READMEs ├── README_ja-JP.md ├── README_zh-CN.md └── README_zh-TW.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── loader ├── build.gradle.kts └── src │ ├── CMakeLists.txt │ ├── common │ ├── daemon.cpp │ ├── dl.cpp │ ├── elf_util.cpp │ ├── files.cpp │ ├── logging.cpp │ ├── misc.cpp │ └── socket_utils.cpp │ ├── external │ └── CMakeLists.txt │ ├── include │ ├── api.hpp │ ├── daemon.h │ ├── dl.h │ ├── elf_util.h │ ├── files.hpp │ ├── logging.h │ ├── misc.hpp │ ├── native_bridge_callbacks.h │ ├── socket_utils.h │ └── solist.hpp │ ├── injector │ ├── art_method.hpp │ ├── entry.cpp │ ├── gen_jni_hooks.py │ ├── hook.cpp │ ├── jni_helper.hpp │ ├── jni_hooks.hpp │ ├── module.hpp │ ├── unmount.cpp │ └── zygisk.hpp │ └── ptracer │ ├── main.cpp │ ├── monitor.cpp │ ├── monitor.h │ ├── ptracer.cpp │ ├── utils.cpp │ └── utils.hpp ├── module ├── .gitignore ├── build.gradle.kts └── src │ ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── update-binary │ │ └── updater-script │ ├── customize.sh │ ├── mazoku │ ├── module.prop │ ├── post-fs-data.sh │ ├── sepolicy.rule │ ├── service.sh │ ├── uninstall.sh │ └── verify.sh ├── settings.gradle.kts └── zygiskd ├── build.gradle.kts └── src ├── .gitignore ├── LICENSE ├── companion.c ├── companion.h ├── constants.h ├── dl.c ├── dl.h ├── main.c ├── root_impl ├── apatch.c ├── apatch.h ├── common.c ├── common.h ├── kernelsu.c ├── kernelsu.h ├── magisk.c └── magisk.h ├── utils.c ├── utils.h ├── zygiskd.c └── zygiskd.h /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue_template.yml: -------------------------------------------------------------------------------- 1 | name: Issue report 2 | description: Report an issue 3 | title: "[BUG]: " 4 | labels: ["bug", "not confirmed"] 5 | 6 | body: 7 | - type: input 8 | id: version 9 | attributes: 10 | label: Version 11 | description: The version of the ReZygisk you're using. 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: modules 17 | attributes: 18 | label: Modules 19 | description: "The modules you're using following the format: moduleName by authorName version x.x.x" 20 | validations: 21 | required: true 22 | 23 | - type: textarea 24 | id: description 25 | attributes: 26 | label: Description 27 | description: A clear and concise description of what the bug is. 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: steps 33 | attributes: 34 | label: Steps to reproduce 35 | description: Steps to reproduce the behavior. 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: logs 41 | attributes: 42 | label: Logs 43 | description: If applicable, add logs AS A FILE to help solve the issue. Most of the time it will be from logcat on boot. 44 | validations: 45 | required: false 46 | 47 | - type: checkboxes 48 | id: terms 49 | attributes: 50 | label: Confirmations 51 | description: The following confirmations are required to open a bug report. 52 | options: 53 | - label: My environment meets the minimum requirements. 54 | required: true 55 | - label: I have verified that this is not a duplicate issue. 56 | required: true 57 | 58 | - type: checkboxes 59 | id: code_of_conduct 60 | attributes: 61 | label: Code of Conduct 62 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/voice/blob/main/CODE_OF_CONDUCT.md) 63 | options: 64 | - label: I agree to follow this project's Code of Conduct 65 | required: true 66 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/pull_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Ask for a new feature to be added 3 | title: "[FR]: " 4 | labels: ["enhancement", "not confirmed"] 5 | 6 | body: 7 | - type: textarea 8 | id: description 9 | attributes: 10 | label: Description 11 | description: A clear and concise description of what the feature is. 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: reason 17 | attributes: 18 | label: Reason 19 | description: Why should this feature be added? 20 | validations: 21 | required: true 22 | 23 | - type: checkboxes 24 | id: terms 25 | attributes: 26 | label: Confirmations 27 | description: The following confirmations are required to open a feature request. 28 | options: 29 | - label: This feature is not already implemented. 30 | required: true 31 | - label: I have verified that this is not a duplicate feature request. 32 | required: true 33 | 34 | - type: checkboxes 35 | id: code_of_conduct 36 | attributes: 37 | label: Code of Conduct 38 | description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/PerformanC/voice/blob/main/CODE_OF_CONDUCT.md) 39 | options: 40 | - label: I agree to follow this project's Code of Conduct 41 | required: true -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | Write here about the changes you've made 4 | 5 | ## Why 6 | 7 | Write here why you think this should be merged 8 | 9 | ## Checkmarks 10 | 11 | - [ ] The modified functions have been tested. 12 | - [ ] Used the same indentation as the rest of the project. 13 | - [ ] Updated documentation (changelog). 14 | 15 | ## Additional information 16 | 17 | If you have any additional information, write it here 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | pull_request: 8 | merge_group: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | env: 14 | CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion" 15 | CCACHE_NOHASHDIR: "true" 16 | CCACHE_HARDLINK: "true" 17 | CCACHE_BASEDIR: "${{ github.workspace }}" 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | with: 23 | submodules: "recursive" 24 | fetch-depth: 0 25 | 26 | - name: Setup Java 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: "temurin" 30 | java-version: "17" 31 | 32 | - name: Setup Gradle 33 | uses: gradle/actions/setup-gradle@v4.2.1 34 | with: 35 | gradle-home-cache-cleanup: true 36 | 37 | - name: Set up ccache 38 | uses: hendrikmuhs/ccache-action@v1.2 39 | with: 40 | max-size: 2G 41 | key: ${{ runner.os }} 42 | restore-keys: ${{ runner.os }} 43 | save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} 44 | 45 | - name: Build with Gradle 46 | run: | 47 | echo 'org.gradle.parallel=true' >> gradle.properties 48 | echo 'org.gradle.vfs.watch=true' >> gradle.properties 49 | echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties 50 | echo 'android.native.buildOutput=verbose' >> gradle.properties 51 | sed -i 's/org.gradle.unsafe.configuration-cache=true//g' gradle.properties 52 | ./gradlew zipRelease 53 | ./gradlew zipDebug 54 | 55 | - name: Prepare artifact 56 | if: success() 57 | id: prepareArtifact 58 | run: | 59 | releaseName=`ls module/build/outputs/release/ReZygisk-v*-release.zip | awk -F '(/|.zip)' '{print $5}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT 60 | debugName=`ls module/build/outputs/release/ReZygisk-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT 61 | unzip module/build/outputs/release/ReZygisk-v*-release.zip -d zksu-release 62 | unzip module/build/outputs/release/ReZygisk-v*-debug.zip -d zksu-debug 63 | 64 | - name: Upload release 65 | uses: actions/upload-artifact@v4 66 | with: 67 | name: ${{ steps.prepareArtifact.outputs.releaseName }} 68 | path: "./zksu-release/*" 69 | 70 | - name: Upload debug 71 | uses: actions/upload-artifact@v4 72 | with: 73 | name: ${{ steps.prepareArtifact.outputs.debugName }} 74 | path: "./zksu-debug/*" 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .cxx 4 | .vscode 5 | build 6 | local.properties 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "loader/src/external/lsplt"] 2 | path = loader/src/external/lsplt 3 | url = https://github.com/JingMatrix/LSPlt 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReZygisk 2 | 3 | [简体中文](/READMEs/README_zh-CN.md)|[繁體中文](/READMEs/README_zh-TW.md) 4 | 5 | ReZygisk is a fork of Zygisk Next, a standalone implementation of Zygisk, providing Zygisk API support for KernelSU, Magisk (besides built-in), and APatch (Work In Progress). 6 | 7 | It aims to modernize and re-write the codebase to C (from C++ and Rust), allowing a more efficient and faster implementation of the Zygisk API with a more permissive license. 8 | 9 | > [!NOTE] 10 | > This module/fork is WIP (Work In Progress); only use .zip from the Releases. 11 | > 12 | > Although you may install the .zip from the [Actions](https://github.com/PerformanC/ReZygisk/actions) page, it is only at your discretion to install it since your device might enter bootloop. 13 | 14 | ## Why? 15 | 16 | The latest releases of Zygisk Next are not open-source, reserving entirely the code for its developers. Not only does that limit our ability to contribute to the project, but also impossibilities the audit of the code, which is a major security concern, as Zygisk Next is a module that runs with superuser (root) privileges, having access to the entire system. 17 | 18 | The Zygisk Next developers are famous and trusted in the Android community, however, this doesn't mean that the code is not malicious or vulnerable. We (PerformanC) understand they have their reasons to keep the code closed-source, but we believe the contrary. 19 | 20 | ## Advantages 21 | 22 | - FOSS (Forever) 23 | 24 | ## Dependencies 25 | 26 | | Tool | Description | 27 | |-----------------|----------------------------------------| 28 | | `Android NDK` | Native Development Kit for Android | 29 | 30 | ### C++ Dependencies 31 | 32 | | Dependency | Description | 33 | |------------|-------------------------------| 34 | | `lsplt` | Simple PLT Hook for Android | 35 | 36 | ## Usage 37 | 38 | We're currently in the process of cooking. (Coming Soon) 39 | 40 | ## Installation 41 | 42 | There are currently no available stable releases. (Coming Soon) 43 | 44 | ## Translation 45 | 46 | As of now, we don't have integration with another platform for translations but you may contribute to the [add/new-webui](https://github.com/PerformanC/ReZygisk/tree/add/new-webui) branch. Please don't forget to include your GitHub profile in [TRANSLATOR.md](https://github.com/PerformanC/ReZygisk/blob/add/new-webui/TRANSLATOR.md) so that people can see your contribution. 47 | 48 | ## Support 49 | For any question related to ReZygisk or other PerformanC projects, feel free to join any of the following channels below: 50 | 51 | - Discord Channel: [PerformanC](https://discord.gg/uPveNfTuCJ) 52 | - ReZygisk Telegram Channel: [@rezygiskchat](https://t.me/rezygiskchat) 53 | - PerformanC Telegram Channel: [@performancorg](https://t.me/performancorg) 54 | 55 | ## Contribution 56 | 57 | It is mandatory to follow PerformanC's [Contribution Guidelines](https://github.com/PerformanC/contributing) to contribute to ReZygisk. Following its Security Policy, Code of Conduct, and syntax standard. 58 | 59 | ## License 60 | 61 | ReZygisk is licensed majoritaly under GPL, by Dr-TSNG, but also AGPL 3.0, by The PerformanC Organization, for re-written code. You can read more about it on [Open Source Initiative](https://opensource.org/licenses/AGPL-3.0). 62 | -------------------------------------------------------------------------------- /READMEs/README_ja-JP.md: -------------------------------------------------------------------------------- 1 | # ReZygisk 2 | 3 | [English](https://github.com/PerformanC/ReZygisk/blob/main/README.md)|[简体中文](/READMEs/README_zh-CN.md)|[繁體中文](/READMEs/README_zh-TW.md) 4 | 5 | ReZygiskはkernelSU、Magisk、APatchにZygiskのAPIサポートを提供するスタンドアローンZygiskであるZygisk Nextのフォークです。 6 | 7 | ReZygiskは更に高速かつ効率的なZygisk APIとより寛容なライセンスを、コードベースをC(もともとはC++とRustでした)でアップデート/書き直すことで実現することを目標としています。 8 | 9 | > [!NOTE] 10 | > このモジュール/フォークはWIP(Work in Progress、すべての作業が進行中であることを意味します): ReleasesタブのZipのみを使用するようにしてください。 11 | > 12 | > GitHub [Actions](https://github.com/PerformanC/ReZygisk/actions) よりZipをダウンロードして使用することも可能ですが、デバイスがブートループなどの不具合が起きる可能性があります。ユーザー自身の裁量にて使用してください。 13 | 14 | ## ReZygiskを使う理由 15 | 16 | Zygisk Nextの最新リリースはクローズドソースであり、コードはプロジェクトの開発者のみアクセスできるものです。これはコミュニティがコードに貢献することを妨げるだけではなく、コード監査をも難しくしています。これはZygisk Nextがルート権限で作動するアプリであるため、セキュリティ上深刻な問題です。 17 | 18 | Zygisk Nextの開発者はAndroid Communityにて有名かつ信用されています。が、これはコード自体が悪意の無いこと/脆弱でないことを証明するものではありません。 19 | 20 | 我々(PerformanC)はZygisk Nextの開発者らがコードをクローズドに保つ重要な理由があることは承知していますが、我々はオープンソース/コミュニティドリブンにすることが重要だと考えています。 21 | 22 | ## メリット 23 | 24 | - オープンソース、Free to Use、FOSS (永続的) 25 | 26 | ## 依存関係 27 | 28 | | ツール | 説明 | 29 | |-----------------|----------------------------------------| 30 | | `Android NDK` | Androidのネイティブ開発環境キット | 31 | 32 | ### C++ 依存関係 33 | 34 | | 依存 | 説明 | 35 | |------------|-------------------------------| 36 | | `lsplt` | シンプルなAndroidのPLTフック | 37 | 38 | ## 使い方 39 | 40 | ただいま調理中です、しばらくお待ち下さい!(できるだけ早くお届けします) 41 | 42 | ## インストール 43 | 44 | 現状、ステーブルリリースはありません。(できるだけ早くお届けします) 45 | 46 | ## 翻訳 47 | 48 | 現状では、翻訳を他のプラットフォーム上で展開することはしていません。 49 | 50 | が、[add/new-webui](https://github.com/PerformanC/ReZygisk/tree/add/new-webui) ブランチにて翻訳作業に参加していただくことができます。 51 | 52 | 他の開発者さんたちがあなたの貢献を確認できるように、 [TRANSLATOR.md](https://github.com/PerformanC/ReZygisk/blob/add/new-webui/TRANSLATOR.md) にあなたのプロフィールを追加することを忘れないでください! 53 | 54 | ## サポート 55 | For any question related to ReZygisk or other PerformanC projects, feel free to join any of the following channels below: 56 | ReZygisk/他のPerformanCのプロジェクトに対する質問がある場合は、以下のどれかに参加してください! 57 | 58 | - Discord チャンネル: [PerformanC](https://discord.gg/uPveNfTuCJ) 59 | - ReZygisk Telegram チャンネル: [@rezygiskchat](https://t.me/rezygiskchat) 60 | - PerformanC Telegram チャンネル: [@performancorg](https://t.me/performancorg) 61 | 62 | ## 貢献 63 | 64 | 貢献をしたい場合、PerformanCの[Contribution Guidelines](https://github.com/PerformanC/contributing)に従うことが必要になります。 65 | 66 | セキュリティーポリシー、行動規範、シンタックススタンダードを採用してください。 67 | 68 | ## ライセンス 69 | 70 | ReZygiskは基本的にDr-TSNGによるGPLライセンス下にてライセンスされています。 71 | 72 | ただし、書き直しされたコードに関してはPerformanCによるAGPL3.0ライセンスにてライセンスされています。 73 | 詳細については [Open Source Initiative](https://opensource.org/licenses/AGPL-3.0) を参照してください。 74 | -------------------------------------------------------------------------------- /READMEs/README_zh-CN.md: -------------------------------------------------------------------------------- 1 | # ReZygisk 2 | 3 | [English](https://github.com/PerformanC/ReZygisk/blob/main/README.md) 4 | 5 | ReZygisk 是 Zygisk 的另一个独立实现,从 Zygisk Next 分叉而来,为 KernelSU、Magisk 和 APatch 提供 Zygisk API 支持(目前处于测试阶段)。 6 | 7 | 项目致力于使用 C 语言重写原本的 C++ 和 Rust 代码,从而更加现代、高效地实现 Zygisk API,并使用更宽松的开源许可证。 8 | 9 | > [!NOTE] 10 | > 此模块还在测试阶段,请仅安装正式版本的压缩包。 11 | > 12 | > 您可以从 [Actions](https://github.com/PerformanC/ReZygisk/actions) 页面下载自动构建包,但要注意自负风险。使用不稳定的版本时,设备可能会陷入启动循环(Bootloop)。 13 | 14 | ## 为什么要选择 ReZygisk? 15 | 16 | 最新版本的 Zygisk Next 并不开源,仅其核心开发者有权查阅全部源代码。这不仅阻止了其他开发者贡献代码,还阻止了他人对项目代码进行审计。Zygisk Next 是一个以超级用户(root)权限运行的模块,可以访问整个系统,闭源后存在重大安全隐患。 17 | 18 | Zygisk Next 的开发者们在Android社区享有盛誉,备受信任。但这并不意味着他们的项目就一定没有任何恶意代码和漏洞。我们(PerformanC)理解他们出于某些原因不愿保持开源,但我们坚信,开源是更好的选择。 19 | 20 | ## 优点 21 | 22 | - 永远保持开源 23 | 24 | ## 依赖 25 | 26 | | 工具 | 简介 | 27 | |---------------|------------------------------------| 28 | | `Android NDK` | Android 本地开发工具包 | 29 | 30 | ### C++ 依赖 31 | 32 | | 依赖 | 简介 | 33 | |---------|-----------------------------| 34 | | `lsplt` | Android 程序链接表钩子 | 35 | 36 | ## 用法 37 | 38 | 目前正在编写中 (敬请期待) 39 | 40 | ## 安装 41 | 42 | 目前还没有发布正式版本 (敬请期待) 43 | 44 | ## 翻译 45 | 46 | 您可以向 [add/new-webui](https://github.com/PerformanC/ReZygisk/tree/add/new-webui) 分支贡献翻译。 47 | 48 | 请不要忘记在 [TRANSLATOR.md](https://github.com/PerformanC/ReZygisk/blob/add/new-webui/TRANSLATOR.md) 中添加您的 GitHub 账号信息,以便人们看到您的贡献。 49 | 50 | ## 支持 51 | 52 | 如果您有任何关于 ReZygisk 或者其他 PerformanC 项目的问题,可以随时加入以下群组: 53 | 54 | - Discord 服务器: [PerformanC](https://discord.gg/uPveNfTuCJ) 55 | - ReZygisk Telegram 群组: [@rezygiskchat](https://t.me/rezygiskchat) 56 | - PerformanC Telegram 群组: [@performancorg](https://t.me/performancorg) 57 | 58 | ## 贡献 59 | 60 | 要对 ReZygisk 做出贡献,请遵守 PerformanC 的 [贡献指南](https://github.com/PerformanC/contributing) 。 61 | 62 | 请遵循其安全政策、行为准则和语法标准。 63 | 64 | ## 开源许可证 65 | 66 | ReZygisk 项目中,旧的 Zygisk Next 部分采用 GPL 许可证,但由 PerformanC 组织重写的代码则采用 AGPL 3.0 许可证。 67 | 68 | 您可以在 [Open Source Initiative](https://opensource.org/licenses/AGPL-3.0) 上阅读更多相关信息。 69 | -------------------------------------------------------------------------------- /READMEs/README_zh-TW.md: -------------------------------------------------------------------------------- 1 | # ReZygisk 2 | > 繁體中文(README_zh-TW.md)是根據[英文版自述檔案(README.md)](https://github.com/PerformanC/ReZygisk/blob/main/README.md)翻譯,僅供參考以便理解英文內容,翻譯可能滯後。 3 | 4 | ReZygisk 是 Zygisk Next 的一個分支,這是一個獨立實現的 Zygisk,為 KernelSU、Magisk(除了內建支援外)和 APatch(開發中)提供 Zygisk API 支援。 5 | 6 | 此專案致力於用 C 語言重寫原有的 C++ 和 Rust 代碼,藉此以更現代且高效的方式實現 Zygisk API,並採用更寬鬆的授權條款。 7 | 8 | > [!NOTE] 9 | > 此模組/分支仍在開發中(WIP);請僅安裝正式版本的壓縮包。 10 | > 11 | > 雖然你可以從 [Actions](https://github.com/PerformanC/ReZygisk/actions) 頁面安裝 .zip 檔,但若因此導致裝置進入開機循環(Bootloop),後果須自行承擔。 12 | 13 | ## 為什麼選擇ReZygisk? 14 | 15 | 最新版本的 Zygisk Next 已轉為閉源,只有核心開發者能查閱完整的原始碼。這不僅限制了其他開發者的貢獻,也無法進行代碼審計。由於 Zygisk Next 是一個以超級使用者(root)權限運行的模組,能夠存取整個系統,若閉源將帶來重大的安全風險。 16 | 17 | 雖然 Zygisk Next 的開發者在 Android 社群中享有盛譽,並且備受信任,但這並不代表他們的專案就完全沒有任何惡意程式碼或漏洞。我們(PerformanC)理解他們因某些原因選擇保持閉源,但我們堅信開源才是更好的選擇。 18 | 19 | ## 優勢 20 | 21 | - 永遠是自由及開放原始碼軟體(FOSS) 22 | 23 | ## 依賴項 24 | 25 | | 工具 | 說明 | 26 | |-----------------|---------------------------------------| 27 | | `Android NDK` | Android 原生開發工具包 | 28 | 29 | ### C++ 依賴項 30 | 31 | | 依賴 | 說明 | 32 | |----------|----------------------------------------------| 33 | | `lsplt` | Android 的簡單 PLT(程式連結表) 勾取 | 34 | 35 | ## 用法 36 | 37 | 我們目前正在開發中。(敬請期待) 38 | 39 | ## 安裝 40 | 41 | 目前沒有穩定版本可供下載。(敬請期待) 42 | 43 | ## 翻譯 44 | 45 | 目前我們尚未與其他平台整合進行翻譯,但您可以為 [add/new-webui](https://github.com/PerformanC/ReZygisk/tree/add/new-webui)分支做出貢獻。請別忘了在 [TRANSLATOR.md](https://github.com/PerformanC/ReZygisk/blob/add/new-webui/TRANSLATOR.md) 中包含您的 GitHub 個人檔案,讓大家能夠看到您的貢獻。 46 | 47 | ## 支援 48 | 如有關於 ReZygisk 或其他 PerformanC 專案的任何問題,歡迎加入以下任一頻道: 49 | 50 | - Discord 頻道: [PerformanC](https://discord.gg/uPveNfTuCJ) 51 | - ReZygisk Telegram 頻道: [@rezygiskchat](https://t.me/rezygiskchat) 52 | - PerformanC Telegram 頻道: [@performancorg](https://t.me/performancorg) 53 | 54 | ## 貢獻 55 | 56 | 要為 ReZygisk 貢獻,必須遵循 PerformanC 的[貢獻指南](https://github.com/PerformanC/contributing),並遵守其安全政策、行為準則以及語法標準。 57 | 58 | ## 授權條款 59 | 60 | 在 ReZygisk 專案中,舊的 Zygisk Next 部分採用了 GPL 授權,而由 PerformanC 組織重寫的程式碼則採用 AGPL 3.0 授權。 61 | 62 | 您可以在[開放原始碼倡議(Open Source Initiative)](https://opensource.org/licenses/AGPL-3.0)上閱讀更多相關資訊。 63 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.gradle.LibraryExtension 2 | import java.io.ByteArrayOutputStream 3 | 4 | plugins { 5 | alias(libs.plugins.agp.lib) apply false 6 | } 7 | 8 | fun String.execute(currentWorkingDir: File = file("./")): String { 9 | val byteOut = ByteArrayOutputStream() 10 | project.exec { 11 | workingDir = currentWorkingDir 12 | commandLine = split("\\s".toRegex()) 13 | standardOutput = byteOut 14 | } 15 | return String(byteOut.toByteArray()).trim() 16 | } 17 | 18 | val gitCommitCount = "git rev-list HEAD --count".execute().toInt() 19 | val gitCommitHash = "git rev-parse --verify --short HEAD".execute() 20 | 21 | val moduleId by extra("zygisksu") 22 | val moduleName by extra("ReZygisk") 23 | val verName by extra("v1.0.0") 24 | val verCode by extra(gitCommitCount) 25 | val commitHash by extra(gitCommitHash) 26 | val minAPatchVersion by extra(10655) 27 | val minKsuVersion by extra(10940) 28 | val minKsudVersion by extra(11425) 29 | val maxKsuVersion by extra(20000) 30 | val minMagiskVersion by extra(26402) 31 | 32 | val androidMinSdkVersion by extra(26) 33 | val androidTargetSdkVersion by extra(34) 34 | val androidCompileSdkVersion by extra(34) 35 | val androidBuildToolsVersion by extra("34.0.0") 36 | val androidCompileNdkVersion by extra("27.2.12479018") 37 | val androidSourceCompatibility by extra(JavaVersion.VERSION_11) 38 | val androidTargetCompatibility by extra(JavaVersion.VERSION_11) 39 | 40 | tasks.register("Delete", Delete::class) { 41 | delete(rootProject.buildDir) 42 | } 43 | 44 | fun Project.configureBaseExtension() { 45 | extensions.findByType(LibraryExtension::class)?.run { 46 | namespace = "icu.nullptr.zygisk.next" 47 | compileSdk = androidCompileSdkVersion 48 | ndkVersion = androidCompileNdkVersion 49 | buildToolsVersion = androidBuildToolsVersion 50 | 51 | defaultConfig { 52 | minSdk = androidMinSdkVersion 53 | targetSdk = androidTargetSdkVersion 54 | } 55 | 56 | lint { 57 | abortOnError = true 58 | } 59 | } 60 | } 61 | 62 | subprojects { 63 | plugins.withId("com.android.library") { 64 | configureBaseExtension() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=false 2 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.2.0" 3 | kotlin = "1.9.22" 4 | 5 | [plugins] 6 | agp-lib = { id = "com.android.library", version.ref = "agp" } 7 | kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 8 | lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" } 9 | rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version = "0.9.3" } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JingMatrix/ReZygisk/0671f2e497140e40071f67e67481541def4258bb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /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 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /loader/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | import org.gradle.internal.os.OperatingSystem 3 | 4 | plugins { 5 | alias(libs.plugins.agp.lib) 6 | } 7 | 8 | val verCode: Int by rootProject.extra 9 | val verName: String by rootProject.extra 10 | val commitHash: String by rootProject.extra 11 | 12 | fun Project.findInPath(executable: String, property: String): String? { 13 | val pathEnv = System.getenv("PATH") 14 | return pathEnv.split(File.pathSeparator).map { folder -> 15 | Paths.get("${folder}${File.separator}${executable}${if (OperatingSystem.current().isWindows) ".exe" else ""}") 16 | .toFile() 17 | }.firstOrNull { path -> 18 | path.exists() 19 | }?.absolutePath ?: properties.getOrDefault(property, null) as? String? 20 | } 21 | 22 | val ccachePath by lazy { 23 | project.findInPath("ccache", "ccache.path")?.also { 24 | println("loader: Use ccache: $it") 25 | } 26 | } 27 | 28 | val defaultCFlags = arrayOf( 29 | "-Wall", "-Wextra", 30 | "-fno-rtti", "-fno-exceptions", 31 | "-fno-stack-protector", "-fomit-frame-pointer", 32 | "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__" 33 | ) 34 | 35 | val releaseFlags = arrayOf( 36 | "-Ofast", "-flto=thin", 37 | "-Wno-unused", "-Wno-unused-parameter", 38 | "-fvisibility=hidden", "-fvisibility-inlines-hidden", 39 | "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", 40 | "-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all" 41 | ) 42 | 43 | android { 44 | buildFeatures { 45 | androidResources = false 46 | buildConfig = false 47 | prefab = true 48 | } 49 | 50 | externalNativeBuild.cmake { 51 | path("src/CMakeLists.txt") 52 | buildStagingDirectory = layout.buildDirectory.get().asFile 53 | } 54 | 55 | defaultConfig { 56 | externalNativeBuild.cmake { 57 | arguments += "-DANDROID_STL=none" 58 | arguments += "-DLSPLT_STANDALONE=ON" 59 | cFlags("-std=c18", *defaultCFlags) 60 | cppFlags("-std=c++20", *defaultCFlags) 61 | ccachePath?.let { 62 | arguments += "-DNDK_CCACHE=$it" 63 | } 64 | } 65 | } 66 | 67 | buildTypes { 68 | debug { 69 | externalNativeBuild.cmake { 70 | arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug" 71 | } 72 | } 73 | release { 74 | externalNativeBuild.cmake { 75 | cFlags += releaseFlags 76 | cppFlags += releaseFlags 77 | arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release" 78 | } 79 | } 80 | } 81 | } 82 | 83 | dependencies { 84 | implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") 85 | } 86 | -------------------------------------------------------------------------------- /loader/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | project("loader") 3 | 4 | find_package(cxx REQUIRED CONFIG) 5 | 6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 7 | 8 | add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\") 9 | 10 | aux_source_directory(common COMMON_SRC_LIST) 11 | add_library(common STATIC ${COMMON_SRC_LIST}) 12 | target_include_directories(common PRIVATE include) 13 | target_link_libraries(common cxx::cxx log) 14 | 15 | aux_source_directory(injector INJECTOR_SRC_LIST) 16 | add_library(zygisk SHARED ${INJECTOR_SRC_LIST}) 17 | target_include_directories(zygisk PRIVATE include) 18 | target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap) 19 | 20 | aux_source_directory(ptracer PTRACER_SRC_LIST) 21 | add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST}) 22 | target_include_directories(libzygisk_ptrace.so PRIVATE include) 23 | target_link_libraries(libzygisk_ptrace.so cxx::cxx log common) 24 | 25 | add_subdirectory(external) 26 | -------------------------------------------------------------------------------- /loader/src/common/daemon.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "daemon.h" 10 | #include "dl.h" 11 | #include "socket_utils.h" 12 | 13 | namespace zygiskd { 14 | static std::string TMP_PATH; 15 | void Init(const char *path) { 16 | TMP_PATH = path; 17 | } 18 | 19 | std::string GetTmpPath() { 20 | return TMP_PATH; 21 | } 22 | 23 | int Connect(uint8_t retry) { 24 | retry++; 25 | 26 | int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); 27 | struct sockaddr_un addr = { 28 | .sun_family = AF_UNIX, 29 | .sun_path = { 0 } 30 | }; 31 | 32 | auto socket_path = TMP_PATH + kCPSocketName; 33 | strcpy(addr.sun_path, socket_path.c_str()); 34 | socklen_t socklen = sizeof(addr); 35 | 36 | while (--retry) { 37 | int r = connect(fd, (struct sockaddr *)&addr, socklen); 38 | if (r == 0) return fd; 39 | if (retry) { 40 | PLOGE("Retrying to connect to zygiskd, sleep 1s"); 41 | 42 | sleep(1); 43 | } 44 | } 45 | 46 | close(fd); 47 | 48 | return -1; 49 | } 50 | 51 | bool PingHeartbeat() { 52 | int fd = Connect(5); 53 | if (fd == -1) { 54 | PLOGE("Connect to zygiskd"); 55 | 56 | return false; 57 | } 58 | 59 | socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat); 60 | 61 | close(fd); 62 | 63 | return true; 64 | } 65 | 66 | int RequestLogcatFd() { 67 | int fd = Connect(1); 68 | if (fd == -1) { 69 | PLOGE("RequestLogcatFd"); 70 | 71 | return -1; 72 | } 73 | 74 | socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd); 75 | 76 | return fd; 77 | } 78 | 79 | uint32_t GetProcessFlags(uid_t uid) { 80 | int fd = Connect(1); 81 | if (fd == -1) { 82 | PLOGE("GetProcessFlags"); 83 | 84 | return 0; 85 | } 86 | 87 | socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags); 88 | socket_utils::write_u32(fd, uid); 89 | 90 | uint32_t res = socket_utils::read_u32(fd); 91 | 92 | close(fd); 93 | 94 | return res; 95 | } 96 | 97 | std::vector ReadModules() { 98 | std::vector modules; 99 | int fd = Connect(1); 100 | if (fd == -1) { 101 | PLOGE("ReadModules"); 102 | 103 | return modules; 104 | } 105 | 106 | socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules); 107 | size_t len = socket_utils::read_usize(fd); 108 | for (size_t i = 0; i < len; i++) { 109 | std::string name = socket_utils::read_string(fd); 110 | int module_fd = socket_utils::recv_fd(fd); 111 | modules.emplace_back(name, module_fd); 112 | } 113 | 114 | close(fd); 115 | 116 | return modules; 117 | } 118 | 119 | int ConnectCompanion(size_t index) { 120 | int fd = Connect(1); 121 | if (fd == -1) { 122 | PLOGE("ConnectCompanion"); 123 | 124 | return -1; 125 | } 126 | 127 | socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket); 128 | socket_utils::write_usize(fd, index); 129 | 130 | uint8_t res = socket_utils::read_u8(fd); 131 | 132 | if (res == 1) return fd; 133 | else { 134 | close(fd); 135 | 136 | return -1; 137 | } 138 | } 139 | 140 | int GetModuleDir(size_t index) { 141 | int fd = Connect(1); 142 | if (fd == -1) { 143 | PLOGE("GetModuleDir"); 144 | 145 | return -1; 146 | } 147 | 148 | socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir); 149 | socket_utils::write_usize(fd, index); 150 | int nfd = socket_utils::recv_fd(fd); 151 | 152 | close(fd); 153 | 154 | return nfd; 155 | } 156 | 157 | void ZygoteRestart() { 158 | int fd = Connect(1); 159 | if (fd == -1) { 160 | if (errno == ENOENT) LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)"); 161 | else PLOGE("Could not notify ZygoteRestart"); 162 | 163 | return; 164 | } 165 | 166 | if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) 167 | PLOGE("Failed to request ZygoteRestart"); 168 | 169 | close(fd); 170 | } 171 | 172 | void SystemServerStarted() { 173 | int fd = Connect(1); 174 | if (fd == -1) PLOGE("Failed to report system server started"); 175 | else { 176 | if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) 177 | PLOGE("Failed to report system server started"); 178 | } 179 | 180 | close(fd); 181 | } 182 | 183 | void GetInfo(struct zygote_info *info) { 184 | /* TODO: Optimize and avoid re-connect twice here */ 185 | int fd = Connect(1); 186 | 187 | if (fd != -1) { 188 | info->running = true; 189 | 190 | socket_utils::write_u8(fd, (uint8_t) SocketAction::GetInfo); 191 | 192 | int flags = socket_utils::read_u32(fd); 193 | 194 | if (flags & (1 << 27)) { 195 | info->root_impl = ZYGOTE_ROOT_IMPL_APATCH; 196 | } else if (flags & (1 << 29)) { 197 | info->root_impl = ZYGOTE_ROOT_IMPL_KERNELSU; 198 | } else if (flags & (1 << 30)) { 199 | info->root_impl = ZYGOTE_ROOT_IMPL_MAGISK; 200 | } else { 201 | info->root_impl = ZYGOTE_ROOT_IMPL_NONE; 202 | } 203 | 204 | info->pid = socket_utils::read_u32(fd); 205 | 206 | info->modules = (struct zygote_modules *)malloc(sizeof(struct zygote_modules)); 207 | if (info->modules == NULL) { 208 | info->modules->modules_count = 0; 209 | 210 | close(fd); 211 | 212 | return; 213 | } 214 | 215 | info->modules->modules_count = socket_utils::read_usize(fd); 216 | 217 | if (info->modules->modules_count == 0) { 218 | info->modules->modules = NULL; 219 | 220 | close(fd); 221 | 222 | return; 223 | } 224 | 225 | info->modules->modules = (char **)malloc(sizeof(char *) * info->modules->modules_count); 226 | if (info->modules->modules == NULL) { 227 | free(info->modules); 228 | info->modules = NULL; 229 | info->modules->modules_count = 0; 230 | 231 | close(fd); 232 | 233 | return; 234 | } 235 | 236 | for (size_t i = 0; i < info->modules->modules_count; i++) { 237 | /* INFO by ThePedroo: Ugly solution to read with std::string existance (temporary) */ 238 | std::string name = socket_utils::read_string(fd); 239 | 240 | char module_path[PATH_MAX]; 241 | snprintf(module_path, sizeof(module_path), "/data/adb/modules/%s/module.prop", name.c_str()); 242 | 243 | FILE *module_prop = fopen(module_path, "r"); 244 | if (module_prop == NULL) { 245 | info->modules->modules[i] = strdup(name.c_str()); 246 | } else { 247 | char line[1024]; 248 | while (fgets(line, sizeof(line), module_prop) != NULL) { 249 | if (strncmp(line, "name=", 5) == 0) { 250 | info->modules->modules[i] = strndup(line + 5, strlen(line) - 6); 251 | 252 | break; 253 | } 254 | } 255 | 256 | fclose(module_prop); 257 | } 258 | } 259 | 260 | close(fd); 261 | } else info->running = false; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /loader/src/common/dl.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "dl.h" 9 | #include "logging.h" 10 | 11 | extern "C" [[gnu::weak]] struct android_namespace_t* 12 | //NOLINTNEXTLINE 13 | __loader_android_create_namespace([[maybe_unused]] const char* name, 14 | [[maybe_unused]] const char* ld_library_path, 15 | [[maybe_unused]] const char* default_library_path, 16 | [[maybe_unused]] uint64_t type, 17 | [[maybe_unused]] const char* permitted_when_isolated_path, 18 | [[maybe_unused]] android_namespace_t* parent, 19 | [[maybe_unused]] const void* caller_addr); 20 | 21 | void* DlopenExt(const char* path, int flags) { 22 | auto info = android_dlextinfo{}; 23 | auto* dir = dirname(path); 24 | auto* ns = &__loader_android_create_namespace == nullptr ? nullptr : 25 | __loader_android_create_namespace(path, dir, nullptr, 26 | 2, /* ANDROID_NAMESPACE_TYPE_SHARED */ 27 | nullptr, nullptr, 28 | reinterpret_cast(&DlopenExt)); 29 | if (ns) { 30 | info.flags = ANDROID_DLEXT_USE_NAMESPACE; 31 | info.library_namespace = ns; 32 | 33 | LOGD("Open %s with namespace %p", path, ns); 34 | } else { 35 | LOGD("Cannot create namespace for %s", path); 36 | } 37 | 38 | auto* handle = android_dlopen_ext(path, flags, &info); 39 | if (handle) { 40 | LOGD("dlopen %s: %p", path, handle); 41 | } else { 42 | LOGE("dlopen %s: %s", path, dlerror()); 43 | } 44 | return handle; 45 | } 46 | 47 | void* DlopenMem(int fd, int flags) { 48 | auto info = android_dlextinfo{ 49 | .flags = ANDROID_DLEXT_USE_LIBRARY_FD, 50 | .library_fd = fd 51 | }; 52 | 53 | auto* handle = android_dlopen_ext("/jit-cache-zygisk", flags, &info); 54 | if (handle) { 55 | LOGV("dlopen fd %d: %p", fd, handle); 56 | } else { 57 | LOGE("dlopen fd %d: %s", fd, dlerror()); 58 | } 59 | return handle; 60 | } 61 | -------------------------------------------------------------------------------- /loader/src/common/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 "elf_util.h" 28 | 29 | using namespace SandHook; 30 | 31 | template 32 | inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) { 33 | return reinterpret_cast, T, T *>>( 34 | reinterpret_cast(head) + off); 35 | } 36 | 37 | ElfImg::ElfImg(std::string_view base_name) : elf(base_name) { 38 | if (!findModuleBase()) { 39 | base = nullptr; 40 | return; 41 | } 42 | 43 | //load elf 44 | int fd = open(elf.data(), O_RDONLY); 45 | if (fd < 0) { 46 | // LOGE("failed to open %s", elf.data()); 47 | return; 48 | } 49 | 50 | size = lseek(fd, 0, SEEK_END); 51 | if (size <= 0) { 52 | // LOGE("lseek() failed for %s", elf.data()); 53 | } 54 | 55 | header = reinterpret_cast(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); 56 | 57 | close(fd); 58 | 59 | section_header = offsetOf(header, header->e_shoff); 60 | 61 | auto shoff = reinterpret_cast(section_header); 62 | char *section_str = offsetOf(header, section_header[header->e_shstrndx].sh_offset); 63 | 64 | for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) { 65 | auto *section_h = (ElfW(Shdr) *) shoff; 66 | char *sname = section_h->sh_name + section_str; 67 | auto entsize = section_h->sh_entsize; 68 | switch (section_h->sh_type) { 69 | case SHT_DYNSYM: { 70 | if (bias == -4396) { 71 | dynsym = section_h; 72 | dynsym_offset = section_h->sh_offset; 73 | dynsym_start = offsetOf(header, dynsym_offset); 74 | } 75 | break; 76 | } 77 | case SHT_SYMTAB: { 78 | if (strcmp(sname, ".symtab") == 0) { 79 | symtab = section_h; 80 | symtab_offset = section_h->sh_offset; 81 | symtab_size = section_h->sh_size; 82 | symtab_count = symtab_size / entsize; 83 | symtab_start = offsetOf(header, symtab_offset); 84 | } 85 | break; 86 | } 87 | case SHT_STRTAB: { 88 | if (bias == -4396) { 89 | strtab = section_h; 90 | symstr_offset = section_h->sh_offset; 91 | strtab_start = offsetOf(header, symstr_offset); 92 | } 93 | if (strcmp(sname, ".strtab") == 0) { 94 | symstr_offset_for_symtab = section_h->sh_offset; 95 | } 96 | break; 97 | } 98 | case SHT_PROGBITS: { 99 | if (strtab == nullptr || dynsym == nullptr) break; 100 | if (bias == -4396) { 101 | bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset; 102 | } 103 | break; 104 | } 105 | case SHT_HASH: { 106 | auto *d_un = offsetOf(header, section_h->sh_offset); 107 | nbucket_ = d_un[0]; 108 | bucket_ = d_un + 2; 109 | chain_ = bucket_ + nbucket_; 110 | break; 111 | } 112 | case SHT_GNU_HASH: { 113 | auto *d_buf = reinterpret_cast(((size_t) header) + 114 | section_h->sh_offset); 115 | gnu_nbucket_ = d_buf[0]; 116 | gnu_symndx_ = d_buf[1]; 117 | gnu_bloom_size_ = d_buf[2]; 118 | gnu_shift2_ = d_buf[3]; 119 | gnu_bloom_filter_ = reinterpret_cast(d_buf + 4); 120 | gnu_bucket_ = reinterpret_cast(gnu_bloom_filter_ + 121 | gnu_bloom_size_); 122 | gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_; 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | 129 | ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const { 130 | if (nbucket_ == 0) return 0; 131 | 132 | char *strings = (char *) strtab_start; 133 | 134 | for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) { 135 | auto *sym = dynsym_start + n; 136 | if (name == strings + sym->st_name) { 137 | return sym->st_value; 138 | } 139 | } 140 | return 0; 141 | } 142 | 143 | ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const { 144 | static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8; 145 | 146 | if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0; 147 | 148 | auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_]; 149 | uintptr_t mask = 0 150 | | (uintptr_t) 1 << (hash % bloom_mask_bits) 151 | | (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits); 152 | if ((mask & bloom_word) == mask) { 153 | auto sym_index = gnu_bucket_[hash % gnu_nbucket_]; 154 | if (sym_index >= gnu_symndx_) { 155 | char *strings = (char *) strtab_start; 156 | do { 157 | auto *sym = dynsym_start + sym_index; 158 | if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0 159 | && name == strings + sym->st_name) { 160 | return sym->st_value; 161 | } 162 | } while ((gnu_chain_[sym_index++] & 1) == 0); 163 | } 164 | } 165 | return 0; 166 | } 167 | 168 | ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const { 169 | if (symtabs_.empty()) { 170 | symtabs_.reserve(symtab_count); 171 | if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { 172 | for (ElfW(Off) i = 0; i < symtab_count; i++) { 173 | unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); 174 | const char *st_name = offsetOf(header, symstr_offset_for_symtab + 175 | symtab_start[i].st_name); 176 | if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { 177 | symtabs_.emplace(st_name, &symtab_start[i]); 178 | } 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 | std::string_view ElfImg::LinearLookupByPrefix(std::string_view name) const { 191 | if (symtabs_.empty()) { 192 | symtabs_.reserve(symtab_count); 193 | if (symtab_start != nullptr && symstr_offset_for_symtab != 0) { 194 | for (ElfW(Off) i = 0; i < symtab_count; i++) { 195 | unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info); 196 | const char *st_name = offsetOf(header, symstr_offset_for_symtab + 197 | symtab_start[i].st_name); 198 | if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) { 199 | symtabs_.emplace(st_name, &symtab_start[i]); 200 | } 201 | } 202 | } 203 | } 204 | 205 | auto size = name.size(); 206 | for (auto symtab : symtabs_) { 207 | if (symtab.first.size() < size) continue; 208 | 209 | if (symtab.first.substr(0, size) == name) { 210 | return symtab.first; 211 | } 212 | } 213 | 214 | return ""; 215 | } 216 | 217 | 218 | ElfImg::~ElfImg() { 219 | //open elf file local 220 | if (buffer) { 221 | free(buffer); 222 | buffer = nullptr; 223 | } 224 | //use mmap 225 | if (header) { 226 | munmap(header, size); 227 | } 228 | } 229 | 230 | ElfW(Addr) ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const { 231 | if (auto offset = GnuLookup(name, gnu_hash); offset > 0) { 232 | // LOGD("found %s %p in %s in dynsym by gnuhash", name.data(), reinterpret_cast(offset), elf.data()); 233 | return offset; 234 | } else if (offset = ElfLookup(name, elf_hash); offset > 0) { 235 | // LOGD("found %s %p in %s in dynsym by elfhash", name.data(), reinterpret_cast(offset), elf.data()); 236 | return offset; 237 | } else if (offset = LinearLookup(name); offset > 0) { 238 | // LOGD("found %s %p in %s in symtab by linear lookup", name.data(), reinterpret_cast(offset), elf.data()); 239 | return offset; 240 | } else { 241 | return 0; 242 | } 243 | 244 | } 245 | 246 | bool ElfImg::findModuleBase() { 247 | dl_iterate_phdr([](struct dl_phdr_info *info, size_t size, void *data) -> int { 248 | (void) size; 249 | 250 | if ((info)->dlpi_name == nullptr) { 251 | return 0; 252 | } 253 | 254 | auto *self = reinterpret_cast(data); 255 | if (strstr(info->dlpi_name, self->elf.data())) { 256 | self->elf = info->dlpi_name; 257 | self->base = reinterpret_cast(info->dlpi_addr); 258 | return 1; 259 | } 260 | return 0; 261 | }, this); 262 | return base != 0; 263 | } 264 | -------------------------------------------------------------------------------- /loader/src/common/files.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "files.hpp" 4 | #include "misc.hpp" 5 | 6 | using namespace std::string_view_literals; 7 | 8 | void file_readline(bool trim, FILE *fp, const std::function &fn) { 9 | size_t len = 1024; 10 | char *buf = (char *) malloc(len); 11 | char *start; 12 | ssize_t read; 13 | while ((read = getline(&buf, &len, fp)) >= 0) { 14 | start = buf; 15 | if (trim) { 16 | while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos) 17 | --read; 18 | buf[read] = '\0'; 19 | while (*start == ' ') 20 | ++start; 21 | } 22 | if (!fn(start)) 23 | break; 24 | } 25 | free(buf); 26 | } 27 | 28 | void file_readline(bool trim, const char *file, const std::function &fn) { 29 | if (auto fp = open_file(file, "re")) 30 | file_readline(trim, fp.get(), fn); 31 | } 32 | void file_readline(const char *file, const std::function &fn) { 33 | file_readline(false, file, fn); 34 | } 35 | 36 | std::vector parse_mount_info(const char *pid) { 37 | char buf[PATH_MAX] = {}; 38 | snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid); 39 | std::vector result; 40 | 41 | file_readline(buf, [&result](std::string_view line) -> bool { 42 | int root_start = 0, root_end = 0; 43 | int target_start = 0, target_end = 0; 44 | int vfs_option_start = 0, vfs_option_end = 0; 45 | int type_start = 0, type_end = 0; 46 | int source_start = 0, source_end = 0; 47 | int fs_option_start = 0, fs_option_end = 0; 48 | int optional_start = 0, optional_end = 0; 49 | unsigned int id, parent, maj, min; 50 | sscanf(line.data(), 51 | "%u " // (1) id 52 | "%u " // (2) parent 53 | "%u:%u " // (3) maj:min 54 | "%n%*s%n " // (4) mountroot 55 | "%n%*s%n " // (5) target 56 | "%n%*s%n" // (6) vfs options (fs-independent) 57 | "%n%*[^-]%n - " // (7) optional fields 58 | "%n%*s%n " // (8) FS type 59 | "%n%*s%n " // (9) source 60 | "%n%*s%n", // (10) fs options (fs specific) 61 | &id, &parent, &maj, &min, &root_start, &root_end, &target_start, 62 | &target_end, &vfs_option_start, &vfs_option_end, 63 | &optional_start, &optional_end, &type_start, &type_end, 64 | &source_start, &source_end, &fs_option_start, &fs_option_end); 65 | 66 | auto root = line.substr(root_start, root_end - root_start); 67 | auto target = line.substr(target_start, target_end - target_start); 68 | auto vfs_option = 69 | line.substr(vfs_option_start, vfs_option_end - vfs_option_start); 70 | ++optional_start; 71 | --optional_end; 72 | auto optional = line.substr( 73 | optional_start, 74 | optional_end - optional_start > 0 ? optional_end - optional_start : 0); 75 | 76 | auto type = line.substr(type_start, type_end - type_start); 77 | auto source = line.substr(source_start, source_end - source_start); 78 | auto fs_option = 79 | line.substr(fs_option_start, fs_option_end - fs_option_start); 80 | 81 | unsigned int shared = 0; 82 | unsigned int master = 0; 83 | unsigned int propagate_from = 0; 84 | if (auto pos = optional.find("shared:"); pos != std::string_view::npos) { 85 | shared = parse_int(optional.substr(pos + 7)); 86 | } 87 | if (auto pos = optional.find("master:"); pos != std::string_view::npos) { 88 | master = parse_int(optional.substr(pos + 7)); 89 | } 90 | if (auto pos = optional.find("propagate_from:"); 91 | pos != std::string_view::npos) { 92 | propagate_from = parse_int(optional.substr(pos + 15)); 93 | } 94 | 95 | result.emplace_back(mount_info { 96 | .id = id, 97 | .parent = parent, 98 | .device = static_cast(makedev(maj, min)), 99 | .root {root}, 100 | .target {target}, 101 | .vfs_option {vfs_option}, 102 | .optional { 103 | .shared = shared, 104 | .master = master, 105 | .propagate_from = propagate_from, 106 | }, 107 | .type {type}, 108 | .source {source}, 109 | .fs_option {fs_option}, 110 | }); 111 | return true; 112 | }); 113 | return result; 114 | } 115 | 116 | sDIR make_dir(DIR *dp) { 117 | return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; }); 118 | } 119 | 120 | sFILE make_file(FILE *fp) { 121 | return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; }); 122 | } 123 | -------------------------------------------------------------------------------- /loader/src/common/logging.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "logging.h" 5 | #include "socket_utils.h" 6 | 7 | namespace logging { 8 | static int logfd = -1; 9 | 10 | void setfd(int fd) { 11 | close(logfd); 12 | logfd = fd; 13 | } 14 | 15 | int getfd() { 16 | return logfd; 17 | } 18 | 19 | void log(int prio, const char* tag, const char* fmt, ...) { 20 | if (logfd == -1) { 21 | va_list ap; 22 | va_start(ap, fmt); 23 | __android_log_vprint(prio, tag, fmt, ap); 24 | va_end(ap); 25 | } else { 26 | char buf[BUFSIZ]; 27 | va_list ap; 28 | va_start(ap, fmt); 29 | vsnprintf(buf, sizeof(buf), fmt, ap); 30 | va_end(ap); 31 | socket_utils::write_u8(logfd, prio); 32 | socket_utils::write_string(logfd, tag); 33 | socket_utils::write_string(logfd, buf); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /loader/src/common/misc.cpp: -------------------------------------------------------------------------------- 1 | #include "misc.hpp" 2 | 3 | int new_daemon_thread(thread_entry entry, void *arg) { 4 | pthread_t thread; 5 | pthread_attr_t attr; 6 | pthread_attr_init(&attr); 7 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 8 | errno = pthread_create(&thread, &attr, entry, arg); 9 | if (errno) { 10 | PLOGE("pthread_create"); 11 | } 12 | return errno; 13 | } 14 | 15 | int parse_int(std::string_view s) { 16 | int val = 0; 17 | for (char c : s) { 18 | if (!c) break; 19 | if (c > '9' || c < '0') 20 | return -1; 21 | val = val * 10 + c - '0'; 22 | } 23 | return val; 24 | } 25 | 26 | std::list split_str(std::string_view s, std::string_view delimiter) { 27 | std::list ret; 28 | size_t pos = 0; 29 | while (pos < s.size()) { 30 | auto next = s.find(delimiter, pos); 31 | if (next == std::string_view::npos) { 32 | ret.emplace_back(s.substr(pos)); 33 | break; 34 | } 35 | ret.emplace_back(s.substr(pos, next - pos)); 36 | pos = next + delimiter.size(); 37 | } 38 | return ret; 39 | } 40 | 41 | std::string join_str(const std::list& list, std::string_view delimiter) { 42 | std::string ret; 43 | for (auto& s : list) { 44 | if (!ret.empty()) 45 | ret += delimiter; 46 | ret += s; 47 | } 48 | return ret; 49 | } 50 | -------------------------------------------------------------------------------- /loader/src/common/socket_utils.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "socket_utils.h" 6 | 7 | namespace socket_utils { 8 | ssize_t xread(int fd, void* buf, size_t count) { 9 | size_t read_sz = 0; 10 | ssize_t ret; 11 | do { 12 | ret = read(fd, (std::byte*) buf + read_sz, count - read_sz); 13 | if (ret < 0) { 14 | if (errno == EINTR) continue; 15 | PLOGE("read"); 16 | return ret; 17 | } 18 | read_sz += ret; 19 | } while (read_sz != count && ret != 0); 20 | if (read_sz != count) { 21 | PLOGE("read (%zu != %zu)", count, read_sz); 22 | } 23 | return read_sz; 24 | } 25 | 26 | size_t xwrite(int fd, const void* buf, size_t count) { 27 | size_t write_sz = 0; 28 | ssize_t ret; 29 | do { 30 | ret = write(fd, (std::byte*) buf + write_sz, count - write_sz); 31 | if (ret < 0) { 32 | if (errno == EINTR) continue; 33 | PLOGE("write"); 34 | return write_sz; 35 | } 36 | write_sz += ret; 37 | } while (write_sz != count && ret != 0); 38 | if (write_sz != count) { 39 | PLOGE("write (%zu != %zu)", count, write_sz); 40 | } 41 | return write_sz; 42 | } 43 | 44 | ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) { 45 | int rec = recvmsg(sockfd, msg, flags); 46 | if (rec < 0) PLOGE("recvmsg"); 47 | return rec; 48 | } 49 | 50 | template 51 | inline T read_exact_or(int fd, T fail) { 52 | T res; 53 | return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail; 54 | } 55 | 56 | template 57 | inline bool write_exact(int fd, T val) { 58 | return sizeof(T) == xwrite(fd, &val, sizeof(T)); 59 | } 60 | 61 | uint8_t read_u8(int fd) { 62 | return read_exact_or(fd, 0); 63 | } 64 | 65 | uint32_t read_u32(int fd) { 66 | return read_exact_or(fd, 0); 67 | } 68 | 69 | size_t read_usize(int fd) { 70 | return read_exact_or(fd, 0); 71 | } 72 | 73 | bool write_usize(int fd, size_t val) { 74 | return write_exact(fd, val); 75 | } 76 | 77 | std::string read_string(int fd) { 78 | size_t len = read_usize(fd); 79 | 80 | char buf[len + 1]; 81 | xread(fd, buf, len); 82 | 83 | buf[len] = '\0'; 84 | 85 | return buf; 86 | } 87 | 88 | bool write_u8(int fd, uint8_t val) { 89 | return write_exact(fd, val); 90 | } 91 | 92 | void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) { 93 | iovec iov = { 94 | .iov_base = &cnt, 95 | .iov_len = sizeof(cnt), 96 | }; 97 | msghdr msg = { 98 | .msg_iov = &iov, 99 | .msg_iovlen = 1, 100 | .msg_control = cmsgbuf, 101 | .msg_controllen = bufsz 102 | }; 103 | 104 | xrecvmsg(sockfd, &msg, MSG_WAITALL); 105 | cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); 106 | 107 | if (msg.msg_controllen != bufsz || 108 | cmsg == nullptr || 109 | // TODO: pass from rust: 20, expected: 16 110 | // cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || 111 | cmsg->cmsg_level != SOL_SOCKET || 112 | cmsg->cmsg_type != SCM_RIGHTS) { 113 | return nullptr; 114 | } 115 | 116 | return CMSG_DATA(cmsg); 117 | } 118 | 119 | int recv_fd(int sockfd) { 120 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 121 | 122 | void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); 123 | if (data == nullptr) return -1; 124 | 125 | int result; 126 | memcpy(&result, data, sizeof(int)); 127 | return result; 128 | } 129 | 130 | bool write_u32(int fd, uint32_t val) { 131 | return write_exact(fd, val); 132 | } 133 | 134 | bool write_string(int fd, std::string_view str) { 135 | return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size()); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /loader/src/external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(external) 2 | 3 | OPTION(LSPLT_BUILD_SHARED OFF) 4 | add_subdirectory(lsplt/lsplt/src/main/jni) 5 | 6 | add_library(phmap INTERFACE) 7 | target_include_directories(phmap INTERFACE parallel-hashmap) 8 | target_compile_options(phmap INTERFACE -Wno-unused-value) 9 | -------------------------------------------------------------------------------- /loader/src/include/daemon.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__LP64__) 9 | # define LP_SELECT(lp32, lp64) lp64 10 | #else 11 | # define LP_SELECT(lp32, lp64) lp32 12 | #endif 13 | 14 | constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock"; 15 | 16 | class UniqueFd { 17 | using Fd = int; 18 | public: 19 | UniqueFd() = default; 20 | 21 | UniqueFd(Fd fd) : fd_(fd) {} 22 | 23 | ~UniqueFd() { if (fd_ >= 0) close(fd_); } 24 | 25 | // Disallow copy 26 | UniqueFd(const UniqueFd&) = delete; 27 | 28 | UniqueFd& operator=(const UniqueFd&) = delete; 29 | 30 | // Allow move 31 | UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); } 32 | 33 | UniqueFd& operator=(UniqueFd&& other) { 34 | std::swap(fd_, other.fd_); 35 | return *this; 36 | } 37 | 38 | // Implict cast to Fd 39 | operator const Fd&() const { return fd_; } 40 | 41 | private: 42 | Fd fd_ = -1; 43 | }; 44 | 45 | struct zygote_modules { 46 | char **modules; 47 | size_t modules_count; 48 | }; 49 | 50 | enum zygote_root_impl { 51 | ZYGOTE_ROOT_IMPL_NONE, 52 | ZYGOTE_ROOT_IMPL_APATCH, 53 | ZYGOTE_ROOT_IMPL_KERNELSU, 54 | ZYGOTE_ROOT_IMPL_MAGISK 55 | }; 56 | 57 | struct zygote_info { 58 | struct zygote_modules *modules; 59 | enum zygote_root_impl root_impl; 60 | pid_t pid; 61 | bool running; 62 | }; 63 | 64 | namespace zygiskd { 65 | 66 | struct Module { 67 | std::string name; 68 | UniqueFd memfd; 69 | 70 | inline explicit Module(std::string name, int memfd) : name(name), memfd(memfd) {} 71 | }; 72 | 73 | enum class SocketAction { 74 | PingHeartBeat, 75 | RequestLogcatFd, 76 | GetProcessFlags, 77 | GetInfo, 78 | ReadModules, 79 | RequestCompanionSocket, 80 | GetModuleDir, 81 | ZygoteRestart, 82 | SystemServerStarted, 83 | }; 84 | 85 | void Init(const char *path); 86 | 87 | std::string GetTmpPath(); 88 | 89 | bool PingHeartbeat(); 90 | 91 | int RequestLogcatFd(); 92 | 93 | std::vector ReadModules(); 94 | 95 | uint32_t GetProcessFlags(uid_t uid); 96 | 97 | int ConnectCompanion(size_t index); 98 | 99 | int GetModuleDir(size_t index); 100 | 101 | void ZygoteRestart(); 102 | 103 | void SystemServerStarted(); 104 | 105 | void GetInfo(struct zygote_info *info); 106 | } 107 | -------------------------------------------------------------------------------- /loader/src/include/dl.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void *DlopenExt(const char *path, int flags); 6 | 7 | void *DlopenMem(int memfd, int flags); 8 | -------------------------------------------------------------------------------- /loader/src/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 | 30 | #define SHT_GNU_HASH 0x6ffffff6 31 | 32 | namespace SandHook { 33 | class ElfImg { 34 | public: 35 | 36 | ElfImg(std::string_view elf); 37 | 38 | constexpr ElfW(Addr) getSymbOffset(std::string_view name) const { 39 | return getSymbOffset(name, GnuHash(name), ElfHash(name)); 40 | } 41 | 42 | constexpr ElfW(Addr) getSymbAddress(std::string_view name) const { 43 | ElfW(Addr) offset = getSymbOffset(name); 44 | if (offset > 0 && base != nullptr) { 45 | return static_cast((uintptr_t) base + offset - bias); 46 | } else { 47 | return 0; 48 | } 49 | } 50 | 51 | std::string_view findSymbolNameByPrefix(std::string_view prefix) const { 52 | return LinearLookupByPrefix(prefix); 53 | } 54 | 55 | template 56 | constexpr T getSymbAddress(std::string_view name) const { 57 | return reinterpret_cast(getSymbAddress(name)); 58 | } 59 | 60 | bool isValid() const { 61 | return base != nullptr; 62 | } 63 | 64 | const std::string name() const { 65 | return elf; 66 | } 67 | 68 | ~ElfImg(); 69 | 70 | private: 71 | ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const; 72 | 73 | ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const; 74 | 75 | ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const; 76 | 77 | ElfW(Addr) LinearLookup(std::string_view name) const; 78 | 79 | std::string_view LinearLookupByPrefix(std::string_view name) const; 80 | 81 | constexpr static uint32_t ElfHash(std::string_view name); 82 | 83 | constexpr static uint32_t GnuHash(std::string_view name); 84 | 85 | bool findModuleBase(); 86 | 87 | std::string elf; 88 | void *base = nullptr; 89 | char *buffer = nullptr; 90 | off_t size = 0; 91 | off_t bias = -4396; 92 | ElfW(Ehdr) *header = nullptr; 93 | ElfW(Shdr) *section_header = nullptr; 94 | ElfW(Shdr) *symtab = nullptr; 95 | ElfW(Shdr) *strtab = nullptr; 96 | ElfW(Shdr) *dynsym = nullptr; 97 | ElfW(Sym) *symtab_start = nullptr; 98 | ElfW(Sym) *dynsym_start = nullptr; 99 | ElfW(Sym) *strtab_start = nullptr; 100 | ElfW(Off) symtab_count = 0; 101 | ElfW(Off) symstr_offset = 0; 102 | ElfW(Off) symstr_offset_for_symtab = 0; 103 | ElfW(Off) symtab_offset = 0; 104 | ElfW(Off) dynsym_offset = 0; 105 | ElfW(Off) symtab_size = 0; 106 | 107 | uint32_t nbucket_{}; 108 | uint32_t *bucket_ = nullptr; 109 | uint32_t *chain_ = nullptr; 110 | 111 | uint32_t gnu_nbucket_{}; 112 | uint32_t gnu_symndx_{}; 113 | uint32_t gnu_bloom_size_; 114 | uint32_t gnu_shift2_; 115 | uintptr_t *gnu_bloom_filter_; 116 | uint32_t *gnu_bucket_; 117 | uint32_t *gnu_chain_; 118 | 119 | mutable std::unordered_map symtabs_; 120 | }; 121 | 122 | constexpr uint32_t ElfImg::ElfHash(std::string_view name) { 123 | uint32_t h = 0, g = 0; 124 | for (unsigned char p: name) { 125 | h = (h << 4) + p; 126 | g = h & 0xf0000000; 127 | h ^= g; 128 | h ^= g >> 24; 129 | } 130 | return h; 131 | } 132 | 133 | constexpr uint32_t ElfImg::GnuHash(std::string_view name) { 134 | uint32_t h = 5381; 135 | for (unsigned char p: name) { 136 | h += (h << 5) + p; 137 | } 138 | return h; 139 | } 140 | } 141 | 142 | #endif //SANDHOOK_ELF_UTIL_H -------------------------------------------------------------------------------- /loader/src/include/files.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct mount_info { 7 | unsigned int id; 8 | unsigned int parent; 9 | dev_t device; 10 | std::string root; 11 | std::string target; 12 | std::string vfs_option; 13 | struct { 14 | unsigned int shared; 15 | unsigned int master; 16 | unsigned int propagate_from; 17 | } optional; 18 | std::string type; 19 | std::string source; 20 | std::string fs_option; 21 | }; 22 | 23 | void file_readline(bool trim, FILE *fp, const std::function &fn); 24 | void file_readline(bool trim, const char *file, const std::function &fn); 25 | void file_readline(const char *file, const std::function &fn); 26 | 27 | std::vector parse_mount_info(const char *pid); 28 | 29 | using sFILE = std::unique_ptr; 30 | using sDIR = std::unique_ptr; 31 | sDIR make_dir(DIR *dp); 32 | sFILE make_file(FILE *fp); 33 | 34 | static inline sDIR open_dir(const char *path) { 35 | return make_dir(opendir(path)); 36 | } 37 | 38 | static inline sDIR xopen_dir(const char *path) { 39 | return make_dir(opendir(path)); 40 | } 41 | 42 | static inline sDIR xopen_dir(int dirfd) { 43 | return make_dir(fdopendir(dirfd)); 44 | } 45 | 46 | static inline sFILE open_file(const char *path, const char *mode) { 47 | return make_file(fopen(path, mode)); 48 | } 49 | 50 | static inline sFILE xopen_file(const char *path, const char *mode) { 51 | return make_file(fopen(path, mode)); 52 | } 53 | 54 | static inline sFILE xopen_file(int fd, const char *mode) { 55 | return make_file(fdopen(fd, mode)); 56 | } 57 | -------------------------------------------------------------------------------- /loader/src/include/logging.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef LOG_TAG 8 | #if defined(__LP64__) 9 | # define LOG_TAG "zygisk-core64" 10 | #else 11 | # define LOG_TAG "zygisk-core32" 12 | #endif 13 | #endif 14 | 15 | #ifndef NDEBUG 16 | #define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 17 | #define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 18 | #else 19 | #define LOGD(...) 20 | #define LOGV(...) 21 | #endif 22 | #define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 23 | #define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 24 | #define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 25 | #define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 26 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 27 | 28 | namespace logging { 29 | void setfd(int fd); 30 | 31 | int getfd(); 32 | 33 | [[gnu::format(printf, 3, 4)]] 34 | void log(int prio, const char* tag, const char* fmt, ...); 35 | } 36 | -------------------------------------------------------------------------------- /loader/src/include/misc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "logging.h" 10 | 11 | #define DISALLOW_COPY_AND_MOVE(clazz) \ 12 | clazz(const clazz &) = delete; \ 13 | clazz(clazz &&) = delete; 14 | 15 | class mutex_guard { 16 | DISALLOW_COPY_AND_MOVE(mutex_guard) 17 | public: 18 | explicit mutex_guard(pthread_mutex_t &m): mutex(&m) { 19 | pthread_mutex_lock(mutex); 20 | } 21 | void unlock() { 22 | pthread_mutex_unlock(mutex); 23 | mutex = nullptr; 24 | } 25 | ~mutex_guard() { 26 | if (mutex) pthread_mutex_unlock(mutex); 27 | } 28 | private: 29 | pthread_mutex_t *mutex; 30 | }; 31 | 32 | using thread_entry = void *(*)(void *); 33 | int new_daemon_thread(thread_entry entry, void *arg); 34 | 35 | static inline bool str_contains(std::string_view s, std::string_view ss) { 36 | return s.find(ss) != std::string_view::npos; 37 | } 38 | 39 | template 40 | class stateless_allocator { 41 | public: 42 | using value_type = T; 43 | T *allocate(size_t num) { return static_cast(Impl::allocate(sizeof(T) * num)); } 44 | void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); } 45 | stateless_allocator() = default; 46 | stateless_allocator(const stateless_allocator&) = default; 47 | stateless_allocator(stateless_allocator&&) = default; 48 | template 49 | stateless_allocator(const stateless_allocator&) {} 50 | bool operator==(const stateless_allocator&) { return true; } 51 | bool operator!=(const stateless_allocator&) { return false; } 52 | }; 53 | 54 | template 55 | class reversed_container { 56 | public: 57 | reversed_container(T &base) : base(base) {} 58 | decltype(std::declval().rbegin()) begin() { return base.rbegin(); } 59 | decltype(std::declval().crbegin()) begin() const { return base.crbegin(); } 60 | decltype(std::declval().crbegin()) cbegin() const { return base.crbegin(); } 61 | decltype(std::declval().rend()) end() { return base.rend(); } 62 | decltype(std::declval().crend()) end() const { return base.crend(); } 63 | decltype(std::declval().crend()) cend() const { return base.crend(); } 64 | private: 65 | T &base; 66 | }; 67 | 68 | template 69 | reversed_container reversed(T &base) { 70 | return reversed_container(base); 71 | } 72 | 73 | template 74 | static inline void default_new(T *&p) { p = new T(); } 75 | 76 | template 77 | static inline void default_new(std::unique_ptr &p) { p.reset(new T()); } 78 | 79 | struct StringCmp { 80 | using is_transparent = void; 81 | bool operator()(std::string_view a, std::string_view b) const { return a < b; } 82 | }; 83 | 84 | /* 85 | * Bionic's atoi runs through strtol(). 86 | * Use our own implementation for faster conversion. 87 | */ 88 | int parse_int(std::string_view s); 89 | 90 | std::list split_str(std::string_view s, std::string_view delimiter); 91 | 92 | std::string join_str(const std::list& list, std::string_view delimiter); 93 | 94 | template 95 | static inline T align_to(T v, int a) { 96 | static_assert(std::is_integral::value); 97 | return (v + a - 1) / a * a; 98 | } 99 | -------------------------------------------------------------------------------- /loader/src/include/native_bridge_callbacks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | template 7 | struct NativeBridgeCallbacks; 8 | 9 | template<> 10 | struct NativeBridgeCallbacks<__ANDROID_API_Q__> { 11 | [[maybe_unused]] uint32_t version; 12 | [[maybe_unused]] void *initialize; 13 | [[maybe_unused]] void *loadLibrary; 14 | [[maybe_unused]] void *getTrampoline; 15 | [[maybe_unused]] void *isSupported; 16 | [[maybe_unused]] void *getAppEnv; 17 | [[maybe_unused]] void *isCompatibleWith; 18 | [[maybe_unused]] void *getSignalHandler; 19 | [[maybe_unused]] void *unloadLibrary; 20 | [[maybe_unused]] void *getError; 21 | [[maybe_unused]] void *isPathSupported; 22 | [[maybe_unused]] void *initAnonymousNamespace; 23 | [[maybe_unused]] void *createNamespace; 24 | [[maybe_unused]] void *linkNamespaces; 25 | [[maybe_unused]] void *loadLibraryExt; 26 | [[maybe_unused]] void *getVendorNamespace; 27 | [[maybe_unused]] void *getExportedNamespace; 28 | }; 29 | 30 | template<> 31 | struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> { 32 | [[maybe_unused]] void *preZygoteFork; 33 | }; 34 | -------------------------------------------------------------------------------- /loader/src/include/socket_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "logging.h" 7 | 8 | namespace socket_utils { 9 | 10 | ssize_t xread(int fd, void *buf, size_t count); 11 | 12 | size_t xwrite(int fd, const void *buf, size_t count); 13 | 14 | uint8_t read_u8(int fd); 15 | 16 | uint32_t read_u32(int fd); 17 | 18 | size_t read_usize(int fd); 19 | 20 | std::string read_string(int fd); 21 | 22 | bool write_u8(int fd, uint8_t val); 23 | 24 | bool write_u32(int fd, uint32_t val); 25 | 26 | int recv_fd(int fd); 27 | 28 | bool write_usize(int fd, size_t val); 29 | 30 | bool write_string(int fd, std::string_view str); 31 | } 32 | -------------------------------------------------------------------------------- /loader/src/include/solist.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Original from https://github.com/LSPosed/NativeDetector/blob/master/app/src/main/jni/solist.cpp 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include "elf_util.h" 8 | #include "logging.h" 9 | 10 | namespace SoList { 11 | class SoInfo { 12 | public: 13 | #ifdef __LP64__ 14 | inline static size_t solist_size_offset = 0x18; 15 | inline static size_t solist_next_offset = 0x28; 16 | constexpr static size_t solist_realpath_offset = 0x1a8; 17 | #else 18 | inline static size_t solist_size_offset = 0x90; 19 | inline static size_t solist_next_offset = 0xa4; 20 | constexpr static size_t solist_realpath_offset = 0x174; 21 | #endif 22 | 23 | inline static const char *(*get_realpath_sym)(SoInfo *) = NULL; 24 | inline static const char *(*get_soname_sym)(SoInfo *) = NULL; 25 | inline static void (*soinfo_free)(SoInfo *) = NULL; 26 | 27 | inline SoInfo *get_next() { 28 | return *(SoInfo **) ((uintptr_t) this + solist_next_offset); 29 | } 30 | 31 | inline size_t get_size() { 32 | return *(size_t *) ((uintptr_t) this + solist_size_offset); 33 | } 34 | 35 | inline const char *get_path() { 36 | if (get_realpath_sym) return get_realpath_sym(this); 37 | 38 | return ((std::string *) ((uintptr_t) this + solist_realpath_offset))->c_str(); 39 | } 40 | 41 | inline const char *get_name() { 42 | if (get_soname_sym) return get_soname_sym(this); 43 | 44 | return ((std::string *) ((uintptr_t) this + solist_realpath_offset - sizeof(void *)))->c_str(); 45 | } 46 | 47 | void set_next(SoInfo *si) { 48 | *(SoInfo **) ((uintptr_t) this + solist_next_offset) = si; 49 | } 50 | 51 | void set_size(size_t size) { 52 | *(size_t *) ((uintptr_t) this + solist_size_offset) = size; 53 | } 54 | }; 55 | 56 | class ProtectedDataGuard { 57 | public: 58 | ProtectedDataGuard() { 59 | if (ctor != nullptr) 60 | (this->*ctor)(); 61 | } 62 | 63 | ~ProtectedDataGuard() { 64 | if (dtor != nullptr) 65 | (this->*dtor)(); 66 | } 67 | 68 | static bool setup(const SandHook::ElfImg &linker) { 69 | ctor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( 70 | "__dl__ZN18ProtectedDataGuardC2Ev")), .adj = 0}}.f; 71 | dtor = MemFunc{.data = {.p = reinterpret_cast(linker.getSymbAddress( 72 | "__dl__ZN18ProtectedDataGuardD2Ev")), .adj = 0}}.f; 73 | return ctor != nullptr && dtor != nullptr; 74 | } 75 | 76 | ProtectedDataGuard(const ProtectedDataGuard &) = delete; 77 | 78 | void operator=(const ProtectedDataGuard &) = delete; 79 | 80 | private: 81 | using FuncType = void (ProtectedDataGuard::*)(); 82 | 83 | inline static FuncType ctor = NULL; 84 | inline static FuncType dtor = NULL; 85 | 86 | union MemFunc { 87 | FuncType f; 88 | 89 | struct { 90 | void *p; 91 | std::ptrdiff_t adj; 92 | } data; 93 | }; 94 | }; 95 | 96 | 97 | static SoInfo *solist = NULL; 98 | static SoInfo *somain = NULL; 99 | static SoInfo **sonext = NULL; 100 | 101 | static uint64_t *g_module_load_counter = NULL; 102 | static uint64_t *g_module_unload_counter = NULL; 103 | 104 | static bool Initialize(); 105 | 106 | template 107 | inline T *getStaticPointer(const SandHook::ElfImg &linker, const char *name) { 108 | auto *addr = reinterpret_cast(linker.getSymbAddress(name)); 109 | 110 | return addr == NULL ? NULL : *addr; 111 | } 112 | 113 | static bool DropSoPath(const char* target_path) { 114 | bool path_found = false; 115 | if (solist == NULL && !Initialize()) { 116 | LOGE("Failed to initialize solist"); 117 | return path_found; 118 | } 119 | for (auto iter = solist; iter; iter = iter->get_next()) { 120 | if (iter->get_name() && iter->get_path() && strstr(iter->get_path(), target_path)) { 121 | SoList::ProtectedDataGuard guard; 122 | LOGI("dropping solist record for %s loaded at %s with size %zu", iter->get_name(), iter->get_path(), iter->get_size()); 123 | if (iter->get_size() > 0) { 124 | iter->set_size(0); 125 | SoInfo::soinfo_free(iter); 126 | path_found = true; 127 | } 128 | } 129 | } 130 | return path_found; 131 | } 132 | 133 | static void ResetCounters(size_t load, size_t unload) { 134 | if (solist == NULL && !Initialize()) { 135 | LOGE("Failed to initialize solist"); 136 | return; 137 | } 138 | if (g_module_load_counter == NULL || g_module_unload_counter == NULL) { 139 | LOGI("g_module counters not defined, skip reseting them"); 140 | return; 141 | } 142 | auto loaded_modules = *g_module_load_counter; 143 | auto unloaded_modules = *g_module_unload_counter; 144 | if (loaded_modules >= load) { 145 | *g_module_load_counter = loaded_modules - load; 146 | LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter); 147 | } 148 | if (unloaded_modules >= unload) { 149 | *g_module_unload_counter = unloaded_modules - unload; 150 | LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter); 151 | } 152 | } 153 | 154 | static bool Initialize() { 155 | SandHook::ElfImg linker("/linker"); 156 | if (!ProtectedDataGuard::setup(linker)) return false; 157 | LOGD("found symbol ProtectedDataGuard"); 158 | 159 | /* INFO: Since Android 15, the symbol names for the linker have a suffix, 160 | this makes it impossible to hardcode the symbol names. To allow 161 | this to work on all versions, we need to iterate over the loaded 162 | symbols and find the correct ones. 163 | 164 | See #63 for more information. 165 | */ 166 | 167 | std::string_view solist_sym_name = linker.findSymbolNameByPrefix("__dl__ZL6solist"); 168 | if (solist_sym_name.empty()) return false; 169 | LOGD("found symbol name %s", solist_sym_name.data()); 170 | 171 | std::string_view soinfo_free_name = linker.findSymbolNameByPrefix("__dl__ZL11soinfo_freeP6soinfo"); 172 | if (soinfo_free_name.empty()) return false; 173 | LOGD("found symbol name %s", soinfo_free_name.data()); 174 | 175 | /* INFO: The size isn't a magic number, it's the size for the string: .llvm.7690929523238822858 */ 176 | char llvm_sufix[25 + 1]; 177 | 178 | if (solist_sym_name.length() != strlen("__dl__ZL6solist")) { 179 | strncpy(llvm_sufix, solist_sym_name.data() + strlen("__dl__ZL6solist"), sizeof(llvm_sufix)); 180 | } else { 181 | llvm_sufix[0] = '\0'; 182 | } 183 | 184 | solist = getStaticPointer(linker, solist_sym_name.data()); 185 | if (solist == NULL) return false; 186 | LOGD("found symbol solist"); 187 | 188 | char somain_sym_name[sizeof("__dl__ZL6somain") + sizeof(llvm_sufix)]; 189 | snprintf(somain_sym_name, sizeof(somain_sym_name), "__dl__ZL6somain%s", llvm_sufix); 190 | 191 | char sonext_sym_name[sizeof("__dl__ZL6sonext") + sizeof(llvm_sufix)]; 192 | snprintf(sonext_sym_name, sizeof(somain_sym_name), "__dl__ZL6sonext%s", llvm_sufix); 193 | 194 | char vdso_sym_name[sizeof("__dl__ZL4vdso") + sizeof(llvm_sufix)]; 195 | snprintf(vdso_sym_name, sizeof(vdso_sym_name), "__dl__ZL4vdso%s", llvm_sufix); 196 | 197 | somain = getStaticPointer(linker, somain_sym_name); 198 | if (somain == NULL) return false; 199 | LOGD("found symbol somain"); 200 | 201 | sonext = linker.getSymbAddress(sonext_sym_name); 202 | if (sonext == NULL) return false; 203 | LOGD("found symbol sonext"); 204 | 205 | SoInfo *vdso = getStaticPointer(linker, vdso_sym_name); 206 | if (vdso != NULL) LOGD("found symbol vdso"); 207 | 208 | SoInfo::get_realpath_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo12get_realpathEv")); 209 | if (SoInfo::get_realpath_sym == NULL) return false; 210 | LOGD("found symbol get_realpath_sym"); 211 | 212 | SoInfo::get_soname_sym = reinterpret_cast(linker.getSymbAddress("__dl__ZNK6soinfo10get_sonameEv")); 213 | if (SoInfo::get_soname_sym == NULL) return false; 214 | LOGD("found symbol get_soname_sym"); 215 | 216 | SoInfo::soinfo_free = reinterpret_cast(linker.getSymbAddress(soinfo_free_name)); 217 | if (SoInfo::soinfo_free == NULL) return false; 218 | LOGD("found symbol soinfo_free"); 219 | 220 | g_module_load_counter = reinterpret_cast(linker.getSymbAddress("__dl__ZL21g_module_load_counter")); 221 | if (g_module_load_counter != NULL) LOGD("found symbol g_module_load_counter"); 222 | 223 | g_module_unload_counter = reinterpret_cast(linker.getSymbAddress("__dl__ZL23g_module_unload_counter")); 224 | if (g_module_unload_counter != NULL) LOGD("found symbol g_module_unload_counter"); 225 | 226 | for (size_t i = 0; i < 1024 / sizeof(void *); i++) { 227 | auto possible_field = (uintptr_t) solist + i * sizeof(void *); 228 | auto possible_size_of_somain = *(size_t *)((uintptr_t) somain + i * sizeof(void *)); 229 | if (possible_size_of_somain < 0x100000 && possible_size_of_somain > 0x100) { 230 | SoInfo::solist_size_offset = i * sizeof(void *); 231 | LOGD("solist_size_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_size_offset); 232 | } 233 | if (*(void **)possible_field == somain || (vdso != NULL && *(void **)possible_field == vdso)) { 234 | SoInfo::solist_next_offset = i * sizeof(void *); 235 | LOGD("solist_next_offset is %zu * %zu = %p", i, sizeof(void *), (void*) SoInfo::solist_next_offset); 236 | break; 237 | } 238 | } 239 | 240 | return true; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /loader/src/injector/art_method.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "jni_helper.hpp" 4 | 5 | template 6 | constexpr inline auto RoundUpTo(T v, size_t size) { 7 | return v + size - 1 - ((v + size - 1) & (size - 1)); 8 | } 9 | 10 | inline static constexpr auto kPointerSize = sizeof(void *); 11 | 12 | namespace lsplant::art { 13 | 14 | class ArtMethod { 15 | 16 | public: 17 | void *GetData() { 18 | return *reinterpret_cast(reinterpret_cast(this) + data_offset); 19 | } 20 | 21 | static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) { 22 | if (art_method_field) [[likely]] { 23 | return reinterpret_cast( 24 | JNI_GetLongField(env, method, art_method_field)); 25 | } else { 26 | return reinterpret_cast(env->FromReflectedMethod(method)); 27 | } 28 | } 29 | 30 | static bool Init(JNIEnv *env) { 31 | ScopedLocalRef executable{env, nullptr}; 32 | executable = JNI_FindClass(env, "java/lang/reflect/Executable"); 33 | if (!executable) { 34 | LOGE("Failed to found Executable"); 35 | return false; 36 | } 37 | 38 | if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J"); 39 | !art_method_field) { 40 | LOGE("Failed to find artMethod field"); 41 | return false; 42 | } 43 | 44 | auto throwable = JNI_FindClass(env, "java/lang/Throwable"); 45 | if (!throwable) { 46 | LOGE("Failed to found Executable"); 47 | return false; 48 | } 49 | auto clazz = JNI_FindClass(env, "java/lang/Class"); 50 | static_assert(std::is_same_v); 51 | jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors", 52 | "()[Ljava/lang/reflect/Constructor;"); 53 | const auto constructors = 54 | JNI_Cast(JNI_CallObjectMethod(env, throwable, get_declared_constructors)); 55 | if (constructors.size() < 2) { 56 | LOGE("Throwable has less than 2 constructors"); 57 | return false; 58 | } 59 | auto &first_ctor = constructors[0]; 60 | auto &second_ctor = constructors[1]; 61 | auto *first = FromReflectedMethod(env, first_ctor.get()); 62 | auto *second = FromReflectedMethod(env, second_ctor.get()); 63 | art_method_size = reinterpret_cast(second) - reinterpret_cast(first); 64 | LOGD("ArtMethod size: %zu", art_method_size); 65 | if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] { 66 | LOGW("ArtMethod size exceeds maximum assume. There may be something wrong."); 67 | } 68 | entry_point_offset = art_method_size - kPointerSize; 69 | data_offset = entry_point_offset - kPointerSize; 70 | LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset); 71 | LOGD("ArtMethod::data offset: %zu", data_offset); 72 | return true; 73 | } 74 | 75 | private: 76 | inline static jfieldID art_method_field = nullptr; 77 | inline static size_t art_method_size = 0; 78 | inline static size_t entry_point_offset = 0; 79 | inline static size_t data_offset = 0; 80 | }; 81 | 82 | } // namespace lsplant::art 83 | -------------------------------------------------------------------------------- /loader/src/injector/entry.cpp: -------------------------------------------------------------------------------- 1 | #include "daemon.h" 2 | #include "logging.h" 3 | #include "zygisk.hpp" 4 | 5 | using namespace std; 6 | 7 | void *start_addr = nullptr; 8 | size_t block_size = 0; 9 | 10 | extern "C" [[gnu::visibility("default")]] 11 | void entry(void* addr, size_t size, const char* path) { 12 | LOGI("Zygisk library injected, version %s", ZKSU_VERSION); 13 | start_addr = addr; 14 | block_size = size; 15 | zygiskd::Init(path); 16 | 17 | if (!zygiskd::PingHeartbeat()) { 18 | LOGE("Zygisk daemon is not running"); 19 | return; 20 | } 21 | 22 | #ifdef NDEBUG 23 | logging::setfd(zygiskd::RequestLogcatFd()); 24 | #endif 25 | 26 | LOGI("start plt hooking"); 27 | hook_functions(); 28 | clean_trace(path, 1, 0, false); 29 | } 30 | -------------------------------------------------------------------------------- /loader/src/injector/module.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "api.hpp" 6 | 7 | namespace { 8 | 9 | struct ZygiskContext; 10 | struct ZygiskModule; 11 | 12 | struct AppSpecializeArgs_v1; 13 | using AppSpecializeArgs_v2 = AppSpecializeArgs_v1; 14 | struct AppSpecializeArgs_v3; 15 | using AppSpecializeArgs_v4 = AppSpecializeArgs_v3; 16 | struct AppSpecializeArgs_v5; 17 | 18 | struct module_abi_v1; 19 | using module_abi_v2 = module_abi_v1; 20 | using module_abi_v3 = module_abi_v1; 21 | using module_abi_v4 = module_abi_v1; 22 | using module_abi_v5 = module_abi_v1; 23 | 24 | struct api_abi_v1; 25 | struct api_abi_v2; 26 | using api_abi_v3 = api_abi_v2; 27 | struct api_abi_v4; 28 | using api_abi_v5 = api_abi_v4; 29 | 30 | union ApiTable; 31 | 32 | struct AppSpecializeArgs_v3 { 33 | jint &uid; 34 | jint &gid; 35 | jintArray &gids; 36 | jint &runtime_flags; 37 | jobjectArray &rlimits; 38 | jint &mount_external; 39 | jstring &se_info; 40 | jstring &nice_name; 41 | jstring &instruction_set; 42 | jstring &app_data_dir; 43 | 44 | jintArray *fds_to_ignore = nullptr; 45 | jboolean *is_child_zygote = nullptr; 46 | jboolean *is_top_app = nullptr; 47 | jobjectArray *pkg_data_info_list = nullptr; 48 | jobjectArray *whitelisted_data_info_list = nullptr; 49 | jboolean *mount_data_dirs = nullptr; 50 | jboolean *mount_storage_dirs = nullptr; 51 | 52 | AppSpecializeArgs_v3( 53 | jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, 54 | jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name, 55 | jstring &instruction_set, jstring &app_data_dir) : 56 | uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), rlimits(rlimits), 57 | mount_external(mount_external), se_info(se_info), nice_name(nice_name), 58 | instruction_set(instruction_set), app_data_dir(app_data_dir) {} 59 | }; 60 | 61 | struct AppSpecializeArgs_v5 : public AppSpecializeArgs_v3 { 62 | jboolean *mount_sysprop_overrides = nullptr; 63 | 64 | AppSpecializeArgs_v5( 65 | jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, 66 | jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name, 67 | jstring &instruction_set, jstring &app_data_dir) : AppSpecializeArgs_v3( 68 | uid, gid, gids, runtime_flags, rlimits, mount_external, 69 | se_info, nice_name, instruction_set, app_data_dir) {} 70 | }; 71 | 72 | struct AppSpecializeArgs_v1 { 73 | jint &uid; 74 | jint &gid; 75 | jintArray &gids; 76 | jint &runtime_flags; 77 | jint &mount_external; 78 | jstring &se_info; 79 | jstring &nice_name; 80 | jstring &instruction_set; 81 | jstring &app_data_dir; 82 | 83 | jboolean *const is_child_zygote; 84 | jboolean *const is_top_app; 85 | jobjectArray *const pkg_data_info_list; 86 | jobjectArray *const whitelisted_data_info_list; 87 | jboolean *const mount_data_dirs; 88 | jboolean *const mount_storage_dirs; 89 | 90 | AppSpecializeArgs_v1(const AppSpecializeArgs_v5 *a) : 91 | uid(a->uid), gid(a->gid), gids(a->gids), runtime_flags(a->runtime_flags), 92 | mount_external(a->mount_external), se_info(a->se_info), nice_name(a->nice_name), 93 | instruction_set(a->instruction_set), app_data_dir(a->app_data_dir), 94 | is_child_zygote(a->is_child_zygote), is_top_app(a->is_top_app), 95 | pkg_data_info_list(a->pkg_data_info_list), 96 | whitelisted_data_info_list(a->whitelisted_data_info_list), 97 | mount_data_dirs(a->mount_data_dirs), mount_storage_dirs(a->mount_storage_dirs) {} 98 | }; 99 | 100 | struct ServerSpecializeArgs_v1 { 101 | jint &uid; 102 | jint &gid; 103 | jintArray &gids; 104 | jint &runtime_flags; 105 | jlong &permitted_capabilities; 106 | jlong &effective_capabilities; 107 | 108 | ServerSpecializeArgs_v1( 109 | jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, 110 | jlong &permitted_capabilities, jlong &effective_capabilities) : 111 | uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), 112 | permitted_capabilities(permitted_capabilities), 113 | effective_capabilities(effective_capabilities) {} 114 | }; 115 | 116 | struct module_abi_v1 { 117 | long api_version; 118 | void *impl; 119 | void (*preAppSpecialize)(void *, void *); 120 | void (*postAppSpecialize)(void *, const void *); 121 | void (*preServerSpecialize)(void *, void *); 122 | void (*postServerSpecialize)(void *, const void *); 123 | }; 124 | 125 | enum : uint32_t { 126 | PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT, 127 | PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST, 128 | 129 | PROCESS_IS_MANAGER = (1u << 28), 130 | PROCESS_ROOT_IS_APATCH = (1u << 27), 131 | PROCESS_ROOT_IS_KSU = (1u << 29), 132 | PROCESS_ROOT_IS_MAGISK = (1u << 30), 133 | PROCESS_IS_SYS_UI = (1u << 31), 134 | 135 | PRIVATE_MASK = PROCESS_IS_SYS_UI 136 | }; 137 | 138 | struct api_abi_base { 139 | ZygiskModule *impl; 140 | bool (*registerModule)(ApiTable *, long *); 141 | }; 142 | 143 | struct api_abi_v1 : public api_abi_base { 144 | /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); 145 | /* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **); 146 | /* 2 */ void (*pltHookExclude)(const char *, const char *); 147 | /* 3 */ bool (*pltHookCommit)(); 148 | /* 4 */ int (*connectCompanion)(ZygiskModule *); 149 | /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option); 150 | }; 151 | 152 | struct api_abi_v2 : public api_abi_v1 { 153 | /* 6 */ int (*getModuleDir)(ZygiskModule *); 154 | /* 7 */ uint32_t (*getFlags)(ZygiskModule *); 155 | }; 156 | 157 | struct api_abi_v4 : public api_abi_base { 158 | /* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int); 159 | /* 1 */ void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **); 160 | /* 2 */ bool (*exemptFd)(int); 161 | /* 3 */ bool (*pltHookCommit)(); 162 | /* 4 */ int (*connectCompanion)(ZygiskModule *); 163 | /* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option); 164 | /* 6 */ int (*getModuleDir)(ZygiskModule *); 165 | /* 7 */ uint32_t (*getFlags)(ZygiskModule *); 166 | }; 167 | 168 | union ApiTable { 169 | api_abi_base base; 170 | api_abi_v1 v1; 171 | api_abi_v2 v2; 172 | api_abi_v4 v4; 173 | }; 174 | 175 | #define call_app(method) \ 176 | switch (*mod.api_version) { \ 177 | case 1: \ 178 | case 2: { \ 179 | AppSpecializeArgs_v1 a(args); \ 180 | mod.v1->method(mod.v1->impl, &a); \ 181 | break; \ 182 | } \ 183 | case 3: \ 184 | case 4: \ 185 | case 5: \ 186 | mod.v1->method(mod.v1->impl, args);\ 187 | break; \ 188 | } 189 | 190 | struct ZygiskModule { 191 | 192 | void onLoad(void *env) { 193 | entry.fn(&api, env); 194 | } 195 | void preAppSpecialize(AppSpecializeArgs_v5 *args) const { 196 | call_app(preAppSpecialize) 197 | } 198 | void postAppSpecialize(const AppSpecializeArgs_v5 *args) const { 199 | call_app(postAppSpecialize) 200 | } 201 | void preServerSpecialize(ServerSpecializeArgs_v1 *args) const { 202 | mod.v1->preServerSpecialize(mod.v1->impl, args); 203 | } 204 | void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const { 205 | mod.v1->postServerSpecialize(mod.v1->impl, args); 206 | } 207 | 208 | bool valid() const; 209 | int connectCompanion() const; 210 | int getModuleDir() const; 211 | void setOption(zygisk::Option opt); 212 | static uint32_t getFlags(); 213 | bool tryUnload() const { return unload && dlclose(handle) == 0; }; 214 | void clearApi() { memset(&api, 0, sizeof(api)); } 215 | int getId() const { return id; } 216 | 217 | ZygiskModule(int id, void *handle, void *entry); 218 | 219 | static bool RegisterModuleImpl(ApiTable *api, long *module); 220 | 221 | private: 222 | const int id; 223 | bool unload = false; 224 | 225 | void * const handle; 226 | union { 227 | void * const ptr; 228 | void (* const fn)(void *, void *); 229 | } entry; 230 | 231 | ApiTable api; 232 | 233 | union { 234 | long *api_version; 235 | module_abi_v1 *v1; 236 | } mod; 237 | }; 238 | 239 | } // namespace 240 | -------------------------------------------------------------------------------- /loader/src/injector/unmount.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "files.hpp" 5 | #include "logging.h" 6 | #include "misc.hpp" 7 | #include "zygisk.hpp" 8 | 9 | using namespace std::string_view_literals; 10 | 11 | namespace { 12 | constexpr auto MODULE_DIR = "/data/adb/modules"; 13 | constexpr auto KSU_OVERLAY_SOURCE = "KSU"; 14 | constexpr auto AP_OVERLAY_SOURCE = "APatch"; 15 | const std::vector DEVICE_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"}; 16 | 17 | void lazy_unmount(const char* mountpoint) { 18 | if (umount2(mountpoint, MNT_DETACH) != -1) { 19 | LOGD("Unmounted (%s)", mountpoint); 20 | } else { 21 | #ifndef NDEBUG 22 | PLOGE("Unmount (%s)", mountpoint); 23 | #endif 24 | } 25 | } 26 | } 27 | 28 | void revert_unmount_ksu() { 29 | std::string ksu_loop; 30 | std::vector targets; 31 | 32 | // Unmount ksu module dir last 33 | targets.emplace_back(MODULE_DIR); 34 | 35 | for (auto& info: parse_mount_info("self")) { 36 | if (info.target == MODULE_DIR) { 37 | ksu_loop = info.source; 38 | continue; 39 | } 40 | // Unmount everything mounted to /data/adb 41 | if (info.target.starts_with("/data/adb")) { 42 | targets.emplace_back(info.target); 43 | } 44 | // Unmount ksu overlays 45 | if (info.type == "overlay" 46 | && info.source == KSU_OVERLAY_SOURCE 47 | && std::find(DEVICE_PARTITIONS.begin(), DEVICE_PARTITIONS.end(), info.target) != DEVICE_PARTITIONS.end()) { 48 | targets.emplace_back(info.target); 49 | } 50 | // Unmount temp dir 51 | if (info.type == "tmpfs" && info.source == KSU_OVERLAY_SOURCE) { 52 | targets.emplace_back(info.target); 53 | } 54 | } 55 | for (auto& info: parse_mount_info("self")) { 56 | // Unmount everything from ksu loop except ksu module dir 57 | if (info.source == ksu_loop && info.target != MODULE_DIR) { 58 | targets.emplace_back(info.target); 59 | } 60 | } 61 | 62 | // Do unmount 63 | for (auto& s: reversed(targets)) { 64 | lazy_unmount(s.data()); 65 | } 66 | } 67 | 68 | void revert_unmount_magisk() { 69 | std::vector targets; 70 | 71 | // Unmount dummy skeletons and MAGISKTMP 72 | // since mirror nodes are always mounted under skeleton, we don't have to specifically unmount 73 | for (auto& info: parse_mount_info("self")) { 74 | if (info.source == "magisk" || info.source == "worker" || // magisktmp tmpfs 75 | info.root.starts_with("/adb/modules")) { // bind mount from data partition 76 | targets.push_back(info.target); 77 | } 78 | // Unmount everything mounted to /data/adb 79 | if (info.target.starts_with("/data/adb")) { 80 | targets.emplace_back(info.target); 81 | } 82 | } 83 | 84 | for (auto& s: reversed(targets)) { 85 | lazy_unmount(s.data()); 86 | } 87 | } 88 | 89 | void revert_unmount_apatch() { 90 | std::string ap_loop; 91 | std::vector targets; 92 | 93 | // Unmount ksu module dir last 94 | targets.emplace_back(MODULE_DIR); 95 | 96 | for (auto& info: parse_mount_info("self")) { 97 | if (info.target == MODULE_DIR) { 98 | ap_loop = info.source; 99 | continue; 100 | } 101 | // Unmount everything mounted to /data/adb 102 | if (info.target.starts_with("/data/adb")) { 103 | targets.emplace_back(info.target); 104 | } 105 | // Unmount ksu overlays 106 | if (info.type == "overlay" 107 | && info.source == AP_OVERLAY_SOURCE 108 | && std::find(DEVICE_PARTITIONS.begin(), DEVICE_PARTITIONS.end(), info.target) != DEVICE_PARTITIONS.end()) { 109 | targets.emplace_back(info.target); 110 | } 111 | // Unmount temp dir 112 | if (info.type == "tmpfs" && info.source == AP_OVERLAY_SOURCE) { 113 | targets.emplace_back(info.target); 114 | } 115 | } 116 | for (auto& info: parse_mount_info("self")) { 117 | // Unmount everything from ksu loop except ksu module dir 118 | if (info.source == ap_loop && info.target != MODULE_DIR) { 119 | targets.emplace_back(info.target); 120 | } 121 | } 122 | 123 | // Do unmount 124 | for (auto& s: reversed(targets)) { 125 | lazy_unmount(s.data()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /loader/src/injector/zygisk.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern void *start_addr; 6 | extern size_t block_size; 7 | 8 | void hook_functions(); 9 | 10 | void clean_trace(const char* path, size_t load = 1, size_t unload = 0, bool spoof_maps = false); 11 | 12 | void revert_unmount_ksu(); 13 | 14 | void revert_unmount_magisk(); 15 | 16 | void revert_unmount_apatch(); 17 | -------------------------------------------------------------------------------- /loader/src/ptracer/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "monitor.h" 4 | #include "utils.hpp" 5 | #include "daemon.h" 6 | 7 | int main(int argc, char **argv) { 8 | zygiskd::Init("/data/adb/rezygisk"); 9 | 10 | printf("The ReZygisk Tracer %s\n\n", ZKSU_VERSION); 11 | 12 | if (argc >= 2 && strcmp(argv[1], "monitor") == 0) { 13 | init_monitor(); 14 | 15 | printf("[ReZygisk]: Started monitoring...\n"); 16 | 17 | return 0; 18 | } else if (argc >= 3 && strcmp(argv[1], "trace") == 0) { 19 | if (argc >= 4 && strcmp(argv[3], "--restart") == 0) zygiskd::ZygoteRestart(); 20 | 21 | long pid = strtol(argv[2], 0, 0); 22 | if (!trace_zygote(pid)) { 23 | kill(pid, SIGKILL); 24 | 25 | return 1; 26 | } 27 | 28 | printf("[ReZygisk]: Tracing %ld...\n", pid); 29 | 30 | return 0; 31 | } else if (argc >= 2 && strcmp(argv[1], "ctl") == 0) { 32 | enum Command command; 33 | 34 | if (strcmp(argv[2], "start") == 0) command = START; 35 | else if (strcmp(argv[2], "stop") == 0) command = STOP; 36 | else if (strcmp(argv[2], "exit") == 0) command = EXIT; 37 | else { 38 | printf("[ReZygisk]: Usage: %s ctl \n", argv[0]); 39 | 40 | return 1; 41 | } 42 | 43 | if (send_control_command(command) == -1) { 44 | printf("[ReZygisk]: Failed to send the command, is the daemon running?\n"); 45 | 46 | return 1; 47 | } 48 | 49 | printf("[ReZygisk]: command sent\n"); 50 | 51 | return 0; 52 | } else if (argc >= 2 && strcmp(argv[1], "version") == 0) { 53 | /* INFO: Noop*/ 54 | 55 | return 0; 56 | } else if (argc >= 2 && strcmp(argv[1], "info") == 0) { 57 | struct zygote_info info; 58 | zygiskd::GetInfo(&info); 59 | 60 | printf("Daemon process PID: %d\n", info.pid); 61 | 62 | switch (info.root_impl) { 63 | case ZYGOTE_ROOT_IMPL_NONE: { 64 | printf("Root implementation: none\n"); 65 | 66 | break; 67 | } 68 | case ZYGOTE_ROOT_IMPL_APATCH: { 69 | printf("Root implementation: APatch\n"); 70 | 71 | break; 72 | } 73 | case ZYGOTE_ROOT_IMPL_KERNELSU: { 74 | printf("Root implementation: KernelSU\n"); 75 | 76 | break; 77 | } 78 | case ZYGOTE_ROOT_IMPL_MAGISK: { 79 | printf("Root implementation: Magisk\n"); 80 | 81 | break; 82 | } 83 | } 84 | 85 | if (info.modules->modules_count != 0) { 86 | printf("Modules: %zu\n", info.modules->modules_count); 87 | 88 | for (size_t i = 0; i < info.modules->modules_count; i++) { 89 | printf(" - %s\n", info.modules->modules[i]); 90 | 91 | free(info.modules->modules[i]); 92 | } 93 | 94 | free(info.modules->modules); 95 | } else { 96 | printf("Modules: N/A\n"); 97 | } 98 | 99 | return 0; 100 | } else { 101 | printf( 102 | "Available commands:\n" 103 | " - monitor\n" 104 | " - trace [--restart]\n" 105 | " - ctl \n" 106 | " - version: Shows the version of ReZygisk.\n" 107 | " - info: Shows information about the created daemon/injection.\n" 108 | "\n" 109 | "<...>: Obligatory\n" 110 | "[...]: Optional\n"); 111 | 112 | return 1; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /loader/src/ptracer/monitor.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_HPP 2 | #define MAIN_HPP 3 | 4 | #include 5 | 6 | void init_monitor(); 7 | 8 | bool trace_zygote(int pid); 9 | 10 | enum Command { 11 | START = 1, 12 | STOP = 2, 13 | EXIT = 3, 14 | 15 | /* sent from daemon */ 16 | ZYGOTE64_INJECTED = 4, 17 | ZYGOTE32_INJECTED = 5, 18 | DAEMON64_SET_INFO = 6, 19 | DAEMON32_SET_INFO = 7, 20 | DAEMON64_SET_ERROR_INFO = 8, 21 | DAEMON32_SET_ERROR_INFO = 9, 22 | SYSTEM_SERVER_STARTED = 10 23 | }; 24 | 25 | int send_control_command(enum Command cmd); 26 | 27 | #endif /* MAIN_HPP */ -------------------------------------------------------------------------------- /loader/src/ptracer/ptracer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "utils.hpp" 18 | 19 | bool inject_on_main(int pid, const char *lib_path) { 20 | LOGI("injecting %s to zygote %d", lib_path, pid); 21 | 22 | /* 23 | parsing KernelArgumentBlock 24 | 25 | https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/KernelArgumentBlock.h;l=30;drc=6d1ee77ee32220e4202c3066f7e1f69572967ad8 26 | */ 27 | 28 | struct user_regs_struct regs {}, 29 | backup {}; 30 | 31 | /* WARNING: C++ keyword */ 32 | std::vector map = MapInfo::Scan(std::to_string(pid)); 33 | if (!get_regs(pid, regs)) return false; 34 | 35 | uintptr_t arg = (uintptr_t)regs.REG_SP; 36 | 37 | LOGV("kernel argument %" PRIxPTR " %s", arg, get_addr_mem_region(map, arg).c_str()); 38 | 39 | int argc; 40 | char **argv = (char **)((uintptr_t *)arg + 1); 41 | LOGV("argv %p", (void *)argv); 42 | 43 | read_proc(pid, arg, &argc, sizeof(argc)); 44 | LOGV("argc %d", argc); 45 | 46 | /* WARNING: C++ keyword */ 47 | auto envp = argv + argc + 1; 48 | LOGV("envp %p", (void *)envp); 49 | 50 | /* WARNING: C++ keyword */ 51 | auto p = envp; 52 | while (1) { 53 | uintptr_t *buf; 54 | read_proc(pid, (uintptr_t)p, &buf, sizeof(buf)); 55 | 56 | if (buf == NULL) break; 57 | 58 | /* TODO: Why ++p? */ 59 | p++; 60 | } 61 | 62 | /* TODO: Why ++p? */ 63 | p++; 64 | 65 | ElfW(auxv_t) *auxv = (ElfW(auxv_t) *)p; 66 | LOGV("auxv %p %s", auxv, get_addr_mem_region(map, (uintptr_t) auxv).c_str()); 67 | 68 | ElfW(auxv_t) *v = auxv; 69 | uintptr_t entry_addr = 0; 70 | uintptr_t addr_of_entry_addr = 0; 71 | 72 | while (1) { 73 | ElfW(auxv_t) buf; 74 | 75 | read_proc(pid, (uintptr_t)v, &buf, sizeof(buf)); 76 | 77 | if (buf.a_type == AT_ENTRY) { 78 | entry_addr = (uintptr_t)buf.a_un.a_val; 79 | addr_of_entry_addr = (uintptr_t)v + offsetof(ElfW(auxv_t), a_un); 80 | 81 | LOGV("entry address %" PRIxPTR " %s (entry=%" PRIxPTR ", entry_addr=%" PRIxPTR ")", entry_addr, 82 | get_addr_mem_region(map, entry_addr).c_str(), (uintptr_t)v, addr_of_entry_addr); 83 | 84 | break; 85 | } 86 | 87 | if (buf.a_type == AT_NULL) break; 88 | 89 | v++; 90 | } 91 | 92 | if (entry_addr == 0) { 93 | LOGE("failed to get entry"); 94 | 95 | return false; 96 | } 97 | 98 | /* 99 | Replace the program entry with an invalid address 100 | For arm32 compatibility, we set the last bit to the same as the entry address 101 | */ 102 | 103 | /* INFO: (-0x0F & ~1) is a value below zero, while the one after "|" 104 | is an unsigned (must be 0 or greater) value, so we must 105 | cast the second value to signed long (intptr_t) to avoid 106 | undefined behavior. 107 | */ 108 | uintptr_t break_addr = (uintptr_t)((intptr_t)(-0x0F & ~1) | (intptr_t)((uintptr_t)entry_addr & 1)); 109 | if (!write_proc(pid, (uintptr_t)addr_of_entry_addr, &break_addr, sizeof(break_addr))) return false; 110 | 111 | ptrace(PTRACE_CONT, pid, 0, 0); 112 | 113 | int status; 114 | wait_for_trace(pid, &status, __WALL); 115 | if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) { 116 | if (!get_regs(pid, regs)) return false; 117 | 118 | if (((int)regs.REG_IP & ~1) != ((int)break_addr & ~1)) { 119 | LOGE("stopped at unknown addr %p", (void *) regs.REG_IP); 120 | 121 | return false; 122 | } 123 | 124 | /* The linker has been initialized now, we can do dlopen */ 125 | LOGD("stopped at entry"); 126 | 127 | /* restore entry address */ 128 | if (!write_proc(pid, (uintptr_t) addr_of_entry_addr, &entry_addr, sizeof(entry_addr))) return false; 129 | 130 | /* backup registers */ 131 | memcpy(&backup, ®s, sizeof(regs)); 132 | 133 | /* WARNING: C++ keyword */ 134 | map = MapInfo::Scan(std::to_string(pid)); 135 | 136 | /* WARNING: C++ keyword */ 137 | std::vector local_map = MapInfo::Scan(); 138 | void *libc_return_addr = find_module_return_addr(map, "libc.so"); 139 | LOGD("libc return addr %p", libc_return_addr); 140 | 141 | /* call dlopen */ 142 | void *dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen"); 143 | if (dlopen_addr == NULL) return false; 144 | 145 | /* WARNING: C++ keyword */ 146 | std::vector args; 147 | 148 | /* WARNING: C++ keyword */ 149 | uintptr_t str = push_string(pid, regs, lib_path); 150 | 151 | args.clear(); 152 | args.push_back((long) str); 153 | args.push_back((long) RTLD_NOW); 154 | 155 | uintptr_t remote_handle = remote_call(pid, regs, (uintptr_t)dlopen_addr, (uintptr_t)libc_return_addr, args); 156 | LOGD("remote handle %p", (void *)remote_handle); 157 | if (remote_handle == 0) { 158 | LOGE("handle is null"); 159 | 160 | /* call dlerror */ 161 | void *dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror"); 162 | if (dlerror_addr == NULL) { 163 | LOGE("find dlerror"); 164 | 165 | return false; 166 | } 167 | 168 | args.clear(); 169 | 170 | uintptr_t dlerror_str_addr = remote_call(pid, regs, (uintptr_t)dlerror_addr, (uintptr_t)libc_return_addr, args); 171 | LOGD("dlerror str %p", (void*) dlerror_str_addr); 172 | if (dlerror_str_addr == 0) return false; 173 | 174 | void *strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen"); 175 | if (strlen_addr == NULL) { 176 | LOGE("find strlen"); 177 | 178 | return false; 179 | } 180 | 181 | args.clear(); 182 | args.push_back(dlerror_str_addr); 183 | 184 | uintptr_t dlerror_len = remote_call(pid, regs, (uintptr_t)strlen_addr, (uintptr_t)libc_return_addr, args); 185 | if (dlerror_len <= 0) { 186 | LOGE("dlerror len <= 0"); 187 | 188 | return false; 189 | } 190 | 191 | /* NOTICE: C++ -> C */ 192 | char *err = (char *)malloc((dlerror_len + 1) * sizeof(char)); 193 | if (err == NULL) { 194 | LOGE("malloc err"); 195 | 196 | return false; 197 | } 198 | 199 | read_proc(pid, dlerror_str_addr, err, dlerror_len + 1); 200 | 201 | LOGE("dlerror info %s", err); 202 | 203 | free(err); 204 | 205 | return false; 206 | } 207 | 208 | /* call dlsym(handle, "entry") */ 209 | void *dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym"); 210 | if (dlsym_addr == NULL) return false; 211 | 212 | args.clear(); 213 | str = push_string(pid, regs, "entry"); 214 | args.push_back(remote_handle); 215 | args.push_back((long) str); 216 | 217 | uintptr_t injector_entry = remote_call(pid, regs, (uintptr_t)dlsym_addr, (uintptr_t)libc_return_addr, args); 218 | LOGD("injector entry %p", (void *)injector_entry); 219 | if (injector_entry == 0) { 220 | LOGE("injector entry is null"); 221 | 222 | return false; 223 | } 224 | 225 | /* record the address range of libzygisk.so */ 226 | map = MapInfo::Scan(std::to_string(pid)); 227 | void *start_addr = nullptr; 228 | size_t block_size = 0; 229 | for (auto &info : map) { 230 | if (strstr(info.path.c_str(), "libzygisk.so")) { 231 | void *addr = (void *)info.start; 232 | if (start_addr == nullptr) start_addr = addr; 233 | size_t size = info.end - info.start; 234 | block_size += size; 235 | LOGD("found block %s: [%p-%p] with size %zu", info.path.c_str(), addr, (void *)info.end, size); 236 | } 237 | } 238 | 239 | /* call injector entry(start_addr, block_size, path) */ 240 | args.clear(); 241 | args.push_back((uintptr_t) start_addr); 242 | args.push_back(block_size); 243 | str = push_string(pid, regs, zygiskd::GetTmpPath().c_str()); 244 | args.push_back((long) str); 245 | 246 | remote_call(pid, regs, injector_entry, (uintptr_t)libc_return_addr, args); 247 | 248 | /* reset pc to entry */ 249 | backup.REG_IP = (long) entry_addr; 250 | LOGD("invoke entry"); 251 | 252 | /* restore registers */ 253 | if (!set_regs(pid, backup)) return false; 254 | 255 | return true; 256 | } else { 257 | char status_str[64]; 258 | parse_status(status, status_str, sizeof(status_str)); 259 | 260 | LOGE("stopped by other reason: %s", status_str); 261 | } 262 | 263 | return false; 264 | } 265 | 266 | #define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event)) 267 | #define WAIT_OR_DIE wait_for_trace(pid, &status, __WALL); 268 | #define CONT_OR_DIE \ 269 | if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) { \ 270 | PLOGE("cont"); \ 271 | \ 272 | return false; \ 273 | } 274 | 275 | bool trace_zygote(int pid) { 276 | LOGI("start tracing %d (tracer %d)", pid, getpid()); 277 | 278 | int status; 279 | 280 | if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL) == -1) { 281 | PLOGE("seize"); 282 | 283 | return false; 284 | } 285 | 286 | WAIT_OR_DIE 287 | 288 | if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) { 289 | /* WARNING: C++ keyword */ 290 | std::string lib_path = zygiskd::GetTmpPath(); 291 | lib_path += "/lib" LP_SELECT("", "64") "/libzygisk.so"; 292 | 293 | if (!inject_on_main(pid, lib_path.c_str())) { 294 | LOGE("failed to inject"); 295 | 296 | return false; 297 | } 298 | 299 | LOGD("inject done, continue process"); 300 | if (kill(pid, SIGCONT)) { 301 | PLOGE("kill"); 302 | 303 | return false; 304 | } 305 | 306 | CONT_OR_DIE 307 | WAIT_OR_DIE 308 | 309 | if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP)) { 310 | CONT_OR_DIE 311 | WAIT_OR_DIE 312 | 313 | if (STOPPED_WITH(SIGCONT, 0)) { 314 | LOGD("received SIGCONT"); 315 | 316 | ptrace(PTRACE_DETACH, pid, 0, SIGCONT); 317 | } 318 | } else { 319 | char status_str[64]; 320 | parse_status(status, status_str, sizeof(status_str)); 321 | 322 | LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", status_str); 323 | 324 | ptrace(PTRACE_DETACH, pid, 0, 0); 325 | 326 | return false; 327 | } 328 | } else { 329 | char status_str[64]; 330 | parse_status(status, status_str, sizeof(status_str)); 331 | 332 | LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", status_str); 333 | 334 | ptrace(PTRACE_DETACH, pid, 0, 0); 335 | 336 | return false; 337 | } 338 | 339 | return true; 340 | } 341 | -------------------------------------------------------------------------------- /loader/src/ptracer/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include "daemon.h" 7 | 8 | #ifdef __LP64__ 9 | #define LOG_TAG "zygisk-ptrace64" 10 | #else 11 | #define LOG_TAG "zygisk-ptrace32" 12 | #endif 13 | 14 | #include "logging.h" 15 | 16 | struct MapInfo { 17 | /// \brief The start address of the memory region. 18 | uintptr_t start; 19 | /// \brief The end address of the memory region. 20 | uintptr_t end; 21 | /// \brief The permissions of the memory region. This is a bit mask of the following values: 22 | /// - PROT_READ 23 | /// - PROT_WRITE 24 | /// - PROT_EXEC 25 | uint8_t perms; 26 | /// \brief Whether the memory region is private. 27 | bool is_private; 28 | /// \brief The offset of the memory region. 29 | uintptr_t offset; 30 | /// \brief The device number of the memory region. 31 | /// Major can be obtained by #major() 32 | /// Minor can be obtained by #minor() 33 | dev_t dev; 34 | /// \brief The inode number of the memory region. 35 | ino_t inode; 36 | /// \brief The path of the memory region. 37 | std::string path; 38 | 39 | /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. 40 | /// This is useful to find out the inode of the library to hook. 41 | /// \return A list of \ref MapInfo entries. 42 | static std::vector Scan(const std::string& pid = "self"); 43 | }; 44 | 45 | #if defined(__x86_64__) 46 | #define REG_SP rsp 47 | #define REG_IP rip 48 | #define REG_RET rax 49 | #elif defined(__i386__) 50 | #define REG_SP esp 51 | #define REG_IP eip 52 | #define REG_RET eax 53 | #elif defined(__aarch64__) 54 | #define REG_SP sp 55 | #define REG_IP pc 56 | #define REG_RET regs[0] 57 | #elif defined(__arm__) 58 | #define REG_SP uregs[13] 59 | #define REG_IP uregs[15] 60 | #define REG_RET uregs[0] 61 | #define user_regs_struct user_regs 62 | #endif 63 | 64 | ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len); 65 | 66 | ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len); 67 | 68 | bool get_regs(int pid, struct user_regs_struct ®s); 69 | 70 | bool set_regs(int pid, struct user_regs_struct ®s); 71 | 72 | std::string get_addr_mem_region(std::vector &info, uintptr_t addr); 73 | 74 | void *find_module_base(std::vector &info, std::string_view suffix); 75 | 76 | void *find_func_addr( 77 | std::vector &local_info, 78 | std::vector &remote_info, 79 | std::string_view module, 80 | std::string_view func); 81 | 82 | void align_stack(struct user_regs_struct ®s, long preserve = 0); 83 | 84 | uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str); 85 | 86 | uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, 87 | std::vector &args); 88 | 89 | int fork_dont_care(); 90 | 91 | void wait_for_trace(int pid, int* status, int flags); 92 | 93 | void parse_status(int status, char *buf, size_t len); 94 | 95 | #define WPTEVENT(x) (x >> 16) 96 | 97 | #define CASE_CONST_RETURN(x) case x: return #x; 98 | 99 | inline const char* parse_ptrace_event(int status) { 100 | status = status >> 16; 101 | switch (status) { 102 | CASE_CONST_RETURN(PTRACE_EVENT_FORK) 103 | CASE_CONST_RETURN(PTRACE_EVENT_VFORK) 104 | CASE_CONST_RETURN(PTRACE_EVENT_CLONE) 105 | CASE_CONST_RETURN(PTRACE_EVENT_EXEC) 106 | CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE) 107 | CASE_CONST_RETURN(PTRACE_EVENT_EXIT) 108 | CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP) 109 | CASE_CONST_RETURN(PTRACE_EVENT_STOP) 110 | default: 111 | return "(no event)"; 112 | } 113 | } 114 | 115 | inline const char* sigabbrev_np(int sig) { 116 | if (sig > 0 && sig < NSIG) return sys_signame[sig]; 117 | return "(unknown)"; 118 | } 119 | 120 | int get_program(int pid, char *buf, size_t size); 121 | void *find_module_return_addr(std::vector &info, std::string_view suffix); 122 | 123 | // pid = 0, fd != nullptr -> set to fd 124 | // pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd 125 | bool switch_mnt_ns(int pid, int *fd); 126 | -------------------------------------------------------------------------------- /module/.gitignore: -------------------------------------------------------------------------------- 1 | public_key 2 | private_key 3 | -------------------------------------------------------------------------------- /module/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import android.databinding.tool.ext.capitalizeUS 2 | import java.security.MessageDigest 3 | import org.apache.tools.ant.filters.ReplaceTokens 4 | 5 | import org.apache.tools.ant.filters.FixCrLfFilter 6 | 7 | import org.apache.commons.codec.binary.Hex 8 | import java.nio.ByteBuffer 9 | import java.nio.ByteOrder 10 | import java.security.KeyFactory 11 | import java.security.KeyPairGenerator 12 | import java.security.Signature 13 | import java.security.interfaces.EdECPrivateKey 14 | import java.security.interfaces.EdECPublicKey 15 | import java.security.spec.EdECPrivateKeySpec 16 | import java.security.spec.NamedParameterSpec 17 | import java.util.TreeSet 18 | 19 | plugins { 20 | alias(libs.plugins.agp.lib) 21 | } 22 | 23 | val moduleId: String by rootProject.extra 24 | val moduleName: String by rootProject.extra 25 | val verCode: Int by rootProject.extra 26 | val verName: String by rootProject.extra 27 | val minAPatchVersion: Int by rootProject.extra 28 | val minKsuVersion: Int by rootProject.extra 29 | val minKsudVersion: Int by rootProject.extra 30 | val maxKsuVersion: Int by rootProject.extra 31 | val minMagiskVersion: Int by rootProject.extra 32 | val commitHash: String by rootProject.extra 33 | 34 | android.buildFeatures { 35 | androidResources = false 36 | buildConfig = false 37 | } 38 | 39 | androidComponents.onVariants { variant -> 40 | val variantLowered = variant.name.lowercase() 41 | val variantCapped = variant.name.capitalizeUS() 42 | val buildTypeLowered = variant.buildType?.lowercase() 43 | 44 | val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered") 45 | val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-') 46 | 47 | val prepareModuleFilesTask = task("prepareModuleFiles$variantCapped") { 48 | group = "module" 49 | dependsOn( 50 | ":loader:assemble$variantCapped", 51 | ":zygiskd:buildAndStrip", 52 | ) 53 | into(moduleDir) 54 | from("${rootProject.projectDir}/README.md") 55 | from("$projectDir/src") { 56 | exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "uninstall.sh", "mazoku") 57 | filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 58 | } 59 | from("$projectDir/src") { 60 | include("module.prop") 61 | expand( 62 | "moduleId" to moduleId, 63 | "moduleName" to moduleName, 64 | "versionName" to "$verName ($verCode-$commitHash-$variantLowered)", 65 | "versionCode" to verCode 66 | ) 67 | } 68 | from("$projectDir/src/mazoku") 69 | from("$projectDir/src") { 70 | include("customize.sh", "post-fs-data.sh", "service.sh", "uninstall.sh") 71 | val tokens = mapOf( 72 | "DEBUG" to if (buildTypeLowered == "debug") "true" else "false", 73 | "MIN_APATCH_VERSION" to "$minAPatchVersion", 74 | "MIN_KSU_VERSION" to "$minKsuVersion", 75 | "MIN_KSUD_VERSION" to "$minKsudVersion", 76 | "MAX_KSU_VERSION" to "$maxKsuVersion", 77 | "MIN_MAGISK_VERSION" to "$minMagiskVersion", 78 | ) 79 | filter("tokens" to tokens) 80 | filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) 81 | } 82 | into("bin") { 83 | from(project(":zygiskd").layout.buildDirectory.getAsFile().get()) 84 | include("**/zygiskd") 85 | } 86 | into("lib") { 87 | from(project(":loader").layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/out/lib")) 88 | } 89 | 90 | val root = moduleDir.get() 91 | 92 | doLast { 93 | if (file("private_key").exists()) { 94 | println("=== Guards the peace of Machikado ===") 95 | val privateKey = file("private_key").readBytes() 96 | val publicKey = file("public_key").readBytes() 97 | val namedSpec = NamedParameterSpec("ed25519") 98 | val privKeySpec = EdECPrivateKeySpec(namedSpec, privateKey) 99 | val kf = KeyFactory.getInstance("ed25519") 100 | val privKey = kf.generatePrivate(privKeySpec); 101 | val sig = Signature.getInstance("ed25519") 102 | fun File.sha(realFile: File? = null) { 103 | sig.update(this.name.toByteArray()) 104 | sig.update(0) // null-terminated string 105 | val real = realFile ?: this 106 | val buffer = ByteBuffer.allocate(8) 107 | .order(ByteOrder.LITTLE_ENDIAN) 108 | .putLong(real.length()) 109 | .array() 110 | sig.update(buffer) 111 | real.forEachBlock { bytes, size -> 112 | sig.update(bytes, 0, size) 113 | } 114 | } 115 | 116 | fun getSign(name: String, abi32: String, abi64: String) { 117 | val set = TreeSet> { o1, o2 -> 118 | o1.first.path.replace("\\", "/") 119 | .compareTo(o2.first.path.replace("\\", "/")) 120 | } 121 | set.add(Pair(root.file("module.prop").asFile, null)) 122 | set.add(Pair(root.file("sepolicy.rule").asFile, null)) 123 | set.add(Pair(root.file("post-fs-data.sh").asFile, null)) 124 | set.add(Pair(root.file("service.sh").asFile, null)) 125 | set.add(Pair(root.file("mazoku").asFile, null)) 126 | set.add( 127 | Pair( 128 | root.file("lib/libzygisk.so").asFile, 129 | root.file("lib/$abi32/libzygisk.so").asFile 130 | ) 131 | ) 132 | set.add( 133 | Pair( 134 | root.file("lib64/libzygisk.so").asFile, 135 | root.file("lib/$abi64/libzygisk.so").asFile 136 | ) 137 | ) 138 | set.add( 139 | Pair( 140 | root.file("bin/zygisk-ptrace32").asFile, 141 | root.file("lib/$abi32/libzygisk_ptrace.so").asFile 142 | ) 143 | ) 144 | set.add( 145 | Pair( 146 | root.file("bin/zygisk-ptrace64").asFile, 147 | root.file("lib/$abi64/libzygisk_ptrace.so").asFile 148 | ) 149 | ) 150 | set.add( 151 | Pair( 152 | root.file("bin/zygiskd32").asFile, 153 | root.file("bin/$abi32/zygiskd").asFile 154 | ) 155 | ) 156 | set.add( 157 | Pair( 158 | root.file("bin/zygiskd64").asFile, 159 | root.file("bin/$abi64/zygiskd").asFile 160 | ) 161 | ) 162 | sig.initSign(privKey) 163 | set.forEach { it.first.sha(it.second) } 164 | val signFile = root.file(name).asFile 165 | signFile.writeBytes(sig.sign()) 166 | signFile.appendBytes(publicKey) 167 | } 168 | 169 | getSign("machikado.arm", "armeabi-v7a", "arm64-v8a") 170 | getSign("machikado.x86", "x86", "x86_64") 171 | } else { 172 | println("no private_key found, this build will not be signed") 173 | root.file("machikado.arm").asFile.createNewFile() 174 | root.file("machikado.x86").asFile.createNewFile() 175 | } 176 | 177 | fileTree(moduleDir).visit { 178 | if (isDirectory) return@visit 179 | val md = MessageDigest.getInstance("SHA-256") 180 | file.forEachBlock(4096) { bytes, size -> 181 | md.update(bytes, 0, size) 182 | } 183 | file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest())) 184 | } 185 | } 186 | } 187 | 188 | val zipTask = task("zip$variantCapped") { 189 | group = "module" 190 | dependsOn(prepareModuleFilesTask) 191 | archiveFileName.set(zipFileName) 192 | destinationDirectory.set(layout.buildDirectory.file("outputs/release").get().asFile) 193 | from(moduleDir) 194 | } 195 | 196 | val pushTask = task("push$variantCapped") { 197 | group = "module" 198 | dependsOn(zipTask) 199 | commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp") 200 | } 201 | 202 | val installKsuTask = task("installKsu$variantCapped") { 203 | group = "module" 204 | dependsOn(pushTask) 205 | doLast { 206 | exec { 207 | commandLine( 208 | "adb", "shell", "echo", 209 | "/data/adb/ksud module install /data/local/tmp/$zipFileName", 210 | "> /data/local/tmp/install.sh" 211 | ) 212 | } 213 | exec { commandLine("adb", "shell", "chmod", "755", "/data/local/tmp/install.sh") } 214 | exec { commandLine("adb", "shell", "su", "-c", "/data/local/tmp/install.sh") } 215 | } 216 | } 217 | 218 | val installAPatchTask = task("installAPatch$variantCapped") { 219 | group = "module" 220 | dependsOn(pushTask) 221 | commandLine("adb", "shell", "su", "-c", "/data/adb/apd module install /data/local/tmp/$zipFileName") 222 | } 223 | 224 | val installMagiskTask = task("installMagisk$variantCapped") { 225 | group = "module" 226 | dependsOn(pushTask) 227 | commandLine("adb", "shell", "su", "-M", "-c", "magisk --install-module /data/local/tmp/$zipFileName") 228 | } 229 | 230 | task("installAPatchAndReboot$variantCapped") { 231 | group = "module" 232 | dependsOn(installAPatchTask) 233 | commandLine("adb", "reboot") 234 | } 235 | 236 | task("installKsuAndReboot$variantCapped") { 237 | group = "module" 238 | dependsOn(installKsuTask) 239 | commandLine("adb", "reboot") 240 | } 241 | 242 | task("installMagiskAndReboot$variantCapped") { 243 | group = "module" 244 | dependsOn(installMagiskTask) 245 | commandLine("adb", "reboot") 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /module/src/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 -------------------------------------------------------------------------------- /module/src/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /module/src/customize.sh: -------------------------------------------------------------------------------- 1 | # shellcheck disable=SC2034 2 | SKIPUNZIP=1 3 | 4 | DEBUG=@DEBUG@ 5 | MIN_KSU_VERSION=@MIN_KSU_VERSION@ 6 | MIN_KSUD_VERSION=@MIN_KSUD_VERSION@ 7 | MAX_KSU_VERSION=@MAX_KSU_VERSION@ 8 | MIN_MAGISK_VERSION=@MIN_MAGISK_VERSION@ 9 | MIN_APATCH_VERSION=@MIN_APATCH_VERSION@ 10 | 11 | if [ "$BOOTMODE" ] && [ "$KSU" ]; then 12 | ui_print "- Installing from KernelSU app" 13 | ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)" 14 | if ! [ "$KSU_KERNEL_VER_CODE" ] || [ "$KSU_KERNEL_VER_CODE" -lt "$MIN_KSU_VERSION" ]; then 15 | ui_print "*********************************************************" 16 | ui_print "! KernelSU version is too old!" 17 | ui_print "! Please update KernelSU to latest version" 18 | abort "*********************************************************" 19 | elif [ "$KSU_KERNEL_VER_CODE" -ge "$MAX_KSU_VERSION" ]; then 20 | ui_print "*********************************************************" 21 | ui_print "! KernelSU version abnormal!" 22 | ui_print "! Please integrate KernelSU into your kernel" 23 | ui_print " as submodule instead of copying the source code" 24 | abort "*********************************************************" 25 | fi 26 | if ! [ "$KSU_VER_CODE" ] || [ "$KSU_VER_CODE" -lt "$MIN_KSUD_VERSION" ]; then 27 | ui_print "*********************************************************" 28 | ui_print "! ksud version is too old!" 29 | ui_print "! Please update KernelSU Manager to latest version" 30 | abort "*********************************************************" 31 | fi 32 | if [ "$(which magisk)" ]; then 33 | ui_print "*********************************************************" 34 | ui_print "! Multiple root implementation is NOT supported!" 35 | ui_print "! Please uninstall Magisk before installing ReZygisk" 36 | abort "*********************************************************" 37 | fi 38 | elif [ "$BOOTMODE" ] && [ "$APATCH" ]; then 39 | ui_print "- Installing from APatch app" 40 | if ! [ "$APATCH_VER_CODE" ] || [ "$APATCH_VER_CODE" -lt "$MIN_APATCH_VERSION" ]; then 41 | ui_print "*********************************************************" 42 | ui_print "! APatch version is too old!" 43 | ui_print "! Please update APatch to latest version" 44 | abort "*********************************************************" 45 | fi 46 | elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then 47 | ui_print "- Installing from Magisk app" 48 | if [ "$MAGISK_VER_CODE" -lt "$MIN_MAGISK_VERSION" ]; then 49 | ui_print "*********************************************************" 50 | ui_print "! Magisk version is too old!" 51 | ui_print "! Please update Magisk to latest version" 52 | abort "*********************************************************" 53 | fi 54 | else 55 | ui_print "*********************************************************" 56 | ui_print "! Install from recovery is not supported" 57 | ui_print "! Please install from KernelSU or Magisk app" 58 | abort "*********************************************************" 59 | fi 60 | 61 | VERSION=$(grep_prop version "${TMPDIR}/module.prop") 62 | ui_print "- Installing ReZygisk $VERSION" 63 | 64 | # check android 65 | if [ "$API" -lt 26 ]; then 66 | ui_print "! Unsupported sdk: $API" 67 | abort "! Minimal supported sdk is 26 (Android 8.0)" 68 | else 69 | ui_print "- Device sdk: $API" 70 | fi 71 | 72 | # check architecture 73 | if [ "$ARCH" != "arm" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "x86" ] && [ "$ARCH" != "x64" ]; then 74 | abort "! Unsupported platform: $ARCH" 75 | else 76 | ui_print "- Device platform: $ARCH" 77 | fi 78 | 79 | ui_print "- Extracting verify.sh" 80 | unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2 81 | if [ ! -f "$TMPDIR/verify.sh" ]; then 82 | ui_print "*********************************************************" 83 | ui_print "! Unable to extract verify.sh!" 84 | ui_print "! This zip may be corrupted, please try downloading again" 85 | abort "*********************************************************" 86 | fi 87 | . "$TMPDIR/verify.sh" 88 | extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip" 89 | extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip" 90 | extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR" 91 | 92 | if [ "$KSU" ]; then 93 | ui_print "- Checking SELinux patches" 94 | if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then 95 | ui_print "*********************************************************" 96 | ui_print "! Unable to apply SELinux patches!" 97 | ui_print "! Your kernel may not support SELinux patch fully" 98 | abort "*********************************************************" 99 | fi 100 | fi 101 | 102 | ui_print "- Extracting module files" 103 | extract "$ZIPFILE" 'module.prop' "$MODPATH" 104 | extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH" 105 | extract "$ZIPFILE" 'service.sh' "$MODPATH" 106 | extract "$ZIPFILE" 'uninstall.sh' "$MODPATH" 107 | extract "$ZIPFILE" 'mazoku' "$MODPATH" 108 | mv "$TMPDIR/sepolicy.rule" "$MODPATH" 109 | 110 | mkdir "$MODPATH/bin" 111 | mkdir "$MODPATH/lib" 112 | mkdir "$MODPATH/lib64" 113 | 114 | if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then 115 | ui_print "- Extracting x86 libraries" 116 | extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true 117 | mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" 118 | extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true 119 | extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true 120 | mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32" 121 | 122 | ui_print "- Extracting x64 libraries" 123 | extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true 124 | mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" 125 | extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true 126 | extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true 127 | mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64" 128 | 129 | extract "$ZIPFILE" 'machikado.x86' "$MODPATH" true 130 | mv "$MODPATH/machikado.x86" "$MODPATH/machikado" 131 | else 132 | ui_print "- Extracting arm libraries" 133 | extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true 134 | mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" 135 | extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true 136 | extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true 137 | mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32" 138 | 139 | ui_print "- Extracting arm64 libraries" 140 | extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true 141 | mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" 142 | extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true 143 | extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true 144 | mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64" 145 | 146 | extract "$ZIPFILE" 'machikado.arm' "$MODPATH" true 147 | mv "$MODPATH/machikado.arm" "$MODPATH/machikado" 148 | fi 149 | 150 | ui_print "- Setting permissions" 151 | set_perm_recursive "$MODPATH/bin" 0 0 0755 0755 152 | set_perm_recursive "$MODPATH/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 153 | set_perm_recursive "$MODPATH/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0 154 | 155 | # If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control 156 | HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable) 157 | if [ "$HUAWEI_MAPLE_ENABLED" == "1" ]; then 158 | ui_print "- Add ro.maple.enable=0" 159 | echo "ro.maple.enable=0" >>"$MODPATH/system.prop" 160 | fi 161 | -------------------------------------------------------------------------------- /module/src/mazoku: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JingMatrix/ReZygisk/0671f2e497140e40071f67e67481541def4258bb/module/src/mazoku -------------------------------------------------------------------------------- /module/src/module.prop: -------------------------------------------------------------------------------- 1 | id=${moduleId} 2 | name=${moduleName} 3 | version=${versionName} 4 | versionCode=${versionCode} 5 | author=The PerformanC Organization 6 | description=Standalone implementation of Zygisk. 7 | -------------------------------------------------------------------------------- /module/src/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | MODDIR=${0%/*} 4 | if [ "$ZYGISK_ENABLED" ]; then 5 | exit 0 6 | fi 7 | 8 | cd "$MODDIR" 9 | 10 | if [ "$(which magisk)" ]; then 11 | for file in ../*; do 12 | if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then 13 | if [ -f "$file/post-fs-data.sh" ]; then 14 | cd "$file" 15 | log -p i -t "zygisk-sh" "Manually trigger post-fs-data.sh for $file" 16 | sh "$(realpath ./post-fs-data.sh)" 17 | cd "$MODDIR" 18 | fi 19 | fi 20 | done 21 | fi 22 | 23 | create_sys_perm() { 24 | mkdir -p $1 25 | chmod 555 $1 26 | chcon u:object_r:system_file:s0 $1 27 | } 28 | 29 | export TMP_PATH=/data/adb/rezygisk 30 | 31 | if [ -d $TMP_PATH ]; then 32 | rm -rf $TMP_PATH 33 | fi 34 | 35 | create_sys_perm $TMP_PATH 36 | 37 | if [ -f $MODDIR/lib64/libzygisk.so ];then 38 | create_sys_perm $TMP_PATH/lib64 39 | cp $MODDIR/lib64/libzygisk.so $TMP_PATH/lib64/libzygisk.so 40 | chcon u:object_r:system_file:s0 $TMP_PATH/lib64/libzygisk.so 41 | fi 42 | 43 | if [ -f $MODDIR/lib/libzygisk.so ];then 44 | create_sys_perm $TMP_PATH/lib 45 | cp $MODDIR/lib/libzygisk.so $TMP_PATH/lib/libzygisk.so 46 | chcon u:object_r:system_file:s0 $TMP_PATH/lib/libzygisk.so 47 | fi 48 | 49 | [ "$DEBUG" = true ] && export RUST_BACKTRACE=1 50 | ./bin/zygisk-ptrace64 monitor & 51 | -------------------------------------------------------------------------------- /module/src/sepolicy.rule: -------------------------------------------------------------------------------- 1 | allow zygote tmpfs file * 2 | allow zygote appdomain_tmpfs file * 3 | 4 | type magisk_file file_type 5 | typeattribute magisk_file mlstrustedobject 6 | 7 | allow * magisk_file file * 8 | allow * magisk_file dir * 9 | allow * magisk_file fifo_file * 10 | allow * magisk_file chr_file * 11 | allow * magisk_file lnk_file * 12 | allow * magisk_file sock_file * 13 | 14 | allow system_server system_server process execmem 15 | allow zygote zygote process execmem 16 | 17 | allow zygote adb_data_file dir search 18 | allow zygote mnt_vendor_file dir search 19 | allow zygote system_file dir mounton 20 | allow zygote labeledfs filesystem mount 21 | allow zygote fs_type filesystem unmount 22 | -------------------------------------------------------------------------------- /module/src/service.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | DEBUG=@DEBUG@ 4 | 5 | MODDIR=${0%/*} 6 | if [ "$ZYGISK_ENABLED" ]; then 7 | exit 0 8 | fi 9 | 10 | cd "$MODDIR" 11 | 12 | if [ "$(which magisk)" ]; then 13 | for file in ../*; do 14 | if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then 15 | if [ -f "$file/service.sh" ]; then 16 | cd "$file" 17 | log -p i -t "zygisk-sh" "Manually trigger service.sh for $file" 18 | sh "$(realpath ./service.sh)" & 19 | cd "$MODDIR" 20 | fi 21 | fi 22 | done 23 | fi 24 | -------------------------------------------------------------------------------- /module/src/uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | export TMP_PATH=/data/adb/rezygisk 4 | 5 | rm -rf $TMP_PATH -------------------------------------------------------------------------------- /module/src/verify.sh: -------------------------------------------------------------------------------- 1 | TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip" 2 | mkdir "$TMPDIR_FOR_VERIFY" 3 | 4 | abort_verify() { 5 | ui_print "*********************************************************" 6 | ui_print "! $1" 7 | ui_print "! This zip may be corrupted, please try downloading again" 8 | abort "*********************************************************" 9 | } 10 | 11 | # extract 12 | extract() { 13 | zip=$1 14 | file=$2 15 | dir=$3 16 | junk_paths=$4 17 | [ -z "$junk_paths" ] && junk_paths=false 18 | opts="-o" 19 | [ $junk_paths = true ] && opts="-oj" 20 | 21 | file_path="" 22 | hash_path="" 23 | if [ $junk_paths = true ]; then 24 | file_path="$dir/$(basename "$file")" 25 | hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256" 26 | else 27 | file_path="$dir/$file" 28 | hash_path="$TMPDIR_FOR_VERIFY/$file.sha256" 29 | fi 30 | 31 | unzip $opts "$zip" "$file" -d "$dir" >&2 32 | [ -f "$file_path" ] || abort_verify "$file not exists" 33 | 34 | unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2 35 | [ -f "$hash_path" ] || abort_verify "$file.sha256 not exists" 36 | 37 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 38 | ui_print "- Verified $file" >&1 39 | } 40 | 41 | file="META-INF/com/google/android/update-binary" 42 | file_path="$TMPDIR_FOR_VERIFY/$file" 43 | hash_path="$file_path.sha256" 44 | unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2 45 | [ -f "$file_path" ] || abort_verify "$file not exists" 46 | if [ -f "$hash_path" ]; then 47 | (echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file" 48 | ui_print "- Verified $file" >&1 49 | else 50 | ui_print "- Download from Magisk app" 51 | fi 52 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 2 | 3 | pluginManagement { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | gradlePluginPortal() 8 | } 9 | } 10 | 11 | dependencyResolutionManagement { 12 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | rootProject.name = "ReZygisk" 20 | include( 21 | ":loader", 22 | ":module", 23 | ":zygiskd", 24 | ) 25 | -------------------------------------------------------------------------------- /zygiskd/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | import org.gradle.internal.os.OperatingSystem 3 | 4 | fun getLatestNDKPath(): String { 5 | val android_home = System.getenv("ANDROID_HOME") 6 | if (android_home == null) { 7 | throw Exception("ANDROID_HOME not set") 8 | } 9 | 10 | val ndkPath = android_home + "/ndk" 11 | 12 | val ndkDir = Paths.get(ndkPath) 13 | if (!ndkDir.toFile().exists()) { 14 | throw Exception("NDK not found at $ndkPath") 15 | } 16 | 17 | val ndkVersion = ndkDir.toFile().listFiles().filter { it.isDirectory }.map { it.name }.sorted().last() 18 | return ndkPath + "/" + ndkVersion 19 | } 20 | 21 | val minAPatchVersion: Int by rootProject.extra 22 | val minKsuVersion: Int by rootProject.extra 23 | val maxKsuVersion: Int by rootProject.extra 24 | val minMagiskVersion: Int by rootProject.extra 25 | val verCode: Int by rootProject.extra 26 | val verName: String by rootProject.extra 27 | val commitHash: String by rootProject.extra 28 | 29 | val CStandardFlags = arrayOf( 30 | "-D_GNU_SOURCE", "-std=c99", "-Wpedantic", "-Wall", "-Wextra", "-Werror", 31 | "-Wformat", "-Wuninitialized", "-Wshadow", "-Wno-zero-length-array", 32 | "-Wno-fixed-enum-extension", "-Iroot_impl", "-llog", 33 | "-DMIN_APATCH_VERSION=$minAPatchVersion", 34 | "-DMIN_KSU_VERSION=$minKsuVersion", 35 | "-DMAX_KSU_VERSION=$maxKsuVersion", 36 | "-DMIN_MAGISK_VERSION=$minMagiskVersion", 37 | "-DZKSU_VERSION=\"$verName\"" 38 | ) 39 | 40 | val CFlagsRelease = arrayOf( 41 | "-Wl,--strip-all", "-flto=thin", "-Ofast" 42 | ) 43 | 44 | val CFlagsDebug = arrayOf( 45 | "-g", "-O0" 46 | ) 47 | 48 | val Files = arrayOf( 49 | "root_impl/apatch.c", 50 | "root_impl/common.c", 51 | "root_impl/kernelsu.c", 52 | "root_impl/magisk.c", 53 | "companion.c", 54 | "dl.c", 55 | "main.c", 56 | "utils.c", 57 | "zygiskd.c" 58 | ) 59 | 60 | task("buildAndStrip") { 61 | group = "build" 62 | description = "Build the native library and strip the debug symbols." 63 | 64 | val isDebug = gradle.startParameter.taskNames.any { it.lowercase().contains("debug") } 65 | doLast { 66 | val ndkPath = getLatestNDKPath() 67 | 68 | val aarch64Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "aarch64-linux-android34-clang").toString() 69 | val armv7aCompiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "armv7a-linux-androideabi34-clang").toString() 70 | val x86Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "i686-linux-android34-clang").toString() 71 | val x86_64Compiler = Paths.get(ndkPath, "toolchains", "llvm", "prebuilt", "linux-x86_64", "bin", "x86_64-linux-android34-clang").toString() 72 | 73 | if (!Paths.get(aarch64Compiler).toFile().exists()) { 74 | throw Exception("aarch64 compiler not found at $aarch64Compiler") 75 | } 76 | 77 | if (!Paths.get(armv7aCompiler).toFile().exists()) { 78 | throw Exception("armv7a compiler not found at $armv7aCompiler") 79 | } 80 | 81 | if (!Paths.get(x86Compiler).toFile().exists()) { 82 | throw Exception("x86 compiler not found at $x86Compiler") 83 | } 84 | 85 | if (!Paths.get(x86_64Compiler).toFile().exists()) { 86 | throw Exception("x86_64 compiler not found at $x86_64Compiler") 87 | } 88 | 89 | val Files = Files.map { Paths.get(project.projectDir.toString(), "src", it).toString() }.toTypedArray() 90 | 91 | val buildDir = getLayout().getBuildDirectory().getAsFile().get() 92 | buildDir.mkdirs() 93 | 94 | val aarch64OutputDir = Paths.get(buildDir.toString(), "arm64-v8a").toFile() 95 | val armv7aOutputDir = Paths.get(buildDir.toString(), "armeabi-v7a").toFile() 96 | val x86OutputDir = Paths.get(buildDir.toString(), "x86").toFile() 97 | val x86_64OutputDir = Paths.get(buildDir.toString(), "x86_64").toFile() 98 | 99 | aarch64OutputDir.mkdirs() 100 | armv7aOutputDir.mkdirs() 101 | x86OutputDir.mkdirs() 102 | x86_64OutputDir.mkdirs() 103 | 104 | val compileArgs = (if (isDebug) CFlagsDebug else CFlagsRelease) + CStandardFlags 105 | 106 | exec { 107 | commandLine(aarch64Compiler, "-o", Paths.get(aarch64OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) 108 | } 109 | exec { 110 | commandLine(armv7aCompiler, "-o", Paths.get(armv7aOutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) 111 | } 112 | exec { 113 | commandLine(x86Compiler, "-o", Paths.get(x86OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) 114 | } 115 | exec { 116 | commandLine(x86_64Compiler, "-o", Paths.get(x86_64OutputDir.toString(), "zygiskd").toString(), *compileArgs, *Files) 117 | } 118 | } 119 | } 120 | 121 | -------------------------------------------------------------------------------- /zygiskd/src/.gitignore: -------------------------------------------------------------------------------- 1 | zygiskd64 2 | zygiskd32 -------------------------------------------------------------------------------- /zygiskd/src/companion.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "companion.h" 18 | #include "dl.h" 19 | #include "utils.h" 20 | 21 | typedef void (*zygisk_companion_entry_func)(int); 22 | 23 | struct companion_module_thread_args { 24 | int fd; 25 | zygisk_companion_entry_func entry; 26 | }; 27 | 28 | zygisk_companion_entry_func load_module(int fd) { 29 | char path[PATH_MAX]; 30 | snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); 31 | 32 | void *handle = android_dlopen(path, RTLD_NOW); 33 | void *entry = dlsym(handle, "zygisk_companion_entry"); 34 | if (entry == NULL) return NULL; 35 | 36 | return (zygisk_companion_entry_func)entry; 37 | } 38 | 39 | void *entry_thread(void *arg) { 40 | struct companion_module_thread_args *args = (struct companion_module_thread_args *)arg; 41 | 42 | int fd = args->fd; 43 | zygisk_companion_entry_func module_entry = args->entry; 44 | 45 | module_entry(fd); 46 | 47 | close(fd); 48 | free(args); 49 | 50 | pthread_exit(NULL); 51 | } 52 | 53 | /* WARNING: Dynamic memory based */ 54 | void companion_entry(int fd) { 55 | LOGI("New companion entry.\n - Client fd: %d\n", fd); 56 | 57 | /* TODO: Use non-NULL string termination */ 58 | char name[256 + 1]; 59 | ssize_t name_length = read_string(fd, name, sizeof(name) - 1); 60 | if (name_length == -1) { 61 | LOGE("Failed to read module name\n"); 62 | 63 | ssize_t ret = write_uint8_t(fd, 2); 64 | ASSURE_SIZE_WRITE("ZygiskdCompanion", "name", ret, sizeof(uint8_t)); 65 | 66 | exit(0); 67 | } 68 | name[name_length] = '\0'; 69 | 70 | LOGI(" - Module name: `%.*s`\n", (int)name_length, name); 71 | 72 | int library_fd = read_fd(fd); 73 | ssize_t ret = 0; 74 | if (library_fd == -1) { 75 | LOGE("Failed to receive library fd\n"); 76 | 77 | ret = write_uint8_t(fd, 2); 78 | ASSURE_SIZE_WRITE("ZygiskdCompanion", "library_fd", ret, sizeof(uint8_t)); 79 | 80 | exit(0); 81 | } 82 | 83 | LOGI(" - Library fd: %d\n", library_fd); 84 | 85 | zygisk_companion_entry_func module_entry = load_module(library_fd); 86 | close(library_fd); 87 | 88 | if (module_entry == NULL) { 89 | LOGI("No companion module entry for module: %.*s\n", (int)name_length, name); 90 | 91 | ret = write_uint8_t(fd, 0); 92 | ASSURE_SIZE_WRITE("ZygiskdCompanion", "module_entry", ret, sizeof(uint8_t)); 93 | 94 | exit(0); 95 | } else { 96 | ret = write_uint8_t(fd, 1); 97 | ASSURE_SIZE_WRITE("ZygiskdCompanion", "module_entry", ret, sizeof(uint8_t)); 98 | } 99 | 100 | while (1) { 101 | if (!check_unix_socket(fd, true)) { 102 | LOGI("Something went wrong in companion. Bye!\n"); 103 | 104 | exit(0); 105 | 106 | break; 107 | } 108 | 109 | int client_fd = read_fd(fd); 110 | if (fd == -1) { 111 | LOGE("Failed to receive client fd\n"); 112 | 113 | exit(0); 114 | } 115 | 116 | struct companion_module_thread_args *args = malloc(sizeof(struct companion_module_thread_args)); 117 | if (args == NULL) { 118 | LOGE("Failed to allocate memory for thread args\n"); 119 | 120 | exit(0); 121 | } 122 | 123 | args->fd = client_fd; 124 | args->entry = module_entry; 125 | 126 | LOGI("New companion request.\n - Module name: %.*s\n - Client fd: %d\n", (int)name_length, name, args->fd); 127 | 128 | ret = write_uint8_t(args->fd, 1); 129 | ASSURE_SIZE_WRITE("ZygiskdCompanion", "client_fd", ret, sizeof(uint8_t)); 130 | 131 | pthread_t thread; 132 | pthread_create(&thread, NULL, entry_thread, args); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /zygiskd/src/companion.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPANION_H 2 | #define COMPANION_H 3 | 4 | void companion_entry(int fd); 5 | 6 | #endif /* COMPANION_H */ 7 | -------------------------------------------------------------------------------- /zygiskd/src/constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS_H 2 | #define CONSTANTS_H 3 | 4 | #include 5 | 6 | #define bool _Bool 7 | #define true 1 8 | #define false 0 9 | 10 | #if DEBUG == false 11 | #define MAX_LOG_LEVEL ANDROID_LOG_VERBOSE 12 | #else 13 | #define MAX_LOG_LEVEL ANDROID_LOG_INFO 14 | #endif 15 | 16 | #if (defined(__LP64__) || defined(_LP64)) 17 | #define lp_select(a, b) b 18 | #else 19 | #define lp_select(a, b) a 20 | #endif 21 | 22 | #define ZYGOTE_INJECTED lp_select(5, 4) 23 | #define DAEMON_SET_INFO lp_select(7, 6) 24 | #define DAEMON_SET_ERROR_INFO lp_select(9, 8) 25 | #define SYSTEM_SERVER_STARTED 10 26 | 27 | enum DaemonSocketAction { 28 | PingHeartbeat, 29 | RequestLogcatFd, 30 | GetProcessFlags, 31 | GetInfo, 32 | ReadModules, 33 | RequestCompanionSocket, 34 | GetModuleDir, 35 | ZygoteRestart, 36 | SystemServerStarted 37 | }; 38 | 39 | enum ProcessFlags: uint32_t { 40 | PROCESS_GRANTED_ROOT = (1u << 0), 41 | PROCESS_ON_DENYLIST = (1u << 1), 42 | PROCESS_IS_MANAGER = (1u << 28), 43 | PROCESS_ROOT_IS_APATCH = (1u << 27), 44 | PROCESS_ROOT_IS_KSU = (1u << 29), 45 | PROCESS_ROOT_IS_MAGISK = (1u << 30), 46 | PROCESS_IS_SYS_UI = (1u << 31), 47 | PROCESS_IS_SYSUI = (1u << 31) 48 | }; 49 | 50 | enum RootImplState { 51 | Supported, 52 | TooOld, 53 | Inexistent, 54 | Abnormal 55 | }; 56 | 57 | #endif /* CONSTANTS_H */ 58 | -------------------------------------------------------------------------------- /zygiskd/src/dl.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include "companion.h" 16 | #include "dl.h" 17 | #include "utils.h" 18 | 19 | #define ANDROID_NAMESPACE_TYPE_SHARED 0x2 20 | #define ANDROID_DLEXT_USE_NAMESPACE 0x200 21 | 22 | typedef struct AndroidNamespace { 23 | unsigned char _unused[0]; 24 | } AndroidNamespace; 25 | 26 | typedef struct AndroidDlextinfo { 27 | uint64_t flags; 28 | void *reserved_addr; 29 | size_t reserved_size; 30 | int relro_fd; 31 | int library_fd; 32 | off64_t library_fd_offset; 33 | AndroidNamespace *library_namespace; 34 | } AndroidDlextinfo; 35 | 36 | typedef AndroidNamespace *(*AndroidCreateNamespaceFn)( 37 | const char *name, 38 | const char *ld_library_path, 39 | const char *default_library_path, 40 | uint64_t type, 41 | const char *permitted_when_isolated_path, 42 | AndroidNamespace *parent, 43 | const void *caller_addr 44 | ); 45 | 46 | extern void *android_dlopen_ext(const char *filename, int flags, const AndroidDlextinfo *extinfo); 47 | 48 | void *android_dlopen(char *path, int flags) { 49 | char *dir = dirname(path); 50 | struct AndroidDlextinfo info = { 51 | .flags = 0, 52 | .reserved_addr = NULL, 53 | .reserved_size = 0, 54 | .relro_fd = 0, 55 | .library_fd = 0, 56 | .library_fd_offset = 0, 57 | .library_namespace = NULL, 58 | }; 59 | 60 | void *handle = dlsym(RTLD_DEFAULT, "__loader_android_create_namespace"); 61 | AndroidCreateNamespaceFn android_create_namespace_fn = (AndroidCreateNamespaceFn)handle; 62 | 63 | AndroidNamespace *ns = android_create_namespace_fn( 64 | path, 65 | dir, 66 | NULL, 67 | ANDROID_NAMESPACE_TYPE_SHARED, 68 | NULL, 69 | NULL, 70 | (const void *)&android_dlopen 71 | ); 72 | 73 | if (ns != NULL) { 74 | info.flags = ANDROID_DLEXT_USE_NAMESPACE; 75 | info.library_namespace = ns; 76 | 77 | LOGI("Open %s with namespace %p\n", path, (void *)ns); 78 | } else { 79 | LOGI("Cannot create namespace for %s\n", path); 80 | } 81 | 82 | void *result = android_dlopen_ext(path, flags, &info); 83 | if (result == NULL) { 84 | LOGE("Failed to dlopen %s: %s\n", path, dlerror()); 85 | } 86 | 87 | return result; 88 | } 89 | -------------------------------------------------------------------------------- /zygiskd/src/dl.h: -------------------------------------------------------------------------------- 1 | #ifndef DL_H 2 | #define DL_H 3 | 4 | void *android_dlopen(char *path, int flags); 5 | 6 | #endif /* DL_H */ 7 | -------------------------------------------------------------------------------- /zygiskd/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include "root_impl/common.h" 9 | #include "companion.h" 10 | #include "zygiskd.h" 11 | 12 | #include "utils.h" 13 | 14 | int __android_log_print(int prio, const char *tag, const char *fmt, ...); 15 | 16 | int main(int argc, char *argv[]) { 17 | #ifdef __LP64__ 18 | LOGI("Welcome to ReZygisk %s Zygiskd64!\n", ZKSU_VERSION); 19 | #else 20 | LOGI("Welcome to ReZygisk %s Zygiskd32!\n", ZKSU_VERSION); 21 | #endif 22 | 23 | if (argc > 1) { 24 | if (strcmp(argv[1], "companion") == 0) { 25 | if (argc < 3) { 26 | LOGI("Usage: zygiskd companion \n"); 27 | 28 | return 1; 29 | } 30 | 31 | int fd = atoi(argv[2]); 32 | companion_entry(fd); 33 | 34 | return 0; 35 | } 36 | 37 | else if (strcmp(argv[1], "version") == 0) { 38 | LOGI("ReZygisk Daemon %s\n", ZKSU_VERSION); 39 | 40 | return 0; 41 | } 42 | 43 | else if (strcmp(argv[1], "root") == 0) { 44 | root_impls_setup(); 45 | 46 | struct root_impl impl; 47 | get_impl(&impl); 48 | 49 | char impl_name[LONGEST_ROOT_IMPL_NAME]; 50 | stringify_root_impl_name(impl, impl_name); 51 | 52 | LOGI("Root implementation: %s\n", impl_name); 53 | 54 | return 0; 55 | } 56 | 57 | else { 58 | LOGI("Usage: zygiskd [companion|version|root]\n"); 59 | 60 | return 0; 61 | } 62 | } 63 | 64 | if (switch_mount_namespace((pid_t)1) == false) { 65 | LOGE("Failed to switch mount namespace\n"); 66 | 67 | return 1; 68 | } 69 | root_impls_setup(); 70 | zygiskd_start(argv); 71 | 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/apatch.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../constants.h" 8 | #include "../utils.h" 9 | #include "common.h" 10 | 11 | #include "apatch.h" 12 | 13 | void apatch_get_existence(struct root_impl_state *state) { 14 | struct stat s; 15 | if (stat("/data/adb/apd", &s) != 0) { 16 | if (errno != ENOENT) { 17 | LOGE("Failed to stat APatch apd binary: %s\n", strerror(errno)); 18 | } 19 | errno = 0; 20 | 21 | state->state = Inexistent; 22 | 23 | return; 24 | } 25 | 26 | char *PATH = getenv("PATH"); 27 | if (PATH == NULL) { 28 | LOGE("Failed to get PATH environment variable: %s\n", strerror(errno)); 29 | errno = 0; 30 | 31 | state->state = Inexistent; 32 | 33 | return; 34 | } 35 | 36 | if (strstr(PATH, "/data/adb/ap/bin") == NULL) { 37 | LOGE("APatch's APD binary is not in PATH\n"); 38 | 39 | state->state = Inexistent; 40 | 41 | return; 42 | } 43 | 44 | char apatch_version[32]; 45 | char *const argv[] = { "apd", "-V", NULL }; 46 | 47 | if (!exec_command(apatch_version, sizeof(apatch_version), "/data/adb/apd", argv)) { 48 | LOGE("Failed to execute apd binary: %s\n", strerror(errno)); 49 | errno = 0; 50 | 51 | state->state = Inexistent; 52 | 53 | return; 54 | } 55 | 56 | int version = atoi(apatch_version + strlen("apd ")); 57 | 58 | if (version == 0) state->state = Abnormal; 59 | else if (version >= MIN_APATCH_VERSION && version <= 999999) state->state = Supported; 60 | else if (version >= 1 && version <= MIN_APATCH_VERSION - 1) state->state = TooOld; 61 | else state->state = Abnormal; 62 | } 63 | 64 | struct package_config { 65 | uid_t uid; 66 | bool root_granted; 67 | bool umount_needed; 68 | }; 69 | 70 | struct packages_config { 71 | struct package_config *configs; 72 | size_t size; 73 | }; 74 | 75 | /* WARNING: Dynamic memory based */ 76 | bool _apatch_get_package_config(struct packages_config *restrict config) { 77 | config->configs = NULL; 78 | config->size = 0; 79 | 80 | FILE *fp = fopen("/data/adb/ap/package_config", "r"); 81 | if (fp == NULL) { 82 | LOGE("Failed to open APatch's package_config: %s\n", strerror(errno)); 83 | 84 | return false; 85 | } 86 | 87 | char line[1048]; 88 | /* INFO: Skip the CSV header */ 89 | if (fgets(line, sizeof(line), fp) == NULL) { 90 | LOGE("Failed to read APatch's package_config header: %s\n", strerror(errno)); 91 | 92 | fclose(fp); 93 | 94 | return false; 95 | } 96 | 97 | while (fgets(line, sizeof(line), fp) != NULL) { 98 | config->configs = realloc(config->configs, (config->size + 1) * sizeof(struct package_config)); 99 | if (config->configs == NULL) { 100 | LOGE("Failed to realloc APatch config struct: %s\n", strerror(errno)); 101 | 102 | fclose(fp); 103 | 104 | return false; 105 | } 106 | 107 | strtok(line, ","); 108 | 109 | char *exclude_str = strtok(NULL, ","); 110 | if (exclude_str == NULL) continue; 111 | 112 | char *allow_str = strtok(NULL, ","); 113 | if (allow_str == NULL) continue; 114 | 115 | char *uid_str = strtok(NULL, ","); 116 | if (uid_str == NULL) continue; 117 | 118 | config->configs[config->size].uid = atoi(uid_str); 119 | config->configs[config->size].root_granted = strcmp(allow_str, "1") == 0; 120 | config->configs[config->size].umount_needed = strcmp(exclude_str, "1") == 0; 121 | 122 | config->size++; 123 | } 124 | 125 | fclose(fp); 126 | 127 | return true; 128 | } 129 | 130 | void _apatch_free_package_config(struct packages_config *restrict config) { 131 | free(config->configs); 132 | } 133 | 134 | bool apatch_uid_granted_root(uid_t uid) { 135 | struct packages_config config; 136 | if (!_apatch_get_package_config(&config)) { 137 | _apatch_free_package_config(&config); 138 | 139 | return false; 140 | } 141 | 142 | for (size_t i = 0; i < config.size; i++) { 143 | if (config.configs[i].uid != uid) continue; 144 | 145 | /* INFO: This allow us to copy the information to avoid use-after-free */ 146 | bool root_granted = config.configs[i].root_granted; 147 | 148 | _apatch_free_package_config(&config); 149 | 150 | return root_granted; 151 | } 152 | 153 | _apatch_free_package_config(&config); 154 | 155 | return false; 156 | } 157 | 158 | bool apatch_uid_should_umount(uid_t uid) { 159 | struct packages_config config; 160 | if (!_apatch_get_package_config(&config)) { 161 | _apatch_free_package_config(&config); 162 | 163 | return false; 164 | } 165 | 166 | for (size_t i = 0; i < config.size; i++) { 167 | if (config.configs[i].uid != uid) continue; 168 | 169 | /* INFO: This allow us to copy the information to avoid use-after-free */ 170 | bool umount_needed = config.configs[i].umount_needed; 171 | 172 | _apatch_free_package_config(&config); 173 | 174 | return umount_needed; 175 | } 176 | 177 | _apatch_free_package_config(&config); 178 | 179 | return false; 180 | } 181 | 182 | bool apatch_uid_is_manager(uid_t uid) { 183 | struct stat s; 184 | if (stat("/data/user_de/0/me.bmax.apatch", &s) == -1) { 185 | if (errno != ENOENT) { 186 | LOGE("Failed to stat APatch manager data directory: %s\n", strerror(errno)); 187 | } 188 | errno = 0; 189 | 190 | return false; 191 | } 192 | 193 | return s.st_uid == uid; 194 | } 195 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/apatch.h: -------------------------------------------------------------------------------- 1 | #ifndef APATCH_H 2 | #define APATCH_H 3 | 4 | #include "../constants.h" 5 | 6 | void apatch_get_existence(struct root_impl_state *state); 7 | 8 | bool apatch_uid_granted_root(uid_t uid); 9 | 10 | bool apatch_uid_should_umount(uid_t uid); 11 | 12 | bool apatch_uid_is_manager(uid_t uid); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "../utils.h" 6 | #include "kernelsu.h" 7 | #include "apatch.h" 8 | #include "magisk.h" 9 | 10 | #include "common.h" 11 | 12 | static struct root_impl impl; 13 | 14 | void root_impls_setup(void) { 15 | struct root_impl_state state_ksu; 16 | ksu_get_existence(&state_ksu); 17 | 18 | struct root_impl_state state_apatch; 19 | apatch_get_existence(&state_apatch); 20 | 21 | struct root_impl_state state_magisk; 22 | magisk_get_existence(&state_magisk); 23 | 24 | /* INFO: Check if it's only one supported, if not, it's multile and that's bad. 25 | Remember that true here is equal to the integer 1. */ 26 | if ((state_ksu.state == Supported ? 1 : 0) + (state_apatch.state == Supported ? 1 : 0) + (state_magisk.state == Supported ? 1 : 0) >= 2) { 27 | impl.impl = Multiple; 28 | } else if (state_ksu.state == Supported) { 29 | impl.impl = KernelSU; 30 | } else if (state_apatch.state == Supported) { 31 | impl.impl = APatch; 32 | } else if (state_magisk.state == Supported) { 33 | impl.impl = Magisk; 34 | impl.variant = state_magisk.variant; 35 | } else { 36 | impl.impl = None; 37 | } 38 | 39 | switch (impl.impl) { 40 | case None: { 41 | LOGI("No root implementation found.\n"); 42 | 43 | break; 44 | } 45 | case Multiple: { 46 | LOGI("Multiple root implementations found.\n"); 47 | 48 | break; 49 | } 50 | case KernelSU: { 51 | LOGI("KernelSU root implementation found.\n"); 52 | 53 | break; 54 | } 55 | case APatch: { 56 | LOGI("APatch root implementation found.\n"); 57 | 58 | break; 59 | } 60 | case Magisk: { 61 | if (state_magisk.variant == 0) { 62 | LOGI("Magisk Official root implementation found.\n"); 63 | } else { 64 | LOGI("Magisk Kitsune root implementation found.\n"); 65 | } 66 | 67 | break; 68 | } 69 | } 70 | } 71 | 72 | void get_impl(struct root_impl *uimpl) { 73 | uimpl->impl = impl.impl; 74 | uimpl->variant = impl.variant; 75 | } 76 | 77 | bool uid_granted_root(uid_t uid) { 78 | switch (impl.impl) { 79 | case KernelSU: { 80 | return ksu_uid_granted_root(uid); 81 | } 82 | case APatch: { 83 | return apatch_uid_granted_root(uid); 84 | } 85 | case Magisk: { 86 | return magisk_uid_granted_root(uid); 87 | } 88 | default: { 89 | return false; 90 | } 91 | } 92 | } 93 | 94 | bool uid_should_umount(uid_t uid) { 95 | switch (impl.impl) { 96 | case KernelSU: { 97 | return ksu_uid_should_umount(uid); 98 | } 99 | case APatch: { 100 | return apatch_uid_should_umount(uid); 101 | } 102 | case Magisk: { 103 | return magisk_uid_should_umount(uid); 104 | } 105 | default: { 106 | return false; 107 | } 108 | } 109 | } 110 | 111 | bool uid_is_manager(uid_t uid) { 112 | switch (impl.impl) { 113 | case KernelSU: { 114 | return ksu_uid_is_manager(uid); 115 | } 116 | case APatch: { 117 | return apatch_uid_is_manager(uid); 118 | } 119 | case Magisk: { 120 | return magisk_uid_is_manager(uid); 121 | } 122 | default: { 123 | return false; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include 5 | 6 | #include "../constants.h" 7 | 8 | enum root_impls { 9 | None, 10 | Multiple, 11 | KernelSU, 12 | APatch, 13 | Magisk 14 | }; 15 | 16 | struct root_impl_state { 17 | enum RootImplState state; 18 | uint8_t variant; 19 | }; 20 | 21 | struct root_impl { 22 | enum root_impls impl; 23 | uint8_t variant; 24 | }; 25 | 26 | #define LONGEST_ROOT_IMPL_NAME sizeof("Magisk Official") 27 | 28 | void root_impls_setup(void); 29 | 30 | void get_impl(struct root_impl *uimpl); 31 | 32 | bool uid_granted_root(uid_t uid); 33 | 34 | bool uid_should_umount(uid_t uid); 35 | 36 | bool uid_is_manager(uid_t uid); 37 | 38 | #endif /* COMMON_H */ 39 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/kernelsu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../constants.h" 8 | #include "../utils.h" 9 | #include "common.h" 10 | 11 | #include "kernelsu.h" 12 | 13 | /* INFO: It would be presumed it is a unsigned int, 14 | so we need to cast it to signed int to 15 | avoid any potential UB. 16 | */ 17 | #define KERNEL_SU_OPTION 0xdeadbeef 18 | 19 | #define CMD_GET_VERSION 2 20 | #define CMD_UID_GRANTED_ROOT 12 21 | #define CMD_UID_SHOULD_UMOUNT 13 22 | 23 | void ksu_get_existence(struct root_impl_state *state) { 24 | int version = 0; 25 | prctl((signed int)KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, 0); 26 | 27 | if (version == 0) state->state = Abnormal; 28 | else if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) { 29 | /* INFO: Some custom kernels for custom ROMs have pre-installed KernelSU. 30 | Some users don't want to use KernelSU, but, for example, Magisk. 31 | This if allows this to happen, as it checks if "ksud" exists, 32 | which in case it doesn't, it won't be considered as supported. */ 33 | struct stat s; 34 | if (stat("/data/adb/ksud", &s) == -1) { 35 | if (errno != ENOENT) { 36 | LOGE("Failed to stat KSU daemon: %s\n", strerror(errno)); 37 | } 38 | errno = 0; 39 | state->state = Abnormal; 40 | 41 | return; 42 | } 43 | 44 | state->state = Supported; 45 | } 46 | else if (version >= 1 && version <= MIN_KSU_VERSION - 1) state->state = TooOld; 47 | else state->state = Abnormal; 48 | } 49 | 50 | bool ksu_uid_granted_root(uid_t uid) { 51 | uint32_t result = 0; 52 | bool granted = false; 53 | prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &granted, &result); 54 | 55 | if (result != KERNEL_SU_OPTION) return false; 56 | 57 | return granted; 58 | } 59 | 60 | bool ksu_uid_should_umount(uid_t uid) { 61 | uint32_t result = 0; 62 | bool umount = false; 63 | prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &umount, &result); 64 | 65 | if (result != KERNEL_SU_OPTION) return false; 66 | 67 | return umount; 68 | } 69 | 70 | bool ksu_uid_is_manager(uid_t uid) { 71 | struct stat s; 72 | if (stat("/data/user_de/0/me.weishu.kernelsu", &s) == -1) { 73 | if (errno != ENOENT) { 74 | LOGE("Failed to stat KSU manager data directory: %s\n", strerror(errno)); 75 | } 76 | errno = 0; 77 | 78 | return false; 79 | } 80 | 81 | return s.st_uid == uid; 82 | } 83 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/kernelsu.h: -------------------------------------------------------------------------------- 1 | #ifndef KERNELSU_H 2 | #define KERNELSU_H 3 | 4 | #include "../constants.h" 5 | 6 | void ksu_get_existence(struct root_impl_state *state); 7 | 8 | bool ksu_uid_granted_root(uid_t uid); 9 | 10 | bool ksu_uid_should_umount(uid_t uid); 11 | 12 | bool ksu_uid_is_manager(uid_t uid); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/magisk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "../constants.h" 12 | #include "../utils.h" 13 | #include "common.h" 14 | 15 | #include "magisk.h" 16 | 17 | char *supported_variants[] = { 18 | "kitsune" 19 | }; 20 | 21 | char *magisk_managers[] = { 22 | "com.topjohnwu.magisk", 23 | "io.github.huskydg.magisk" 24 | }; 25 | 26 | #define SBIN_MAGISK lp_select("/sbin/magisk32", "/sbin/magisk64") 27 | #define BITLESS_SBIN_MAGISK "/sbin/magisk" 28 | #define DEBUG_RAMDISK_MAGISK lp_select("/debug_ramdisk/magisk32", "/debug_ramdisk/magisk64") 29 | #define BITLESS_DEBUG_RAMDISK_MAGISK "/debug_ramdisk/magisk" 30 | 31 | enum magisk_variants variant = Official; 32 | /* INFO: Longest path */ 33 | static char path_to_magisk[sizeof(DEBUG_RAMDISK_MAGISK)]; 34 | 35 | void magisk_get_existence(struct root_impl_state *state) { 36 | struct stat s; 37 | if (stat(SBIN_MAGISK, &s) != 0) { 38 | if (errno != ENOENT) { 39 | LOGE("Failed to stat Magisk /sbin/magisk binary: %s\n", strerror(errno)); 40 | } 41 | errno = 0; 42 | 43 | if (stat(BITLESS_SBIN_MAGISK, &s) != 0) { 44 | if (errno != ENOENT) { 45 | LOGE("Failed to stat Magisk %s binary: %s\n", BITLESS_SBIN_MAGISK, strerror(errno)); 46 | } 47 | errno = 0; 48 | 49 | if (stat(DEBUG_RAMDISK_MAGISK, &s) != 0) { 50 | if (errno != ENOENT) { 51 | LOGE("Failed to stat Magisk %s binary: %s\n", DEBUG_RAMDISK_MAGISK, strerror(errno)); 52 | } 53 | errno = 0; 54 | 55 | if (stat(BITLESS_DEBUG_RAMDISK_MAGISK, &s) != 0) { 56 | if (errno != ENOENT) { 57 | LOGE("Failed to stat Magisk /debug_ramdisk/magisk binary: %s\n", strerror(errno)); 58 | } 59 | errno = 0; 60 | 61 | state->state = Inexistent; 62 | 63 | return; 64 | } 65 | 66 | /* INFO: /debug_ramdisk/magisk64 (or 32) doesn't exist but /debug_ramdisk/magisk does */ 67 | strcpy(path_to_magisk, BITLESS_DEBUG_RAMDISK_MAGISK); 68 | } else { 69 | /* INFO: /sbin/magisk doesn't exist but /debug_ramdisk/magisk does */ 70 | strcpy(path_to_magisk, DEBUG_RAMDISK_MAGISK); 71 | } 72 | } else { 73 | /* INFO: /sbin/magisk64 (or 32) doesn't exist but /sbin/magisk does */ 74 | strcpy(path_to_magisk, BITLESS_SBIN_MAGISK); 75 | } 76 | } else { 77 | /* INFO: /sbin/magisk64 (or 32) exists */ 78 | strcpy(path_to_magisk, SBIN_MAGISK); 79 | } 80 | 81 | char *argv[] = { "magisk", "-v", NULL }; 82 | 83 | char magisk_info[128]; 84 | if (!exec_command(magisk_info, sizeof(magisk_info), (const char *)path_to_magisk, argv)) { 85 | LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 86 | errno = 0; 87 | 88 | state->state = Abnormal; 89 | 90 | return; 91 | } 92 | 93 | state->variant = (uint8_t)Official; 94 | 95 | for (unsigned long i = 0; i < sizeof(supported_variants) / sizeof(supported_variants[0]); i++) { 96 | if (strstr(magisk_info, supported_variants[i])) { 97 | variant = (enum magisk_variants)(i + 1); 98 | state->variant = (uint8_t)variant; 99 | 100 | break; 101 | } 102 | } 103 | 104 | argv[1] = "-V"; 105 | 106 | char magisk_version[32]; 107 | if (!exec_command(magisk_version, sizeof(magisk_version), (const char *)path_to_magisk, argv)) { 108 | LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 109 | errno = 0; 110 | 111 | state->state = Abnormal; 112 | 113 | return; 114 | } 115 | 116 | if (atoi(magisk_version) >= MIN_MAGISK_VERSION) state->state = Supported; 117 | else state->state = TooOld; 118 | } 119 | 120 | bool magisk_uid_granted_root(uid_t uid) { 121 | char sqlite_cmd[256]; 122 | snprintf(sqlite_cmd, sizeof(sqlite_cmd), "select 1 from policies where uid=%d and policy=2 limit 1", uid); 123 | 124 | char *const argv[] = { "magisk", "--sqlite", sqlite_cmd, NULL }; 125 | 126 | char result[32]; 127 | if (!exec_command(result, sizeof(result), (const char *)path_to_magisk, argv)) { 128 | LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 129 | errno = 0; 130 | 131 | return false; 132 | } 133 | 134 | return result[0] != '\0'; 135 | } 136 | 137 | bool magisk_uid_should_umount(uid_t uid) { 138 | char uid_str[16]; 139 | snprintf(uid_str, sizeof(uid_str), "%d", uid); 140 | 141 | char *const argv_pm[] = { "pm", "list", "packages", "--uid", uid_str, NULL }; 142 | 143 | char result[256]; 144 | if (!exec_command(result, sizeof(result), "/system/bin/pm", argv_pm)) { 145 | LOGE("Failed to execute pm binary: %s\n", strerror(errno)); 146 | errno = 0; 147 | 148 | /* INFO: It's better if we do NOT umount than the opposite */ 149 | return false; 150 | } 151 | 152 | if (result[0] == '\0') { 153 | LOGE("Failed to get package name from UID %d\n", uid); 154 | 155 | return false; 156 | } 157 | 158 | char *package_name = strtok(result + strlen("package:"), " "); 159 | 160 | char sqlite_cmd[256]; 161 | snprintf(sqlite_cmd, sizeof(sqlite_cmd), "select 1 from denylist where package_name=\"%s\" limit 1", package_name); 162 | 163 | char *const argv[] = { "magisk", "--sqlite", sqlite_cmd, NULL }; 164 | 165 | if (!exec_command(result, sizeof(result), (const char *)path_to_magisk, argv)) { 166 | LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 167 | errno = 0; 168 | 169 | return false; 170 | } 171 | 172 | return result[0] != '\0'; 173 | } 174 | 175 | bool magisk_uid_is_manager(uid_t uid) { 176 | char *const argv[] = { "magisk", "--sqlite", "select value from strings where key=\"requester\" limit 1", NULL }; 177 | 178 | char output[128]; 179 | if (!exec_command(output, sizeof(output), (const char *)path_to_magisk, argv)) { 180 | LOGE("Failed to execute magisk binary: %s\n", strerror(errno)); 181 | errno = 0; 182 | 183 | return false; 184 | } 185 | 186 | char stat_path[PATH_MAX]; 187 | if (output[0] == '\0') 188 | snprintf(stat_path, sizeof(stat_path), "/data/user_de/0/%s", magisk_managers[(int)variant]); 189 | else 190 | snprintf(stat_path, sizeof(stat_path), "/data/user_de/0/%s", output + strlen("value=")); 191 | 192 | struct stat s; 193 | if (stat(stat_path, &s) == -1) { 194 | LOGE("Failed to stat %s: %s\n", stat_path, strerror(errno)); 195 | errno = 0; 196 | 197 | return false; 198 | } 199 | 200 | return s.st_uid == uid; 201 | } 202 | -------------------------------------------------------------------------------- /zygiskd/src/root_impl/magisk.h: -------------------------------------------------------------------------------- 1 | #ifndef MAGISK_H 2 | #define MAGISK_H 3 | 4 | #include "../constants.h" 5 | 6 | enum magisk_variants { 7 | Official, 8 | Kitsune 9 | }; 10 | 11 | void magisk_get_existence(struct root_impl_state *state); 12 | 13 | bool magisk_uid_granted_root(uid_t uid); 14 | 15 | bool magisk_uid_should_umount(uid_t uid); 16 | 17 | bool magisk_uid_is_manager(uid_t uid); 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /zygiskd/src/utils.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "utils.h" 20 | #include "root_impl/common.h" 21 | 22 | bool switch_mount_namespace(pid_t pid) { 23 | char path[PATH_MAX]; 24 | snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); 25 | 26 | int nsfd = open(path, O_RDONLY | O_CLOEXEC); 27 | if (nsfd == -1) { 28 | LOGE("Failed to open nsfd: %s\n", strerror(errno)); 29 | 30 | return false; 31 | } 32 | 33 | if (setns(nsfd, CLONE_NEWNS) == -1) { 34 | LOGE("Failed to setns: %s\n", strerror(errno)); 35 | 36 | close(nsfd); 37 | 38 | return false; 39 | } 40 | 41 | close(nsfd); 42 | 43 | return true; 44 | } 45 | 46 | int __system_property_get(const char *, char *); 47 | 48 | void get_property(const char *restrict name, char *restrict output) { 49 | __system_property_get(name, output); 50 | } 51 | 52 | void set_socket_create_context(const char *restrict context) { 53 | char path[PATH_MAX]; 54 | snprintf(path, PATH_MAX, "/proc/thread-self/attr/sockcreate"); 55 | 56 | FILE *sockcreate = fopen(path, "w"); 57 | if (sockcreate == NULL) { 58 | LOGE("Failed to open /proc/thread-self/attr/sockcreate: %s Now trying to via gettid().\n", strerror(errno)); 59 | 60 | goto fail; 61 | } 62 | 63 | if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) { 64 | LOGE("Failed to write to /proc/thread-self/attr/sockcreate: %s Now trying to via gettid().\n", strerror(errno)); 65 | 66 | fclose(sockcreate); 67 | 68 | goto fail; 69 | } 70 | 71 | fclose(sockcreate); 72 | 73 | return; 74 | 75 | fail: 76 | snprintf(path, PATH_MAX, "/proc/self/task/%d/attr/sockcreate", gettid()); 77 | 78 | sockcreate = fopen(path, "w"); 79 | if (sockcreate == NULL) { 80 | LOGE("Failed to open %s: %s\n", path, strerror(errno)); 81 | 82 | return; 83 | } 84 | 85 | if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) { 86 | LOGE("Failed to write to %s: %s\n", path, strerror(errno)); 87 | 88 | return; 89 | } 90 | 91 | fclose(sockcreate); 92 | } 93 | 94 | static void get_current_attr(char *restrict output, size_t size) { 95 | char path[PATH_MAX]; 96 | snprintf(path, PATH_MAX, "/proc/self/attr/current"); 97 | 98 | FILE *current = fopen(path, "r"); 99 | if (current == NULL) { 100 | LOGE("fopen: %s\n", strerror(errno)); 101 | 102 | return; 103 | } 104 | 105 | if (fread(output, 1, size, current) == 0) { 106 | LOGE("fread: %s\n", strerror(errno)); 107 | 108 | return; 109 | } 110 | 111 | fclose(current); 112 | } 113 | 114 | void unix_datagram_sendto(const char *restrict path, void *restrict buf, size_t len) { 115 | char current_attr[PATH_MAX]; 116 | get_current_attr(current_attr, sizeof(current_attr)); 117 | 118 | set_socket_create_context(current_attr); 119 | 120 | struct sockaddr_un addr; 121 | addr.sun_family = AF_UNIX; 122 | 123 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 124 | 125 | int socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0); 126 | if (socket_fd == -1) { 127 | LOGE("socket: %s\n", strerror(errno)); 128 | 129 | return; 130 | } 131 | 132 | if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 133 | LOGE("connect: %s\n", strerror(errno)); 134 | 135 | return; 136 | } 137 | 138 | if (sendto(socket_fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) { 139 | LOGE("sendto: %s\n", strerror(errno)); 140 | 141 | return; 142 | } 143 | 144 | set_socket_create_context("u:r:zygote:s0"); 145 | 146 | close(socket_fd); 147 | } 148 | 149 | int chcon(const char *restrict path, const char *context) { 150 | char command[PATH_MAX]; 151 | snprintf(command, PATH_MAX, "chcon %s %s", context, path); 152 | 153 | return system(command); 154 | } 155 | 156 | int unix_listener_from_path(char *restrict path) { 157 | if (remove(path) == -1 && errno != ENOENT) { 158 | LOGE("remove: %s\n", strerror(errno)); 159 | 160 | return -1; 161 | } 162 | 163 | int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); 164 | if (socket_fd == -1) { 165 | LOGE("socket: %s\n", strerror(errno)); 166 | 167 | return -1; 168 | } 169 | 170 | struct sockaddr_un addr = { 171 | .sun_family = AF_UNIX 172 | }; 173 | strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); 174 | 175 | if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { 176 | LOGE("bind: %s\n", strerror(errno)); 177 | 178 | return -1; 179 | } 180 | 181 | if (listen(socket_fd, 2) == -1) { 182 | LOGE("listen: %s\n", strerror(errno)); 183 | 184 | return -1; 185 | } 186 | 187 | if (chcon(path, "u:object_r:magisk_file:s0") == -1) { 188 | LOGE("chcon: %s\n", strerror(errno)); 189 | 190 | return -1; 191 | } 192 | 193 | return socket_fd; 194 | } 195 | 196 | ssize_t write_fd(int fd, int sendfd) { 197 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 198 | char buf[1] = { 0 }; 199 | 200 | struct iovec iov = { 201 | .iov_base = buf, 202 | .iov_len = 1 203 | }; 204 | 205 | struct msghdr msg = { 206 | .msg_iov = &iov, 207 | .msg_iovlen = 1, 208 | .msg_control = cmsgbuf, 209 | .msg_controllen = sizeof(cmsgbuf) 210 | }; 211 | 212 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 213 | cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 214 | cmsg->cmsg_level = SOL_SOCKET; 215 | cmsg->cmsg_type = SCM_RIGHTS; 216 | 217 | memcpy(CMSG_DATA(cmsg), &sendfd, sizeof(int)); 218 | 219 | ssize_t ret = sendmsg(fd, &msg, 0); 220 | if (ret == -1) { 221 | LOGE("sendmsg: %s\n", strerror(errno)); 222 | 223 | return -1; 224 | } 225 | 226 | return ret; 227 | } 228 | 229 | int read_fd(int fd) { 230 | char cmsgbuf[CMSG_SPACE(sizeof(int))]; 231 | char buf[1] = { 0 }; 232 | 233 | struct iovec iov = { 234 | .iov_base = buf, 235 | .iov_len = 1 236 | }; 237 | 238 | struct msghdr msg = { 239 | .msg_iov = &iov, 240 | .msg_iovlen = 1, 241 | .msg_control = cmsgbuf, 242 | .msg_controllen = sizeof(cmsgbuf) 243 | }; 244 | 245 | ssize_t ret = recvmsg(fd, &msg, 0); 246 | if (ret == -1) { 247 | LOGE("recvmsg: %s\n", strerror(errno)); 248 | 249 | return -1; 250 | } 251 | 252 | struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); 253 | if (cmsg == NULL) { 254 | LOGE("CMSG_FIRSTHDR: %s\n", strerror(errno)); 255 | 256 | return -1; 257 | } 258 | 259 | int sendfd; 260 | memcpy(&sendfd, CMSG_DATA(cmsg), sizeof(int)); 261 | 262 | return sendfd; 263 | } 264 | 265 | #define write_func(type) \ 266 | ssize_t write_## type(int fd, type val) { \ 267 | return write(fd, &val, sizeof(type)); \ 268 | } 269 | 270 | #define read_func(type) \ 271 | ssize_t read_## type(int fd, type *val) { \ 272 | return read(fd, val, sizeof(type)); \ 273 | } 274 | 275 | write_func(int) 276 | read_func(int) 277 | 278 | write_func(size_t) 279 | read_func(size_t) 280 | 281 | write_func(uint32_t) 282 | read_func(uint32_t) 283 | 284 | write_func(uint8_t) 285 | read_func(uint8_t) 286 | 287 | ssize_t write_string(int fd, const char *restrict str) { 288 | size_t len[1]; 289 | len[0] = strlen(str); 290 | 291 | ssize_t written_bytes = write(fd, &len, sizeof(size_t)); 292 | if (written_bytes != sizeof(size_t)) { 293 | LOGE("Failed to write string length: Not all bytes were written (%zd != %zu).\n", written_bytes, sizeof(size_t)); 294 | 295 | return -1; 296 | } 297 | 298 | written_bytes = write(fd, str, len[0]); 299 | if ((size_t)written_bytes != len[0]) { 300 | LOGE("Failed to write string: Not all bytes were written.\n"); 301 | 302 | return -1; 303 | } 304 | 305 | return written_bytes; 306 | } 307 | 308 | ssize_t read_string(int fd, char *restrict str, size_t len) { 309 | size_t str_len_buf[1]; 310 | 311 | ssize_t read_bytes = read(fd, &str_len_buf, sizeof(size_t)); 312 | if (read_bytes != (ssize_t)sizeof(size_t)) { 313 | LOGE("Failed to read string length: Not all bytes were read (%zd != %zu).\n", read_bytes, sizeof(size_t)); 314 | 315 | return -1; 316 | } 317 | 318 | size_t str_len = str_len_buf[0]; 319 | 320 | if (str_len > len) { 321 | LOGE("Failed to read string: Buffer is too small (%zu > %zu).\n", str_len, len); 322 | 323 | return -1; 324 | } 325 | 326 | read_bytes = read(fd, str, str_len); 327 | if (read_bytes != (ssize_t)str_len) { 328 | LOGE("Failed to read string: Promised bytes doesn't exist (%zd != %zu).\n", read_bytes, str_len); 329 | 330 | return -1; 331 | } 332 | 333 | return read_bytes; 334 | } 335 | 336 | /* INFO: Cannot use restrict here as execv does not have restrict */ 337 | bool exec_command(char *restrict buf, size_t len, const char *restrict file, char *const argv[]) { 338 | int link[2]; 339 | pid_t pid; 340 | 341 | if (pipe(link) == -1) { 342 | LOGE("pipe: %s\n", strerror(errno)); 343 | 344 | return false; 345 | } 346 | 347 | if ((pid = fork()) == -1) { 348 | LOGE("fork: %s\n", strerror(errno)); 349 | 350 | close(link[0]); 351 | close(link[1]); 352 | 353 | return false; 354 | } 355 | 356 | if (pid == 0) { 357 | dup2(link[1], STDOUT_FILENO); 358 | close(link[0]); 359 | close(link[1]); 360 | 361 | execv(file, argv); 362 | 363 | LOGE("execv failed: %s\n", strerror(errno)); 364 | _exit(1); 365 | } else { 366 | close(link[1]); 367 | 368 | int nbytes = read(link[0], buf, len); 369 | if (nbytes > 0) buf[nbytes - 1] = '\0'; 370 | /* INFO: If something went wrong, at least we must ensure it is NULL-terminated */ 371 | else buf[0] = '\0'; 372 | 373 | wait(NULL); 374 | 375 | close(link[0]); 376 | } 377 | 378 | return true; 379 | } 380 | 381 | bool check_unix_socket(int fd, bool block) { 382 | struct pollfd pfd = { 383 | .fd = fd, 384 | .events = POLLIN, 385 | .revents = 0 386 | }; 387 | 388 | int timeout = block ? -1 : 0; 389 | poll(&pfd, 1, timeout); 390 | 391 | return pfd.revents & ~POLLIN ? false : true; 392 | } 393 | 394 | /* INFO: Cannot use restrict here as execv does not have restrict */ 395 | int non_blocking_execv(const char *restrict file, char *const argv[]) { 396 | int link[2]; 397 | pid_t pid; 398 | 399 | if (pipe(link) == -1) { 400 | LOGE("pipe: %s\n", strerror(errno)); 401 | 402 | return -1; 403 | } 404 | 405 | if ((pid = fork()) == -1) { 406 | LOGE("fork: %s\n", strerror(errno)); 407 | 408 | return -1; 409 | } 410 | 411 | if (pid == 0) { 412 | dup2(link[1], STDOUT_FILENO); 413 | close(link[0]); 414 | close(link[1]); 415 | 416 | execv(file, argv); 417 | } else { 418 | close(link[1]); 419 | 420 | return link[0]; 421 | } 422 | 423 | return -1; 424 | } 425 | 426 | void stringify_root_impl_name(struct root_impl impl, char *restrict output) { 427 | switch (impl.impl) { 428 | case None: { 429 | strcpy(output, "None"); 430 | 431 | break; 432 | } 433 | case Multiple: { 434 | strcpy(output, "Multiple"); 435 | 436 | break; 437 | } 438 | case KernelSU: { 439 | strcpy(output, "KernelSU"); 440 | 441 | break; 442 | } 443 | case APatch: { 444 | strcpy(output, "APatch"); 445 | 446 | break; 447 | } 448 | case Magisk: { 449 | if (impl.variant == 0) { 450 | strcpy(output, "Magisk Official"); 451 | } else { 452 | strcpy(output, "Magisk Kitsune"); 453 | } 454 | 455 | break; 456 | } 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /zygiskd/src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | 6 | #include "constants.h" 7 | #include "root_impl/common.h" 8 | 9 | #define CONCAT_(x,y) x##y 10 | #define CONCAT(x,y) CONCAT_(x,y) 11 | 12 | #define LOGI(...) \ 13 | __android_log_print(ANDROID_LOG_INFO, lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ 14 | printf(__VA_ARGS__); 15 | 16 | #define LOGE(...) \ 17 | __android_log_print(ANDROID_LOG_ERROR , lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ 18 | printf(__VA_ARGS__); 19 | 20 | #define ASSURE_SIZE_WRITE(area_name, subarea_name, sent_size, expected_size) \ 21 | if (sent_size != (ssize_t)(expected_size)) { \ 22 | LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 23 | \ 24 | return; \ 25 | } 26 | 27 | #define ASSURE_SIZE_READ(area_name, subarea_name, sent_size, expected_size) \ 28 | if (sent_size != (ssize_t)(expected_size)) { \ 29 | LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 30 | \ 31 | return; \ 32 | } 33 | 34 | #define ASSURE_SIZE_WRITE_BREAK(area_name, subarea_name, sent_size, expected_size) \ 35 | if (sent_size != (ssize_t)(expected_size)) { \ 36 | LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 37 | \ 38 | break; \ 39 | } 40 | 41 | #define ASSURE_SIZE_READ_BREAK(area_name, subarea_name, sent_size, expected_size) \ 42 | if (sent_size != (ssize_t)(expected_size)) { \ 43 | LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 44 | \ 45 | break; \ 46 | } 47 | 48 | #define ASSURE_SIZE_WRITE_WR(area_name, subarea_name, sent_size, expected_size) \ 49 | if (sent_size != (ssize_t)(expected_size)) { \ 50 | LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 51 | \ 52 | return -1; \ 53 | } 54 | 55 | #define ASSURE_SIZE_READ_WR(area_name, subarea_name, sent_size, expected_size) \ 56 | if (sent_size != (ssize_t)(expected_size)) { \ 57 | LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ 58 | \ 59 | return -1; \ 60 | } 61 | 62 | #define write_func_def(type) \ 63 | ssize_t write_## type(int fd, type val) 64 | 65 | #define read_func_def(type) \ 66 | ssize_t read_## type(int fd, type *val) 67 | 68 | bool switch_mount_namespace(pid_t pid); 69 | 70 | void get_property(const char *name, char *restrict output); 71 | 72 | void set_socket_create_context(const char *restrict context); 73 | 74 | void unix_datagram_sendto(const char *restrict path, void *restrict buf, size_t len); 75 | 76 | int chcon(const char *path, const char *restrict context); 77 | 78 | int unix_listener_from_path(char *path); 79 | 80 | ssize_t write_fd(int fd, int sendfd); 81 | int read_fd(int fd); 82 | 83 | write_func_def(int); 84 | read_func_def(int); 85 | 86 | write_func_def(size_t); 87 | read_func_def(size_t); 88 | 89 | write_func_def(uint32_t); 90 | read_func_def(uint32_t); 91 | 92 | write_func_def(uint8_t); 93 | read_func_def(uint8_t); 94 | 95 | ssize_t write_string(int fd, const char *restrict str); 96 | 97 | ssize_t read_string(int fd, char *restrict str, size_t len); 98 | 99 | bool exec_command(char *restrict buf, size_t len, const char *restrict file, char *const argv[]); 100 | 101 | bool check_unix_socket(int fd, bool block); 102 | 103 | int non_blocking_execv(const char *restrict file, char *const argv[]); 104 | 105 | void stringify_root_impl_name(struct root_impl impl, char *restrict output); 106 | 107 | #endif /* UTILS_H */ 108 | -------------------------------------------------------------------------------- /zygiskd/src/zygiskd.h: -------------------------------------------------------------------------------- 1 | #ifndef ZYGISKD_H 2 | #define ZYGISKD_H 3 | 4 | void zygiskd_start(char *restrict argv[]); 5 | 6 | #endif /* ZYGISKD_H */ 7 | --------------------------------------------------------------------------------