You've already forked ZygiskNext
mirror of
https://github.com/Dr-TSNG/ZygiskNext.git
synced 2025-08-27 23:46:34 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aff353f4e8 | |||
| 347d6b788a |
@@ -1,73 +0,0 @@
|
||||
name: Bug report/反馈 Bug
|
||||
description: Report errors or unexpected behavior./反馈错误或异常行为。
|
||||
labels: [bug]
|
||||
title: "[Bug] Short description/简单描述"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting issues for Zygisk Next!
|
||||
To make it easier for us to help you please enter detailed information below.
|
||||
Note: We will **NEVER** handle any issue related to root detection or writable system.
|
||||
|
||||
感谢给 Zygisk Next 汇报问题!
|
||||
为了使我们更好地帮助你,请提供以下信息。
|
||||
为了防止重复汇报,标题请务必使用英文。
|
||||
请注意:我们**不会**处理任何有关 root 检测和 system 分区可写相关的问题。
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce/复现步骤
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behaviour/预期行为
|
||||
placeholder: Tell us what should happen/正常情况下应该发生什么
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual behaviour/实际行为
|
||||
placeholder: Tell us what happens instead/实际上发生了什么
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: KernelSU Module List/KernelSU 模块列表
|
||||
render: Shell
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Zygisk Next version/Zygisk Next 版本
|
||||
description: Don't use 'latest'. Specify actual version, otherwise your issue will be closed./不要填用“最新版”。给出具体版本号,不然 issue 会被关闭。
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Android version/Android 版本
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: KernelSU version/KernelSU 版本
|
||||
validations:
|
||||
required: true
|
||||
- type: checkboxes
|
||||
id: latest
|
||||
attributes:
|
||||
label: Version requirement/版本要求
|
||||
options:
|
||||
- label: I am using latest debug CI version of Zygisk Next and enable verbose log/我正在使用最新 CI 调试版本 Zygisk Next 且启用详细日志
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Logs/日志
|
||||
description: For usage issues, please provide the log zip saved from KernelSU manager; for activation issues, please provide [bugreport](https://developer.android.com/studio/debug/bug-report). Without logs zip, the issue will be closed. /使用问题请提供从 KernelSU 管理器保存的日志压缩包;激活问题请提供 [bugreport](https://developer.android.google.cn/studio/debug/bug-report?hl=zh-cn) 日志。没有日志附件的问题会被关闭。
|
||||
placeholder: Upload logs zip by clicking the bar on the bottom. Upload logs to other websites or using external links is prohibited. /点击文本框底栏上传日志压缩包,禁止上传到其它网站或使用外链提供日志。
|
||||
validations:
|
||||
required: true
|
||||
@@ -1 +0,0 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Feature request/新特性请求
|
||||
description: Suggest an idea./提出建议
|
||||
labels: [enhancement]
|
||||
title: "[Feature Request] Short description/简单描述"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Note: We will **NEVER** handle any issue related to root detection or writable system.
|
||||
|
||||
请注意:我们**不会**处理任何有关 root 检测和 system 分区可写相关的问题。
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Is your feature request related to a problem?/你的请求是否与某个问题相关?
|
||||
placeholder: A clear and concise description of what the problem is./请清晰准确表述该问题。
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Describe the solution you'd like/描述你想要的解决方案
|
||||
placeholder: A clear and concise description of what you want to happen./请清晰准确描述新特性的预期行为
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional context/其他信息
|
||||
placeholder: Add any other context or screenshots about the feature request here./其他关于新特性的信息或者截图
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,25 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
|
||||
url = f'https://api.telegram.org/bot{os.environ["BOT_TOKEN"]}'
|
||||
url += f'/sendMediaGroup?chat_id={urllib.parse.quote(os.environ["CHANNEL_ID"])}&media='
|
||||
|
||||
# https://core.telegram.org/bots/api#markdownv2-style
|
||||
msg = os.environ["COMMIT_MESSAGE"]
|
||||
for c in ['\\', '_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!']:
|
||||
msg = msg.replace(c, f'\\{c}')
|
||||
commit_url = os.environ["COMMIT_URL"]
|
||||
commit_id = os.environ["COMMIT_ID"][:7]
|
||||
|
||||
caption = f"[{commit_id}]({commit_url})\n{msg}"[:1024]
|
||||
|
||||
data = json.dumps([
|
||||
{"type": "document", "media": "attach://Release"},
|
||||
{"type": "document", "media":"attach://Debug"},
|
||||
{"type": "document", "media": "attach://ReleaseSymbol"},
|
||||
{"type": "document", "media": "attach://DebugSymbol","caption": caption,"parse_mode":"MarkdownV2"}
|
||||
])
|
||||
|
||||
url += urllib.parse.quote(data)
|
||||
print(url)
|
||||
@@ -1,139 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
post_telegram:
|
||||
description: 'Post to Telegram'
|
||||
required: true
|
||||
type: boolean
|
||||
push:
|
||||
branches: [ master ]
|
||||
tags: [ v* ]
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CCACHE_COMPILERCHECK: "%compiler% -dumpmachine; %compiler% -dumpversion"
|
||||
CCACHE_NOHASHDIR: "true"
|
||||
CCACHE_HARDLINK: "true"
|
||||
CCACHE_BASEDIR: "${{ github.workspace }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: "recursive"
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Write key
|
||||
if: ${{ ( github.event_name != 'pull_request' && github.ref == 'refs/heads/master' ) || github.ref_type == 'tag' }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.PRIVATE_KEY }}" ]; then
|
||||
echo '${{ secrets.PUBLIC_KEY }}' | base64 --decode > module/public_key
|
||||
echo '${{ secrets.PRIVATE_KEY }}' | base64 --decode > module/private_key
|
||||
fi
|
||||
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: "17"
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
gradle-home-cache-cleanup: true
|
||||
|
||||
- name: Setup rust-cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: zygiskd/src -> ../build/intermediates/rust
|
||||
cache-targets: false
|
||||
|
||||
- name: Setup Rust
|
||||
run: |
|
||||
rustup override set nightly
|
||||
rustup target add aarch64-linux-android
|
||||
rustup target add x86_64-linux-android
|
||||
rustup target add i686-linux-android
|
||||
rustup target add armv7-linux-androideabi
|
||||
|
||||
- name: Set up ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
with:
|
||||
max-size: 2G
|
||||
key: ${{ runner.os }}
|
||||
restore-keys: ${{ runner.os }}
|
||||
save: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
echo 'org.gradle.parallel=true' >> gradle.properties
|
||||
echo 'org.gradle.vfs.watch=true' >> gradle.properties
|
||||
echo 'org.gradle.jvmargs=-Xmx2048m' >> gradle.properties
|
||||
echo 'android.native.buildOutput=verbose' >> gradle.properties
|
||||
sed -i 's/org.gradle.unsafe.configuration-cache=true//g' gradle.properties
|
||||
./gradlew zipRelease
|
||||
./gradlew zipDebug
|
||||
|
||||
- name: Prepare artifact
|
||||
if: success()
|
||||
id: prepareArtifact
|
||||
run: |
|
||||
releaseName=`ls module/build/outputs/release/Zygisk-Next-v*-release.zip | awk -F '(/|.zip)' '{print $5}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
|
||||
debugName=`ls module/build/outputs/release/Zygisk-Next-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT
|
||||
unzip module/build/outputs/release/Zygisk-Next-v*-release.zip -d zksu-release
|
||||
unzip module/build/outputs/release/Zygisk-Next-v*-debug.zip -d zksu-debug
|
||||
releaseSymbolName="SYMBOLS-$releaseName.zip"
|
||||
debugSymbolName="SYMBOLS-$debugName.zip"
|
||||
echo "releaseSymbolName=$releaseSymbolName" >> $GITHUB_OUTPUT
|
||||
echo "debugSymbolName=$debugSymbolName" >> $GITHUB_OUTPUT
|
||||
zip -r $releaseSymbolName zygiskd/build/symbols/release
|
||||
zip -r $debugSymbolName zygiskd/build/symbols/debug
|
||||
|
||||
- name: Upload release
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.releaseName }}
|
||||
path: "./zksu-release/*"
|
||||
|
||||
- name: Upload debug
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.debugName }}
|
||||
path: "./zksu-debug/*"
|
||||
|
||||
- name: Upload release symbols
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.releaseName }}-symbols
|
||||
path: "zygiskd/build/symbols/release"
|
||||
|
||||
- name: Upload debug symbols
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ steps.prepareArtifact.outputs.debugName }}-symbols
|
||||
path: "zygiskd/build/symbols/debug"
|
||||
|
||||
- name: Post to channel
|
||||
if: ${{ success() && github.event_name != 'pull_request' && github.ref == 'refs/heads/master' && github.ref_type != 'tag' && inputs.post_telegram != 'false' }}
|
||||
env:
|
||||
CHANNEL_ID: ${{ secrets.CHANNEL_ID }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
|
||||
COMMIT_URL: ${{ github.event.head_commit.url }}
|
||||
COMMIT_ID: ${{ github.event.head_commit.id }}
|
||||
run: |
|
||||
if [ ! -z "${{ secrets.BOT_TOKEN }}" ]; then
|
||||
OUTPUT="module/build/outputs/release"
|
||||
export Release=$(find $OUTPUT -name "Zygisk-Next-v*-release.zip")
|
||||
export Debug=$(find $OUTPUT -name "Zygisk-Next-v*-debug.zip")
|
||||
export ReleaseSymbol="${{ steps.prepareArtifact.outputs.releaseSymbolName }}"
|
||||
export DebugSymbol="${{ steps.prepareArtifact.outputs.debugSymbolName }}"
|
||||
URL=$(python3 .github/scripts/telegram_url.py)
|
||||
curl -v "$URL" -F Release="@$Release" -F Debug="@$Debug" -F ReleaseSymbol="@$ReleaseSymbol" -F DebugSymbol="@$DebugSymbol"
|
||||
fi
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Issue moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited, reopened]
|
||||
|
||||
jobs:
|
||||
autoclose:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check issue
|
||||
uses: tachiyomiorg/issue-moderator-action@v1
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
auto-close-rules: |
|
||||
[
|
||||
{
|
||||
"type": "title",
|
||||
"regex": ".*(Short description|简单描述).*",
|
||||
"message": "You did not fill out the description in the title/你没有填写标题"
|
||||
}
|
||||
]
|
||||
auto-close-ignore-label: do-not-autoclose
|
||||
@@ -1,6 +0,0 @@
|
||||
.gradle
|
||||
.idea
|
||||
.cxx
|
||||
build
|
||||
local.properties
|
||||
Cargo.lock
|
||||
@@ -1,3 +0,0 @@
|
||||
[submodule "loader/src/external/lsplt"]
|
||||
path = loader/src/external/lsplt
|
||||
url = https://github.com/LSPosed/lsplt
|
||||
@@ -1,674 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -2,6 +2,22 @@
|
||||
|
||||
Standalone implementation of Zygisk, providing Zygisk API support for KernelSU and a replacement of Magisk's built-in Zygisk.
|
||||
|
||||
## Copyright Notice
|
||||
|
||||
Copyright © 2024 Zygisk Next developers. All rights reserved.
|
||||
|
||||
The software Zygisk Next, starting from version v4-0.9.2, is no longer under the GPL-3.0 License. Instead, all rights to the software are reserved by the owner.
|
||||
|
||||
The following conditions now apply:
|
||||
|
||||
1. **No Modifications:** The software may not be modified in any way. This includes but is not limited to changing, adding, or removing any part of the software's code or functionality.
|
||||
|
||||
2. **No Redistribution:** The software may not be redistributed in any form. This includes but is not limited to renaming, selling, or including the software as part of another project.
|
||||
|
||||
3. **No Picking:** No parts, pieces, or components of the software may be extracted and submitted to other projects. This includes, but is not limited to, code snippets, functions, and released binaries.
|
||||
|
||||
4. **No Claim to Succession:** Any fork of the software that was created before the license change may not claim to be an official or unofficial successor to the project. This includes but is not limited to using the project's name, branding, or reputation to imply a connection to the original project.
|
||||
|
||||
## Requirements
|
||||
|
||||
### General
|
||||
@@ -11,8 +27,7 @@ Standalone implementation of Zygisk, providing Zygisk API support for KernelSU a
|
||||
### KernelSU
|
||||
|
||||
+ Minimal KernelSU version: 10940
|
||||
+ Minimal ksud version: 11424
|
||||
+ Kernel has full SELinux patch support
|
||||
+ Minimal KernelSU Manager (ksud) version: 11575
|
||||
|
||||
### Magisk
|
||||
|
||||
@@ -21,6 +36,4 @@ Standalone implementation of Zygisk, providing Zygisk API support for KernelSU a
|
||||
|
||||
## Compatibility
|
||||
|
||||
`PROCESS_ON_DENYLIST` cannot be flagged correctly for isolated processes on Magisk DenyList currently.
|
||||
|
||||
Zygisk Next only guarantees the same behavior of Zygisk API, but will NOT ensure Magisk's internal features.
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
import com.android.build.gradle.LibraryExtension
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.lib) apply false
|
||||
}
|
||||
|
||||
fun String.execute(currentWorkingDir: File = file("./")): String {
|
||||
val byteOut = ByteArrayOutputStream()
|
||||
project.exec {
|
||||
workingDir = currentWorkingDir
|
||||
commandLine = split("\\s".toRegex())
|
||||
standardOutput = byteOut
|
||||
}
|
||||
return String(byteOut.toByteArray()).trim()
|
||||
}
|
||||
|
||||
val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
||||
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
||||
|
||||
val moduleId by extra("zygisksu")
|
||||
val moduleName by extra("Zygisk Next")
|
||||
val verName by extra("v4-0.9.1.1")
|
||||
val verCode by extra(gitCommitCount)
|
||||
val commitHash by extra(gitCommitHash)
|
||||
val minKsuVersion by extra(10940)
|
||||
val minKsudVersion by extra(11425)
|
||||
val maxKsuVersion by extra(20000)
|
||||
val minMagiskVersion by extra(26402)
|
||||
|
||||
val androidMinSdkVersion by extra(26)
|
||||
val androidTargetSdkVersion by extra(34)
|
||||
val androidCompileSdkVersion by extra(34)
|
||||
val androidBuildToolsVersion by extra("34.0.0")
|
||||
val androidCompileNdkVersion by extra("26.0.10792818")
|
||||
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
|
||||
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)
|
||||
|
||||
tasks.register("Delete", Delete::class) {
|
||||
delete(rootProject.buildDir)
|
||||
}
|
||||
|
||||
fun Project.configureBaseExtension() {
|
||||
extensions.findByType(LibraryExtension::class)?.run {
|
||||
namespace = "icu.nullptr.zygisk.next"
|
||||
compileSdk = androidCompileSdkVersion
|
||||
ndkVersion = androidCompileNdkVersion
|
||||
buildToolsVersion = androidBuildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk = androidMinSdkVersion
|
||||
targetSdk = androidTargetSdkVersion
|
||||
}
|
||||
|
||||
lint {
|
||||
abortOnError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subprojects {
|
||||
plugins.withId("com.android.library") {
|
||||
configureBaseExtension()
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
android.useAndroidX=false
|
||||
@@ -1,9 +0,0 @@
|
||||
[versions]
|
||||
agp = "8.2.0"
|
||||
kotlin = "1.9.22"
|
||||
|
||||
[plugins]
|
||||
agp-lib = { id = "com.android.library", version.ref = "agp" }
|
||||
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" }
|
||||
rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version = "0.9.3" }
|
||||
Vendored
BIN
Binary file not shown.
-7
@@ -1,7 +0,0 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
@@ -1,185 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
Vendored
-89
@@ -1,89 +0,0 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -1,84 +0,0 @@
|
||||
import java.nio.file.Paths
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.lib)
|
||||
}
|
||||
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val commitHash: String by rootProject.extra
|
||||
|
||||
fun Project.findInPath(executable: String, property: String): String? {
|
||||
val pathEnv = System.getenv("PATH")
|
||||
return pathEnv.split(File.pathSeparator).map { folder ->
|
||||
Paths.get("${folder}${File.separator}${executable}${if (OperatingSystem.current().isWindows) ".exe" else ""}")
|
||||
.toFile()
|
||||
}.firstOrNull { path ->
|
||||
path.exists()
|
||||
}?.absolutePath ?: properties.getOrDefault(property, null) as? String?
|
||||
}
|
||||
|
||||
val ccachePath by lazy {
|
||||
project.findInPath("ccache", "ccache.path")?.also {
|
||||
println("loader: Use ccache: $it")
|
||||
}
|
||||
}
|
||||
|
||||
val defaultCFlags = arrayOf(
|
||||
"-Wall", "-Wextra",
|
||||
"-fno-rtti", "-fno-exceptions",
|
||||
"-fno-stack-protector", "-fomit-frame-pointer",
|
||||
"-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__"
|
||||
)
|
||||
|
||||
val releaseFlags = arrayOf(
|
||||
"-Oz", "-flto",
|
||||
"-Wno-unused", "-Wno-unused-parameter",
|
||||
"-fvisibility=hidden", "-fvisibility-inlines-hidden",
|
||||
"-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
|
||||
"-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all"
|
||||
)
|
||||
|
||||
android {
|
||||
buildFeatures {
|
||||
androidResources = false
|
||||
buildConfig = false
|
||||
prefab = true
|
||||
}
|
||||
|
||||
externalNativeBuild.cmake {
|
||||
path("src/CMakeLists.txt")
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
externalNativeBuild.cmake {
|
||||
arguments += "-DANDROID_STL=none"
|
||||
arguments += "-DLSPLT_STANDALONE=ON"
|
||||
cFlags("-std=c18", *defaultCFlags)
|
||||
cppFlags("-std=c++20", *defaultCFlags)
|
||||
ccachePath?.let {
|
||||
arguments += "-DNDK_CCACHE=$it"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
externalNativeBuild.cmake {
|
||||
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug"
|
||||
}
|
||||
}
|
||||
release {
|
||||
externalNativeBuild.cmake {
|
||||
cFlags += releaseFlags
|
||||
cppFlags += releaseFlags
|
||||
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project("loader")
|
||||
|
||||
find_package(cxx REQUIRED CONFIG)
|
||||
|
||||
add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\")
|
||||
|
||||
aux_source_directory(common COMMON_SRC_LIST)
|
||||
add_library(common STATIC ${COMMON_SRC_LIST})
|
||||
target_include_directories(common PRIVATE include)
|
||||
target_link_libraries(common cxx::cxx log)
|
||||
|
||||
aux_source_directory(injector INJECTOR_SRC_LIST)
|
||||
add_library(zygisk SHARED ${INJECTOR_SRC_LIST})
|
||||
target_include_directories(zygisk PRIVATE include)
|
||||
target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap)
|
||||
|
||||
aux_source_directory(ptracer PTRACER_SRC_LIST)
|
||||
add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST})
|
||||
target_include_directories(libzygisk_ptrace.so PRIVATE include)
|
||||
target_link_libraries(libzygisk_ptrace.so cxx::cxx log common)
|
||||
|
||||
add_subdirectory(external)
|
||||
@@ -1,142 +0,0 @@
|
||||
#include <linux/un.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "daemon.h"
|
||||
#include "dl.h"
|
||||
#include "socket_utils.h"
|
||||
|
||||
namespace zygiskd {
|
||||
static std::string TMP_PATH;
|
||||
void Init(const char *path) {
|
||||
TMP_PATH = path;
|
||||
}
|
||||
|
||||
std::string GetTmpPath() {
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
int Connect(uint8_t retry) {
|
||||
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path={0},
|
||||
};
|
||||
auto socket_path = TMP_PATH + kCPSocketName;
|
||||
strcpy(addr.sun_path, socket_path.c_str());
|
||||
socklen_t socklen = sizeof(addr);
|
||||
|
||||
while (retry--) {
|
||||
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
|
||||
if (r == 0) return fd;
|
||||
if (retry) {
|
||||
PLOGE("Retrying to connect to zygiskd, sleep 1s");
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool PingHeartbeat() {
|
||||
UniqueFd fd = Connect(5);
|
||||
if (fd == -1) {
|
||||
PLOGE("Connect to zygiskd");
|
||||
return false;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::PingHeartBeat);
|
||||
return true;
|
||||
}
|
||||
|
||||
int RequestLogcatFd() {
|
||||
int fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("RequestLogcatFd");
|
||||
return -1;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd);
|
||||
return fd;
|
||||
}
|
||||
|
||||
uint32_t GetProcessFlags(uid_t uid) {
|
||||
UniqueFd fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("GetProcessFlags");
|
||||
return 0;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags);
|
||||
socket_utils::write_u32(fd, uid);
|
||||
return socket_utils::read_u32(fd);
|
||||
}
|
||||
|
||||
std::vector<Module> ReadModules() {
|
||||
std::vector<Module> modules;
|
||||
UniqueFd fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("ReadModules");
|
||||
return modules;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules);
|
||||
size_t len = socket_utils::read_usize(fd);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
std::string name = socket_utils::read_string(fd);
|
||||
int module_fd = socket_utils::recv_fd(fd);
|
||||
modules.emplace_back(name, module_fd);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
int ConnectCompanion(size_t index) {
|
||||
int fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("ConnectCompanion");
|
||||
return -1;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket);
|
||||
socket_utils::write_usize(fd, index);
|
||||
if (socket_utils::read_u8(fd) == 1) {
|
||||
return fd;
|
||||
} else {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int GetModuleDir(size_t index) {
|
||||
UniqueFd fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("GetModuleDir");
|
||||
return -1;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir);
|
||||
socket_utils::write_usize(fd, index);
|
||||
return socket_utils::recv_fd(fd);
|
||||
}
|
||||
|
||||
void ZygoteRestart() {
|
||||
UniqueFd fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
if (errno == ENOENT) {
|
||||
LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)");
|
||||
} else {
|
||||
PLOGE("Could not notify ZygoteRestart");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) {
|
||||
PLOGE("Failed to request ZygoteRestart");
|
||||
}
|
||||
}
|
||||
|
||||
void SystemServerStarted() {
|
||||
UniqueFd fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("Failed to report system server started");
|
||||
} else {
|
||||
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) {
|
||||
PLOGE("Failed to report system server started");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <libgen.h>
|
||||
#include <climits>
|
||||
#include <cstring>
|
||||
#include <android/dlext.h>
|
||||
|
||||
#include "dl.h"
|
||||
#include "logging.h"
|
||||
|
||||
extern "C" [[gnu::weak]] struct android_namespace_t*
|
||||
//NOLINTNEXTLINE
|
||||
__loader_android_create_namespace([[maybe_unused]] const char* name,
|
||||
[[maybe_unused]] const char* ld_library_path,
|
||||
[[maybe_unused]] const char* default_library_path,
|
||||
[[maybe_unused]] uint64_t type,
|
||||
[[maybe_unused]] const char* permitted_when_isolated_path,
|
||||
[[maybe_unused]] android_namespace_t* parent,
|
||||
[[maybe_unused]] const void* caller_addr);
|
||||
|
||||
void* DlopenExt(const char* path, int flags) {
|
||||
auto info = android_dlextinfo{};
|
||||
auto* dir = dirname(path);
|
||||
auto* ns = &__loader_android_create_namespace == nullptr ? nullptr :
|
||||
__loader_android_create_namespace(path, dir, nullptr,
|
||||
2, /* ANDROID_NAMESPACE_TYPE_SHARED */
|
||||
nullptr, nullptr,
|
||||
reinterpret_cast<void*>(&DlopenExt));
|
||||
if (ns) {
|
||||
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
|
||||
info.library_namespace = ns;
|
||||
|
||||
LOGD("Open %s with namespace %p", path, ns);
|
||||
} else {
|
||||
LOGD("Cannot create namespace for %s", path);
|
||||
}
|
||||
|
||||
auto* handle = android_dlopen_ext(path, flags, &info);
|
||||
if (handle) {
|
||||
LOGD("dlopen %s: %p", path, handle);
|
||||
} else {
|
||||
LOGE("dlopen %s: %s", path, dlerror());
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
|
||||
void* DlopenMem(int fd, int flags) {
|
||||
auto info = android_dlextinfo{
|
||||
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
|
||||
.library_fd = fd
|
||||
};
|
||||
|
||||
auto* handle = android_dlopen_ext("/jit-cache", flags, &info);
|
||||
if (handle) {
|
||||
LOGV("dlopen fd %d: %p", fd, handle);
|
||||
} else {
|
||||
LOGE("dlopen fd %d: %s", fd, dlerror());
|
||||
}
|
||||
return handle;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
#include <sys/sysmacros.h>
|
||||
|
||||
#include "files.hpp"
|
||||
#include "misc.hpp"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn) {
|
||||
size_t len = 1024;
|
||||
char *buf = (char *) malloc(len);
|
||||
char *start;
|
||||
ssize_t read;
|
||||
while ((read = getline(&buf, &len, fp)) >= 0) {
|
||||
start = buf;
|
||||
if (trim) {
|
||||
while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos)
|
||||
--read;
|
||||
buf[read] = '\0';
|
||||
while (*start == ' ')
|
||||
++start;
|
||||
}
|
||||
if (!fn(start))
|
||||
break;
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn) {
|
||||
if (auto fp = open_file(file, "re"))
|
||||
file_readline(trim, fp.get(), fn);
|
||||
}
|
||||
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn) {
|
||||
file_readline(false, file, fn);
|
||||
}
|
||||
|
||||
std::vector<mount_info> parse_mount_info(const char *pid) {
|
||||
char buf[PATH_MAX] = {};
|
||||
snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid);
|
||||
std::vector<mount_info> result;
|
||||
|
||||
file_readline(buf, [&result](std::string_view line) -> bool {
|
||||
int root_start = 0, root_end = 0;
|
||||
int target_start = 0, target_end = 0;
|
||||
int vfs_option_start = 0, vfs_option_end = 0;
|
||||
int type_start = 0, type_end = 0;
|
||||
int source_start = 0, source_end = 0;
|
||||
int fs_option_start = 0, fs_option_end = 0;
|
||||
int optional_start = 0, optional_end = 0;
|
||||
unsigned int id, parent, maj, min;
|
||||
sscanf(line.data(),
|
||||
"%u " // (1) id
|
||||
"%u " // (2) parent
|
||||
"%u:%u " // (3) maj:min
|
||||
"%n%*s%n " // (4) mountroot
|
||||
"%n%*s%n " // (5) target
|
||||
"%n%*s%n" // (6) vfs options (fs-independent)
|
||||
"%n%*[^-]%n - " // (7) optional fields
|
||||
"%n%*s%n " // (8) FS type
|
||||
"%n%*s%n " // (9) source
|
||||
"%n%*s%n", // (10) fs options (fs specific)
|
||||
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
|
||||
&target_end, &vfs_option_start, &vfs_option_end,
|
||||
&optional_start, &optional_end, &type_start, &type_end,
|
||||
&source_start, &source_end, &fs_option_start, &fs_option_end);
|
||||
|
||||
auto root = line.substr(root_start, root_end - root_start);
|
||||
auto target = line.substr(target_start, target_end - target_start);
|
||||
auto vfs_option =
|
||||
line.substr(vfs_option_start, vfs_option_end - vfs_option_start);
|
||||
++optional_start;
|
||||
--optional_end;
|
||||
auto optional = line.substr(
|
||||
optional_start,
|
||||
optional_end - optional_start > 0 ? optional_end - optional_start : 0);
|
||||
|
||||
auto type = line.substr(type_start, type_end - type_start);
|
||||
auto source = line.substr(source_start, source_end - source_start);
|
||||
auto fs_option =
|
||||
line.substr(fs_option_start, fs_option_end - fs_option_start);
|
||||
|
||||
unsigned int shared = 0;
|
||||
unsigned int master = 0;
|
||||
unsigned int propagate_from = 0;
|
||||
if (auto pos = optional.find("shared:"); pos != std::string_view::npos) {
|
||||
shared = parse_int(optional.substr(pos + 7));
|
||||
}
|
||||
if (auto pos = optional.find("master:"); pos != std::string_view::npos) {
|
||||
master = parse_int(optional.substr(pos + 7));
|
||||
}
|
||||
if (auto pos = optional.find("propagate_from:");
|
||||
pos != std::string_view::npos) {
|
||||
propagate_from = parse_int(optional.substr(pos + 15));
|
||||
}
|
||||
|
||||
result.emplace_back(mount_info {
|
||||
.id = id,
|
||||
.parent = parent,
|
||||
.device = static_cast<dev_t>(makedev(maj, min)),
|
||||
.root {root},
|
||||
.target {target},
|
||||
.vfs_option {vfs_option},
|
||||
.optional {
|
||||
.shared = shared,
|
||||
.master = master,
|
||||
.propagate_from = propagate_from,
|
||||
},
|
||||
.type {type},
|
||||
.source {source},
|
||||
.fs_option {fs_option},
|
||||
});
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
sDIR make_dir(DIR *dp) {
|
||||
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
|
||||
}
|
||||
|
||||
sFILE make_file(FILE *fp) {
|
||||
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#include <android/log.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "logging.h"
|
||||
#include "socket_utils.h"
|
||||
|
||||
namespace logging {
|
||||
static int logfd = -1;
|
||||
|
||||
void setfd(int fd) {
|
||||
close(logfd);
|
||||
logfd = fd;
|
||||
}
|
||||
|
||||
int getfd() {
|
||||
return logfd;
|
||||
}
|
||||
|
||||
void log(int prio, const char* tag, const char* fmt, ...) {
|
||||
if (logfd == -1) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
__android_log_vprint(prio, tag, fmt, ap);
|
||||
va_end(ap);
|
||||
} else {
|
||||
char buf[BUFSIZ];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
socket_utils::write_u8(logfd, prio);
|
||||
socket_utils::write_string(logfd, tag);
|
||||
socket_utils::write_string(logfd, buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#include "misc.hpp"
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
errno = pthread_create(&thread, &attr, entry, arg);
|
||||
if (errno) {
|
||||
PLOGE("pthread_create");
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
int parse_int(std::string_view s) {
|
||||
int val = 0;
|
||||
for (char c : s) {
|
||||
if (!c) break;
|
||||
if (c > '9' || c < '0')
|
||||
return -1;
|
||||
val = val * 10 + c - '0';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
std::list<std::string> split_str(std::string_view s, std::string_view delimiter) {
|
||||
std::list<std::string> ret;
|
||||
size_t pos = 0;
|
||||
while (pos < s.size()) {
|
||||
auto next = s.find(delimiter, pos);
|
||||
if (next == std::string_view::npos) {
|
||||
ret.emplace_back(s.substr(pos));
|
||||
break;
|
||||
}
|
||||
ret.emplace_back(s.substr(pos, next - pos));
|
||||
pos = next + delimiter.size();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string join_str(const std::list<std::string>& list, std::string_view delimiter) {
|
||||
std::string ret;
|
||||
for (auto& s : list) {
|
||||
if (!ret.empty())
|
||||
ret += delimiter;
|
||||
ret += s;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
#include <cstddef>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "socket_utils.h"
|
||||
|
||||
namespace socket_utils {
|
||||
|
||||
ssize_t xread(int fd, void* buf, size_t count) {
|
||||
size_t read_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = read(fd, (std::byte*) buf + read_sz, count - read_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
PLOGE("read");
|
||||
return ret;
|
||||
}
|
||||
read_sz += ret;
|
||||
} while (read_sz != count && ret != 0);
|
||||
if (read_sz != count) {
|
||||
PLOGE("read (%zu != %zu)", count, read_sz);
|
||||
}
|
||||
return read_sz;
|
||||
}
|
||||
|
||||
size_t xwrite(int fd, const void* buf, size_t count) {
|
||||
size_t write_sz = 0;
|
||||
ssize_t ret;
|
||||
do {
|
||||
ret = write(fd, (std::byte*) buf + write_sz, count - write_sz);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
PLOGE("write");
|
||||
return write_sz;
|
||||
}
|
||||
write_sz += ret;
|
||||
} while (write_sz != count && ret != 0);
|
||||
if (write_sz != count) {
|
||||
PLOGE("write (%zu != %zu)", count, write_sz);
|
||||
}
|
||||
return write_sz;
|
||||
}
|
||||
|
||||
ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) {
|
||||
int rec = recvmsg(sockfd, msg, flags);
|
||||
if (rec < 0) PLOGE("recvmsg");
|
||||
return rec;
|
||||
}
|
||||
|
||||
void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) {
|
||||
iovec iov = {
|
||||
.iov_base = &cnt,
|
||||
.iov_len = sizeof(cnt),
|
||||
};
|
||||
msghdr msg = {
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
.msg_control = cmsgbuf,
|
||||
.msg_controllen = bufsz
|
||||
};
|
||||
|
||||
xrecvmsg(sockfd, &msg, MSG_WAITALL);
|
||||
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
||||
|
||||
if (msg.msg_controllen != bufsz ||
|
||||
cmsg == nullptr ||
|
||||
// TODO: pass from rust: 20, expected: 16
|
||||
// cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CMSG_DATA(cmsg);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline T read_exact_or(int fd, T fail) {
|
||||
T res;
|
||||
return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : fail;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline bool write_exact(int fd, T val) {
|
||||
return sizeof(T) == xwrite(fd, &val, sizeof(T));
|
||||
}
|
||||
|
||||
uint8_t read_u8(int fd) {
|
||||
return read_exact_or<uint8_t>(fd, 0);
|
||||
}
|
||||
|
||||
uint32_t read_u32(int fd) {
|
||||
return read_exact_or<uint32_t>(fd, 0);
|
||||
}
|
||||
|
||||
size_t read_usize(int fd) {
|
||||
return read_exact_or<size_t>(fd, 0);
|
||||
}
|
||||
|
||||
bool write_usize(int fd, size_t val) {
|
||||
return write_exact<size_t>(fd, val);
|
||||
}
|
||||
|
||||
std::string read_string(int fd) {
|
||||
auto len = read_usize(fd);
|
||||
char buf[len + 1];
|
||||
buf[len] = '\0';
|
||||
xread(fd, buf, len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool write_u8(int fd, uint8_t val) {
|
||||
return write_exact<uint8_t>(fd, val);
|
||||
}
|
||||
|
||||
bool write_u32(int fd, uint32_t val) {
|
||||
return write_exact<uint32_t>(fd, val);
|
||||
}
|
||||
|
||||
bool write_string(int fd, std::string_view str) {
|
||||
return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size());
|
||||
}
|
||||
|
||||
int recv_fd(int sockfd) {
|
||||
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||
|
||||
void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
|
||||
if (data == nullptr) return -1;
|
||||
|
||||
int result;
|
||||
memcpy(&result, data, sizeof(int));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
project(external)
|
||||
|
||||
OPTION(LSPLT_BUILD_SHARED OFF)
|
||||
add_subdirectory(lsplt/lsplt/src/main/jni)
|
||||
|
||||
add_library(phmap INTERFACE)
|
||||
target_include_directories(phmap INTERFACE parallel-hashmap)
|
||||
target_compile_options(phmap INTERFACE -Wno-unused-value)
|
||||
Vendored
-1
Submodule loader/src/external/lsplt deleted from 5d2b820cf9
@@ -1,395 +0,0 @@
|
||||
/* Copyright 2022 John "topjohnwu" Wu
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// This is the public API for Zygisk modules.
|
||||
// DO NOT MODIFY ANY CODE IN THIS HEADER.
|
||||
|
||||
// WARNING: this file may contain changes that are not finalized.
|
||||
// Always use the following published header for development:
|
||||
// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define ZYGISK_API_VERSION 4
|
||||
|
||||
/*
|
||||
|
||||
***************
|
||||
* Introduction
|
||||
***************
|
||||
|
||||
On Android, all app processes are forked from a special daemon called "Zygote".
|
||||
For each new app process, zygote will fork a new process and perform "specialization".
|
||||
This specialization operation enforces the Android security sandbox on the newly forked
|
||||
process to make sure that 3rd party application code is only loaded after it is being
|
||||
restricted within a sandbox.
|
||||
|
||||
On Android, there is also this special process called "system_server". This single
|
||||
process hosts a significant portion of system services, which controls how the
|
||||
Android operating system and apps interact with each other.
|
||||
|
||||
The Zygisk framework provides a way to allow developers to build modules and run custom
|
||||
code before and after system_server and any app processes' specialization.
|
||||
This enable developers to inject code and alter the behavior of system_server and app processes.
|
||||
|
||||
Please note that modules will only be loaded after zygote has forked the child process.
|
||||
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
|
||||
|
||||
*********************
|
||||
* Development Guide
|
||||
*********************
|
||||
|
||||
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
|
||||
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
|
||||
|
||||
Example code:
|
||||
|
||||
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
||||
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
||||
|
||||
class ExampleModule : public zygisk::ModuleBase {
|
||||
public:
|
||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||
this->api = api;
|
||||
this->env = env;
|
||||
}
|
||||
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
|
||||
JNINativeMethod methods[] = {
|
||||
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
|
||||
};
|
||||
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
|
||||
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
|
||||
}
|
||||
private:
|
||||
zygisk::Api *api;
|
||||
JNIEnv *env;
|
||||
};
|
||||
|
||||
REGISTER_ZYGISK_MODULE(ExampleModule)
|
||||
|
||||
-----------------------------------------------------------------------------------------
|
||||
|
||||
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
|
||||
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
|
||||
never runs in a true superuser environment.
|
||||
|
||||
If your module require access to superuser permissions, you can create and register
|
||||
a root companion handler function. This function runs in a separate root companion
|
||||
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
|
||||
your target process and the root companion process.
|
||||
|
||||
Example code:
|
||||
|
||||
static void example_handler(int socket) { ... }
|
||||
|
||||
REGISTER_ZYGISK_COMPANION(example_handler)
|
||||
|
||||
*/
|
||||
|
||||
namespace zygisk {
|
||||
|
||||
struct Api;
|
||||
struct AppSpecializeArgs;
|
||||
struct ServerSpecializeArgs;
|
||||
|
||||
class ModuleBase {
|
||||
public:
|
||||
|
||||
// This method is called as soon as the module is loaded into the target process.
|
||||
// A Zygisk API handle will be passed as an argument.
|
||||
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
|
||||
|
||||
// This method is called before the app process is specialized.
|
||||
// At this point, the process just got forked from zygote, but no app specific specialization
|
||||
// is applied. This means that the process does not have any sandbox restrictions and
|
||||
// still runs with the same privilege of zygote.
|
||||
//
|
||||
// All the arguments that will be sent and used for app specialization is passed as a single
|
||||
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
|
||||
// process will be specialized.
|
||||
//
|
||||
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
|
||||
// get a socket to do IPC calls with a root companion process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the app process is specialized.
|
||||
// At this point, the process has all sandbox restrictions enabled for this application.
|
||||
// This means that this method runs with the same privilege of the app's own code.
|
||||
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called before the system server process is specialized.
|
||||
// See preAppSpecialize(args) for more info.
|
||||
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
|
||||
|
||||
// This method is called after the system server process is specialized.
|
||||
// At this point, the process runs with the privilege of system_server.
|
||||
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs {
|
||||
// Required arguments. These arguments are guaranteed to exist on all Android versions.
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jobjectArray &rlimits;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
// Optional arguments. Please check whether the pointer is null before de-referencing
|
||||
jintArray *const fds_to_ignore;
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
jboolean *const mount_sysprop_overrides;
|
||||
|
||||
AppSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs() = delete;
|
||||
};
|
||||
|
||||
namespace internal {
|
||||
struct api_table;
|
||||
template <class T> void entry_impl(api_table *, JNIEnv *);
|
||||
}
|
||||
|
||||
// These values are used in Api::setOption(Option)
|
||||
enum Option : int {
|
||||
// Force Magisk's denylist unmount routines to run on this process.
|
||||
//
|
||||
// Setting this option only makes sense in preAppSpecialize.
|
||||
// The actual unmounting happens during app process specialization.
|
||||
//
|
||||
// Set this option to force all Magisk and modules' files to be unmounted from the
|
||||
// mount namespace of the process, regardless of the denylist enforcement status.
|
||||
FORCE_DENYLIST_UNMOUNT = 0,
|
||||
|
||||
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
|
||||
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
|
||||
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
|
||||
DLCLOSE_MODULE_LIBRARY = 1,
|
||||
};
|
||||
|
||||
// Bit masks of the return value of Api::getFlags()
|
||||
enum StateFlag : uint32_t {
|
||||
// The user has granted root access to the current process
|
||||
PROCESS_GRANTED_ROOT = (1u << 0),
|
||||
|
||||
// The current process was added on the denylist
|
||||
PROCESS_ON_DENYLIST = (1u << 1),
|
||||
};
|
||||
|
||||
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
|
||||
// from the specialized process afterwards.
|
||||
struct Api {
|
||||
|
||||
// Connect to a root companion process and get a Unix domain socket for IPC.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
|
||||
//
|
||||
// The pre[XXX]Specialize methods run with the same privilege of zygote.
|
||||
// If you would like to do some operations with superuser permissions, register a handler
|
||||
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
|
||||
// Another good use case for a companion process is that if you want to share some resources
|
||||
// across multiple processes, hold the resources in the companion process and pass it over.
|
||||
//
|
||||
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
|
||||
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
|
||||
//
|
||||
// Returns a file descriptor to a socket that is connected to the socket passed to your
|
||||
// module's companion request handler. Returns -1 if the connection attempt failed.
|
||||
int connectCompanion();
|
||||
|
||||
// Get the file descriptor of the root folder of the current module.
|
||||
//
|
||||
// This API only works in the pre[XXX]Specialize methods.
|
||||
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
|
||||
// or in the root companion process (assuming that you sent the fd over the socket).
|
||||
// Both restrictions are due to SELinux and UID.
|
||||
//
|
||||
// Returns -1 if errors occurred.
|
||||
int getModuleDir();
|
||||
|
||||
// Set various options for your module.
|
||||
// Please note that this method accepts one single option at a time.
|
||||
// Check zygisk::Option for the full list of options available.
|
||||
void setOption(Option opt);
|
||||
|
||||
// Get information about the current process.
|
||||
// Returns bitwise-or'd zygisk::StateFlag values.
|
||||
uint32_t getFlags();
|
||||
|
||||
// Exempt the provided file descriptor from being automatically closed.
|
||||
//
|
||||
// This API only make sense in preAppSpecialize; calling this method in any other situation
|
||||
// is either a no-op (returns true) or an error (returns false).
|
||||
//
|
||||
// When false is returned, the provided file descriptor will eventually be closed by zygote.
|
||||
bool exemptFd(int fd);
|
||||
|
||||
// Hook JNI native methods for a class
|
||||
//
|
||||
// Lookup all registered JNI native methods and replace it with your own methods.
|
||||
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
|
||||
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
|
||||
// will be set to nullptr.
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
|
||||
|
||||
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
|
||||
//
|
||||
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
|
||||
//
|
||||
// <address> <perms> <offset> <dev> <inode> <pathname>
|
||||
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
|
||||
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
|
||||
//
|
||||
// The `dev` and `inode` pair uniquely identifies a file being mapped into memory.
|
||||
// For matching ELFs loaded in memory, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
|
||||
private:
|
||||
internal::api_table *tbl;
|
||||
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
|
||||
};
|
||||
|
||||
// Register a class as a Zygisk module
|
||||
|
||||
#define REGISTER_ZYGISK_MODULE(clazz) \
|
||||
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
|
||||
zygisk::internal::entry_impl<clazz>(table, env); \
|
||||
}
|
||||
|
||||
// Register a root companion request handler function for your module
|
||||
//
|
||||
// The function runs in a superuser daemon process and handles a root companion request from
|
||||
// your module running in a target process. The function has to accept an integer value,
|
||||
// which is a Unix domain socket that is connected to the target process.
|
||||
// See Api::connectCompanion() for more info.
|
||||
//
|
||||
// NOTE: the function can run concurrently on multiple threads.
|
||||
// Be aware of race conditions if you have globally shared resources.
|
||||
|
||||
#define REGISTER_ZYGISK_COMPANION(func) \
|
||||
void zygisk_companion_entry(int client) { func(client); }
|
||||
|
||||
/*********************************************************
|
||||
* The following is internal ABI implementation detail.
|
||||
* You do not have to understand what it is doing.
|
||||
*********************************************************/
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct module_abi {
|
||||
long api_version;
|
||||
ModuleBase *impl;
|
||||
|
||||
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
|
||||
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
|
||||
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
|
||||
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
|
||||
|
||||
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
|
||||
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
|
||||
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
|
||||
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
|
||||
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
|
||||
}
|
||||
};
|
||||
|
||||
struct api_table {
|
||||
// Base
|
||||
void *impl;
|
||||
bool (*registerModule)(api_table *, module_abi *);
|
||||
|
||||
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
bool (*exemptFd)(int);
|
||||
bool (*pltHookCommit)();
|
||||
int (*connectCompanion)(void * /* impl */);
|
||||
void (*setOption)(void * /* impl */, Option);
|
||||
int (*getModuleDir)(void * /* impl */);
|
||||
uint32_t (*getFlags)(void * /* impl */);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void entry_impl(api_table *table, JNIEnv *env) {
|
||||
ModuleBase *module = new T();
|
||||
if (!table->registerModule(table, new module_abi(module)))
|
||||
return;
|
||||
auto api = new Api();
|
||||
api->tbl = table;
|
||||
module->onLoad(api, env);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline int Api::connectCompanion() {
|
||||
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
|
||||
}
|
||||
inline int Api::getModuleDir() {
|
||||
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
|
||||
}
|
||||
inline void Api::setOption(Option opt) {
|
||||
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
|
||||
}
|
||||
inline uint32_t Api::getFlags() {
|
||||
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
|
||||
}
|
||||
inline bool Api::exemptFd(int fd) {
|
||||
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
|
||||
}
|
||||
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
|
||||
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
|
||||
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
|
||||
}
|
||||
inline bool Api::pltHookCommit() {
|
||||
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
|
||||
}
|
||||
|
||||
} // namespace zygisk
|
||||
|
||||
extern "C" {
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_companion_entry(int);
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__LP64__)
|
||||
# define LP_SELECT(lp32, lp64) lp64
|
||||
#else
|
||||
# define LP_SELECT(lp32, lp64) lp32
|
||||
#endif
|
||||
|
||||
constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock";
|
||||
|
||||
class UniqueFd {
|
||||
using Fd = int;
|
||||
public:
|
||||
UniqueFd() = default;
|
||||
|
||||
UniqueFd(Fd fd) : fd_(fd) {}
|
||||
|
||||
~UniqueFd() { if (fd_ >= 0) close(fd_); }
|
||||
|
||||
// Disallow copy
|
||||
UniqueFd(const UniqueFd&) = delete;
|
||||
|
||||
UniqueFd& operator=(const UniqueFd&) = delete;
|
||||
|
||||
// Allow move
|
||||
UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); }
|
||||
|
||||
UniqueFd& operator=(UniqueFd&& other) {
|
||||
std::swap(fd_, other.fd_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Implict cast to Fd
|
||||
operator const Fd&() const { return fd_; }
|
||||
|
||||
private:
|
||||
Fd fd_ = -1;
|
||||
};
|
||||
|
||||
namespace zygiskd {
|
||||
|
||||
struct Module {
|
||||
std::string name;
|
||||
UniqueFd memfd;
|
||||
|
||||
inline explicit Module(std::string name, int memfd) : name(name), memfd(memfd) {}
|
||||
};
|
||||
|
||||
enum class SocketAction {
|
||||
PingHeartBeat,
|
||||
RequestLogcatFd,
|
||||
GetProcessFlags,
|
||||
ReadModules,
|
||||
RequestCompanionSocket,
|
||||
GetModuleDir,
|
||||
ZygoteRestart,
|
||||
SystemServerStarted,
|
||||
};
|
||||
|
||||
void Init(const char *path);
|
||||
|
||||
std::string GetTmpPath();
|
||||
|
||||
bool PingHeartbeat();
|
||||
|
||||
int RequestLogcatFd();
|
||||
|
||||
std::vector<Module> ReadModules();
|
||||
|
||||
uint32_t GetProcessFlags(uid_t uid);
|
||||
|
||||
int ConnectCompanion(size_t index);
|
||||
|
||||
int GetModuleDir(size_t index);
|
||||
|
||||
void ZygoteRestart();
|
||||
|
||||
void SystemServerStarted();
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
void *DlopenExt(const char *path, int flags);
|
||||
|
||||
void *DlopenMem(int memfd, int flags);
|
||||
@@ -1,56 +0,0 @@
|
||||
#include <dirent.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct mount_info {
|
||||
unsigned int id;
|
||||
unsigned int parent;
|
||||
dev_t device;
|
||||
std::string root;
|
||||
std::string target;
|
||||
std::string vfs_option;
|
||||
struct {
|
||||
unsigned int shared;
|
||||
unsigned int master;
|
||||
unsigned int propagate_from;
|
||||
} optional;
|
||||
std::string type;
|
||||
std::string source;
|
||||
std::string fs_option;
|
||||
};
|
||||
|
||||
void file_readline(bool trim, FILE *fp, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(bool trim, const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
void file_readline(const char *file, const std::function<bool(std::string_view)> &fn);
|
||||
|
||||
std::vector<mount_info> parse_mount_info(const char *pid);
|
||||
|
||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
||||
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
|
||||
sDIR make_dir(DIR *dp);
|
||||
sFILE make_file(FILE *fp);
|
||||
|
||||
static inline sDIR open_dir(const char *path) {
|
||||
return make_dir(opendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(const char *path) {
|
||||
return make_dir(opendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(int dirfd) {
|
||||
return make_dir(fdopendir(dirfd));
|
||||
}
|
||||
|
||||
static inline sFILE open_file(const char *path, const char *mode) {
|
||||
return make_file(fopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(const char *path, const char *mode) {
|
||||
return make_file(fopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(int fd, const char *mode) {
|
||||
return make_file(fdopen(fd, mode));
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef LOG_TAG
|
||||
#if defined(__LP64__)
|
||||
# define LOG_TAG "zygisk-core64"
|
||||
#else
|
||||
# define LOG_TAG "zygisk-core32"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||
#else
|
||||
#define LOGD(...)
|
||||
#define LOGV(...)
|
||||
#endif
|
||||
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||
|
||||
namespace logging {
|
||||
void setfd(int fd);
|
||||
|
||||
int getfd();
|
||||
|
||||
[[gnu::format(printf, 3, 4)]]
|
||||
void log(int prio, const char* tag, const char* fmt, ...);
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <pthread.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
#define DISALLOW_COPY_AND_MOVE(clazz) \
|
||||
clazz(const clazz &) = delete; \
|
||||
clazz(clazz &&) = delete;
|
||||
|
||||
class mutex_guard {
|
||||
DISALLOW_COPY_AND_MOVE(mutex_guard)
|
||||
public:
|
||||
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
void unlock() {
|
||||
pthread_mutex_unlock(mutex);
|
||||
mutex = nullptr;
|
||||
}
|
||||
~mutex_guard() {
|
||||
if (mutex) pthread_mutex_unlock(mutex);
|
||||
}
|
||||
private:
|
||||
pthread_mutex_t *mutex;
|
||||
};
|
||||
|
||||
using thread_entry = void *(*)(void *);
|
||||
int new_daemon_thread(thread_entry entry, void *arg);
|
||||
|
||||
static inline bool str_contains(std::string_view s, std::string_view ss) {
|
||||
return s.find(ss) != std::string_view::npos;
|
||||
}
|
||||
|
||||
template<typename T, typename Impl>
|
||||
class stateless_allocator {
|
||||
public:
|
||||
using value_type = T;
|
||||
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
|
||||
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
|
||||
stateless_allocator() = default;
|
||||
stateless_allocator(const stateless_allocator&) = default;
|
||||
stateless_allocator(stateless_allocator&&) = default;
|
||||
template <typename U>
|
||||
stateless_allocator(const stateless_allocator<U, Impl>&) {}
|
||||
bool operator==(const stateless_allocator&) { return true; }
|
||||
bool operator!=(const stateless_allocator&) { return false; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class reversed_container {
|
||||
public:
|
||||
reversed_container(T &base) : base(base) {}
|
||||
decltype(std::declval<T>().rbegin()) begin() { return base.rbegin(); }
|
||||
decltype(std::declval<T>().crbegin()) begin() const { return base.crbegin(); }
|
||||
decltype(std::declval<T>().crbegin()) cbegin() const { return base.crbegin(); }
|
||||
decltype(std::declval<T>().rend()) end() { return base.rend(); }
|
||||
decltype(std::declval<T>().crend()) end() const { return base.crend(); }
|
||||
decltype(std::declval<T>().crend()) cend() const { return base.crend(); }
|
||||
private:
|
||||
T &base;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
reversed_container<T> reversed(T &base) {
|
||||
return reversed_container<T>(base);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(T *&p) { p = new T(); }
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
|
||||
|
||||
struct StringCmp {
|
||||
using is_transparent = void;
|
||||
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
|
||||
};
|
||||
|
||||
/*
|
||||
* Bionic's atoi runs through strtol().
|
||||
* Use our own implementation for faster conversion.
|
||||
*/
|
||||
int parse_int(std::string_view s);
|
||||
|
||||
std::list<std::string> split_str(std::string_view s, std::string_view delimiter);
|
||||
|
||||
std::string join_str(const std::list<std::string>& list, std::string_view delimiter);
|
||||
|
||||
template <typename T>
|
||||
static inline T align_to(T v, int a) {
|
||||
static_assert(std::is_integral<T>::value);
|
||||
return (v + a - 1) / a * a;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <android/api-level.h>
|
||||
#include <cstdint>
|
||||
|
||||
template<unsigned>
|
||||
struct NativeBridgeCallbacks;
|
||||
|
||||
template<>
|
||||
struct NativeBridgeCallbacks<__ANDROID_API_Q__> {
|
||||
[[maybe_unused]] uint32_t version;
|
||||
[[maybe_unused]] void *initialize;
|
||||
[[maybe_unused]] void *loadLibrary;
|
||||
[[maybe_unused]] void *getTrampoline;
|
||||
[[maybe_unused]] void *isSupported;
|
||||
[[maybe_unused]] void *getAppEnv;
|
||||
[[maybe_unused]] void *isCompatibleWith;
|
||||
[[maybe_unused]] void *getSignalHandler;
|
||||
[[maybe_unused]] void *unloadLibrary;
|
||||
[[maybe_unused]] void *getError;
|
||||
[[maybe_unused]] void *isPathSupported;
|
||||
[[maybe_unused]] void *initAnonymousNamespace;
|
||||
[[maybe_unused]] void *createNamespace;
|
||||
[[maybe_unused]] void *linkNamespaces;
|
||||
[[maybe_unused]] void *loadLibraryExt;
|
||||
[[maybe_unused]] void *getVendorNamespace;
|
||||
[[maybe_unused]] void *getExportedNamespace;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> {
|
||||
[[maybe_unused]] void *preZygoteFork;
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
namespace socket_utils {
|
||||
|
||||
ssize_t xread(int fd, void *buf, size_t count);
|
||||
|
||||
size_t xwrite(int fd, const void *buf, size_t count);
|
||||
|
||||
uint8_t read_u8(int fd);
|
||||
|
||||
uint32_t read_u32(int fd);
|
||||
|
||||
size_t read_usize(int fd);
|
||||
|
||||
std::string read_string(int fd);
|
||||
|
||||
bool write_u8(int fd, uint8_t val);
|
||||
|
||||
bool write_u32(int fd, uint32_t val);
|
||||
|
||||
int recv_fd(int fd);
|
||||
|
||||
bool write_usize(int fd, size_t val);
|
||||
|
||||
bool write_string(int fd, std::string_view str);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "jni_helper.hpp"
|
||||
|
||||
template <typename T>
|
||||
constexpr inline auto RoundUpTo(T v, size_t size) {
|
||||
return v + size - 1 - ((v + size - 1) & (size - 1));
|
||||
}
|
||||
|
||||
inline static constexpr auto kPointerSize = sizeof(void *);
|
||||
|
||||
namespace lsplant::art {
|
||||
|
||||
class ArtMethod {
|
||||
|
||||
public:
|
||||
void *GetData() {
|
||||
return *reinterpret_cast<void **>(reinterpret_cast<uintptr_t>(this) + data_offset);
|
||||
}
|
||||
|
||||
static art::ArtMethod *FromReflectedMethod(JNIEnv *env, jobject method) {
|
||||
if (art_method_field) [[likely]] {
|
||||
return reinterpret_cast<art::ArtMethod *>(
|
||||
JNI_GetLongField(env, method, art_method_field));
|
||||
} else {
|
||||
return reinterpret_cast<art::ArtMethod *>(env->FromReflectedMethod(method));
|
||||
}
|
||||
}
|
||||
|
||||
static bool Init(JNIEnv *env) {
|
||||
ScopedLocalRef<jclass> executable{env, nullptr};
|
||||
executable = JNI_FindClass(env, "java/lang/reflect/Executable");
|
||||
if (!executable) {
|
||||
LOGE("Failed to found Executable");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (art_method_field = JNI_GetFieldID(env, executable, "artMethod", "J");
|
||||
!art_method_field) {
|
||||
LOGE("Failed to find artMethod field");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto throwable = JNI_FindClass(env, "java/lang/Throwable");
|
||||
if (!throwable) {
|
||||
LOGE("Failed to found Executable");
|
||||
return false;
|
||||
}
|
||||
auto clazz = JNI_FindClass(env, "java/lang/Class");
|
||||
static_assert(std::is_same_v<decltype(clazz)::BaseType, jclass>);
|
||||
jmethodID get_declared_constructors = JNI_GetMethodID(env, clazz, "getDeclaredConstructors",
|
||||
"()[Ljava/lang/reflect/Constructor;");
|
||||
const auto constructors =
|
||||
JNI_Cast<jobjectArray>(JNI_CallObjectMethod(env, throwable, get_declared_constructors));
|
||||
if (constructors.size() < 2) {
|
||||
LOGE("Throwable has less than 2 constructors");
|
||||
return false;
|
||||
}
|
||||
auto &first_ctor = constructors[0];
|
||||
auto &second_ctor = constructors[1];
|
||||
auto *first = FromReflectedMethod(env, first_ctor.get());
|
||||
auto *second = FromReflectedMethod(env, second_ctor.get());
|
||||
art_method_size = reinterpret_cast<uintptr_t>(second) - reinterpret_cast<uintptr_t>(first);
|
||||
LOGD("ArtMethod size: %zu", art_method_size);
|
||||
if (RoundUpTo(4 * 9, kPointerSize) + kPointerSize * 3 < art_method_size) [[unlikely]] {
|
||||
LOGW("ArtMethod size exceeds maximum assume. There may be something wrong.");
|
||||
}
|
||||
entry_point_offset = art_method_size - kPointerSize;
|
||||
data_offset = entry_point_offset - kPointerSize;
|
||||
LOGD("ArtMethod::entrypoint offset: %zu", entry_point_offset);
|
||||
LOGD("ArtMethod::data offset: %zu", data_offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
inline static jfieldID art_method_field = nullptr;
|
||||
inline static size_t art_method_size = 0;
|
||||
inline static size_t entry_point_offset = 0;
|
||||
inline static size_t data_offset = 0;
|
||||
};
|
||||
|
||||
} // namespace lsplant::art
|
||||
@@ -1,27 +0,0 @@
|
||||
#include "daemon.h"
|
||||
#include "logging.h"
|
||||
#include "zygisk.hpp"
|
||||
#include "module.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void *self_handle = nullptr;
|
||||
|
||||
extern "C" [[gnu::visibility("default")]]
|
||||
void entry(void* handle, const char* path) {
|
||||
LOGI("Zygisk library injected, version %s", ZKSU_VERSION);
|
||||
self_handle = handle;
|
||||
zygiskd::Init(path);
|
||||
|
||||
if (!zygiskd::PingHeartbeat()) {
|
||||
LOGE("Zygisk daemon is not running");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef NDEBUG
|
||||
logging::setfd(zygiskd::RequestLogcatFd());
|
||||
#endif
|
||||
|
||||
LOGI("Start hooking");
|
||||
hook_functions();
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# keep sync with https://github.com/topjohnwu/Magisk/blob/master/native/src/core/zygisk/gen_jni_hooks.py
|
||||
|
||||
primitives = ['jint', 'jboolean', 'jlong']
|
||||
|
||||
class JType:
|
||||
def __init__(self, cpp, jni):
|
||||
self.cpp = cpp
|
||||
self.jni = jni
|
||||
|
||||
|
||||
class JArray(JType):
|
||||
def __init__(self, type):
|
||||
if type.cpp in primitives:
|
||||
name = type.cpp + 'Array'
|
||||
else:
|
||||
name = 'jobjectArray'
|
||||
super().__init__(name, '[' + type.jni)
|
||||
|
||||
|
||||
class Argument:
|
||||
def __init__(self, name, type, set_arg = False):
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.set_arg = set_arg
|
||||
|
||||
def cpp(self):
|
||||
return f'{self.type.cpp} {self.name}'
|
||||
|
||||
# Args we don't care, give it an auto generated name
|
||||
class Anon(Argument):
|
||||
cnt = 0
|
||||
def __init__(self, type):
|
||||
super().__init__(f'_{Anon.cnt}', type)
|
||||
Anon.cnt += 1
|
||||
|
||||
class Return:
|
||||
def __init__(self, value, type):
|
||||
self.value = value
|
||||
self.type = type
|
||||
|
||||
class Method:
|
||||
def __init__(self, name, ret, args):
|
||||
self.name = name
|
||||
self.ret = ret
|
||||
self.args = args
|
||||
|
||||
def cpp(self):
|
||||
return ', '.join(map(lambda x: x.cpp(), self.args))
|
||||
|
||||
def name_list(self):
|
||||
return ', '.join(map(lambda x: x.name, self.args))
|
||||
|
||||
def jni(self):
|
||||
args = ''.join(map(lambda x: x.type.jni, self.args))
|
||||
return f'({args}){self.ret.type.jni}'
|
||||
|
||||
def body(self):
|
||||
return ''
|
||||
|
||||
class JNIHook(Method):
|
||||
def __init__(self, ver, ret, args):
|
||||
name = f'{self.base_name()}_{ver}'
|
||||
super().__init__(name, ret, args)
|
||||
|
||||
def base_name(self):
|
||||
return ''
|
||||
|
||||
def orig_method(self):
|
||||
return f'reinterpret_cast<decltype(&{self.name})>({self.base_name()}_orig)'
|
||||
|
||||
def ind(i):
|
||||
return '\n' + ' ' * i
|
||||
|
||||
# Common types
|
||||
jint = JType('jint', 'I')
|
||||
jintArray = JArray(jint)
|
||||
jstring = JType('jstring', 'Ljava/lang/String;')
|
||||
jboolean = JType('jboolean', 'Z')
|
||||
jlong = JType('jlong', 'J')
|
||||
void = JType('void', 'V')
|
||||
|
||||
class ForkAndSpec(JNIHook):
|
||||
def __init__(self, ver, args):
|
||||
super().__init__(ver, Return('ctx.pid', jint), args)
|
||||
|
||||
def base_name(self):
|
||||
return 'nativeForkAndSpecialize'
|
||||
|
||||
def init_args(self):
|
||||
return 'AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);'
|
||||
|
||||
def body(self):
|
||||
decl = ''
|
||||
decl += ind(1) + self.init_args()
|
||||
for a in self.args:
|
||||
if a.set_arg:
|
||||
decl += ind(1) + f'args.{a.name} = &{a.name};'
|
||||
decl += ind(1) + 'ZygiskContext ctx(env, &args);'
|
||||
decl += ind(1) + f'ctx.{self.base_name()}_pre();'
|
||||
decl += ind(1) + self.orig_method() + '('
|
||||
decl += ind(2) + f'env, clazz, {self.name_list()}'
|
||||
decl += ind(1) + ');'
|
||||
decl += ind(1) + f'ctx.{self.base_name()}_post();'
|
||||
return decl
|
||||
|
||||
class SpecApp(ForkAndSpec):
|
||||
def __init__(self, ver, args):
|
||||
super().__init__(ver, args)
|
||||
self.ret = Return('', void)
|
||||
|
||||
def base_name(self):
|
||||
return 'nativeSpecializeAppProcess'
|
||||
|
||||
class ForkServer(ForkAndSpec):
|
||||
def base_name(self):
|
||||
return 'nativeForkSystemServer'
|
||||
|
||||
def init_args(self):
|
||||
return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);'
|
||||
|
||||
# Common args
|
||||
uid = Argument('uid', jint)
|
||||
gid = Argument('gid', jint)
|
||||
gids = Argument('gids', jintArray)
|
||||
runtime_flags = Argument('runtime_flags', jint)
|
||||
rlimits = Argument('rlimits', JArray(jintArray))
|
||||
mount_external = Argument('mount_external', jint)
|
||||
se_info = Argument('se_info', jstring)
|
||||
nice_name = Argument('nice_name', jstring)
|
||||
fds_to_close = Argument('fds_to_close', jintArray)
|
||||
instruction_set = Argument('instruction_set', jstring)
|
||||
app_data_dir = Argument('app_data_dir', jstring)
|
||||
|
||||
# o
|
||||
fds_to_ignore = Argument('fds_to_ignore', jintArray, True)
|
||||
|
||||
# p
|
||||
is_child_zygote = Argument('is_child_zygote', jboolean, True)
|
||||
|
||||
# q_alt
|
||||
is_top_app = Argument('is_top_app', jboolean, True)
|
||||
|
||||
# r
|
||||
pkg_data_info_list = Argument('pkg_data_info_list', JArray(jstring), True)
|
||||
whitelisted_data_info_list = Argument('whitelisted_data_info_list', JArray(jstring), True)
|
||||
mount_data_dirs = Argument('mount_data_dirs', jboolean, True)
|
||||
mount_storage_dirs = Argument('mount_storage_dirs', jboolean, True)
|
||||
|
||||
# u
|
||||
mount_sysprop_overrides = Argument('mount_sysprop_overrides', jboolean, True)
|
||||
|
||||
# server
|
||||
permitted_capabilities = Argument('permitted_capabilities', jlong)
|
||||
effective_capabilities = Argument('effective_capabilities', jlong)
|
||||
|
||||
# Method definitions
|
||||
fas_l = ForkAndSpec('l', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, fds_to_close, instruction_set, app_data_dir])
|
||||
|
||||
fas_o = ForkAndSpec('o', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir])
|
||||
|
||||
fas_p = ForkAndSpec('p', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir])
|
||||
|
||||
fas_q_alt = ForkAndSpec('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app])
|
||||
|
||||
fas_r = ForkAndSpec('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app,
|
||||
pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs])
|
||||
|
||||
fas_u = ForkAndSpec('u', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app,
|
||||
pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides])
|
||||
|
||||
fas_samsung_m = ForkAndSpec('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir])
|
||||
|
||||
fas_samsung_n = ForkAndSpec('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir, Anon(jint)])
|
||||
|
||||
fas_samsung_o = ForkAndSpec('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir])
|
||||
|
||||
fas_samsung_p = ForkAndSpec('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, is_child_zygote,
|
||||
instruction_set, app_data_dir])
|
||||
|
||||
spec_q = SpecApp('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir])
|
||||
|
||||
spec_q_alt = SpecApp('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
|
||||
nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app])
|
||||
|
||||
spec_r = SpecApp('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name,
|
||||
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
|
||||
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs])
|
||||
|
||||
spec_u = SpecApp('u', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name,
|
||||
is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list,
|
||||
whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides])
|
||||
|
||||
spec_samsung_q = SpecApp('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external,
|
||||
se_info, Anon(jint), Anon(jint), nice_name, is_child_zygote, instruction_set, app_data_dir])
|
||||
|
||||
server_l = ForkServer('l', [uid, gid, gids, runtime_flags, rlimits,
|
||||
permitted_capabilities, effective_capabilities])
|
||||
|
||||
server_samsung_q = ForkServer('samsung_q', [uid, gid, gids, runtime_flags, Anon(jint), Anon(jint), rlimits,
|
||||
permitted_capabilities, effective_capabilities])
|
||||
|
||||
hook_map = {}
|
||||
|
||||
def gen_jni_def(clz, methods):
|
||||
if clz not in hook_map:
|
||||
hook_map[clz] = []
|
||||
|
||||
decl = ''
|
||||
for m in methods:
|
||||
decl += ind(0) + f'[[clang::no_stack_protector]] {m.ret.type.cpp} {m.name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{'
|
||||
decl += m.body()
|
||||
if m.ret.value:
|
||||
decl += ind(1) + f'return {m.ret.value};'
|
||||
decl += ind(0) + '}'
|
||||
|
||||
decl += ind(0) + f'std::array {m.base_name()}_methods = {{'
|
||||
for m in methods:
|
||||
decl += ind(1) + 'JNINativeMethod {'
|
||||
decl += ind(2) + f'"{m.base_name()}",'
|
||||
decl += ind(2) + f'"{m.jni()}",'
|
||||
decl += ind(2) + f'(void *) &{m.name}'
|
||||
decl += ind(1) + '},'
|
||||
decl += ind(0) + '};'
|
||||
decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl
|
||||
decl += ind(0)
|
||||
|
||||
hook_map[clz].append(m.base_name())
|
||||
|
||||
return decl
|
||||
|
||||
with open('jni_hooks.hpp', 'w') as f:
|
||||
f.write('// Generated by gen_jni_hooks.py\n')
|
||||
f.write('\nnamespace {\n')
|
||||
|
||||
zygote = 'com/android/internal/os/Zygote'
|
||||
|
||||
methods = [fas_l, fas_o, fas_p, fas_q_alt, fas_r, fas_u, fas_samsung_m, fas_samsung_n, fas_samsung_o, fas_samsung_p]
|
||||
f.write(gen_jni_def(zygote, methods))
|
||||
|
||||
methods = [spec_q, spec_q_alt, spec_r, spec_u, spec_samsung_q]
|
||||
f.write(gen_jni_def(zygote, methods))
|
||||
|
||||
methods = [server_l, server_samsung_q]
|
||||
f.write(gen_jni_def(zygote, methods))
|
||||
|
||||
f.write('\n} // namespace\n')
|
||||
|
||||
f.write("""
|
||||
static void do_hook_zygote(JNIEnv *env) {
|
||||
vector<JNINativeMethod> hooks;
|
||||
const char *clz;
|
||||
clz = "com/android/internal/os/Zygote";
|
||||
hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size());
|
||||
for (auto &method : nativeForkAndSpecialize_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeForkAndSpecialize_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size());
|
||||
for (auto &method : nativeSpecializeAppProcess_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeSpecializeAppProcess_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size());
|
||||
for (auto &method : nativeForkSystemServer_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeForkSystemServer_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
jni_hook_list->emplace(clz, std::move(hooks));
|
||||
}
|
||||
""")
|
||||
@@ -1,786 +0,0 @@
|
||||
#include <android/dlext.h>
|
||||
#include <sys/mount.h>
|
||||
#include <dlfcn.h>
|
||||
#include <regex.h>
|
||||
#include <bitset>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <array>
|
||||
|
||||
#include <lsplt.hpp>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "dl.h"
|
||||
#include "daemon.h"
|
||||
#include "zygisk.hpp"
|
||||
#include "module.hpp"
|
||||
#include "files.hpp"
|
||||
#include "misc.hpp"
|
||||
|
||||
#include "art_method.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void hook_unloader();
|
||||
static void unhook_functions();
|
||||
|
||||
namespace {
|
||||
|
||||
enum {
|
||||
POST_SPECIALIZE,
|
||||
APP_FORK_AND_SPECIALIZE,
|
||||
APP_SPECIALIZE,
|
||||
SERVER_FORK_AND_SPECIALIZE,
|
||||
DO_REVERT_UNMOUNT,
|
||||
SKIP_FD_SANITIZATION,
|
||||
|
||||
FLAG_MAX
|
||||
};
|
||||
|
||||
#define DCL_PRE_POST(name) \
|
||||
void name##_pre(); \
|
||||
void name##_post();
|
||||
|
||||
#define MAX_FD_SIZE 1024
|
||||
|
||||
struct ZygiskContext;
|
||||
|
||||
// Current context
|
||||
ZygiskContext *g_ctx;
|
||||
|
||||
struct ZygiskContext {
|
||||
JNIEnv *env;
|
||||
union {
|
||||
void *ptr;
|
||||
AppSpecializeArgs_v3 *app;
|
||||
ServerSpecializeArgs_v1 *server;
|
||||
} args;
|
||||
|
||||
const char *process;
|
||||
list<ZygiskModule> modules;
|
||||
|
||||
int pid;
|
||||
bitset<FLAG_MAX> flags;
|
||||
uint32_t info_flags;
|
||||
bitset<MAX_FD_SIZE> allowed_fds;
|
||||
vector<int> exempted_fds;
|
||||
|
||||
struct RegisterInfo {
|
||||
regex_t regex;
|
||||
string symbol;
|
||||
void *callback;
|
||||
void **backup;
|
||||
};
|
||||
|
||||
struct IgnoreInfo {
|
||||
regex_t regex;
|
||||
string symbol;
|
||||
};
|
||||
|
||||
pthread_mutex_t hook_info_lock;
|
||||
vector<RegisterInfo> register_info;
|
||||
vector<IgnoreInfo> ignore_info;
|
||||
|
||||
ZygiskContext(JNIEnv *env, void *args) :
|
||||
env(env), args{args}, process(nullptr), pid(-1), info_flags(0),
|
||||
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {
|
||||
g_ctx = this;
|
||||
}
|
||||
~ZygiskContext();
|
||||
|
||||
/* Zygisksu changed: Load module fds */
|
||||
void run_modules_pre();
|
||||
void run_modules_post();
|
||||
DCL_PRE_POST(fork)
|
||||
DCL_PRE_POST(app_specialize)
|
||||
DCL_PRE_POST(nativeForkAndSpecialize)
|
||||
DCL_PRE_POST(nativeSpecializeAppProcess)
|
||||
DCL_PRE_POST(nativeForkSystemServer)
|
||||
|
||||
void sanitize_fds();
|
||||
bool exempt_fd(int fd);
|
||||
bool is_child() const { return pid <= 0; }
|
||||
|
||||
// Compatibility shim
|
||||
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
|
||||
void plt_hook_exclude(const char *regex, const char *symbol);
|
||||
void plt_hook_process_regex();
|
||||
|
||||
bool plt_hook_commit();
|
||||
};
|
||||
|
||||
#undef DCL_PRE_POST
|
||||
|
||||
// Global variables
|
||||
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
|
||||
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
|
||||
bool should_unmap_zygisk = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
|
||||
#define DCL_HOOK_FUNC(ret, func, ...) \
|
||||
ret (*old_##func)(__VA_ARGS__); \
|
||||
ret new_##func(__VA_ARGS__)
|
||||
|
||||
// Skip actual fork and return cached result if applicable
|
||||
DCL_HOOK_FUNC(int, fork) {
|
||||
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
|
||||
}
|
||||
|
||||
// Unmount stuffs in the process's private mount namespace
|
||||
DCL_HOOK_FUNC(int, unshare, int flags) {
|
||||
int res = old_unshare(flags);
|
||||
if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0 &&
|
||||
// For some unknown reason, unmounting app_process in SysUI can break.
|
||||
// This is reproducible on the official AVD running API 26 and 27.
|
||||
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
|
||||
(g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) {
|
||||
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
|
||||
if (g_ctx->info_flags & PROCESS_ROOT_IS_KSU) {
|
||||
revert_unmount_ksu();
|
||||
} else if (g_ctx->info_flags & PROCESS_ROOT_IS_MAGISK) {
|
||||
revert_unmount_magisk();
|
||||
}
|
||||
}
|
||||
|
||||
/* Zygisksu changed: No umount app_process */
|
||||
|
||||
// Restore errno back to 0
|
||||
errno = 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// Close logd_fd if necessary to prevent crashing
|
||||
// For more info, check comments in zygisk_log_write
|
||||
DCL_HOOK_FUNC(void, android_log_close) {
|
||||
if (g_ctx == nullptr) {
|
||||
// Happens during un-managed fork like nativeForkApp, nativeForkUsap
|
||||
logging::setfd(-1);
|
||||
} else if (!g_ctx->flags[SKIP_FD_SANITIZATION]) {
|
||||
logging::setfd(-1);
|
||||
}
|
||||
old_android_log_close();
|
||||
}
|
||||
|
||||
// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
|
||||
// it will return to our code which has been unmapped, causing segmentation fault.
|
||||
// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.
|
||||
DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
|
||||
int res = old_pthread_attr_destroy((pthread_attr_t *)target);
|
||||
|
||||
// Only perform unloading on the main thread
|
||||
if (gettid() != getpid())
|
||||
return res;
|
||||
|
||||
LOGV("pthread_attr_destroy\n");
|
||||
if (should_unmap_zygisk) {
|
||||
unhook_functions();
|
||||
if (should_unmap_zygisk) {
|
||||
// Because both `pthread_attr_destroy` and `dlclose` have the same function signature,
|
||||
// we can use `musttail` to let the compiler reuse our stack frame and thus
|
||||
// `dlclose` will directly return to the caller of `pthread_attr_destroy`.
|
||||
[[clang::musttail]] return dlclose(self_handle);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void initialize_jni_hook();
|
||||
|
||||
DCL_HOOK_FUNC(char *, strdup, const char *s) {
|
||||
if (s == "com.android.internal.os.ZygoteInit"sv) {
|
||||
LOGV("strdup %s\n", s);
|
||||
initialize_jni_hook();
|
||||
}
|
||||
return old_strdup(s);
|
||||
}
|
||||
|
||||
#undef DCL_HOOK_FUNC
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
static bool can_hook_jni = false;
|
||||
static jint MODIFIER_NATIVE = 0;
|
||||
static jmethodID member_getModifiers = nullptr;
|
||||
|
||||
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) {
|
||||
if (!can_hook_jni) return;
|
||||
auto clazz = env->FindClass(clz);
|
||||
if (clazz == nullptr) {
|
||||
env->ExceptionClear();
|
||||
for (int i = 0; i < numMethods; i++) {
|
||||
methods[i].fnPtr = nullptr;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
vector<JNINativeMethod> hooks;
|
||||
for (int i = 0; i < numMethods; i++) {
|
||||
auto &nm = methods[i];
|
||||
auto mid = env->GetMethodID(clazz, nm.name, nm.signature);
|
||||
bool is_static = false;
|
||||
if (mid == nullptr) {
|
||||
env->ExceptionClear();
|
||||
mid = env->GetStaticMethodID(clazz, nm.name, nm.signature);
|
||||
is_static = true;
|
||||
}
|
||||
if (mid == nullptr) {
|
||||
env->ExceptionClear();
|
||||
nm.fnPtr = nullptr;
|
||||
continue;
|
||||
}
|
||||
auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static);
|
||||
auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers);
|
||||
if ((modifier & MODIFIER_NATIVE) == 0) {
|
||||
nm.fnPtr = nullptr;
|
||||
continue;
|
||||
}
|
||||
auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method);
|
||||
hooks.push_back(nm);
|
||||
auto orig = artMethod->GetData();
|
||||
LOGV("replaced %s %s orig %p", clz, nm.name, orig);
|
||||
nm.fnPtr = orig;
|
||||
}
|
||||
|
||||
if (hooks.empty()) return;
|
||||
env->RegisterNatives(clazz, hooks.data(), hooks.size());
|
||||
}
|
||||
|
||||
// JNI method hook definitions, auto generated
|
||||
#include "jni_hooks.hpp"
|
||||
|
||||
void initialize_jni_hook() {
|
||||
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
|
||||
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
|
||||
if (!get_created_java_vms) {
|
||||
for (auto &map: lsplt::MapInfo::Scan()) {
|
||||
if (!map.path.ends_with("/libnativehelper.so")) continue;
|
||||
void *h = dlopen(map.path.data(), RTLD_LAZY);
|
||||
if (!h) {
|
||||
LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror());
|
||||
break;
|
||||
}
|
||||
get_created_java_vms = reinterpret_cast<decltype(get_created_java_vms)>(dlsym(h, "JNI_GetCreatedJavaVMs"));
|
||||
dlclose(h);
|
||||
break;
|
||||
}
|
||||
if (!get_created_java_vms) {
|
||||
LOGW("JNI_GetCreatedJavaVMs not found\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
JavaVM *vm = nullptr;
|
||||
jsize num = 0;
|
||||
jint res = get_created_java_vms(&vm, 1, &num);
|
||||
if (res != JNI_OK || vm == nullptr) return;
|
||||
JNIEnv *env = nullptr;
|
||||
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
||||
if (res != JNI_OK || env == nullptr) return;
|
||||
|
||||
auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member");
|
||||
if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I");
|
||||
auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier");
|
||||
if (classModifier != nullptr) {
|
||||
auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I");
|
||||
if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId);
|
||||
}
|
||||
if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return;
|
||||
if (!lsplant::art::ArtMethod::Init(env)) {
|
||||
LOGE("failed to init ArtMethod");
|
||||
return;
|
||||
}
|
||||
|
||||
can_hook_jni = true;
|
||||
do_hook_zygote(env);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
|
||||
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
|
||||
// Make sure all pointers are null
|
||||
memset(&api, 0, sizeof(api));
|
||||
api.base.impl = this;
|
||||
api.base.registerModule = &ZygiskModule::RegisterModuleImpl;
|
||||
}
|
||||
|
||||
bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
||||
if (api == nullptr || module == nullptr)
|
||||
return false;
|
||||
|
||||
long api_version = *module;
|
||||
// Unsupported version
|
||||
if (api_version > ZYGISK_API_VERSION)
|
||||
return false;
|
||||
|
||||
// Set the actual module_abi*
|
||||
api->base.impl->mod = { module };
|
||||
|
||||
// Fill in API accordingly with module API version
|
||||
if (api_version >= 1) {
|
||||
api->v1.hookJniNativeMethods = hookJniNativeMethods;
|
||||
api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) {
|
||||
if (g_ctx) g_ctx->plt_hook_register(a, b, c, d);
|
||||
};
|
||||
api->v1.pltHookExclude = [](auto a, auto b) {
|
||||
if (g_ctx) g_ctx->plt_hook_exclude(a, b);
|
||||
};
|
||||
api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); };
|
||||
api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
|
||||
api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };
|
||||
}
|
||||
if (api_version >= 2) {
|
||||
api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };
|
||||
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
||||
}
|
||||
if (api_version >= 4) {
|
||||
api->v4.pltHookCommit = lsplt::CommitHook;
|
||||
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
|
||||
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
|
||||
return;
|
||||
lsplt::RegisterHook(dev, inode, symbol, fn, backup);
|
||||
};
|
||||
api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ZygiskContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) {
|
||||
if (regex == nullptr || symbol == nullptr || fn == nullptr)
|
||||
return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_info_lock);
|
||||
register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});
|
||||
}
|
||||
|
||||
void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
||||
if (!regex) return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_info_lock);
|
||||
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
|
||||
}
|
||||
|
||||
void ZygiskContext::plt_hook_process_regex() {
|
||||
if (register_info.empty())
|
||||
return;
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;
|
||||
for (auto ®: register_info) {
|
||||
if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
bool ignored = false;
|
||||
for (auto &ign: ignore_info) {
|
||||
if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
if (ign.symbol.empty() || ign.symbol == reg.symbol) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignored) {
|
||||
lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ZygiskContext::plt_hook_commit() {
|
||||
{
|
||||
mutex_guard lock(hook_info_lock);
|
||||
plt_hook_process_regex();
|
||||
register_info.clear();
|
||||
ignore_info.clear();
|
||||
}
|
||||
return lsplt::CommitHook();
|
||||
}
|
||||
|
||||
|
||||
bool ZygiskModule::valid() const {
|
||||
if (mod.api_version == nullptr)
|
||||
return false;
|
||||
switch (*mod.api_version) {
|
||||
case 4:
|
||||
case 3:
|
||||
case 2:
|
||||
case 1:
|
||||
return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize &&
|
||||
mod.v1->preServerSpecialize && mod.v1->postServerSpecialize;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Use own zygiskd */
|
||||
int ZygiskModule::connectCompanion() const {
|
||||
return zygiskd::ConnectCompanion(id);
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Use own zygiskd */
|
||||
int ZygiskModule::getModuleDir() const {
|
||||
return zygiskd::GetModuleDir(id);
|
||||
}
|
||||
|
||||
void ZygiskModule::setOption(zygisk::Option opt) {
|
||||
if (g_ctx == nullptr)
|
||||
return;
|
||||
switch (opt) {
|
||||
case zygisk::FORCE_DENYLIST_UNMOUNT:
|
||||
g_ctx->flags[DO_REVERT_UNMOUNT] = true;
|
||||
break;
|
||||
case zygisk::DLCLOSE_MODULE_LIBRARY:
|
||||
unload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ZygiskModule::getFlags() {
|
||||
return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
int sigmask(int how, int signum) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, signum);
|
||||
return sigprocmask(how, &set, nullptr);
|
||||
}
|
||||
|
||||
void ZygiskContext::fork_pre() {
|
||||
// Do our own fork before loading any 3rd party code
|
||||
// First block SIGCHLD, unblock after original fork is done
|
||||
sigmask(SIG_BLOCK, SIGCHLD);
|
||||
pid = old_fork();
|
||||
if (pid != 0 || flags[SKIP_FD_SANITIZATION])
|
||||
return;
|
||||
|
||||
// Record all open fds
|
||||
auto dir = xopen_dir("/proc/self/fd");
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
int fd = parse_int(entry->d_name);
|
||||
if (fd < 0 || fd >= MAX_FD_SIZE) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
// The dirfd should not be allowed
|
||||
allowed_fds[dirfd(dir.get())] = false;
|
||||
}
|
||||
|
||||
void ZygiskContext::sanitize_fds() {
|
||||
if (flags[SKIP_FD_SANITIZATION])
|
||||
return;
|
||||
|
||||
if (flags[APP_FORK_AND_SPECIALIZE]) {
|
||||
auto update_fd_array = [&](int off) -> jintArray {
|
||||
if (exempted_fds.empty())
|
||||
return nullptr;
|
||||
|
||||
jintArray array = env->NewIntArray(static_cast<int>(off + exempted_fds.size()));
|
||||
if (array == nullptr)
|
||||
return nullptr;
|
||||
|
||||
env->SetIntArrayRegion(array, off, static_cast<int>(exempted_fds.size()), exempted_fds.data());
|
||||
for (int fd : exempted_fds) {
|
||||
if (fd >= 0 && fd < MAX_FD_SIZE) {
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
}
|
||||
*args.app->fds_to_ignore = array;
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
return array;
|
||||
};
|
||||
|
||||
if (jintArray fdsToIgnore = *args.app->fds_to_ignore) {
|
||||
int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr);
|
||||
int len = env->GetArrayLength(fdsToIgnore);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int fd = arr[i];
|
||||
if (fd >= 0 && fd < MAX_FD_SIZE) {
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
}
|
||||
if (jintArray newFdList = update_fd_array(len)) {
|
||||
env->SetIntArrayRegion(newFdList, 0, len, arr);
|
||||
}
|
||||
env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT);
|
||||
} else {
|
||||
update_fd_array(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (pid != 0)
|
||||
return;
|
||||
|
||||
// Close all forbidden fds to prevent crashing
|
||||
auto dir = open_dir("/proc/self/fd");
|
||||
int dfd = dirfd(dir.get());
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
int fd = parse_int(entry->d_name);
|
||||
if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZygiskContext::fork_post() {
|
||||
// Unblock SIGCHLD in case the original method didn't
|
||||
sigmask(SIG_UNBLOCK, SIGCHLD);
|
||||
g_ctx = nullptr;
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Load module fds */
|
||||
void ZygiskContext::run_modules_pre() {
|
||||
auto ms = zygiskd::ReadModules();
|
||||
auto size = ms.size();
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
auto& m = ms[i];
|
||||
if (void* handle = DlopenMem(m.memfd, RTLD_NOW);
|
||||
void* entry = handle ? dlsym(handle, "zygisk_module_entry") : nullptr) {
|
||||
modules.emplace_back(i, handle, entry);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &m : modules) {
|
||||
m.onLoad(env);
|
||||
if (flags[APP_SPECIALIZE]) {
|
||||
m.preAppSpecialize(args.app);
|
||||
} else if (flags[SERVER_FORK_AND_SPECIALIZE]) {
|
||||
m.preServerSpecialize(args.server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ZygiskContext::run_modules_post() {
|
||||
flags[POST_SPECIALIZE] = true;
|
||||
for (const auto &m : modules) {
|
||||
if (flags[APP_SPECIALIZE]) {
|
||||
m.postAppSpecialize(args.app);
|
||||
} else if (flags[SERVER_FORK_AND_SPECIALIZE]) {
|
||||
m.postServerSpecialize(args.server);
|
||||
}
|
||||
m.tryUnload();
|
||||
}
|
||||
}
|
||||
|
||||
/* Zygisksu changed: Load module fds */
|
||||
void ZygiskContext::app_specialize_pre() {
|
||||
flags[APP_SPECIALIZE] = true;
|
||||
info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid);
|
||||
if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) {
|
||||
LOGI("current uid %d is manager!", g_ctx->args.app->uid);
|
||||
setenv("ZYGISK_ENABLED", "1", 1);
|
||||
} else {
|
||||
run_modules_pre();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ZygiskContext::app_specialize_post() {
|
||||
run_modules_post();
|
||||
|
||||
// Cleanups
|
||||
env->ReleaseStringUTFChars(args.app->nice_name, process);
|
||||
g_ctx = nullptr;
|
||||
logging::setfd(-1);
|
||||
}
|
||||
|
||||
bool ZygiskContext::exempt_fd(int fd) {
|
||||
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
|
||||
return true;
|
||||
if (!flags[APP_FORK_AND_SPECIALIZE])
|
||||
return false;
|
||||
exempted_fds.push_back(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void ZygiskContext::nativeSpecializeAppProcess_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
LOGV("pre specialize [%s]\n", process);
|
||||
// App specialize does not check FD
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
app_specialize_pre();
|
||||
}
|
||||
|
||||
void ZygiskContext::nativeSpecializeAppProcess_post() {
|
||||
LOGV("post specialize [%s]\n", process);
|
||||
app_specialize_post();
|
||||
}
|
||||
|
||||
/* Zygisksu changed: No system_server status write back */
|
||||
void ZygiskContext::nativeForkSystemServer_pre() {
|
||||
LOGV("pre forkSystemServer\n");
|
||||
flags[SERVER_FORK_AND_SPECIALIZE] = true;
|
||||
|
||||
fork_pre();
|
||||
if (pid != 0)
|
||||
return;
|
||||
|
||||
run_modules_pre();
|
||||
zygiskd::SystemServerStarted();
|
||||
|
||||
sanitize_fds();
|
||||
}
|
||||
|
||||
void ZygiskContext::nativeForkSystemServer_post() {
|
||||
if (pid == 0) {
|
||||
LOGV("post forkSystemServer\n");
|
||||
run_modules_post();
|
||||
}
|
||||
fork_post();
|
||||
}
|
||||
|
||||
void ZygiskContext::nativeForkAndSpecialize_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
LOGV("pre forkAndSpecialize [%s]\n", process);
|
||||
|
||||
flags[APP_FORK_AND_SPECIALIZE] = true;
|
||||
/* Zygisksu changed: No args.app->fds_to_ignore check since we are Android 10+ */
|
||||
if (logging::getfd() != -1) {
|
||||
exempted_fds.push_back(logging::getfd());
|
||||
}
|
||||
|
||||
fork_pre();
|
||||
if (pid == 0) {
|
||||
app_specialize_pre();
|
||||
}
|
||||
sanitize_fds();
|
||||
}
|
||||
|
||||
void ZygiskContext::nativeForkAndSpecialize_post() {
|
||||
if (pid == 0) {
|
||||
LOGV("post forkAndSpecialize [%s]\n", process);
|
||||
app_specialize_post();
|
||||
}
|
||||
fork_post();
|
||||
}
|
||||
|
||||
ZygiskContext::~ZygiskContext() {
|
||||
// This global pointer points to a variable on the stack.
|
||||
// Set this to nullptr to prevent leaking local variable.
|
||||
// This also disables most plt hooked functions.
|
||||
g_ctx = nullptr;
|
||||
|
||||
if (!is_child())
|
||||
return;
|
||||
|
||||
should_unmap_zygisk = true;
|
||||
|
||||
// Unhook JNI methods
|
||||
for (const auto &[clz, methods] : *jni_hook_list) {
|
||||
if (!methods.empty() && env->RegisterNatives(
|
||||
env->FindClass(clz.data()), methods.data(),
|
||||
static_cast<int>(methods.size())) != 0) {
|
||||
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
|
||||
should_unmap_zygisk = false;
|
||||
}
|
||||
}
|
||||
delete jni_hook_list;
|
||||
jni_hook_list = nullptr;
|
||||
|
||||
// Strip out all API function pointers
|
||||
for (auto &m : modules) {
|
||||
m.clearApi();
|
||||
}
|
||||
|
||||
hook_unloader();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool hook_commit() {
|
||||
if (lsplt::CommitHook()) {
|
||||
return true;
|
||||
} else {
|
||||
LOGE("plt_hook failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
||||
if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
|
||||
LOGE("Failed to register plt_hook \"%s\"\n", symbol);
|
||||
return;
|
||||
}
|
||||
plt_hook_list->emplace_back(dev, inode, symbol, old_func);
|
||||
}
|
||||
|
||||
#define PLT_HOOK_REGISTER_SYM(DEV, INODE, SYM, NAME) \
|
||||
hook_register(DEV, INODE, SYM, (void*) new_##NAME, (void **) &old_##NAME)
|
||||
|
||||
#define PLT_HOOK_REGISTER(DEV, INODE, NAME) \
|
||||
PLT_HOOK_REGISTER_SYM(DEV, INODE, #NAME, NAME)
|
||||
|
||||
void hook_functions() {
|
||||
default_new(plt_hook_list);
|
||||
default_new(jni_hook_list);
|
||||
|
||||
ino_t android_runtime_inode = 0;
|
||||
dev_t android_runtime_dev = 0;
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.path.ends_with("libandroid_runtime.so")) {
|
||||
android_runtime_inode = map.inode;
|
||||
android_runtime_dev = map.dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare);
|
||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup);
|
||||
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
|
||||
hook_commit();
|
||||
|
||||
// Remove unhooked methods
|
||||
plt_hook_list->erase(
|
||||
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
|
||||
[](auto &t) { return *std::get<3>(t) == nullptr;}),
|
||||
plt_hook_list->end());
|
||||
}
|
||||
|
||||
static void hook_unloader() {
|
||||
ino_t art_inode = 0;
|
||||
dev_t art_dev = 0;
|
||||
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.path.ends_with("/libart.so")) {
|
||||
art_inode = map.inode;
|
||||
art_dev = map.dev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy);
|
||||
hook_commit();
|
||||
}
|
||||
|
||||
static void unhook_functions() {
|
||||
// Unhook plt_hook
|
||||
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
|
||||
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
|
||||
LOGE("Failed to register plt_hook [%s]\n", sym);
|
||||
}
|
||||
}
|
||||
delete plt_hook_list;
|
||||
if (!hook_commit()) {
|
||||
LOGE("Failed to restore plt_hook\n");
|
||||
should_unmap_zygisk = false;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,339 +0,0 @@
|
||||
// Generated by gen_jni_hooks.py
|
||||
|
||||
namespace {
|
||||
|
||||
void *nativeForkAndSpecialize_orig = nullptr;
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_l)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_o)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_p)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_q_alt)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_r)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
args.mount_sysprop_overrides = &mount_sysprop_overrides;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_u)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_o)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.fds_to_ignore = &fds_to_ignore;
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkAndSpecialize_pre();
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_p)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeForkAndSpecialize_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
std::array nativeForkAndSpecialize_methods = {
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_l
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_o
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_p
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
|
||||
(void *) &nativeForkAndSpecialize_q_alt
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
|
||||
(void *) &nativeForkAndSpecialize_r
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)I",
|
||||
(void *) &nativeForkAndSpecialize_u
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_m
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_n
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_o
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_p
|
||||
},
|
||||
};
|
||||
|
||||
void *nativeSpecializeAppProcess_orig = nullptr;
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q_alt)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app
|
||||
);
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_r(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_r)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs
|
||||
);
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
args.mount_sysprop_overrides = &mount_sysprop_overrides;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_u)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides
|
||||
);
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
args.is_child_zygote = &is_child_zygote;
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeSpecializeAppProcess_pre();
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_samsung_q)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir
|
||||
);
|
||||
ctx.nativeSpecializeAppProcess_post();
|
||||
}
|
||||
std::array nativeSpecializeAppProcess_methods = {
|
||||
JNINativeMethod {
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
(void *) &nativeSpecializeAppProcess_q
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
|
||||
(void *) &nativeSpecializeAppProcess_q_alt
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
|
||||
(void *) &nativeSpecializeAppProcess_r
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)V",
|
||||
(void *) &nativeSpecializeAppProcess_u
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
(void *) &nativeSpecializeAppProcess_samsung_q
|
||||
},
|
||||
};
|
||||
|
||||
void *nativeForkSystemServer_orig = nullptr;
|
||||
[[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkSystemServer_pre();
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
|
||||
);
|
||||
ctx.nativeForkSystemServer_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
[[clang::no_stack_protector]] jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
ZygiskContext ctx(env, &args);
|
||||
ctx.nativeForkSystemServer_pre();
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities
|
||||
);
|
||||
ctx.nativeForkSystemServer_post();
|
||||
return ctx.pid;
|
||||
}
|
||||
std::array nativeForkSystemServer_methods = {
|
||||
JNINativeMethod {
|
||||
"nativeForkSystemServer",
|
||||
"(II[II[[IJJ)I",
|
||||
(void *) &nativeForkSystemServer_l
|
||||
},
|
||||
JNINativeMethod {
|
||||
"nativeForkSystemServer",
|
||||
"(II[IIII[[IJJ)I",
|
||||
(void *) &nativeForkSystemServer_samsung_q
|
||||
},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static void do_hook_zygote(JNIEnv *env) {
|
||||
vector<JNINativeMethod> hooks;
|
||||
const char *clz;
|
||||
clz = "com/android/internal/os/Zygote";
|
||||
hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size());
|
||||
for (auto &method : nativeForkAndSpecialize_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeForkAndSpecialize_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size());
|
||||
for (auto &method : nativeSpecializeAppProcess_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeSpecializeAppProcess_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size());
|
||||
for (auto &method : nativeForkSystemServer_methods) {
|
||||
if (method.fnPtr) {
|
||||
nativeForkSystemServer_orig = method.fnPtr;
|
||||
hooks.emplace_back(method);
|
||||
break;
|
||||
}
|
||||
}
|
||||
jni_hook_list->emplace(clz, std::move(hooks));
|
||||
}
|
||||
@@ -1,223 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include "api.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct ZygiskContext;
|
||||
struct ZygiskModule;
|
||||
|
||||
struct AppSpecializeArgs_v1;
|
||||
using AppSpecializeArgs_v2 = AppSpecializeArgs_v1;
|
||||
struct AppSpecializeArgs_v3;
|
||||
using AppSpecializeArgs_v4 = AppSpecializeArgs_v3;
|
||||
|
||||
struct module_abi_v1;
|
||||
using module_abi_v2 = module_abi_v1;
|
||||
using module_abi_v3 = module_abi_v1;
|
||||
using module_abi_v4 = module_abi_v1;
|
||||
|
||||
struct api_abi_v1;
|
||||
struct api_abi_v2;
|
||||
using api_abi_v3 = api_abi_v2;
|
||||
struct api_abi_v4;
|
||||
|
||||
union ApiTable;
|
||||
|
||||
struct AppSpecializeArgs_v3 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jobjectArray &rlimits;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
jintArray *fds_to_ignore = nullptr;
|
||||
jboolean *is_child_zygote = nullptr;
|
||||
jboolean *is_top_app = nullptr;
|
||||
jobjectArray *pkg_data_info_list = nullptr;
|
||||
jobjectArray *whitelisted_data_info_list = nullptr;
|
||||
jboolean *mount_data_dirs = nullptr;
|
||||
jboolean *mount_storage_dirs = nullptr;
|
||||
jboolean *mount_sysprop_overrides = nullptr;
|
||||
|
||||
AppSpecializeArgs_v3(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jstring &instruction_set, jstring &app_data_dir) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags), rlimits(rlimits),
|
||||
mount_external(mount_external), se_info(se_info), nice_name(nice_name),
|
||||
instruction_set(instruction_set), app_data_dir(app_data_dir) {}
|
||||
};
|
||||
|
||||
struct AppSpecializeArgs_v1 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
jboolean *const is_child_zygote;
|
||||
jboolean *const is_top_app;
|
||||
jobjectArray *const pkg_data_info_list;
|
||||
jobjectArray *const whitelisted_data_info_list;
|
||||
jboolean *const mount_data_dirs;
|
||||
jboolean *const mount_storage_dirs;
|
||||
|
||||
AppSpecializeArgs_v1(const AppSpecializeArgs_v3 *v3) :
|
||||
uid(v3->uid), gid(v3->gid), gids(v3->gids), runtime_flags(v3->runtime_flags),
|
||||
mount_external(v3->mount_external), se_info(v3->se_info), nice_name(v3->nice_name),
|
||||
instruction_set(v3->instruction_set), app_data_dir(v3->app_data_dir),
|
||||
is_child_zygote(v3->is_child_zygote), is_top_app(v3->is_top_app),
|
||||
pkg_data_info_list(v3->pkg_data_info_list),
|
||||
whitelisted_data_info_list(v3->whitelisted_data_info_list),
|
||||
mount_data_dirs(v3->mount_data_dirs), mount_storage_dirs(v3->mount_storage_dirs) {}
|
||||
};
|
||||
|
||||
struct ServerSpecializeArgs_v1 {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ServerSpecializeArgs_v1(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jlong &permitted_capabilities, jlong &effective_capabilities) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags),
|
||||
permitted_capabilities(permitted_capabilities),
|
||||
effective_capabilities(effective_capabilities) {}
|
||||
};
|
||||
|
||||
struct module_abi_v1 {
|
||||
long api_version;
|
||||
void *impl;
|
||||
void (*preAppSpecialize)(void *, void *);
|
||||
void (*postAppSpecialize)(void *, const void *);
|
||||
void (*preServerSpecialize)(void *, void *);
|
||||
void (*postServerSpecialize)(void *, const void *);
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
|
||||
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
|
||||
|
||||
PROCESS_IS_MANAGER = (1u << 28),
|
||||
PROCESS_ROOT_IS_KSU = (1u << 29),
|
||||
PROCESS_ROOT_IS_MAGISK = (1u << 30),
|
||||
PROCESS_IS_SYS_UI = (1u << 31),
|
||||
|
||||
PRIVATE_MASK = PROCESS_IS_SYS_UI
|
||||
};
|
||||
|
||||
struct api_abi_base {
|
||||
ZygiskModule *impl;
|
||||
bool (*registerModule)(ApiTable *, long *);
|
||||
};
|
||||
|
||||
struct api_abi_v1 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **);
|
||||
/* 2 */ void (*pltHookExclude)(const char *, const char *);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
};
|
||||
|
||||
struct api_abi_v2 : public api_abi_v1 {
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
struct api_abi_v4 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
/* 2 */ bool (*exemptFd)(int);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
union ApiTable {
|
||||
api_abi_base base;
|
||||
api_abi_v1 v1;
|
||||
api_abi_v2 v2;
|
||||
api_abi_v4 v4;
|
||||
};
|
||||
|
||||
#define call_app(method) \
|
||||
switch (*mod.api_version) { \
|
||||
case 1: \
|
||||
case 2: { \
|
||||
AppSpecializeArgs_v1 a(args); \
|
||||
mod.v1->method(mod.v1->impl, &a); \
|
||||
break; \
|
||||
} \
|
||||
case 3: \
|
||||
case 4: \
|
||||
mod.v1->method(mod.v1->impl, args);\
|
||||
break; \
|
||||
}
|
||||
|
||||
struct ZygiskModule {
|
||||
|
||||
void onLoad(void *env) {
|
||||
entry.fn(&api, env);
|
||||
}
|
||||
void preAppSpecialize(AppSpecializeArgs_v3 *args) const {
|
||||
call_app(preAppSpecialize)
|
||||
}
|
||||
void postAppSpecialize(const AppSpecializeArgs_v3 *args) const {
|
||||
call_app(postAppSpecialize)
|
||||
}
|
||||
void preServerSpecialize(ServerSpecializeArgs_v1 *args) const {
|
||||
mod.v1->preServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const {
|
||||
mod.v1->postServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
|
||||
bool valid() const;
|
||||
int connectCompanion() const;
|
||||
int getModuleDir() const;
|
||||
void setOption(zygisk::Option opt);
|
||||
static uint32_t getFlags();
|
||||
void tryUnload() const { if (unload) dlclose(handle); }
|
||||
void clearApi() { memset(&api, 0, sizeof(api)); }
|
||||
int getId() const { return id; }
|
||||
|
||||
ZygiskModule(int id, void *handle, void *entry);
|
||||
|
||||
static bool RegisterModuleImpl(ApiTable *api, long *module);
|
||||
|
||||
private:
|
||||
const int id;
|
||||
bool unload = false;
|
||||
|
||||
void * const handle;
|
||||
union {
|
||||
void * const ptr;
|
||||
void (* const fn)(void *, void *);
|
||||
} entry;
|
||||
|
||||
ApiTable api;
|
||||
|
||||
union {
|
||||
long *api_version;
|
||||
module_abi_v1 *v1;
|
||||
} mod;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
@@ -1,86 +0,0 @@
|
||||
#include <mntent.h>
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "files.hpp"
|
||||
#include "logging.h"
|
||||
#include "misc.hpp"
|
||||
#include "zygisk.hpp"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
namespace {
|
||||
constexpr auto MODULE_DIR = "/data/adb/modules";
|
||||
constexpr auto KSU_OVERLAY_SOURCE = "KSU";
|
||||
const std::vector<std::string> KSU_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"};
|
||||
|
||||
void lazy_unmount(const char* mountpoint) {
|
||||
if (umount2(mountpoint, MNT_DETACH) != -1) {
|
||||
LOGD("Unmounted (%s)", mountpoint);
|
||||
} else {
|
||||
#ifndef NDEBUG
|
||||
PLOGE("Unmount (%s)", mountpoint);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void revert_unmount_ksu() {
|
||||
std::string ksu_loop;
|
||||
std::vector<std::string> targets;
|
||||
|
||||
// Unmount ksu module dir last
|
||||
targets.emplace_back(MODULE_DIR);
|
||||
|
||||
for (auto& info: parse_mount_info("self")) {
|
||||
if (info.target == MODULE_DIR) {
|
||||
ksu_loop = info.source;
|
||||
continue;
|
||||
}
|
||||
// Unmount everything mounted to /data/adb
|
||||
if (info.target.starts_with("/data/adb")) {
|
||||
targets.emplace_back(info.target);
|
||||
}
|
||||
// Unmount ksu overlays
|
||||
if (info.type == "overlay"
|
||||
&& info.source == KSU_OVERLAY_SOURCE
|
||||
&& std::find(KSU_PARTITIONS.begin(), KSU_PARTITIONS.end(), info.target) != KSU_PARTITIONS.end()) {
|
||||
targets.emplace_back(info.target);
|
||||
}
|
||||
// Unmount temp dir
|
||||
if (info.type == "tmpfs" && info.source == KSU_OVERLAY_SOURCE) {
|
||||
targets.emplace_back(info.target);
|
||||
}
|
||||
}
|
||||
for (auto& info: parse_mount_info("self")) {
|
||||
// Unmount everything from ksu loop except ksu module dir
|
||||
if (info.source == ksu_loop && info.target != MODULE_DIR) {
|
||||
targets.emplace_back(info.target);
|
||||
}
|
||||
}
|
||||
|
||||
// Do unmount
|
||||
for (auto& s: reversed(targets)) {
|
||||
lazy_unmount(s.data());
|
||||
}
|
||||
}
|
||||
|
||||
void revert_unmount_magisk() {
|
||||
std::vector<std::string> targets;
|
||||
|
||||
// Unmount dummy skeletons and MAGISKTMP
|
||||
// since mirror nodes are always mounted under skeleton, we don't have to specifically unmount
|
||||
for (auto& info: parse_mount_info("self")) {
|
||||
if (info.source == "magisk" || info.source == "worker" || // magisktmp tmpfs
|
||||
info.root.starts_with("/adb/modules")) { // bind mount from data partition
|
||||
targets.push_back(info.target);
|
||||
}
|
||||
// Unmount everything mounted to /data/adb
|
||||
if (info.target.starts_with("/data/adb")) {
|
||||
targets.emplace_back(info.target);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& s: reversed(targets)) {
|
||||
lazy_unmount(s.data());
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
|
||||
extern void *self_handle;
|
||||
|
||||
void hook_functions();
|
||||
|
||||
void revert_unmount_ksu();
|
||||
|
||||
void revert_unmount_magisk();
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <time.h>
|
||||
#include <sys/system_properties.h>
|
||||
|
||||
#include "main.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "daemon.h"
|
||||
#include <sys/mount.h>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
zygiskd::Init(getenv("TMP_PATH"));
|
||||
if (argc >= 2 && argv[1] == "monitor"sv) {
|
||||
init_monitor();
|
||||
return 0;
|
||||
} else if (argc >= 3 && argv[1] == "trace"sv) {
|
||||
if (argc >= 4 && argv[3] == "--restart"sv) {
|
||||
zygiskd::ZygoteRestart();
|
||||
}
|
||||
auto pid = strtol(argv[2], 0, 0);
|
||||
if (!trace_zygote(pid)) {
|
||||
kill(pid, SIGKILL);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
} else if (argc >= 2 && argv[1] == "ctl"sv) {
|
||||
if (argc == 3) {
|
||||
if (argv[2] == "start"sv) {
|
||||
send_control_command(START);
|
||||
return 0;
|
||||
} else if (argv[2] == "stop"sv) {
|
||||
send_control_command(STOP);
|
||||
return 0;
|
||||
} else if (argv[2] == "exit"sv) {
|
||||
send_control_command(EXIT);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
printf("Zygisk Next Tracer %s\n", ZKSU_VERSION);
|
||||
printf("Usage: %s ctl start|stop|exit\n", argv[0]);
|
||||
return 1;
|
||||
} else if (argc >= 2 && argv[1] == "version"sv) {
|
||||
printf("Zygisk Next Tracer %s\n", ZKSU_VERSION);
|
||||
return 0;
|
||||
} else {
|
||||
printf("Zygisk Next Tracer %s\n", ZKSU_VERSION);
|
||||
printf("usage: %s monitor | trace <pid> | ctl <start|stop|exit> | version\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
void init_monitor();
|
||||
bool trace_zygote(int pid);
|
||||
|
||||
enum Command {
|
||||
START = 1,
|
||||
STOP = 2,
|
||||
EXIT = 3,
|
||||
// sent from daemon
|
||||
ZYGOTE64_INJECTED = 4,
|
||||
ZYGOTE32_INJECTED = 5,
|
||||
DAEMON64_SET_INFO = 6,
|
||||
DAEMON32_SET_INFO = 7,
|
||||
DAEMON64_SET_ERROR_INFO = 8,
|
||||
DAEMON32_SET_ERROR_INFO = 9,
|
||||
SYSTEM_SERVER_STARTED = 10
|
||||
};
|
||||
|
||||
void send_control_command(Command cmd);
|
||||
@@ -1,608 +0,0 @@
|
||||
#include <sys/system_properties.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <err.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/mount.h>
|
||||
#include <time.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "main.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "files.hpp"
|
||||
#include "misc.hpp"
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
|
||||
#define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8)))
|
||||
|
||||
static void updateStatus();
|
||||
|
||||
enum TracingState {
|
||||
TRACING = 1,
|
||||
STOPPING,
|
||||
STOPPED,
|
||||
EXITING
|
||||
};
|
||||
|
||||
std::string monitor_stop_reason;
|
||||
|
||||
constexpr char SOCKET_NAME[] = "init_monitor";
|
||||
|
||||
struct EventLoop;
|
||||
|
||||
struct EventHandler {
|
||||
virtual int GetFd() = 0;
|
||||
virtual void HandleEvent(EventLoop& loop, uint32_t event) = 0;
|
||||
};
|
||||
|
||||
struct EventLoop {
|
||||
private:
|
||||
int epoll_fd_;
|
||||
bool running = false;
|
||||
public:
|
||||
bool Init() {
|
||||
epoll_fd_ = epoll_create(1);
|
||||
if (epoll_fd_ == -1) {
|
||||
PLOGE("failed to create");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
running = true;
|
||||
constexpr auto MAX_EVENTS = 2;
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
while (running) {
|
||||
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1);
|
||||
if (nfds == -1) {
|
||||
if (errno != EINTR)
|
||||
PLOGE("epoll_wait");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
reinterpret_cast<EventHandler *>(events[i].data.ptr)->HandleEvent(*this,
|
||||
events[i].events);
|
||||
if (!running) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RegisterHandler(EventHandler &handler, uint32_t events) {
|
||||
struct epoll_event ev{};
|
||||
ev.events = events;
|
||||
ev.data.ptr = &handler;
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, handler.GetFd(), &ev) == -1) {
|
||||
PLOGE("failed to add event handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[maybe_unused]] bool UnregisterHandler(EventHandler &handler) {
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), nullptr) == -1) {
|
||||
PLOGE("failed to del event handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
~EventLoop() {
|
||||
if (epoll_fd_ >= 0) close(epoll_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
static TracingState tracing_state = TRACING;
|
||||
static std::string prop_path;
|
||||
|
||||
|
||||
struct Status {
|
||||
bool supported = false;
|
||||
bool zygote_injected = false;
|
||||
bool daemon_running = false;
|
||||
pid_t daemon_pid = -1;
|
||||
std::string daemon_info;
|
||||
std::string daemon_error_info;
|
||||
};
|
||||
|
||||
static Status status64;
|
||||
static Status status32;
|
||||
|
||||
struct SocketHandler : public EventHandler {
|
||||
int sock_fd_;
|
||||
|
||||
bool Init() {
|
||||
sock_fd_ = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
||||
if (sock_fd_ == -1) {
|
||||
PLOGE("socket create");
|
||||
return false;
|
||||
}
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path={0},
|
||||
};
|
||||
sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME);
|
||||
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path);
|
||||
if (bind(sock_fd_, (struct sockaddr *) &addr, socklen) == -1) {
|
||||
PLOGE("bind socket");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetFd() override {
|
||||
return sock_fd_;
|
||||
}
|
||||
|
||||
void HandleEvent(EventLoop &loop, uint32_t) override {
|
||||
struct [[gnu::packed]] MsgHead {
|
||||
Command cmd;
|
||||
int length;
|
||||
char data[0];
|
||||
};
|
||||
for (;;) {
|
||||
std::vector<uint8_t> buf;
|
||||
buf.resize(sizeof(MsgHead), 0);
|
||||
MsgHead &msg = *reinterpret_cast<MsgHead*>(buf.data());
|
||||
ssize_t real_size;
|
||||
auto nread = recv(sock_fd_, &msg, sizeof(msg), MSG_PEEK);
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
break;
|
||||
}
|
||||
PLOGE("read socket");
|
||||
}
|
||||
if (static_cast<size_t>(nread) < sizeof(Command)) {
|
||||
LOGE("read %zu < %zu", nread, sizeof(Command));
|
||||
continue;
|
||||
}
|
||||
if (msg.cmd >= Command::DAEMON64_SET_INFO && msg.cmd != Command::SYSTEM_SERVER_STARTED) {
|
||||
if (nread != sizeof(msg)) {
|
||||
LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(MsgHead));
|
||||
continue;
|
||||
}
|
||||
real_size = sizeof(MsgHead) + msg.length;
|
||||
} else {
|
||||
if (nread != sizeof(Command)) {
|
||||
LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(Command));
|
||||
continue;
|
||||
}
|
||||
real_size = sizeof(Command);
|
||||
}
|
||||
buf.resize(real_size);
|
||||
nread = recv(sock_fd_, &msg, real_size, 0);
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
break;
|
||||
}
|
||||
PLOGE("recv");
|
||||
continue;
|
||||
}
|
||||
if (nread != real_size) {
|
||||
LOGE("real size %zu != %zu", real_size, nread);
|
||||
continue;
|
||||
}
|
||||
switch (msg.cmd) {
|
||||
case START:
|
||||
if (tracing_state == STOPPING) {
|
||||
tracing_state = TRACING;
|
||||
} else if (tracing_state == STOPPED) {
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
LOGI("start tracing init");
|
||||
tracing_state = TRACING;
|
||||
}
|
||||
updateStatus();
|
||||
break;
|
||||
case STOP:
|
||||
if (tracing_state == TRACING) {
|
||||
LOGI("stop tracing requested");
|
||||
tracing_state = STOPPING;
|
||||
monitor_stop_reason = "user requested";
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0);
|
||||
updateStatus();
|
||||
}
|
||||
break;
|
||||
case EXIT:
|
||||
LOGI("prepare for exit ...");
|
||||
tracing_state = EXITING;
|
||||
monitor_stop_reason = "user requested";
|
||||
updateStatus();
|
||||
loop.Stop();
|
||||
break;
|
||||
case ZYGOTE64_INJECTED:
|
||||
status64.zygote_injected = true;
|
||||
updateStatus();
|
||||
break;
|
||||
case ZYGOTE32_INJECTED:
|
||||
status32.zygote_injected = true;
|
||||
updateStatus();
|
||||
break;
|
||||
case DAEMON64_SET_INFO:
|
||||
LOGD("received daemon64 info %s", msg.data);
|
||||
status64.daemon_info = std::string(msg.data);
|
||||
updateStatus();
|
||||
break;
|
||||
case DAEMON32_SET_INFO:
|
||||
LOGD("received daemon32 info %s", msg.data);
|
||||
status32.daemon_info = std::string(msg.data);
|
||||
updateStatus();
|
||||
break;
|
||||
case DAEMON64_SET_ERROR_INFO:
|
||||
LOGD("received daemon64 error info %s", msg.data);
|
||||
status64.daemon_running = false;
|
||||
status64.daemon_error_info = std::string(msg.data);
|
||||
updateStatus();
|
||||
break;
|
||||
case DAEMON32_SET_ERROR_INFO:
|
||||
LOGD("received daemon32 error info %s", msg.data);
|
||||
status32.daemon_running = false;
|
||||
status32.daemon_error_info = std::string(msg.data);
|
||||
updateStatus();
|
||||
break;
|
||||
case SYSTEM_SERVER_STARTED:
|
||||
LOGD("system server started, mounting prop");
|
||||
if (mount(prop_path.c_str(), "/data/adb/modules/zygisksu/module.prop", nullptr, MS_BIND, nullptr) == -1) {
|
||||
PLOGE("failed to mount prop");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~SocketHandler() {
|
||||
if (sock_fd_ >= 0) close(sock_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
constexpr auto MAX_RETRY_COUNT = 5;
|
||||
|
||||
#define CREATE_ZYGOTE_START_COUNTER(abi) \
|
||||
struct timespec last_zygote##abi{.tv_sec = 0, .tv_nsec = 0}; \
|
||||
int count_zygote##abi = 0; \
|
||||
bool should_stop_inject##abi() { \
|
||||
struct timespec now{}; \
|
||||
clock_gettime(CLOCK_MONOTONIC, &now); \
|
||||
if (now.tv_sec - last_zygote##abi.tv_sec < 30) { \
|
||||
count_zygote##abi++; \
|
||||
} else { \
|
||||
count_zygote##abi = 0; \
|
||||
} \
|
||||
last_zygote##abi = now; \
|
||||
return count_zygote##abi >= MAX_RETRY_COUNT; \
|
||||
}
|
||||
|
||||
CREATE_ZYGOTE_START_COUNTER(64)
|
||||
CREATE_ZYGOTE_START_COUNTER(32)
|
||||
|
||||
static bool ensure_daemon_created(bool is_64bit) {
|
||||
auto &status = is_64bit ? status64 : status32;
|
||||
if (is_64bit) {
|
||||
LOGD("new zygote started, unmounting prop ...");
|
||||
umount2("/data/adb/modules/zygisksu/module.prop", MNT_DETACH);
|
||||
}
|
||||
status.zygote_injected = false;
|
||||
if (status.daemon_pid == -1) {
|
||||
auto pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOGE("create daemon (64=%s)", is_64bit ? "true" : "false");
|
||||
return false;
|
||||
} else if (pid == 0) {
|
||||
std::string daemon_name = "./bin/zygiskd";
|
||||
daemon_name += is_64bit ? "64" : "32";
|
||||
execl(daemon_name.c_str(), daemon_name.c_str(), nullptr);
|
||||
PLOGE("exec daemon %s failed", daemon_name.c_str());
|
||||
exit(1);
|
||||
} else {
|
||||
status.supported = true;
|
||||
status.daemon_pid = pid;
|
||||
status.daemon_running = true;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return status.daemon_running;
|
||||
}
|
||||
}
|
||||
|
||||
struct SigChldHandler : public EventHandler {
|
||||
private:
|
||||
int signal_fd_;
|
||||
struct signalfd_siginfo fdsi;
|
||||
int status;
|
||||
std::set<pid_t> process;
|
||||
public:
|
||||
bool Init() {
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
|
||||
PLOGE("set sigprocmask");
|
||||
return false;
|
||||
}
|
||||
signal_fd_ = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||
if (signal_fd_ == -1) {
|
||||
PLOGE("create signalfd");
|
||||
return false;
|
||||
}
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetFd() override {
|
||||
return signal_fd_;
|
||||
}
|
||||
|
||||
void HandleEvent(EventLoop &, uint32_t) override {
|
||||
for (;;) {
|
||||
ssize_t s = read(signal_fd_, &fdsi, sizeof(fdsi));
|
||||
if (s == -1) {
|
||||
if (errno == EAGAIN) break;
|
||||
PLOGE("read signalfd");
|
||||
continue;
|
||||
}
|
||||
if (s != sizeof(fdsi)) {
|
||||
LOGW("read %zu != %zu", s, sizeof(fdsi));
|
||||
continue;
|
||||
}
|
||||
if (fdsi.ssi_signo != SIGCHLD) {
|
||||
LOGW("no sigchld received");
|
||||
continue;
|
||||
}
|
||||
int pid;
|
||||
while ((pid = waitpid(-1, &status, __WALL | WNOHANG)) != 0) {
|
||||
if (pid == -1) {
|
||||
if (tracing_state == STOPPED && errno == ECHILD) break;
|
||||
PLOGE("waitpid");
|
||||
}
|
||||
if (pid == 1) {
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) {
|
||||
long child_pid;
|
||||
ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid);
|
||||
LOGV("forked %ld", child_pid);
|
||||
} else if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP) &&
|
||||
tracing_state == STOPPING) {
|
||||
if (ptrace(PTRACE_DETACH, 1, 0, 0) == -1)
|
||||
PLOGE("failed to detach init");
|
||||
tracing_state = STOPPED;
|
||||
LOGI("stop tracing init");
|
||||
continue;
|
||||
}
|
||||
if (WIFSTOPPED(status)) {
|
||||
if (WPTEVENT(status) == 0) {
|
||||
if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) {
|
||||
LOGW("inject signal sent to init: %s %d",
|
||||
sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status));
|
||||
ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status));
|
||||
} else {
|
||||
LOGW("suppress stopping signal sent to init: %s %d",
|
||||
sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#define CHECK_DAEMON_EXIT(abi) \
|
||||
if (status##abi.supported && pid == status64.daemon_pid) { \
|
||||
auto status_str = parse_status(status); \
|
||||
LOGW("daemon" #abi "pid %d exited: %s", pid, status_str.c_str()); \
|
||||
status##abi.daemon_running = false; \
|
||||
if (status##abi.daemon_error_info.empty()) { \
|
||||
status##abi.daemon_error_info = status_str; \
|
||||
} \
|
||||
updateStatus(); \
|
||||
continue; \
|
||||
}
|
||||
CHECK_DAEMON_EXIT(64)
|
||||
CHECK_DAEMON_EXIT(32)
|
||||
auto state = process.find(pid);
|
||||
if (state == process.end()) {
|
||||
LOGV("new process %d attached", pid);
|
||||
process.emplace(pid);
|
||||
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
continue;
|
||||
} else {
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
|
||||
auto program = get_program(pid);
|
||||
LOGV("%d program %s", pid, program.c_str());
|
||||
const char* tracer = nullptr;
|
||||
do {
|
||||
if (tracing_state != TRACING) {
|
||||
LOGW("stop injecting %d because not tracing", pid);
|
||||
break;
|
||||
}
|
||||
#define PRE_INJECT(abi, is_64) \
|
||||
if (program == "/system/bin/app_process"#abi) { \
|
||||
tracer = "./bin/zygisk-ptrace"#abi; \
|
||||
if (should_stop_inject##abi()) { \
|
||||
LOGW("zygote" #abi " restart too much times, stop injecting"); \
|
||||
tracing_state = STOPPING; \
|
||||
monitor_stop_reason = "zygote crashed"; \
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
|
||||
break; \
|
||||
} \
|
||||
if (!ensure_daemon_created(is_64)) { \
|
||||
LOGW("daemon" #abi " not running, stop injecting"); \
|
||||
tracing_state = STOPPING; \
|
||||
monitor_stop_reason = "daemon not running"; \
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0); \
|
||||
break; \
|
||||
} \
|
||||
}
|
||||
PRE_INJECT(64, true)
|
||||
PRE_INJECT(32, false)
|
||||
if (tracer != nullptr) {
|
||||
LOGD("stopping %d", pid);
|
||||
kill(pid, SIGSTOP);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
waitpid(pid, &status, __WALL);
|
||||
if (STOPPED_WITH(SIGSTOP, 0)) {
|
||||
LOGD("detaching %d", pid);
|
||||
ptrace(PTRACE_DETACH, pid, 0, SIGSTOP);
|
||||
status = 0;
|
||||
auto p = fork_dont_care();
|
||||
if (p == 0) {
|
||||
execl(tracer, basename(tracer), "trace",
|
||||
std::to_string(pid).c_str(), "--restart", nullptr);
|
||||
PLOGE("failed to exec, kill");
|
||||
kill(pid, SIGKILL);
|
||||
exit(1);
|
||||
} else if (p == -1) {
|
||||
PLOGE("failed to fork, kill");
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (false);
|
||||
updateStatus();
|
||||
} else {
|
||||
LOGW("process %d received unknown status %s", pid,
|
||||
parse_status(status).c_str());
|
||||
}
|
||||
process.erase(state);
|
||||
if (WIFSTOPPED(status)) {
|
||||
LOGV("detach process %d", pid);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
~SigChldHandler() {
|
||||
if (signal_fd_ >= 0) close(signal_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
static std::string pre_section;
|
||||
static std::string post_section;
|
||||
|
||||
static void updateStatus() {
|
||||
auto prop = xopen_file(prop_path.c_str(), "w");
|
||||
std::string status_text = "monitor:";
|
||||
switch (tracing_state) {
|
||||
case TRACING:
|
||||
status_text += "😋tracing";
|
||||
break;
|
||||
case STOPPING:
|
||||
[[fallthrough]];
|
||||
case STOPPED:
|
||||
status_text += "❌stopped";
|
||||
break;
|
||||
case EXITING:
|
||||
status_text += "❌exited";
|
||||
break;
|
||||
}
|
||||
if (tracing_state != TRACING && !monitor_stop_reason.empty()) {
|
||||
status_text += "(";
|
||||
status_text += monitor_stop_reason;
|
||||
status_text += ")";
|
||||
}
|
||||
status_text += ",";
|
||||
#define WRITE_STATUS_ABI(suffix) \
|
||||
if (status##suffix.supported) { \
|
||||
status_text += " zygote" #suffix ":"; \
|
||||
if (tracing_state != TRACING) status_text += "❓unknown,"; \
|
||||
else if (status##suffix.zygote_injected) status_text += "😋injected,"; \
|
||||
else status_text += "❌not injected,"; \
|
||||
status_text += " daemon" #suffix ":"; \
|
||||
if (status##suffix.daemon_running) { \
|
||||
status_text += "😋running"; \
|
||||
if (!status##suffix.daemon_info.empty()) { \
|
||||
status_text += "("; \
|
||||
status_text += status##suffix.daemon_info; \
|
||||
status_text += ")"; \
|
||||
} \
|
||||
} else { \
|
||||
status_text += "❌crashed"; \
|
||||
if (!status##suffix.daemon_error_info.empty()) { \
|
||||
status_text += "("; \
|
||||
status_text += status##suffix.daemon_error_info; \
|
||||
status_text += ")"; \
|
||||
} \
|
||||
} \
|
||||
}
|
||||
WRITE_STATUS_ABI(64)
|
||||
WRITE_STATUS_ABI(32)
|
||||
fprintf(prop.get(), "%s[%s] %s", pre_section.c_str(), status_text.c_str(), post_section.c_str());
|
||||
}
|
||||
|
||||
static bool prepare_environment() {
|
||||
prop_path = zygiskd::GetTmpPath() + "/module.prop";
|
||||
close(open(prop_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644));
|
||||
auto orig_prop = xopen_file("./module.prop", "r");
|
||||
if (orig_prop == nullptr) {
|
||||
PLOGE("failed to open orig prop");
|
||||
return false;
|
||||
}
|
||||
bool post = false;
|
||||
file_readline(false, orig_prop.get(), [&](std::string_view line) -> bool {
|
||||
if (line.starts_with("description=")) {
|
||||
post = true;
|
||||
pre_section += "description=";
|
||||
post_section += line.substr(sizeof("description"));
|
||||
} else {
|
||||
if (post) {
|
||||
post_section += line;
|
||||
} else {
|
||||
pre_section += line;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
updateStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
void init_monitor() {
|
||||
LOGI("Zygisk Next %s", ZKSU_VERSION);
|
||||
LOGI("init monitor started");
|
||||
if (!prepare_environment()) {
|
||||
exit(1);
|
||||
}
|
||||
SocketHandler socketHandler{};
|
||||
socketHandler.Init();
|
||||
SigChldHandler ptraceHandler{};
|
||||
ptraceHandler.Init();
|
||||
EventLoop looper;
|
||||
looper.Init();
|
||||
looper.RegisterHandler(socketHandler, EPOLLIN | EPOLLET);
|
||||
looper.RegisterHandler(ptraceHandler, EPOLLIN | EPOLLET);
|
||||
looper.Loop();
|
||||
LOGI("exit");
|
||||
}
|
||||
|
||||
void send_control_command(Command cmd) {
|
||||
int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (sockfd == -1) err(EXIT_FAILURE, "socket");
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path={0},
|
||||
};
|
||||
sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME);
|
||||
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path);
|
||||
auto nsend = sendto(sockfd, (void *) &cmd, sizeof(cmd), 0, (sockaddr *) &addr, socklen);
|
||||
if (nsend == -1) {
|
||||
err(EXIT_FAILURE, "send");
|
||||
} else if (nsend != sizeof(cmd)) {
|
||||
printf("send %zu != %zu\n", nsend, sizeof(cmd));
|
||||
exit(1);
|
||||
}
|
||||
printf("command sent\n");
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <signal.h>
|
||||
#include <sys/system_properties.h>
|
||||
#include <string>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "utils.hpp"
|
||||
|
||||
bool inject_on_main(int pid, const char *lib_path) {
|
||||
LOGI("injecting %s to zygote %d", lib_path, pid);
|
||||
// parsing KernelArgumentBlock
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/KernelArgumentBlock.h;l=30;drc=6d1ee77ee32220e4202c3066f7e1f69572967ad8
|
||||
struct user_regs_struct regs{}, backup{};
|
||||
auto map = MapInfo::Scan(std::to_string(pid));
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
auto arg = static_cast<uintptr_t>(regs.REG_SP);
|
||||
LOGV("kernel argument %" PRIxPTR " %s", arg, get_addr_mem_region(map, arg).c_str());
|
||||
int argc;
|
||||
auto argv = reinterpret_cast<char **>(reinterpret_cast<uintptr_t *>(arg) + 1);
|
||||
LOGV("argv %p", argv);
|
||||
read_proc(pid, arg, &argc, sizeof(argc));
|
||||
LOGV("argc %d", argc);
|
||||
auto envp = argv + argc + 1;
|
||||
LOGV("envp %p", envp);
|
||||
auto p = envp;
|
||||
while (true) {
|
||||
uintptr_t *buf;
|
||||
read_proc(pid, (uintptr_t) p, &buf, sizeof(buf));
|
||||
if (buf != nullptr) ++p;
|
||||
else break;
|
||||
}
|
||||
++p;
|
||||
auto auxv = reinterpret_cast<ElfW(auxv_t) *>(p);
|
||||
LOGV("auxv %p %s", auxv, get_addr_mem_region(map, (uintptr_t) auxv).c_str());
|
||||
auto v = auxv;
|
||||
uintptr_t entry_addr = 0;
|
||||
uintptr_t addr_of_entry_addr = 0;
|
||||
while (true) {
|
||||
ElfW(auxv_t) buf;
|
||||
read_proc(pid, (uintptr_t) v, &buf, sizeof(buf));
|
||||
if (buf.a_type == AT_ENTRY) {
|
||||
entry_addr = (uintptr_t) buf.a_un.a_val;
|
||||
addr_of_entry_addr = (uintptr_t) v + offsetof(ElfW(auxv_t), a_un);
|
||||
LOGV("entry address %" PRIxPTR " %s (entry=%" PRIxPTR ", entry_addr=%" PRIxPTR ")", entry_addr,
|
||||
get_addr_mem_region(map, entry_addr).c_str(), (uintptr_t) v, addr_of_entry_addr);
|
||||
break;
|
||||
}
|
||||
if (buf.a_type == AT_NULL) break;
|
||||
v++;
|
||||
}
|
||||
if (entry_addr == 0) {
|
||||
LOGE("failed to get entry");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace the program entry with an invalid address
|
||||
// For arm32 compatibility, we set the last bit to the same as the entry address
|
||||
uintptr_t break_addr = (-0x05ec1cff & ~1) | ((uintptr_t) entry_addr & 1);
|
||||
if (!write_proc(pid, (uintptr_t) addr_of_entry_addr, &break_addr, sizeof(break_addr))) return false;
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
int status;
|
||||
wait_for_trace(pid, &status, __WALL);
|
||||
if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) {
|
||||
if (!get_regs(pid, regs)) return false;
|
||||
if (static_cast<uintptr_t>(regs.REG_IP & ~1) != (break_addr & ~1)) {
|
||||
LOGE("stopped at unknown addr %p", (void *) regs.REG_IP);
|
||||
return false;
|
||||
}
|
||||
// The linker has been initialized now, we can do dlopen
|
||||
LOGD("stopped at entry");
|
||||
|
||||
// restore entry address
|
||||
if (!write_proc(pid, (uintptr_t) addr_of_entry_addr, &entry_addr, sizeof(entry_addr))) return false;
|
||||
|
||||
// backup registers
|
||||
memcpy(&backup, ®s, sizeof(regs));
|
||||
map = MapInfo::Scan(std::to_string(pid));
|
||||
auto local_map = MapInfo::Scan();
|
||||
auto libc_return_addr = find_module_return_addr(map, "libc.so");
|
||||
LOGD("libc return addr %p", libc_return_addr);
|
||||
|
||||
// call dlopen
|
||||
auto dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen");
|
||||
if (dlopen_addr == nullptr) return false;
|
||||
std::vector<long> args;
|
||||
auto str = push_string(pid, regs, lib_path);
|
||||
args.clear();
|
||||
args.push_back((long) str);
|
||||
args.push_back((long) RTLD_NOW);
|
||||
auto remote_handle = remote_call(pid, regs, (uintptr_t) dlopen_addr, (uintptr_t) libc_return_addr, args);
|
||||
LOGD("remote handle %p", (void *) remote_handle);
|
||||
if (remote_handle == 0) {
|
||||
LOGE("handle is null");
|
||||
// call dlerror
|
||||
auto dlerror_addr = find_func_addr(local_map, map, "libdl.so", "dlerror");
|
||||
if (dlerror_addr == nullptr) {
|
||||
LOGE("find dlerror");
|
||||
return false;
|
||||
}
|
||||
args.clear();
|
||||
auto dlerror_str_addr = remote_call(pid, regs, (uintptr_t) dlerror_addr, (uintptr_t) libc_return_addr, args);
|
||||
LOGD("dlerror str %p", (void*) dlerror_str_addr);
|
||||
if (dlerror_str_addr == 0) return false;
|
||||
auto strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen");
|
||||
if (strlen_addr == nullptr) {
|
||||
LOGE("find strlen");
|
||||
return false;
|
||||
}
|
||||
args.clear();
|
||||
args.push_back(dlerror_str_addr);
|
||||
auto dlerror_len = remote_call(pid, regs, (uintptr_t) strlen_addr, (uintptr_t) libc_return_addr, args);
|
||||
if (dlerror_len <= 0) {
|
||||
LOGE("dlerror len <= 0");
|
||||
return false;
|
||||
}
|
||||
std::string err;
|
||||
err.resize(dlerror_len + 1, 0);
|
||||
read_proc(pid, (uintptr_t) dlerror_str_addr, err.data(), dlerror_len);
|
||||
LOGE("dlerror info %s", err.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// call dlsym(handle, "entry")
|
||||
auto dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym");
|
||||
if (dlsym_addr == nullptr) return false;
|
||||
args.clear();
|
||||
str = push_string(pid, regs, "entry");
|
||||
args.push_back(remote_handle);
|
||||
args.push_back((long) str);
|
||||
auto injector_entry = remote_call(pid, regs, (uintptr_t) dlsym_addr, (uintptr_t) libc_return_addr, args);
|
||||
LOGD("injector entry %p", (void*) injector_entry);
|
||||
if (injector_entry == 0) {
|
||||
LOGE("injector entry is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// call injector entry(handle, path)
|
||||
args.clear();
|
||||
args.push_back(remote_handle);
|
||||
str = push_string(pid, regs, zygiskd::GetTmpPath().c_str());
|
||||
args.push_back((long) str);
|
||||
remote_call(pid, regs, injector_entry, (uintptr_t) libc_return_addr, args);
|
||||
|
||||
// reset pc to entry
|
||||
backup.REG_IP = (long) entry_addr;
|
||||
LOGD("invoke entry");
|
||||
// restore registers
|
||||
if (!set_regs(pid, backup)) return false;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
LOGE("stopped by other reason: %s", parse_status(status).c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event))
|
||||
|
||||
bool trace_zygote(int pid) {
|
||||
LOGI("start tracing %d", pid);
|
||||
#define WAIT_OR_DIE wait_for_trace(pid, &status, __WALL);
|
||||
#define CONT_OR_DIE \
|
||||
if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) { \
|
||||
PLOGE("cont"); \
|
||||
return false; \
|
||||
}
|
||||
int status;
|
||||
LOGI("tracing %d (tracer %d)", pid, getpid());
|
||||
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_EXITKILL) == -1) {
|
||||
PLOGE("seize");
|
||||
return false;
|
||||
}
|
||||
WAIT_OR_DIE
|
||||
if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) {
|
||||
std::string lib_path = zygiskd::GetTmpPath();
|
||||
lib_path += "/lib" LP_SELECT("", "64") "/libzygisk.so";
|
||||
if (!inject_on_main(pid, lib_path.c_str())) {
|
||||
LOGE("failed to inject");
|
||||
return false;
|
||||
}
|
||||
LOGD("inject done, continue process");
|
||||
if (kill(pid, SIGCONT)) {
|
||||
PLOGE("kill");
|
||||
return false;
|
||||
}
|
||||
CONT_OR_DIE
|
||||
WAIT_OR_DIE
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP)) {
|
||||
CONT_OR_DIE
|
||||
WAIT_OR_DIE
|
||||
if (STOPPED_WITH(SIGCONT, 0)) {
|
||||
LOGD("received SIGCONT");
|
||||
ptrace(PTRACE_DETACH, pid, 0, SIGCONT);
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", parse_status(status).c_str());
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", parse_status(status).c_str());
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
#include <vector>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/sysmacros.h>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
#include <sys/ptrace.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/uio.h>
|
||||
#include <sys/auxv.h>
|
||||
#include <elf.h>
|
||||
#include <link.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/wait.h>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <dlfcn.h>
|
||||
#include <signal.h>
|
||||
#include <sstream>
|
||||
#include <ios>
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "logging.h"
|
||||
#include <sched.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
bool switch_mnt_ns(int pid, int *fd) {
|
||||
int nsfd, old_nsfd = -1;
|
||||
std::string path;
|
||||
if (pid == 0) {
|
||||
if (fd != nullptr) {
|
||||
nsfd = *fd;
|
||||
*fd = -1;
|
||||
} else return false;
|
||||
path = "/proc/self/fd/";
|
||||
path += std::to_string(nsfd);
|
||||
} else {
|
||||
if (fd != nullptr) {
|
||||
old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
|
||||
if (old_nsfd == -1) {
|
||||
PLOGE("get old nsfd");
|
||||
return false;
|
||||
}
|
||||
*fd = old_nsfd;
|
||||
}
|
||||
path = std::string("/proc/") + std::to_string(pid) + "/ns/mnt";
|
||||
nsfd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (nsfd == -1) {
|
||||
PLOGE("open nsfd %s", path.c_str());
|
||||
close(old_nsfd);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (setns(nsfd, CLONE_NEWNS) == -1) {
|
||||
PLOGE("set ns to %s", path.c_str());
|
||||
close(nsfd);
|
||||
close(old_nsfd);
|
||||
return false;
|
||||
}
|
||||
close(nsfd);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<MapInfo> MapInfo::Scan(const std::string& pid) {
|
||||
constexpr static auto kPermLength = 5;
|
||||
constexpr static auto kMapEntry = 7;
|
||||
std::vector<MapInfo> info;
|
||||
std::string file_name = std::string("/proc/") + pid + "/maps";
|
||||
auto maps = std::unique_ptr<FILE, decltype(&fclose)>{fopen(file_name.c_str(), "r"), &fclose};
|
||||
if (maps) {
|
||||
char *line = nullptr;
|
||||
size_t len = 0;
|
||||
ssize_t read;
|
||||
while ((read = getline(&line, &len, maps.get())) > 0) {
|
||||
line[read - 1] = '\0';
|
||||
uintptr_t start = 0;
|
||||
uintptr_t end = 0;
|
||||
uintptr_t off = 0;
|
||||
ino_t inode = 0;
|
||||
unsigned int dev_major = 0;
|
||||
unsigned int dev_minor = 0;
|
||||
std::array<char, kPermLength> perm{'\0'};
|
||||
int path_off;
|
||||
if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start,
|
||||
&end, perm.data(), &off, &dev_major, &dev_minor, &inode,
|
||||
&path_off) != kMapEntry) {
|
||||
continue;
|
||||
}
|
||||
while (path_off < read && isspace(line[path_off])) path_off++;
|
||||
auto &ref = info.emplace_back(MapInfo{start, end, 0, perm[3] == 'p', off,
|
||||
static_cast<dev_t>(makedev(dev_major, dev_minor)),
|
||||
inode, line + path_off});
|
||||
if (perm[0] == 'r') ref.perms |= PROT_READ;
|
||||
if (perm[1] == 'w') ref.perms |= PROT_WRITE;
|
||||
if (perm[2] == 'x') ref.perms |= PROT_EXEC;
|
||||
}
|
||||
free(line);
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len) {
|
||||
LOGV("write to remote addr %" PRIxPTR " size %zu", remote_addr, len);
|
||||
struct iovec local{
|
||||
.iov_base = (void *) buf,
|
||||
.iov_len = len
|
||||
};
|
||||
struct iovec remote{
|
||||
.iov_base = (void *) remote_addr,
|
||||
.iov_len = len
|
||||
};
|
||||
auto l = process_vm_writev(pid, &local, 1, &remote, 1, 0);
|
||||
if (l == -1) {
|
||||
PLOGE("process_vm_writev");
|
||||
} else if (static_cast<size_t>(l) != len) {
|
||||
LOGW("not fully written: %zu, excepted %zu", l, len);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) {
|
||||
struct iovec local{
|
||||
.iov_base = (void *) buf,
|
||||
.iov_len = len
|
||||
};
|
||||
struct iovec remote{
|
||||
.iov_base = (void *) remote_addr,
|
||||
.iov_len = len
|
||||
};
|
||||
auto l = process_vm_readv(pid, &local, 1, &remote, 1, 0);
|
||||
if (l == -1) {
|
||||
PLOGE("process_vm_readv");
|
||||
} else if (static_cast<size_t>(l) != len) {
|
||||
LOGW("not fully read: %zu, excepted %zu", l, len);
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
bool get_regs(int pid, struct user_regs_struct ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("getregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s) {
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#elif defined(__aarch64__) || defined(__arm__)
|
||||
struct iovec iov = {
|
||||
.iov_base = ®s,
|
||||
.iov_len = sizeof(struct user_regs_struct),
|
||||
};
|
||||
if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
PLOGE("setregs");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_addr_mem_region(std::vector<MapInfo> &info, uintptr_t addr) {
|
||||
for (auto &map: info) {
|
||||
if (map.start <= addr && map.end > addr) {
|
||||
auto s = std::string(map.path);
|
||||
s += ' ';
|
||||
s += map.perms & PROT_READ ? 'r' : '-';
|
||||
s += map.perms & PROT_WRITE ? 'w' : '-';
|
||||
s += map.perms & PROT_EXEC ? 'x' : '-';
|
||||
return s;
|
||||
}
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
|
||||
void *find_module_return_addr(std::vector<MapInfo> &info, std::string_view suffix) {
|
||||
for (auto &map: info) {
|
||||
if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) {
|
||||
return (void *) map.start;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *find_module_base(std::vector<MapInfo> &info, std::string_view suffix) {
|
||||
for (auto &map: info) {
|
||||
if (map.offset == 0 && map.path.ends_with(suffix)) {
|
||||
return (void *) map.start;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void *find_func_addr(
|
||||
std::vector<MapInfo> &local_info,
|
||||
std::vector<MapInfo> &remote_info,
|
||||
std::string_view module,
|
||||
std::string_view func) {
|
||||
auto lib = dlopen(module.data(), RTLD_NOW);
|
||||
if (lib == nullptr) {
|
||||
LOGE("failed to open lib %s: %s", module.data(), dlerror());
|
||||
return nullptr;
|
||||
}
|
||||
auto sym = reinterpret_cast<uint8_t *>(dlsym(lib, func.data()));
|
||||
if (sym == nullptr) {
|
||||
LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror());
|
||||
dlclose(lib);
|
||||
return nullptr;
|
||||
}
|
||||
LOGD("sym %s: %p", func.data(), sym);
|
||||
dlclose(lib);
|
||||
auto local_base = reinterpret_cast<uint8_t *>(find_module_base(local_info, module));
|
||||
if (local_base == nullptr) {
|
||||
LOGE("failed to find local base for module %s", module.data());
|
||||
return nullptr;
|
||||
}
|
||||
auto remote_base = reinterpret_cast<uint8_t *>(find_module_base(remote_info, module));
|
||||
if (remote_base == nullptr) {
|
||||
LOGE("failed to find remote base for module %s", module.data());
|
||||
return nullptr;
|
||||
}
|
||||
LOGD("found local base %p remote base %p", local_base, remote_base);
|
||||
auto addr = (sym - local_base) + remote_base;
|
||||
LOGD("addr %p", addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
void align_stack(struct user_regs_struct ®s, long preserve) {
|
||||
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
|
||||
}
|
||||
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) {
|
||||
auto len = strlen(str) + 1;
|
||||
regs.REG_SP -= len;
|
||||
align_stack(regs);
|
||||
auto addr = static_cast<uintptr_t>(regs.REG_SP);
|
||||
if (!write_proc(pid, addr, str, len)) {
|
||||
LOGE("failed to write string %s", str);
|
||||
}
|
||||
LOGD("pushed string %" PRIxPTR, addr);
|
||||
return addr;
|
||||
}
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<long> &args) {
|
||||
align_stack(regs);
|
||||
LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size());
|
||||
for (auto &a: args) {
|
||||
LOGV("arg %p", (void *) a);
|
||||
}
|
||||
#if defined(__x86_64__)
|
||||
if (args.size() >= 1) {
|
||||
regs.rdi = args[0];
|
||||
}
|
||||
if (args.size() >= 2) {
|
||||
regs.rsi = args[1];
|
||||
}
|
||||
if (args.size() >= 3) {
|
||||
regs.rdx = args[2];
|
||||
}
|
||||
if (args.size() >= 4) {
|
||||
regs.rcx = args[3];
|
||||
}
|
||||
if (args.size() >= 5) {
|
||||
regs.r8 = args[4];
|
||||
}
|
||||
if (args.size() >= 6) {
|
||||
regs.r9 = args[5];
|
||||
}
|
||||
if (args.size() > 6) {
|
||||
auto remain = (args.size() - 6) * sizeof(long);
|
||||
align_stack(regs, remain);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
||||
LOGE("failed to push arguments");
|
||||
}
|
||||
}
|
||||
regs.REG_SP -= sizeof(long);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
||||
LOGE("failed to write return addr");
|
||||
}
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__i386__)
|
||||
if (args.size() > 0) {
|
||||
auto remain = (args.size()) * sizeof(long);
|
||||
align_stack(regs, remain);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) {
|
||||
LOGE("failed to push arguments");
|
||||
}
|
||||
}
|
||||
regs.REG_SP -= sizeof(long);
|
||||
if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) {
|
||||
LOGE("failed to write return addr");
|
||||
}
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__aarch64__)
|
||||
for (size_t i = 0; i < args.size() && i < 8; i++) {
|
||||
regs.regs[i] = args[i];
|
||||
}
|
||||
if (args.size() > 8) {
|
||||
auto remain = (args.size() - 8) * sizeof(long);
|
||||
align_stack(regs, remain);
|
||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
||||
}
|
||||
regs.regs[30] = return_addr;
|
||||
regs.REG_IP = func_addr;
|
||||
#elif defined(__arm__)
|
||||
for (size_t i = 0; i < args.size() && i < 4; i++) {
|
||||
regs.uregs[i] = args[i];
|
||||
}
|
||||
if (args.size() > 4) {
|
||||
auto remain = (args.size() - 4) * sizeof(long);
|
||||
align_stack(regs, remain);
|
||||
write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain);
|
||||
}
|
||||
regs.uregs[14] = return_addr;
|
||||
regs.REG_IP = func_addr;
|
||||
constexpr auto CPSR_T_MASK = 1lu << 5;
|
||||
if ((regs.REG_IP & 1) != 0) {
|
||||
regs.REG_IP = regs.REG_IP & ~1;
|
||||
regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK;
|
||||
} else {
|
||||
regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK;
|
||||
}
|
||||
#endif
|
||||
if (!set_regs(pid, regs)) {
|
||||
LOGE("failed to set regs");
|
||||
return 0;
|
||||
}
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
int status;
|
||||
wait_for_trace(pid, &status, __WALL);
|
||||
if (!get_regs(pid, regs)) {
|
||||
LOGE("failed to get regs after call");
|
||||
return 0;
|
||||
}
|
||||
if (WSTOPSIG(status) == SIGSEGV) {
|
||||
if (static_cast<uintptr_t>(regs.REG_IP) != return_addr) {
|
||||
LOGE("wrong return addr %p", (void *) regs.REG_IP);
|
||||
return 0;
|
||||
}
|
||||
return regs.REG_RET;
|
||||
} else {
|
||||
LOGE("stopped by other reason %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fork_dont_care() {
|
||||
auto pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOGE("fork 1");
|
||||
} else if (pid == 0) {
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
PLOGE("fork 2");
|
||||
} else if (pid > 0) {
|
||||
exit(0);
|
||||
}
|
||||
} else {
|
||||
int status;
|
||||
waitpid(pid, &status, __WALL);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
void wait_for_trace(int pid, int* status, int flags) {
|
||||
while (true) {
|
||||
auto result = waitpid(pid, status, flags);
|
||||
if (result == -1) {
|
||||
if (errno == EINTR) {
|
||||
continue;
|
||||
} else {
|
||||
PLOGE("wait %d failed", pid);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
if (!WIFSTOPPED(*status)) {
|
||||
LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str());
|
||||
exit(1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::string parse_status(int status) {
|
||||
std::ostringstream os;
|
||||
os << "0x" << std::hex << status << std::dec << " ";
|
||||
if (WIFEXITED(status)) {
|
||||
os << "exited with " << WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
os << "signaled with " << sigabbrev_np(WTERMSIG(status)) << "(" << WTERMSIG(status) << ")";
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
os << "stopped by ";
|
||||
auto stop_sig = WSTOPSIG(status);
|
||||
os << "signal=" << sigabbrev_np(stop_sig) << "(" << stop_sig << "),";
|
||||
os << "event=" << parse_ptrace_event(status);
|
||||
} else {
|
||||
os << "unknown";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string get_program(int pid) {
|
||||
std::string path = "/proc/";
|
||||
path += std::to_string(pid);
|
||||
path += "/exe";
|
||||
constexpr const auto SIZE = 256;
|
||||
char buf[SIZE + 1];
|
||||
auto sz = readlink(path.c_str(), buf, SIZE);
|
||||
if (sz == -1) {
|
||||
PLOGE("readlink /proc/%d/exe", pid);
|
||||
return "";
|
||||
}
|
||||
buf[sz] = 0;
|
||||
return buf;
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <sys/ptrace.h>
|
||||
#include <map>
|
||||
|
||||
#include "daemon.h"
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LOG_TAG "zygisk-ptrace64"
|
||||
#else
|
||||
#define LOG_TAG "zygisk-ptrace32"
|
||||
#endif
|
||||
#include "logging.h"
|
||||
|
||||
struct MapInfo {
|
||||
/// \brief The start address of the memory region.
|
||||
uintptr_t start;
|
||||
/// \brief The end address of the memory region.
|
||||
uintptr_t end;
|
||||
/// \brief The permissions of the memory region. This is a bit mask of the following values:
|
||||
/// - PROT_READ
|
||||
/// - PROT_WRITE
|
||||
/// - PROT_EXEC
|
||||
uint8_t perms;
|
||||
/// \brief Whether the memory region is private.
|
||||
bool is_private;
|
||||
/// \brief The offset of the memory region.
|
||||
uintptr_t offset;
|
||||
/// \brief The device number of the memory region.
|
||||
/// Major can be obtained by #major()
|
||||
/// Minor can be obtained by #minor()
|
||||
dev_t dev;
|
||||
/// \brief The inode number of the memory region.
|
||||
ino_t inode;
|
||||
/// \brief The path of the memory region.
|
||||
std::string path;
|
||||
|
||||
/// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries.
|
||||
/// This is useful to find out the inode of the library to hook.
|
||||
/// \return A list of \ref MapInfo entries.
|
||||
static std::vector<MapInfo> Scan(const std::string& pid = "self");
|
||||
};
|
||||
|
||||
#if defined(__x86_64__)
|
||||
#define REG_SP rsp
|
||||
#define REG_IP rip
|
||||
#define REG_RET rax
|
||||
#elif defined(__i386__)
|
||||
#define REG_SP esp
|
||||
#define REG_IP eip
|
||||
#define REG_RET eax
|
||||
#elif defined(__aarch64__)
|
||||
#define REG_SP sp
|
||||
#define REG_IP pc
|
||||
#define REG_RET regs[0]
|
||||
#elif defined(__arm__)
|
||||
#define REG_SP uregs[13]
|
||||
#define REG_IP uregs[15]
|
||||
#define REG_RET uregs[0]
|
||||
#define user_regs_struct user_regs
|
||||
#endif
|
||||
|
||||
ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len);
|
||||
|
||||
ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len);
|
||||
|
||||
bool get_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
bool set_regs(int pid, struct user_regs_struct ®s);
|
||||
|
||||
std::string get_addr_mem_region(std::vector<MapInfo> &info, uintptr_t addr);
|
||||
|
||||
void *find_module_base(std::vector<MapInfo> &info, std::string_view suffix);
|
||||
|
||||
void *find_func_addr(
|
||||
std::vector<MapInfo> &local_info,
|
||||
std::vector<MapInfo> &remote_info,
|
||||
std::string_view module,
|
||||
std::string_view func);
|
||||
|
||||
void align_stack(struct user_regs_struct ®s, long preserve = 0);
|
||||
|
||||
uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str);
|
||||
|
||||
uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr,
|
||||
std::vector<long> &args);
|
||||
|
||||
int fork_dont_care();
|
||||
|
||||
void wait_for_trace(int pid, int* status, int flags);
|
||||
|
||||
std::string parse_status(int status);
|
||||
|
||||
#define WPTEVENT(x) (x >> 16)
|
||||
|
||||
#define CASE_CONST_RETURN(x) case x: return #x;
|
||||
|
||||
inline const char* parse_ptrace_event(int status) {
|
||||
status = status >> 16;
|
||||
switch (status) {
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_FORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_CLONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXEC)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_EXIT)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP)
|
||||
CASE_CONST_RETURN(PTRACE_EVENT_STOP)
|
||||
default:
|
||||
return "(no event)";
|
||||
}
|
||||
}
|
||||
|
||||
inline const char* sigabbrev_np(int sig) {
|
||||
if (sig > 0 && sig < NSIG) return sys_signame[sig];
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
std::string get_program(int pid);
|
||||
void *find_module_return_addr(std::vector<MapInfo> &info, std::string_view suffix);
|
||||
|
||||
// pid = 0, fd != nullptr -> set to fd
|
||||
// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd
|
||||
bool switch_mnt_ns(int pid, int *fd);
|
||||
@@ -1,2 +0,0 @@
|
||||
public_key
|
||||
private_key
|
||||
@@ -1,240 +0,0 @@
|
||||
import android.databinding.tool.ext.capitalizeUS
|
||||
import java.security.MessageDigest
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.security.KeyFactory
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.Signature
|
||||
import java.security.interfaces.EdECPrivateKey
|
||||
import java.security.interfaces.EdECPublicKey
|
||||
import java.security.spec.EdECPrivateKeySpec
|
||||
import java.security.spec.NamedParameterSpec
|
||||
import java.util.TreeSet
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.agp.lib)
|
||||
}
|
||||
|
||||
val moduleId: String by rootProject.extra
|
||||
val moduleName: String by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val minKsuVersion: Int by rootProject.extra
|
||||
val minKsudVersion: Int by rootProject.extra
|
||||
val maxKsuVersion: Int by rootProject.extra
|
||||
val minMagiskVersion: Int by rootProject.extra
|
||||
val commitHash: String by rootProject.extra
|
||||
|
||||
android.buildFeatures {
|
||||
androidResources = false
|
||||
buildConfig = false
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
val variantLowered = variant.name.lowercase()
|
||||
val variantCapped = variant.name.capitalizeUS()
|
||||
val buildTypeLowered = variant.buildType?.lowercase()
|
||||
|
||||
val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered")
|
||||
val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-')
|
||||
|
||||
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(
|
||||
":loader:assemble$variantCapped",
|
||||
":zygiskd:buildAndStrip",
|
||||
)
|
||||
into(moduleDir)
|
||||
from("${rootProject.projectDir}/README.md")
|
||||
from("$projectDir/src") {
|
||||
exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh", "mazoku")
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from("$projectDir/src") {
|
||||
include("module.prop")
|
||||
expand(
|
||||
"moduleId" to moduleId,
|
||||
"moduleName" to moduleName,
|
||||
"versionName" to "$verName ($verCode-$commitHash-$variantLowered)",
|
||||
"versionCode" to verCode
|
||||
)
|
||||
}
|
||||
from("$projectDir/src/mazoku")
|
||||
from("$projectDir/src") {
|
||||
include("customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh")
|
||||
val tokens = mapOf(
|
||||
"DEBUG" to if (buildTypeLowered == "debug") "true" else "false",
|
||||
"MIN_KSU_VERSION" to "$minKsuVersion",
|
||||
"MIN_KSUD_VERSION" to "$minKsudVersion",
|
||||
"MAX_KSU_VERSION" to "$maxKsuVersion",
|
||||
"MIN_MAGISK_VERSION" to "$minMagiskVersion",
|
||||
)
|
||||
filter<ReplaceTokens>("tokens" to tokens)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
into("bin") {
|
||||
from(project(":zygiskd").layout.buildDirectory.file("rustJniLibs/android"))
|
||||
include("**/zygiskd")
|
||||
}
|
||||
into("lib") {
|
||||
from(project(":loader").layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/out/lib"))
|
||||
}
|
||||
|
||||
val root = moduleDir.get()
|
||||
|
||||
doLast {
|
||||
if (file("private_key").exists()) {
|
||||
println("=== machikado intergity signing ===")
|
||||
val privateKey = file("private_key").readBytes()
|
||||
val publicKey = file("public_key").readBytes()
|
||||
val namedSpec = NamedParameterSpec("ed25519")
|
||||
val privKeySpec = EdECPrivateKeySpec(namedSpec, privateKey)
|
||||
val kf = KeyFactory.getInstance("ed25519")
|
||||
val privKey = kf.generatePrivate(privKeySpec);
|
||||
val sig = Signature.getInstance("ed25519")
|
||||
fun File.sha(realFile: File? = null) {
|
||||
val path = this.path.replace("\\", "/")
|
||||
sig.update(this.name.toByteArray())
|
||||
sig.update(0) // null-terminated string
|
||||
val real = realFile ?: this
|
||||
val buffer = ByteBuffer.allocate(8)
|
||||
.order(ByteOrder.LITTLE_ENDIAN)
|
||||
.putLong(real.length())
|
||||
.array()
|
||||
sig.update(buffer)
|
||||
real.forEachBlock { bytes, size ->
|
||||
sig.update(bytes, 0, size)
|
||||
}
|
||||
}
|
||||
|
||||
fun getSign(name: String, abi32: String, abi64: String) {
|
||||
val set = TreeSet<Pair<File, File?>> { o1, o2 ->
|
||||
o1.first.path.replace("\\", "/")
|
||||
.compareTo(o2.first.path.replace("\\", "/"))
|
||||
}
|
||||
set.add(Pair(root.file("module.prop").asFile, null))
|
||||
set.add(Pair(root.file("sepolicy.rule").asFile, null))
|
||||
set.add(Pair(root.file("post-fs-data.sh").asFile, null))
|
||||
set.add(Pair(root.file("service.sh").asFile, null))
|
||||
set.add(Pair(root.file("mazoku").asFile, null))
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("lib/libzygisk.so").asFile,
|
||||
root.file("lib/$abi32/libzygisk.so").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("lib64/libzygisk.so").asFile,
|
||||
root.file("lib/$abi64/libzygisk.so").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("bin/zygisk-ptrace32").asFile,
|
||||
root.file("lib/$abi32/libzygisk_ptrace.so").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("bin/zygisk-ptrace64").asFile,
|
||||
root.file("lib/$abi64/libzygisk_ptrace.so").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("bin/zygiskd32").asFile,
|
||||
root.file("bin/$abi32/zygiskd").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("bin/zygiskd64").asFile,
|
||||
root.file("bin/$abi64/zygiskd").asFile
|
||||
)
|
||||
)
|
||||
set.add(
|
||||
Pair(
|
||||
root.file("bin/zygisk-ctl").asFile,
|
||||
root.file("zygisk-ctl.sh").asFile
|
||||
)
|
||||
)
|
||||
sig.initSign(privKey)
|
||||
set.forEach { it.first.sha(it.second) }
|
||||
val signFile = root.file(name).asFile
|
||||
signFile.writeBytes(sig.sign())
|
||||
signFile.appendBytes(publicKey)
|
||||
}
|
||||
|
||||
getSign("machikado.arm", "armeabi-v7a", "arm64-v8a")
|
||||
getSign("machikado.x86", "x86", "x86_64")
|
||||
} else {
|
||||
println("no private_key found, this build will not be signed")
|
||||
root.file("machikado.arm").asFile.createNewFile()
|
||||
root.file("machikado.x86").asFile.createNewFile()
|
||||
}
|
||||
|
||||
fileTree(moduleDir).visit {
|
||||
if (isDirectory) return@visit
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
file.forEachBlock(4096) { bytes, size ->
|
||||
md.update(bytes, 0, size)
|
||||
}
|
||||
file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val zipTask = task<Zip>("zip$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(prepareModuleFilesTask)
|
||||
archiveFileName.set(zipFileName)
|
||||
destinationDirectory.set(layout.buildDirectory.file("outputs/release").get().asFile)
|
||||
from(moduleDir)
|
||||
}
|
||||
|
||||
val pushTask = task<Exec>("push$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(zipTask)
|
||||
commandLine("adb", "push", zipTask.outputs.files.singleFile.path, "/data/local/tmp")
|
||||
}
|
||||
|
||||
val installKsuTask = task("installKsu$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(pushTask)
|
||||
doLast {
|
||||
exec {
|
||||
commandLine(
|
||||
"adb", "shell", "echo",
|
||||
"/data/adb/ksud module install /data/local/tmp/$zipFileName",
|
||||
"> /data/local/tmp/install.sh"
|
||||
)
|
||||
}
|
||||
exec { commandLine("adb", "shell", "chmod", "755", "/data/local/tmp/install.sh") }
|
||||
exec { commandLine("adb", "shell", "su", "-c", "/data/local/tmp/install.sh") }
|
||||
}
|
||||
}
|
||||
|
||||
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(pushTask)
|
||||
commandLine("adb", "shell", "su", "-c", "magisk --install-module /data/local/tmp/$zipFileName")
|
||||
}
|
||||
|
||||
task<Exec>("installKsuAndReboot$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(installKsuTask)
|
||||
commandLine("adb", "reboot")
|
||||
}
|
||||
|
||||
task<Exec>("installMagiskAndReboot$variantCapped") {
|
||||
group = "module"
|
||||
dependsOn(installMagiskTask)
|
||||
commandLine("adb", "reboot")
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
#!/sbin/sh
|
||||
|
||||
#################
|
||||
# Initialization
|
||||
#################
|
||||
|
||||
umask 022
|
||||
|
||||
# echo before loading util_functions
|
||||
ui_print() { echo "$1"; }
|
||||
|
||||
require_new_magisk() {
|
||||
ui_print "*******************************"
|
||||
ui_print " Please install Magisk v19.0+! "
|
||||
ui_print "*******************************"
|
||||
exit 1
|
||||
}
|
||||
|
||||
#########################
|
||||
# Load util_functions.sh
|
||||
#########################
|
||||
|
||||
OUTFD=$2
|
||||
ZIPFILE=$3
|
||||
|
||||
mount /data 2>/dev/null
|
||||
|
||||
[ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk
|
||||
. /data/adb/magisk/util_functions.sh
|
||||
[ $MAGISK_VER_CODE -lt 19000 ] && require_new_magisk
|
||||
|
||||
if [ $MAGISK_VER_CODE -ge 20400 ]; then
|
||||
# New Magisk have complete installation logic within util_functions.sh
|
||||
install_module
|
||||
exit 0
|
||||
fi
|
||||
|
||||
#################
|
||||
# Legacy Support
|
||||
#################
|
||||
|
||||
TMPDIR=/dev/tmp
|
||||
PERSISTDIR=/sbin/.magisk/mirror/persist
|
||||
|
||||
is_legacy_script() {
|
||||
unzip -l "$ZIPFILE" install.sh | grep -q install.sh
|
||||
return $?
|
||||
}
|
||||
|
||||
print_modname() {
|
||||
local len
|
||||
len=`echo -n $MODNAME | wc -c`
|
||||
len=$((len + 2))
|
||||
local pounds=`printf "%${len}s" | tr ' ' '*'`
|
||||
ui_print "$pounds"
|
||||
ui_print " $MODNAME "
|
||||
ui_print "$pounds"
|
||||
ui_print "*******************"
|
||||
ui_print " Powered by Magisk "
|
||||
ui_print "*******************"
|
||||
}
|
||||
|
||||
# Override abort as old scripts have some issues
|
||||
abort() {
|
||||
ui_print "$1"
|
||||
$BOOTMODE || recovery_cleanup
|
||||
[ -n $MODPATH ] && rm -rf $MODPATH
|
||||
rm -rf $TMPDIR
|
||||
exit 1
|
||||
}
|
||||
|
||||
rm -rf $TMPDIR 2>/dev/null
|
||||
mkdir -p $TMPDIR
|
||||
|
||||
# Preperation for flashable zips
|
||||
setup_flashable
|
||||
|
||||
# Mount partitions
|
||||
mount_partitions
|
||||
|
||||
# Detect version and architecture
|
||||
api_level_arch_detect
|
||||
|
||||
# Setup busybox and binaries
|
||||
$BOOTMODE && boot_actions || recovery_actions
|
||||
|
||||
##############
|
||||
# Preparation
|
||||
##############
|
||||
|
||||
# Extract prop file
|
||||
unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2
|
||||
[ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!"
|
||||
|
||||
$BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules
|
||||
MODULEROOT=$NVBASE/$MODDIRNAME
|
||||
MODID=`grep_prop id $TMPDIR/module.prop`
|
||||
MODPATH=$MODULEROOT/$MODID
|
||||
MODNAME=`grep_prop name $TMPDIR/module.prop`
|
||||
|
||||
# Create mod paths
|
||||
rm -rf $MODPATH 2>/dev/null
|
||||
mkdir -p $MODPATH
|
||||
|
||||
##########
|
||||
# Install
|
||||
##########
|
||||
|
||||
if is_legacy_script; then
|
||||
unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2
|
||||
|
||||
# Load install script
|
||||
. $TMPDIR/install.sh
|
||||
|
||||
# Callbacks
|
||||
print_modname
|
||||
on_install
|
||||
|
||||
# Custom uninstaller
|
||||
[ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh
|
||||
|
||||
# Skip mount
|
||||
$SKIPMOUNT && touch $MODPATH/skip_mount
|
||||
|
||||
# prop file
|
||||
$PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop
|
||||
|
||||
# Module info
|
||||
cp -af $TMPDIR/module.prop $MODPATH/module.prop
|
||||
|
||||
# post-fs-data scripts
|
||||
$POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh
|
||||
|
||||
# service scripts
|
||||
$LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh
|
||||
|
||||
ui_print "- Setting permissions"
|
||||
set_permissions
|
||||
else
|
||||
print_modname
|
||||
|
||||
unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2
|
||||
|
||||
if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then
|
||||
ui_print "- Extracting module files"
|
||||
unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2
|
||||
|
||||
# Default permissions
|
||||
set_perm_recursive $MODPATH 0 0 0755 0644
|
||||
fi
|
||||
|
||||
# Load customization script
|
||||
[ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh
|
||||
fi
|
||||
|
||||
# Handle replace folders
|
||||
for TARGET in $REPLACE; do
|
||||
ui_print "- Replace target: $TARGET"
|
||||
mktouch $MODPATH$TARGET/.replace
|
||||
done
|
||||
|
||||
if $BOOTMODE; then
|
||||
# Update info for Magisk Manager
|
||||
mktouch $NVBASE/modules/$MODID/update
|
||||
cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop
|
||||
fi
|
||||
|
||||
# Copy over custom sepolicy rules
|
||||
if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then
|
||||
ui_print "- Installing custom sepolicy patch"
|
||||
PERSISTMOD=$PERSISTDIR/magisk/$MODID
|
||||
mkdir -p $PERSISTMOD
|
||||
cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule
|
||||
fi
|
||||
|
||||
# Remove stuffs that don't belong to modules
|
||||
rm -rf \
|
||||
$MODPATH/system/placeholder $MODPATH/customize.sh \
|
||||
$MODPATH/README.md $MODPATH/.git* 2>/dev/null
|
||||
|
||||
#############
|
||||
# Finalizing
|
||||
#############
|
||||
|
||||
cd /
|
||||
$BOOTMODE || recovery_cleanup
|
||||
rm -rf $TMPDIR
|
||||
|
||||
ui_print "- Done"
|
||||
exit 0
|
||||
@@ -1 +0,0 @@
|
||||
#MAGISK
|
||||
@@ -1,152 +0,0 @@
|
||||
# shellcheck disable=SC2034
|
||||
SKIPUNZIP=1
|
||||
|
||||
DEBUG=@DEBUG@
|
||||
MIN_KSU_VERSION=@MIN_KSU_VERSION@
|
||||
MIN_KSUD_VERSION=@MIN_KSUD_VERSION@
|
||||
MAX_KSU_VERSION=@MAX_KSU_VERSION@
|
||||
MIN_MAGISK_VERSION=@MIN_MAGISK_VERSION@
|
||||
|
||||
if [ "$BOOTMODE" ] && [ "$KSU" ]; then
|
||||
ui_print "- Installing from KernelSU app"
|
||||
ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)"
|
||||
if ! [ "$KSU_KERNEL_VER_CODE" ] || [ "$KSU_KERNEL_VER_CODE" -lt "$MIN_KSU_VERSION" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! KernelSU version is too old!"
|
||||
ui_print "! Please update KernelSU to latest version"
|
||||
abort "*********************************************************"
|
||||
elif [ "$KSU_KERNEL_VER_CODE" -ge "$MAX_KSU_VERSION" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! KernelSU version abnormal!"
|
||||
ui_print "! Please integrate KernelSU into your kernel"
|
||||
ui_print " as submodule instead of copying the source code"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
if ! [ "$KSU_VER_CODE" ] || [ "$KSU_VER_CODE" -lt "$MIN_KSUD_VERSION" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! ksud version is too old!"
|
||||
ui_print "! Please update KernelSU Manager to latest version"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
if [ "$(which magisk)" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Multiple root implementation is NOT supported!"
|
||||
ui_print "! Please uninstall Magisk before installing Zygisk Next"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then
|
||||
ui_print "- Installing from Magisk app"
|
||||
if [ "$MAGISK_VER_CODE" -lt "$MIN_MAGISK_VERSION" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Magisk version is too old!"
|
||||
ui_print "! Please update Magisk to latest version"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
else
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Install from recovery is not supported"
|
||||
ui_print "! Please install from KernelSU or Magisk app"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
|
||||
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
||||
ui_print "- Installing Zygisk Next $VERSION"
|
||||
|
||||
# check android
|
||||
if [ "$API" -lt 26 ]; then
|
||||
ui_print "! Unsupported sdk: $API"
|
||||
abort "! Minimal supported sdk is 26 (Android 8.0)"
|
||||
else
|
||||
ui_print "- Device sdk: $API"
|
||||
fi
|
||||
|
||||
# check architecture
|
||||
if [ "$ARCH" != "arm" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "x86" ] && [ "$ARCH" != "x64" ]; then
|
||||
abort "! Unsupported platform: $ARCH"
|
||||
else
|
||||
ui_print "- Device platform: $ARCH"
|
||||
fi
|
||||
|
||||
ui_print "- Extracting verify.sh"
|
||||
unzip -o "$ZIPFILE" 'verify.sh' -d "$TMPDIR" >&2
|
||||
if [ ! -f "$TMPDIR/verify.sh" ]; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Unable to extract verify.sh!"
|
||||
ui_print "! This zip may be corrupted, please try downloading again"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
. "$TMPDIR/verify.sh"
|
||||
extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
|
||||
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
|
||||
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
|
||||
|
||||
if [ "$KSU" ]; then
|
||||
ui_print "- Checking SELinux patches"
|
||||
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! Unable to apply SELinux patches!"
|
||||
ui_print "! Your kernel may not support SELinux patch fully"
|
||||
abort "*********************************************************"
|
||||
fi
|
||||
fi
|
||||
|
||||
ui_print "- Extracting module files"
|
||||
extract "$ZIPFILE" 'module.prop' "$MODPATH"
|
||||
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
|
||||
extract "$ZIPFILE" 'service.sh' "$MODPATH"
|
||||
extract "$ZIPFILE" 'zygisk-ctl.sh' "$MODPATH"
|
||||
extract "$ZIPFILE" 'mazoku' "$MODPATH"
|
||||
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
|
||||
|
||||
mkdir "$MODPATH/bin"
|
||||
mkdir "$MODPATH/lib"
|
||||
mkdir "$MODPATH/lib64"
|
||||
mv "$MODPATH/zygisk-ctl.sh" "$MODPATH/bin/zygisk-ctl"
|
||||
|
||||
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
ui_print "- Extracting x86 libraries"
|
||||
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||
extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true
|
||||
extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||
|
||||
ui_print "- Extracting x64 libraries"
|
||||
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
||||
extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true
|
||||
extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||
|
||||
extract "$ZIPFILE" 'machikado.x86' "$MODPATH" true
|
||||
mv "$MODPATH/machikado.x86" "$MODPATH/machikado"
|
||||
else
|
||||
ui_print "- Extracting arm libraries"
|
||||
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||
|
||||
ui_print "- Extracting arm64 libraries"
|
||||
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||
|
||||
extract "$ZIPFILE" 'machikado.arm' "$MODPATH" true
|
||||
mv "$MODPATH/machikado.arm" "$MODPATH/machikado"
|
||||
fi
|
||||
|
||||
ui_print "- Setting permissions"
|
||||
set_perm_recursive "$MODPATH/bin" 0 0 0755 0755
|
||||
set_perm_recursive "$MODPATH/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0
|
||||
set_perm_recursive "$MODPATH/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0
|
||||
|
||||
# If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control
|
||||
HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable)
|
||||
if [ "$HUAWEI_MAPLE_ENABLED" == "1" ]; then
|
||||
ui_print "- Add ro.maple.enable=0"
|
||||
echo "ro.maple.enable=0" >>"$MODPATH/system.prop"
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
c—„ˆ]œ‘ „[{Ú‚BÒuÞ5=ÙrEUÕZ„Ê¿èã<¦5ß_oñãMéL•l•¢ÛQ#ÿøœC¾}ù e�äfjÙ‚/©7³‡Ž(â´g<bþRgÛm±d
|
||||
@@ -1,7 +0,0 @@
|
||||
id=${moduleId}
|
||||
name=${moduleName}
|
||||
version=${versionName}
|
||||
versionCode=${versionCode}
|
||||
author=Nullptr, 5ec1cff
|
||||
description=Standalone implementation of Zygisk.
|
||||
updateJson=https://api.nullptr.icu/android/zygisk-next/static/update.json
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
MODDIR=${0%/*}
|
||||
if [ "$ZYGISK_ENABLED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$MODDIR"
|
||||
|
||||
if [ "$(which magisk)" ]; then
|
||||
for file in ../*; do
|
||||
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
|
||||
if [ -f "$file/post-fs-data.sh" ]; then
|
||||
cd "$file"
|
||||
log -p i -t "zygisk-sh" "Manually trigger post-fs-data.sh for $file"
|
||||
sh "$(realpath ./post-fs-data.sh)"
|
||||
cd "$MODDIR"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
create_sys_perm() {
|
||||
mkdir -p $1
|
||||
chmod 555 $1
|
||||
chcon u:object_r:system_file:s0 $1
|
||||
}
|
||||
|
||||
export TMP_PATH=/sbin
|
||||
[ -d /sbin ] || export TMP_PATH=/debug_ramdisk
|
||||
|
||||
create_sys_perm $TMP_PATH
|
||||
|
||||
if [ -f $MODDIR/lib64/libzygisk.so ];then
|
||||
create_sys_perm $TMP_PATH/lib64
|
||||
cp $MODDIR/lib64/libzygisk.so $TMP_PATH/lib64/libzygisk.so
|
||||
chcon u:object_r:system_file:s0 $TMP_PATH/lib64/libzygisk.so
|
||||
fi
|
||||
|
||||
if [ -f $MODDIR/lib/libzygisk.so ];then
|
||||
create_sys_perm $TMP_PATH/lib
|
||||
cp $MODDIR/lib/libzygisk.so $TMP_PATH/lib/libzygisk.so
|
||||
chcon u:object_r:system_file:s0 $TMP_PATH/lib/libzygisk.so
|
||||
fi
|
||||
|
||||
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
|
||||
./bin/zygisk-ptrace64 monitor &
|
||||
@@ -1,20 +0,0 @@
|
||||
allow * tmpfs * *
|
||||
allow zygote appdomain_tmpfs dir *
|
||||
allow zygote appdomain_tmpfs file *
|
||||
|
||||
type magisk_file file_type
|
||||
typeattribute magisk_file mlstrustedobject
|
||||
allow * magisk_file file *
|
||||
allow * magisk_file dir *
|
||||
allow * magisk_file fifo_file *
|
||||
allow * magisk_file chr_file *
|
||||
allow * magisk_file lnk_file *
|
||||
allow * magisk_file sock_file *
|
||||
|
||||
allow system_server system_server process execmem
|
||||
allow zygote adb_data_file dir search
|
||||
allow zygote mnt_vendor_file dir search
|
||||
allow zygote system_file dir mounton
|
||||
allow zygote labeledfs filesystem mount
|
||||
allow zygote fs_type filesystem unmount
|
||||
allow zygote zygote process execmem
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/system/bin/sh
|
||||
|
||||
DEBUG=@DEBUG@
|
||||
|
||||
MODDIR=${0%/*}
|
||||
if [ "$ZYGISK_ENABLED" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cd "$MODDIR"
|
||||
|
||||
if [ "$(which magisk)" ]; then
|
||||
for file in ../*; do
|
||||
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
|
||||
if [ -f "$file/service.sh" ]; then
|
||||
cd "$file"
|
||||
log -p i -t "zygisk-sh" "Manually trigger service.sh for $file"
|
||||
sh "$(realpath ./service.sh)"
|
||||
cd "$MODDIR"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
||||
@@ -1,51 +0,0 @@
|
||||
TMPDIR_FOR_VERIFY="$TMPDIR/.vunzip"
|
||||
mkdir "$TMPDIR_FOR_VERIFY"
|
||||
|
||||
abort_verify() {
|
||||
ui_print "*********************************************************"
|
||||
ui_print "! $1"
|
||||
ui_print "! This zip may be corrupted, please try downloading again"
|
||||
abort "*********************************************************"
|
||||
}
|
||||
|
||||
# extract <zip> <file> <target dir> <junk paths>
|
||||
extract() {
|
||||
zip=$1
|
||||
file=$2
|
||||
dir=$3
|
||||
junk_paths=$4
|
||||
[ -z "$junk_paths" ] && junk_paths=false
|
||||
opts="-o"
|
||||
[ $junk_paths = true ] && opts="-oj"
|
||||
|
||||
file_path=""
|
||||
hash_path=""
|
||||
if [ $junk_paths = true ]; then
|
||||
file_path="$dir/$(basename "$file")"
|
||||
hash_path="$TMPDIR_FOR_VERIFY/$(basename "$file").sha256"
|
||||
else
|
||||
file_path="$dir/$file"
|
||||
hash_path="$TMPDIR_FOR_VERIFY/$file.sha256"
|
||||
fi
|
||||
|
||||
unzip $opts "$zip" "$file" -d "$dir" >&2
|
||||
[ -f "$file_path" ] || abort_verify "$file not exists"
|
||||
|
||||
unzip $opts "$zip" "$file.sha256" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$hash_path" ] || abort_verify "$file.sha256 not exists"
|
||||
|
||||
(echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file"
|
||||
ui_print "- Verified $file" >&1
|
||||
}
|
||||
|
||||
file="META-INF/com/google/android/update-binary"
|
||||
file_path="$TMPDIR_FOR_VERIFY/$file"
|
||||
hash_path="$file_path.sha256"
|
||||
unzip -o "$ZIPFILE" "META-INF/com/google/android/*" -d "$TMPDIR_FOR_VERIFY" >&2
|
||||
[ -f "$file_path" ] || abort_verify "$file not exists"
|
||||
if [ -f "$hash_path" ]; then
|
||||
(echo "$(cat "$hash_path") $file_path" | sha256sum -c -s -) || abort_verify "Failed to verify $file"
|
||||
ui_print "- Verified $file" >&1
|
||||
else
|
||||
ui_print "- Download from Magisk app"
|
||||
fi
|
||||
@@ -1,6 +0,0 @@
|
||||
MODDIR=${0%/*}/..
|
||||
|
||||
export TMP_PATH=/sbin
|
||||
[ -d /sbin ] || export TMP_PATH=/debug_ramdisk
|
||||
|
||||
exec $MODDIR/bin/zygisk-ptrace64 ctl $*
|
||||
@@ -1,24 +0,0 @@
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "ZygiskNext"
|
||||
include(
|
||||
":loader",
|
||||
":module",
|
||||
":zygiskd",
|
||||
)
|
||||
@@ -1,3 +0,0 @@
|
||||
[build]
|
||||
target-dir = "build/intermediates/rust"
|
||||
target = "aarch64-linux-android"
|
||||
@@ -1,35 +0,0 @@
|
||||
[package]
|
||||
name = "zygiskd"
|
||||
authors = ["Nullptr"]
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.69"
|
||||
|
||||
[dependencies]
|
||||
android_logger = "0.13"
|
||||
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||
bitflags = { version = "2.3" }
|
||||
const_format = "0.2"
|
||||
futures = "0.3"
|
||||
konst = "0.3"
|
||||
lazy_static = "1.4"
|
||||
libc = "0.2"
|
||||
log = "0.4"
|
||||
memfd = "0.6"
|
||||
num_enum = "0.5"
|
||||
passfd = "0.1"
|
||||
proc-maps = "0.3"
|
||||
|
||||
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] }
|
||||
tokio = { version = "1.28", features = ["full"] }
|
||||
|
||||
[profile.dev]
|
||||
strip = false
|
||||
panic = "abort"
|
||||
|
||||
[profile.release]
|
||||
strip = false
|
||||
debug = true
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
lto = true
|
||||
@@ -1,67 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.agp.lib)
|
||||
alias(libs.plugins.rust.android)
|
||||
}
|
||||
|
||||
val minKsuVersion: Int by rootProject.extra
|
||||
val maxKsuVersion: Int by rootProject.extra
|
||||
val minMagiskVersion: Int by rootProject.extra
|
||||
val verCode: Int by rootProject.extra
|
||||
val verName: String by rootProject.extra
|
||||
val commitHash: String by rootProject.extra
|
||||
|
||||
android.buildFeatures {
|
||||
androidResources = false
|
||||
buildConfig = false
|
||||
}
|
||||
|
||||
cargo {
|
||||
module = "."
|
||||
libname = "zygiskd"
|
||||
targetIncludes = arrayOf("zygiskd")
|
||||
targets = listOf("arm64", "arm", "x86", "x86_64")
|
||||
targetDirectory = "build/intermediates/rust"
|
||||
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
|
||||
profile = if (isDebug) "debug" else "release"
|
||||
exec = { spec, _ ->
|
||||
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
|
||||
spec.environment("MIN_KSU_VERSION", minKsuVersion)
|
||||
spec.environment("MAX_KSU_VERSION", maxKsuVersion)
|
||||
spec.environment("MIN_MAGISK_VERSION", minMagiskVersion)
|
||||
spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile")
|
||||
}
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
task<Task>("buildAndStrip") {
|
||||
dependsOn(":zygiskd:cargoBuild")
|
||||
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
|
||||
doLast {
|
||||
val dir = File(buildDir, "rustJniLibs/android")
|
||||
val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first()
|
||||
val binDir = File(prebuilt, "bin")
|
||||
val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}")
|
||||
symbolDir.mkdirs()
|
||||
val suffix = if (prebuilt.name.contains("windows")) ".exe" else ""
|
||||
val strip = File(binDir, "llvm-strip$suffix")
|
||||
val objcopy = File(binDir, "llvm-objcopy$suffix")
|
||||
dir.listFiles()!!.forEach {
|
||||
if (!it.isDirectory) return@forEach
|
||||
val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug")
|
||||
symbolPath.parentFile.mkdirs()
|
||||
exec {
|
||||
workingDir = it
|
||||
commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath)
|
||||
}
|
||||
exec {
|
||||
workingDir = it
|
||||
commandLine(strip, "--strip-all", "zygiskd")
|
||||
}
|
||||
exec {
|
||||
workingDir = it
|
||||
commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
use crate::dl;
|
||||
use crate::utils::{check_unix_socket, UnixStreamExt};
|
||||
use anyhow::Result;
|
||||
use passfd::FdPassingExt;
|
||||
use rustix::fs::fstat;
|
||||
use std::ffi::c_void;
|
||||
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::thread;
|
||||
|
||||
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
|
||||
|
||||
pub fn entry(fd: i32) {
|
||||
log::info!("companion entry fd={}", fd);
|
||||
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
||||
let name = stream.read_string().expect("read name");
|
||||
let library = stream.recv_fd().expect("receive library fd");
|
||||
let entry = load_module(library).expect("load module");
|
||||
unsafe { libc::close(library) };
|
||||
|
||||
let entry = match entry {
|
||||
Some(entry) => {
|
||||
log::debug!("Companion process created for `{name}`");
|
||||
stream.write_u8(1).expect("reply 1");
|
||||
entry
|
||||
}
|
||||
None => {
|
||||
log::debug!("No companion entry for `{name}`");
|
||||
stream.write_u8(0).expect("reply 0");
|
||||
std::process::exit(0);
|
||||
}
|
||||
};
|
||||
|
||||
loop {
|
||||
if !check_unix_socket(&stream, true) {
|
||||
log::info!("Something bad happened in zygiskd, terminate companion");
|
||||
std::process::exit(0);
|
||||
}
|
||||
let fd = stream.recv_fd().expect("recv fd");
|
||||
log::trace!("New companion request from module `{name}` fd=`{fd}`");
|
||||
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
||||
stream.write_u8(1).expect("reply success");
|
||||
thread::spawn(move || {
|
||||
let st0 = fstat(&stream).expect("failed to stat stream");
|
||||
unsafe {
|
||||
entry(stream.as_raw_fd());
|
||||
}
|
||||
// Only close client if it is the same file so we don't
|
||||
// accidentally close a re-used file descriptor.
|
||||
// This check is required because the module companion
|
||||
// handler could've closed the file descriptor already.
|
||||
if let Ok(st1) = fstat(&stream) {
|
||||
if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino {
|
||||
std::mem::forget(stream);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn load_module(fd: RawFd) -> Result<Option<ZygiskCompanionEntryFn>> {
|
||||
unsafe {
|
||||
let path = format!("/proc/self/fd/{fd}");
|
||||
let handle = dl::dlopen(&path, libc::RTLD_NOW)?;
|
||||
let symbol = std::ffi::CString::new("zygisk_companion_entry")?;
|
||||
let entry = libc::dlsym(handle, symbol.as_ptr());
|
||||
if entry.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry);
|
||||
Ok(Some(fnptr))
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
use crate::lp_select;
|
||||
use bitflags::bitflags;
|
||||
use konst::primitive::parse_i32;
|
||||
use konst::unwrap_ctx;
|
||||
use log::LevelFilter;
|
||||
use num_enum::TryFromPrimitive;
|
||||
|
||||
pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION")));
|
||||
pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION")));
|
||||
pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION")));
|
||||
pub const ZKSU_VERSION: &str = env!("ZKSU_VERSION");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
|
||||
|
||||
pub const PATH_MODULES_DIR: &str = "..";
|
||||
pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4);
|
||||
pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6);
|
||||
pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8);
|
||||
pub const SYSTEM_SERVER_STARTED: i32 = 10;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||
#[repr(u8)]
|
||||
pub enum DaemonSocketAction {
|
||||
PingHeartbeat,
|
||||
RequestLogcatFd,
|
||||
GetProcessFlags,
|
||||
ReadModules,
|
||||
RequestCompanionSocket,
|
||||
GetModuleDir,
|
||||
ZygoteRestart,
|
||||
SystemServerStarted,
|
||||
}
|
||||
|
||||
// Zygisk process flags
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ProcessFlags: u32 {
|
||||
const PROCESS_GRANTED_ROOT = 1 << 0;
|
||||
const PROCESS_ON_DENYLIST = 1 << 1;
|
||||
const PROCESS_IS_MANAGER = 1 << 28;
|
||||
const PROCESS_ROOT_IS_KSU = 1 << 29;
|
||||
const PROCESS_ROOT_IS_MAGISK = 1 << 30;
|
||||
const PROCESS_IS_SYSUI = 1 << 31;
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
use anyhow::{bail, Result};
|
||||
use std::ffi::{c_char, c_void};
|
||||
|
||||
pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
|
||||
pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct AndroidNamespace {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AndroidDlextinfo {
|
||||
pub flags: u64,
|
||||
pub reserved_addr: *mut c_void,
|
||||
pub reserved_size: libc::size_t,
|
||||
pub relro_fd: libc::c_int,
|
||||
pub library_fd: libc::c_int,
|
||||
pub library_fd_offset: libc::off64_t,
|
||||
pub library_namespace: *mut AndroidNamespace,
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn android_dlopen_ext(
|
||||
filename: *const c_char,
|
||||
flags: libc::c_int,
|
||||
extinfo: *const AndroidDlextinfo,
|
||||
) -> *mut c_void;
|
||||
}
|
||||
|
||||
type AndroidCreateNamespaceFn = unsafe extern "C" fn(
|
||||
*const c_char, // name
|
||||
*const c_char, // ld_library_path
|
||||
*const c_char, // default_library_path
|
||||
u64, // type
|
||||
*const c_char, // permitted_when_isolated_path
|
||||
*mut AndroidNamespace, // parent
|
||||
*const c_void, // caller_addr
|
||||
) -> *mut AndroidNamespace;
|
||||
|
||||
pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
|
||||
let filename = std::ffi::CString::new(path)?;
|
||||
let filename = filename.as_ptr() as *mut _;
|
||||
let dir = libc::dirname(filename);
|
||||
let mut info = AndroidDlextinfo {
|
||||
flags: 0,
|
||||
reserved_addr: std::ptr::null_mut(),
|
||||
reserved_size: 0,
|
||||
relro_fd: 0,
|
||||
library_fd: 0,
|
||||
library_fd_offset: 0,
|
||||
library_namespace: std::ptr::null_mut(),
|
||||
};
|
||||
|
||||
let android_create_namespace_fn = libc::dlsym(
|
||||
libc::RTLD_DEFAULT,
|
||||
std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(),
|
||||
);
|
||||
let android_create_namespace_fn: AndroidCreateNamespaceFn =
|
||||
std::mem::transmute(android_create_namespace_fn);
|
||||
let ns = android_create_namespace_fn(
|
||||
filename,
|
||||
dir,
|
||||
std::ptr::null(),
|
||||
ANDROID_NAMESPACE_TYPE_SHARED,
|
||||
std::ptr::null(),
|
||||
std::ptr::null_mut(),
|
||||
&dlopen as *const _ as *const c_void,
|
||||
);
|
||||
if ns != std::ptr::null_mut() {
|
||||
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
|
||||
info.library_namespace = ns;
|
||||
log::debug!("Open {} with namespace {:p}", path, ns);
|
||||
} else {
|
||||
log::debug!("Cannot create namespace for {}", path);
|
||||
};
|
||||
|
||||
let result = android_dlopen_ext(filename, flags, &info);
|
||||
if result.is_null() {
|
||||
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
|
||||
bail!(e);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
mod companion;
|
||||
mod constants;
|
||||
mod dl;
|
||||
mod root_impl;
|
||||
mod utils;
|
||||
mod zygiskd;
|
||||
|
||||
use crate::constants::ZKSU_VERSION;
|
||||
|
||||
fn init_android_logger(tag: &str) {
|
||||
android_logger::init_once(
|
||||
android_logger::Config::default()
|
||||
.with_max_level(constants::MAX_LOG_LEVEL)
|
||||
.with_tag(tag),
|
||||
);
|
||||
}
|
||||
|
||||
fn start() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() == 3 && args[1] == "companion" {
|
||||
let fd: i32 = args[2].parse().unwrap();
|
||||
companion::entry(fd);
|
||||
return;
|
||||
} else if args.len() == 2 && args[1] == "version" {
|
||||
println!("Zygisk Next daemon {}", ZKSU_VERSION);
|
||||
return;
|
||||
} else if args.len() == 2 && args[1] == "root" {
|
||||
root_impl::setup();
|
||||
println!("root impl: {:?}", root_impl::get_impl());
|
||||
return;
|
||||
}
|
||||
|
||||
utils::switch_mount_namespace(1).expect("switch mnt ns");
|
||||
root_impl::setup();
|
||||
log::info!("current root impl: {:?}", root_impl::get_impl());
|
||||
zygiskd::main().expect("zygiskd main");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let process = std::env::args().next().unwrap();
|
||||
let nice_name = process.split('/').last().unwrap();
|
||||
init_android_logger(nice_name);
|
||||
|
||||
start();
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
use crate::constants::{MAX_KSU_VERSION, MIN_KSU_VERSION};
|
||||
|
||||
const KERNEL_SU_OPTION: u32 = 0xdeadbeefu32;
|
||||
|
||||
const CMD_GET_VERSION: usize = 2;
|
||||
const CMD_UID_GRANTED_ROOT: usize = 12;
|
||||
const CMD_UID_SHOULD_UMOUNT: usize = 13;
|
||||
|
||||
pub enum Version {
|
||||
Supported,
|
||||
TooOld,
|
||||
Abnormal,
|
||||
}
|
||||
|
||||
pub fn get_kernel_su() -> Option<Version> {
|
||||
let mut version = 0;
|
||||
unsafe {
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
CMD_GET_VERSION,
|
||||
&mut version as *mut i32,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
};
|
||||
const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1;
|
||||
match version {
|
||||
0 => None,
|
||||
MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported),
|
||||
1..=MAX_OLD_VERSION => Some(Version::TooOld),
|
||||
_ => Some(Version::Abnormal),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uid_granted_root(uid: i32) -> bool {
|
||||
let mut result: u32 = 0;
|
||||
let mut granted = false;
|
||||
unsafe {
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
CMD_UID_GRANTED_ROOT,
|
||||
uid,
|
||||
&mut granted as *mut bool,
|
||||
&mut result as *mut u32,
|
||||
)
|
||||
};
|
||||
if result != KERNEL_SU_OPTION {
|
||||
log::warn!("uid_granted_root failed");
|
||||
}
|
||||
granted
|
||||
}
|
||||
|
||||
pub fn uid_should_umount(uid: i32) -> bool {
|
||||
let mut result: u32 = 0;
|
||||
let mut umount = false;
|
||||
unsafe {
|
||||
libc::prctl(
|
||||
KERNEL_SU_OPTION as i32,
|
||||
CMD_UID_SHOULD_UMOUNT,
|
||||
uid,
|
||||
&mut umount as *mut bool,
|
||||
&mut result as *mut u32,
|
||||
)
|
||||
};
|
||||
if result != KERNEL_SU_OPTION {
|
||||
log::warn!("uid_granted_root failed");
|
||||
}
|
||||
umount
|
||||
}
|
||||
|
||||
// TODO: signature
|
||||
pub fn uid_is_manager(uid: i32) -> bool {
|
||||
if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.weishu.kernelsu") {
|
||||
return s.st_uid == uid as u32;
|
||||
}
|
||||
false
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
use std::fs;
|
||||
use std::os::android::fs::MetadataExt;
|
||||
use crate::constants::MIN_MAGISK_VERSION;
|
||||
use std::process::{Command, Stdio};
|
||||
use log::info;
|
||||
use crate::utils::LateInit;
|
||||
|
||||
const MAGISK_OFFICIAL: &str = "com.topjohnwu.magisk";
|
||||
const MAGISK_THIRD_PARTIES: &[(&str, &str)] = &[
|
||||
("alpha", "io.github.vvb2060.magisk"),
|
||||
("kitsune", "io.github.huskydg.magisk"),
|
||||
];
|
||||
|
||||
pub enum Version {
|
||||
Supported,
|
||||
TooOld,
|
||||
}
|
||||
|
||||
static VARIANT: LateInit<&str> = LateInit::new();
|
||||
|
||||
pub fn get_magisk() -> Option<Version> {
|
||||
if !VARIANT.initiated() {
|
||||
Command::new("magisk")
|
||||
.arg("-v")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|version| {
|
||||
let third_party = MAGISK_THIRD_PARTIES.iter().find_map(|v| {
|
||||
version.contains(v.0).then_some(v.1)
|
||||
});
|
||||
VARIANT.init(third_party.unwrap_or(MAGISK_OFFICIAL));
|
||||
info!("Magisk variant: {}", *VARIANT);
|
||||
});
|
||||
}
|
||||
Command::new("magisk")
|
||||
.arg("-V")
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.and_then(|output| output.trim().parse::<i32>().ok())
|
||||
.map(|version| {
|
||||
if version >= MIN_MAGISK_VERSION {
|
||||
Version::Supported
|
||||
} else {
|
||||
Version::TooOld
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn uid_granted_root(uid: i32) -> bool {
|
||||
Command::new("magisk")
|
||||
.arg("--sqlite")
|
||||
.arg(format!(
|
||||
"select 1 from policies where uid={uid} and policy=2 limit 1"
|
||||
))
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|output| output.is_empty())
|
||||
== Some(false)
|
||||
}
|
||||
|
||||
pub fn uid_should_umount(uid: i32) -> bool {
|
||||
let output = Command::new("pm")
|
||||
.args(["list", "packages", "--uid", &uid.to_string()])
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok());
|
||||
let line = match output {
|
||||
Some(line) => line,
|
||||
None => return false,
|
||||
};
|
||||
let pkg = line
|
||||
.strip_prefix("package:")
|
||||
.and_then(|line| line.split(' ').next());
|
||||
let pkg = match pkg {
|
||||
Some(pkg) => pkg,
|
||||
None => return false,
|
||||
};
|
||||
Command::new("magisk")
|
||||
.arg("--sqlite")
|
||||
.arg(format!(
|
||||
"select 1 from denylist where package_name=\"{pkg}\" limit 1"
|
||||
))
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|output| output.is_empty())
|
||||
== Some(false)
|
||||
}
|
||||
|
||||
// TODO: signature
|
||||
pub fn uid_is_manager(uid: i32) -> bool {
|
||||
let output = Command::new("magisk")
|
||||
.arg("--sqlite")
|
||||
.arg(format!("select value from strings where key=\"requester\" limit 1"))
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.ok()
|
||||
.and_then(|child| child.wait_with_output().ok())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|output| output.trim().to_string());
|
||||
if let Some(output) = output {
|
||||
if let Some(manager) = output.strip_prefix("value=") {
|
||||
return fs::metadata(format!("/data/user_de/0/{}", manager))
|
||||
.map(|s| s.st_uid() == uid as u32)
|
||||
.unwrap_or(false);
|
||||
}
|
||||
}
|
||||
fs::metadata(format!("/data/user_de/0/{}", *VARIANT))
|
||||
.map(|s| s.st_uid() == uid as u32)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
mod kernelsu;
|
||||
mod magisk;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RootImpl {
|
||||
None,
|
||||
TooOld,
|
||||
Abnormal,
|
||||
Multiple,
|
||||
KernelSU,
|
||||
Magisk,
|
||||
}
|
||||
|
||||
static mut ROOT_IMPL: RootImpl = RootImpl::None;
|
||||
|
||||
pub fn setup() {
|
||||
let ksu_version = kernelsu::get_kernel_su();
|
||||
let magisk_version = magisk::get_magisk();
|
||||
|
||||
let impl_ = match (ksu_version, magisk_version) {
|
||||
(None, None) => RootImpl::None,
|
||||
(Some(_), Some(_)) => RootImpl::Multiple,
|
||||
(Some(ksu_version), None) => match ksu_version {
|
||||
kernelsu::Version::Supported => RootImpl::KernelSU,
|
||||
kernelsu::Version::TooOld => RootImpl::TooOld,
|
||||
kernelsu::Version::Abnormal => RootImpl::Abnormal,
|
||||
},
|
||||
(None, Some(magisk_version)) => match magisk_version {
|
||||
magisk::Version::Supported => RootImpl::Magisk,
|
||||
magisk::Version::TooOld => RootImpl::TooOld,
|
||||
},
|
||||
};
|
||||
unsafe {
|
||||
ROOT_IMPL = impl_;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_impl() -> &'static RootImpl {
|
||||
unsafe { &ROOT_IMPL }
|
||||
}
|
||||
|
||||
pub fn uid_granted_root(uid: i32) -> bool {
|
||||
match get_impl() {
|
||||
RootImpl::KernelSU => kernelsu::uid_granted_root(uid),
|
||||
RootImpl::Magisk => magisk::uid_granted_root(uid),
|
||||
_ => panic!("uid_granted_root: unknown root impl {:?}", get_impl()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uid_should_umount(uid: i32) -> bool {
|
||||
match get_impl() {
|
||||
RootImpl::KernelSU => kernelsu::uid_should_umount(uid),
|
||||
RootImpl::Magisk => magisk::uid_should_umount(uid),
|
||||
_ => panic!("uid_should_umount: unknown root impl {:?}", get_impl()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uid_is_manager(uid: i32) -> bool {
|
||||
match get_impl() {
|
||||
RootImpl::KernelSU => kernelsu::uid_is_manager(uid),
|
||||
RootImpl::Magisk => magisk::uid_is_manager(uid),
|
||||
_ => panic!("uid_is_manager: unknown root impl {:?}", get_impl()),
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
use anyhow::Result;
|
||||
use rustix::net::{
|
||||
bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix,
|
||||
SocketType,
|
||||
};
|
||||
use rustix::path::Arg;
|
||||
use rustix::thread::gettid;
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::os::fd::{AsFd, AsRawFd};
|
||||
use std::os::unix::net::{UnixListener};
|
||||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
use std::{
|
||||
fs,
|
||||
io::{Read, Write},
|
||||
os::unix::net::UnixStream,
|
||||
};
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
#[macro_export]
|
||||
macro_rules! lp_select {
|
||||
($lp32:expr, $lp64:expr) => {
|
||||
$lp64
|
||||
};
|
||||
}
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
#[macro_export]
|
||||
macro_rules! lp_select {
|
||||
($lp32:expr, $lp64:expr) => {
|
||||
$lp32
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[macro_export]
|
||||
macro_rules! debug_select {
|
||||
($debug:expr, $release:expr) => {
|
||||
$debug
|
||||
};
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
#[macro_export]
|
||||
macro_rules! debug_select {
|
||||
($debug:expr, $release:expr) => {
|
||||
$release
|
||||
};
|
||||
}
|
||||
|
||||
pub struct LateInit<T> {
|
||||
cell: OnceLock<T>,
|
||||
}
|
||||
|
||||
impl<T> LateInit<T> {
|
||||
pub const fn new() -> Self {
|
||||
LateInit {
|
||||
cell: OnceLock::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(&self, value: T) {
|
||||
assert!(self.cell.set(value).is_ok())
|
||||
}
|
||||
|
||||
pub fn initiated(&self) -> bool {
|
||||
self.cell.get().is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for LateInit<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &T {
|
||||
self.cell.get().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_socket_create_context(context: &str) -> Result<()> {
|
||||
let path = "/proc/thread-self/attr/sockcreate";
|
||||
match fs::write(path, context) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => {
|
||||
let path = format!(
|
||||
"/proc/self/task/{}/attr/sockcreate",
|
||||
gettid().as_raw_nonzero()
|
||||
);
|
||||
fs::write(path, context)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_current_attr() -> Result<String> {
|
||||
let s = fs::read("/proc/self/attr/current")?;
|
||||
Ok(s.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
pub fn chcon(path: &str, context: &str) -> Result<()> {
|
||||
Command::new("chcon").arg(context).arg(path).status()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> {
|
||||
let tag = CString::new(tag)?;
|
||||
let message = CString::new(message)?;
|
||||
unsafe {
|
||||
__android_log_print(level, tag.as_ptr(), message.as_ptr());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_property(name: &str) -> Result<String> {
|
||||
let name = CString::new(name)?;
|
||||
let mut buf = vec![0u8; 92];
|
||||
let prop = unsafe {
|
||||
__system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char);
|
||||
CStr::from_bytes_until_nul(&buf)?
|
||||
};
|
||||
Ok(prop.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn set_property(name: &str, value: &str) -> Result<()> {
|
||||
let name = CString::new(name)?;
|
||||
let value = CString::new(value)?;
|
||||
unsafe {
|
||||
__system_property_set(name.as_ptr(), value.as_ptr());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn wait_property(name: &str, serial: u32) -> Result<u32> {
|
||||
let name = CString::new(name)?;
|
||||
let info = unsafe { __system_property_find(name.as_ptr()) };
|
||||
let mut serial = serial;
|
||||
unsafe {
|
||||
__system_property_wait(info, serial, &mut serial, std::ptr::null());
|
||||
}
|
||||
Ok(serial)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn get_property_serial(name: &str) -> Result<u32> {
|
||||
let name = CString::new(name)?;
|
||||
let info = unsafe { __system_property_find(name.as_ptr()) };
|
||||
Ok(unsafe { __system_property_serial(info) })
|
||||
}
|
||||
|
||||
pub fn switch_mount_namespace(pid: i32) -> Result<()> {
|
||||
let cwd = std::env::current_dir()?;
|
||||
let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?;
|
||||
rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?;
|
||||
std::env::set_current_dir(cwd)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub trait UnixStreamExt {
|
||||
fn read_u8(&mut self) -> Result<u8>;
|
||||
fn read_u32(&mut self) -> Result<u32>;
|
||||
fn read_usize(&mut self) -> Result<usize>;
|
||||
fn read_string(&mut self) -> Result<String>;
|
||||
fn write_u8(&mut self, value: u8) -> Result<()>;
|
||||
fn write_u32(&mut self, value: u32) -> Result<()>;
|
||||
fn write_usize(&mut self, value: usize) -> Result<()>;
|
||||
fn write_string(&mut self, value: &str) -> Result<()>;
|
||||
}
|
||||
|
||||
impl UnixStreamExt for UnixStream {
|
||||
fn read_u8(&mut self) -> Result<u8> {
|
||||
let mut buf = [0u8; 1];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
fn read_u32(&mut self) -> Result<u32> {
|
||||
let mut buf = [0u8; 4];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(u32::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
fn read_usize(&mut self) -> Result<usize> {
|
||||
let mut buf = [0u8; std::mem::size_of::<usize>()];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(usize::from_ne_bytes(buf))
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String> {
|
||||
let len = self.read_usize()?;
|
||||
let mut buf = vec![0u8; len];
|
||||
self.read_exact(&mut buf)?;
|
||||
Ok(String::from_utf8(buf)?)
|
||||
}
|
||||
|
||||
fn write_u8(&mut self, value: u8) -> Result<()> {
|
||||
self.write_all(&value.to_ne_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_u32(&mut self, value: u32) -> Result<()> {
|
||||
self.write_all(&value.to_ne_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_usize(&mut self, value: usize) -> Result<()> {
|
||||
self.write_all(&value.to_ne_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_string(&mut self, value: &str) -> Result<()> {
|
||||
self.write_usize(value.len())?;
|
||||
self.write_all(value.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unix_listener_from_path(path: &str) -> Result<UnixListener> {
|
||||
let _ = fs::remove_file(path);
|
||||
let addr = SocketAddrUnix::new(path)?;
|
||||
let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?;
|
||||
bind_unix(&socket, &addr)?;
|
||||
listen(&socket, 2)?;
|
||||
chcon(path, "u:object_r:magisk_file:s0")?;
|
||||
Ok(UnixListener::from(socket))
|
||||
}
|
||||
|
||||
pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> {
|
||||
// FIXME: shall we set create context every time?
|
||||
set_socket_create_context(get_current_attr()?.as_str())?;
|
||||
let addr = SocketAddrUnix::new(path.as_bytes())?;
|
||||
let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?;
|
||||
connect_unix(&socket, &addr)?;
|
||||
sendto_unix(socket, buf, SendFlags::empty(), &addr)?;
|
||||
set_socket_create_context("u:r:zygote:s0")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool {
|
||||
unsafe {
|
||||
let mut pfd = libc::pollfd {
|
||||
fd: stream.as_raw_fd(),
|
||||
events: libc::POLLIN,
|
||||
revents: 0,
|
||||
};
|
||||
let timeout = if block { -1 } else { 0 };
|
||||
libc::poll(&mut pfd, 1, timeout);
|
||||
if pfd.revents & !libc::POLLIN != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
|
||||
fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32;
|
||||
fn __system_property_set(name: *const c_char, value: *const c_char) -> u32;
|
||||
fn __system_property_find(name: *const c_char) -> *const c_void;
|
||||
fn __system_property_wait(
|
||||
info: *const c_void,
|
||||
old_serial: u32,
|
||||
new_serial: *mut u32,
|
||||
timeout: *const libc::timespec,
|
||||
) -> bool;
|
||||
fn __system_property_serial(info: *const c_void) -> u32;
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
use crate::constants::{DaemonSocketAction, ProcessFlags};
|
||||
use crate::utils::{check_unix_socket, LateInit, UnixStreamExt};
|
||||
use crate::{constants, lp_select, root_impl, utils};
|
||||
use anyhow::{bail, Result};
|
||||
use log::{debug, error, info, trace, warn};
|
||||
use passfd::FdPassingExt;
|
||||
use rustix::fs::{fcntl_setfd, FdFlags};
|
||||
use std::fs;
|
||||
use std::io::Error;
|
||||
use std::ops::Deref;
|
||||
use std::os::fd::{AsFd, OwnedFd, RawFd};
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::os::unix::{
|
||||
net::{UnixListener, UnixStream},
|
||||
prelude::AsRawFd,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::process::{exit, Command};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
|
||||
struct Module {
|
||||
name: String,
|
||||
lib_fd: OwnedFd,
|
||||
companion: Mutex<Option<Option<UnixStream>>>,
|
||||
}
|
||||
|
||||
struct Context {
|
||||
modules: Vec<Module>,
|
||||
}
|
||||
|
||||
static TMP_PATH: LateInit<String> = LateInit::new();
|
||||
static CONTROLLER_SOCKET: LateInit<String> = LateInit::new();
|
||||
static PATH_CP_NAME: LateInit<String> = LateInit::new();
|
||||
|
||||
pub fn main() -> Result<()> {
|
||||
info!("Welcome to Zygisk Next ({}) !", constants::ZKSU_VERSION);
|
||||
|
||||
TMP_PATH.init(std::env::var("TMP_PATH")?);
|
||||
CONTROLLER_SOCKET.init(format!("{}/init_monitor", TMP_PATH.deref()));
|
||||
PATH_CP_NAME.init(format!(
|
||||
"{}/{}",
|
||||
TMP_PATH.deref(),
|
||||
lp_select!("/cp32.sock", "/cp64.sock")
|
||||
));
|
||||
|
||||
let arch = get_arch()?;
|
||||
debug!("Daemon architecture: {arch}");
|
||||
let modules = load_modules(arch)?;
|
||||
|
||||
{
|
||||
let mut msg = Vec::<u8>::new();
|
||||
let info = match root_impl::get_impl() {
|
||||
root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk => {
|
||||
msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes());
|
||||
let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect();
|
||||
format!(
|
||||
"Root: {:?},module({}): {}",
|
||||
root_impl::get_impl(),
|
||||
modules.len(),
|
||||
module_names.join(",")
|
||||
)
|
||||
}
|
||||
_ => {
|
||||
msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes());
|
||||
format!("Invalid root implementation: {:?}", root_impl::get_impl())
|
||||
}
|
||||
};
|
||||
msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes());
|
||||
msg.extend_from_slice(info.as_bytes());
|
||||
msg.extend_from_slice(&[0u8]);
|
||||
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, msg.as_slice())
|
||||
.expect("failed to send info");
|
||||
}
|
||||
|
||||
let context = Context { modules };
|
||||
let context = Arc::new(context);
|
||||
let listener = create_daemon_socket()?;
|
||||
for stream in listener.incoming() {
|
||||
let mut stream = stream?;
|
||||
let context = Arc::clone(&context);
|
||||
let action = stream.read_u8()?;
|
||||
let action = DaemonSocketAction::try_from(action)?;
|
||||
trace!("New daemon action {:?}", action);
|
||||
match action {
|
||||
DaemonSocketAction::PingHeartbeat => {
|
||||
let value = constants::ZYGOTE_INJECTED;
|
||||
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?;
|
||||
}
|
||||
DaemonSocketAction::ZygoteRestart => {
|
||||
info!("Zygote restarted, clean up companions");
|
||||
for module in &context.modules {
|
||||
let mut companion = module.companion.lock().unwrap();
|
||||
companion.take();
|
||||
}
|
||||
}
|
||||
DaemonSocketAction::SystemServerStarted => {
|
||||
let value = constants::SYSTEM_SERVER_STARTED;
|
||||
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?;
|
||||
}
|
||||
_ => {
|
||||
thread::spawn(move || {
|
||||
if let Err(e) = handle_daemon_action(action, stream, &context) {
|
||||
warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_arch() -> Result<&'static str> {
|
||||
let system_arch = utils::get_property("ro.product.cpu.abi")?;
|
||||
if system_arch.contains("arm") {
|
||||
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
|
||||
}
|
||||
if system_arch.contains("x86") {
|
||||
return Ok(lp_select!("x86", "x86_64"));
|
||||
}
|
||||
bail!("Unsupported system architecture: {}", system_arch);
|
||||
}
|
||||
|
||||
fn load_modules(arch: &str) -> Result<Vec<Module>> {
|
||||
let mut modules = Vec::new();
|
||||
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
warn!("Failed reading modules directory: {}", e);
|
||||
return Ok(modules);
|
||||
}
|
||||
};
|
||||
for entry in dir.into_iter() {
|
||||
let entry = entry?;
|
||||
let name = entry.file_name().into_string().unwrap();
|
||||
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
|
||||
let disabled = entry.path().join("disable");
|
||||
if !so_path.exists() || disabled.exists() {
|
||||
continue;
|
||||
}
|
||||
info!(" Loading module `{name}`...");
|
||||
let lib_fd = match create_library_fd(&so_path) {
|
||||
Ok(fd) => fd,
|
||||
Err(e) => {
|
||||
warn!(" Failed to create memfd for `{name}`: {e}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let companion = Mutex::new(None);
|
||||
let module = Module {
|
||||
name,
|
||||
lib_fd,
|
||||
companion,
|
||||
};
|
||||
modules.push(module);
|
||||
}
|
||||
|
||||
Ok(modules)
|
||||
}
|
||||
|
||||
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
|
||||
let opts = memfd::MemfdOptions::default().allow_sealing(true);
|
||||
let memfd = opts.create("jit-cache")?;
|
||||
let file = fs::File::open(so_path)?;
|
||||
let mut reader = std::io::BufReader::new(file);
|
||||
let mut writer = memfd.as_file();
|
||||
std::io::copy(&mut reader, &mut writer)?;
|
||||
|
||||
let mut seals = memfd::SealsHashSet::new();
|
||||
seals.insert(memfd::FileSeal::SealShrink);
|
||||
seals.insert(memfd::FileSeal::SealGrow);
|
||||
seals.insert(memfd::FileSeal::SealWrite);
|
||||
seals.insert(memfd::FileSeal::SealSeal);
|
||||
memfd.add_seals(&seals)?;
|
||||
|
||||
Ok(OwnedFd::from(memfd.into_file()))
|
||||
}
|
||||
|
||||
fn create_daemon_socket() -> Result<UnixListener> {
|
||||
utils::set_socket_create_context("u:r:zygote:s0")?;
|
||||
let listener = utils::unix_listener_from_path(&PATH_CP_NAME)?;
|
||||
Ok(listener)
|
||||
}
|
||||
|
||||
fn spawn_companion(name: &str, lib_fd: RawFd) -> Result<Option<UnixStream>> {
|
||||
let (mut daemon, companion) = UnixStream::pair()?;
|
||||
|
||||
// FIXME: avoid getting self path from arg0
|
||||
let process = std::env::args().next().unwrap();
|
||||
let nice_name = process.split('/').last().unwrap();
|
||||
|
||||
unsafe {
|
||||
let pid = libc::fork();
|
||||
if pid < 0 {
|
||||
bail!(Error::last_os_error());
|
||||
} else if pid > 0 {
|
||||
drop(companion);
|
||||
let mut status: libc::c_int = 0;
|
||||
libc::waitpid(pid, &mut status, 0);
|
||||
if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 {
|
||||
daemon.write_string(name)?;
|
||||
daemon.send_fd(lib_fd)?;
|
||||
return match daemon.read_u8()? {
|
||||
0 => Ok(None),
|
||||
1 => Ok(Some(daemon)),
|
||||
_ => bail!("Invalid companion response"),
|
||||
};
|
||||
} else {
|
||||
bail!("exited with status {}", status);
|
||||
}
|
||||
} else {
|
||||
// Remove FD_CLOEXEC flag
|
||||
fcntl_setfd(companion.as_fd(), FdFlags::empty())?;
|
||||
}
|
||||
}
|
||||
|
||||
Command::new(&process)
|
||||
.arg0(format!("{}-{}", nice_name, name))
|
||||
.arg("companion")
|
||||
.arg(format!("{}", companion.as_raw_fd()))
|
||||
.spawn()?;
|
||||
exit(0)
|
||||
}
|
||||
|
||||
fn handle_daemon_action(
|
||||
action: DaemonSocketAction,
|
||||
mut stream: UnixStream,
|
||||
context: &Context,
|
||||
) -> Result<()> {
|
||||
match action {
|
||||
DaemonSocketAction::RequestLogcatFd => loop {
|
||||
let level = match stream.read_u8() {
|
||||
Ok(level) => level,
|
||||
Err(_) => break,
|
||||
};
|
||||
let tag = stream.read_string()?;
|
||||
let message = stream.read_string()?;
|
||||
utils::log_raw(level as i32, &tag, &message)?;
|
||||
},
|
||||
DaemonSocketAction::GetProcessFlags => {
|
||||
let uid = stream.read_u32()? as i32;
|
||||
let mut flags = ProcessFlags::empty();
|
||||
if root_impl::uid_is_manager(uid) {
|
||||
flags |= ProcessFlags::PROCESS_IS_MANAGER;
|
||||
} else {
|
||||
if root_impl::uid_granted_root(uid) {
|
||||
flags |= ProcessFlags::PROCESS_GRANTED_ROOT;
|
||||
}
|
||||
if root_impl::uid_should_umount(uid) {
|
||||
flags |= ProcessFlags::PROCESS_ON_DENYLIST;
|
||||
}
|
||||
}
|
||||
match root_impl::get_impl() {
|
||||
root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
|
||||
root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK,
|
||||
_ => panic!("wrong root impl: {:?}", root_impl::get_impl()),
|
||||
}
|
||||
trace!(
|
||||
"Uid {} granted root: {}",
|
||||
uid,
|
||||
flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT)
|
||||
);
|
||||
trace!(
|
||||
"Uid {} on denylist: {}",
|
||||
uid,
|
||||
flags.contains(ProcessFlags::PROCESS_ON_DENYLIST)
|
||||
);
|
||||
stream.write_u32(flags.bits())?;
|
||||
}
|
||||
DaemonSocketAction::ReadModules => {
|
||||
stream.write_usize(context.modules.len())?;
|
||||
for module in context.modules.iter() {
|
||||
stream.write_string(&module.name)?;
|
||||
stream.send_fd(module.lib_fd.as_raw_fd())?;
|
||||
}
|
||||
}
|
||||
DaemonSocketAction::RequestCompanionSocket => {
|
||||
let index = stream.read_usize()?;
|
||||
let module = &context.modules[index];
|
||||
let mut companion = module.companion.lock().unwrap();
|
||||
if let Some(Some(sock)) = companion.as_ref() {
|
||||
if !check_unix_socket(sock, false) {
|
||||
error!("Poll companion for module `{}` crashed", module.name);
|
||||
companion.take();
|
||||
}
|
||||
}
|
||||
if companion.is_none() {
|
||||
match spawn_companion(&module.name, module.lib_fd.as_raw_fd()) {
|
||||
Ok(c) => {
|
||||
if c.is_some() {
|
||||
trace!(" Spawned companion for `{}`", module.name);
|
||||
} else {
|
||||
trace!(
|
||||
" No companion spawned for `{}` because it has not entry",
|
||||
module.name
|
||||
);
|
||||
}
|
||||
*companion = Some(c);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!(" Failed to spawn companion for `{}`: {}", module.name, e);
|
||||
}
|
||||
};
|
||||
}
|
||||
match companion.as_ref() {
|
||||
Some(Some(sock)) => {
|
||||
if let Err(e) = sock.send_fd(stream.as_raw_fd()) {
|
||||
error!(
|
||||
"Failed to send companion fd socket of module `{}`: {}",
|
||||
module.name, e
|
||||
);
|
||||
stream.write_u8(0)?;
|
||||
}
|
||||
// Ok: Send by companion
|
||||
}
|
||||
_ => {
|
||||
stream.write_u8(0)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
DaemonSocketAction::GetModuleDir => {
|
||||
let index = stream.read_usize()?;
|
||||
let module = &context.modules[index];
|
||||
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
|
||||
let dir = fs::File::open(dir)?;
|
||||
stream.send_fd(dir.as_raw_fd())?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user