You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbb792ef13 | ||
|
|
381abc99d5 | ||
|
|
fd603982e8 | ||
|
|
2d384685e7 | ||
|
|
675febfd14 | ||
|
|
ad32c4efb0 | ||
|
|
331b01b0f4 | ||
|
|
8079123e43 | ||
|
|
9a95377d7b | ||
|
|
043cfd93d6 | ||
|
|
32931cf3a8 | ||
|
|
9a649e0f00 | ||
|
|
ded1af2757 | ||
|
|
7e1f85caf5 | ||
|
|
711a3e98d0 | ||
|
|
cbd0f0f0c3 | ||
|
|
977bd7753d | ||
|
|
b041a4fca0 | ||
|
|
d5c896e966 | ||
|
|
5c8a67657c | ||
|
|
28f77fa5e2 | ||
|
|
914c54cdd0 | ||
|
|
8463574e2d | ||
|
|
07c29016a5 | ||
|
|
cd714527e9 | ||
|
|
0741a35c8f | ||
|
|
dce2937ba0 | ||
|
|
92ac6d4f1a | ||
|
|
535379fe45 | ||
|
|
1999ef734c | ||
|
|
12f57d6e8c | ||
|
|
49886d9485 | ||
|
|
056900406e | ||
|
|
370bb0863b | ||
|
|
75495dee38 | ||
|
|
feb34c701d | ||
|
|
483987b7e6 | ||
|
|
b45d55e83d | ||
|
|
f101ffb95f | ||
|
|
faf074b990 | ||
|
|
db60c3185e | ||
|
|
993b18752b | ||
|
|
ad8510dee0 | ||
|
|
5d95f627bf | ||
|
|
99048699fc | ||
|
|
ba1f90cba8 | ||
|
|
0e2bc75966 | ||
|
|
a88d7a5bb8 | ||
|
|
d66dba265c | ||
|
|
4f81f09a67 | ||
|
|
c4163c91b9 | ||
|
|
0254623166 | ||
|
|
b34015b5f0 | ||
|
|
bcb65c4bd9 | ||
|
|
9c34c671fa | ||
|
|
9e48f18247 | ||
|
|
c205893dd2 | ||
|
|
5aea51ce03 | ||
|
|
c59a7887ca | ||
|
|
47a17d2079 | ||
|
|
fd9f8799a1 | ||
|
|
fd6a454275 | ||
|
|
3e541ece3a | ||
|
|
36dcec0264 | ||
|
|
889a44cd0d | ||
|
|
0ac9bb819b | ||
|
|
040643337d | ||
|
|
ba8c313122 | ||
|
|
070aa5f1a1 | ||
|
|
add1c65626 | ||
|
|
ac53ef11a3 | ||
|
|
1c79932cae | ||
|
|
5e2a211ca4 | ||
|
|
f958e57af6 | ||
|
|
f5e0a418c9 | ||
|
|
f78c217552 | ||
|
|
c249ebe22c | ||
|
|
47e515e2fc | ||
|
|
44918d332e | ||
|
|
cbf5920d02 | ||
|
|
a2af28dc6f | ||
|
|
a956dba77d | ||
|
|
abbca19c82 | ||
|
|
4587e39964 | ||
|
|
9df4fb64cd | ||
|
|
9f28e0a7ad | ||
|
|
8a80586fb8 | ||
|
|
218659dcbf | ||
|
|
8c0d5b5395 | ||
|
|
9051f59bf6 | ||
|
|
5f2dd50703 | ||
|
|
77cb323506 | ||
|
|
2b41a8336c | ||
|
|
e730ccd9b2 | ||
|
|
db47f03728 | ||
|
|
42503e7cfe | ||
|
|
14c920c553 | ||
|
|
ceaa2d431c | ||
|
|
ccb5764b72 | ||
|
|
e8958e94b6 | ||
|
|
0338cdb0ed | ||
|
|
99e653c576 | ||
|
|
7c27c32861 | ||
|
|
bea5ed47b8 | ||
|
|
954a712089 | ||
|
|
f6195ddb43 | ||
|
|
8b5e9db347 | ||
|
|
a04f636ac4 | ||
|
|
00f0a6e3fa | ||
|
|
f5bf82fa93 | ||
|
|
33c4ea3c62 | ||
|
|
3eee57eb8f | ||
|
|
250b4b2f8c | ||
|
|
3772e23473 | ||
|
|
8c5acf1ebe | ||
|
|
9d0858be7c | ||
|
|
b7bed4ad35 | ||
|
|
80b19c4412 | ||
|
|
a6f455218f | ||
|
|
87cf885070 | ||
|
|
b775d28c23 | ||
|
|
bf72296d33 | ||
|
|
9d648d9aa4 | ||
|
|
843086f6f3 | ||
|
|
49e3ac9d7a | ||
|
|
446ed92f26 | ||
|
|
2e9cbf79a7 | ||
|
|
cce8e6686f | ||
|
|
ff2658f2de | ||
|
|
f465cbf810 | ||
|
|
09b6673ab0 | ||
|
|
5f8eb4af09 | ||
|
|
8affc8f991 | ||
|
|
ec8475bca5 | ||
|
|
9ff1e27a7d | ||
|
|
6d9cc560cc | ||
|
|
f395cfb490 | ||
|
|
03575edd96 | ||
|
|
915749e59b | ||
|
|
d08b415577 | ||
|
|
f27aed5068 | ||
|
|
5365ab1f12 | ||
|
|
b99d042002 | ||
|
|
57d3d8a0ba | ||
|
|
e69aa5c527 | ||
|
|
9b5eb1bac7 |
73
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
73
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
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
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
29
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
29
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
||||||
25
.github/scripts/telegram_url.py
vendored
Normal file
25
.github/scripts/telegram_url.py
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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)
|
||||||
139
.github/workflows/ci.yml
vendored
Normal file
139
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
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
|
||||||
23
.github/workflows/issue_moderator.yml
vendored
Normal file
23
.github/workflows/issue_moderator.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
12
.gitmodules
vendored
12
.gitmodules
vendored
@@ -1,9 +1,3 @@
|
|||||||
[submodule "loader/src/external/liblsplt"]
|
[submodule "loader/src/external/lsplt"]
|
||||||
path = loader/src/external/liblsplt
|
path = loader/src/external/lsplt
|
||||||
url = https://github.com/LSPosed/LSPlt
|
url = https://github.com/LSPosed/lsplt
|
||||||
[submodule "loader/src/external/parallel-hashmap"]
|
|
||||||
path = loader/src/external/parallel-hashmap
|
|
||||||
url = https://github.com/greg7mdp/parallel-hashmap
|
|
||||||
[submodule "zygiskd/src/external/binder_rs"]
|
|
||||||
path = zygiskd/src/external/binder_rs
|
|
||||||
url = https://github.com/Kernel-SU/binder_rs
|
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -1,26 +1,26 @@
|
|||||||
# Zygisk on KernelSU
|
# Zygisk Next
|
||||||
|
|
||||||
Zygisk loader for KernelSU, allowing Zygisk modules to run without Magisk environment.
|
Standalone implementation of Zygisk, providing Zygisk API support for KernelSU and a replacement of Magisk's built-in Zygisk.
|
||||||
|
|
||||||
Also works as standalone loader for Magisk on purpose of getting rid of LD_PRELOAD. (Coming soon)
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
+ Minimal KernelSU version: 10575
|
### General
|
||||||
+ Minimal ksud version: 10616
|
|
||||||
+ Full SELinux patch support (If non-gki kernel)
|
+ No multiple root implementation installed
|
||||||
|
|
||||||
|
### KernelSU
|
||||||
|
|
||||||
|
+ Minimal KernelSU version: 10940
|
||||||
|
+ Minimal ksud version: 11424
|
||||||
|
+ Kernel has full SELinux patch support
|
||||||
|
|
||||||
|
### Magisk
|
||||||
|
|
||||||
|
+ Minimal version: 26402
|
||||||
|
+ Built-in Zygisk turned off
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
Should work with everything except those rely on Magisk internal behaviors.
|
`PROCESS_ON_DENYLIST` cannot be flagged correctly for isolated processes on Magisk DenyList currently.
|
||||||
|
|
||||||
## Development road map
|
Zygisk Next only guarantees the same behavior of Zygisk API, but will NOT ensure Magisk's internal features.
|
||||||
|
|
||||||
- [x] [Inject] Basic Zygisk loader
|
|
||||||
- [x] [Inject] Stabilize injector
|
|
||||||
- [x] [Inject] Unload
|
|
||||||
- [x] [Daemon] Linker namespace
|
|
||||||
- [x] [Daemon] Separate zygiskd process
|
|
||||||
- [x] [Daemon] Handle 64 bit only devices
|
|
||||||
- [x] [Daemon] Handle zygote death
|
|
||||||
- [ ] [ Misc ] Support Magisk out of box
|
|
||||||
|
|||||||
@@ -2,18 +2,7 @@ import com.android.build.gradle.LibraryExtension
|
|||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.application") apply false
|
alias(libs.plugins.agp.lib) apply false
|
||||||
id("com.android.library") apply false
|
|
||||||
}
|
|
||||||
|
|
||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
maven("https://plugins.gradle.org/m2/")
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath("org.eclipse.jgit:org.eclipse.jgit:6.4.0.202211300538-r")
|
|
||||||
classpath("org.mozilla.rust-android-gradle:plugin:0.9.3")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.execute(currentWorkingDir: File = file("./")): String {
|
fun String.execute(currentWorkingDir: File = file("./")): String {
|
||||||
@@ -30,15 +19,20 @@ val gitCommitCount = "git rev-list HEAD --count".execute().toInt()
|
|||||||
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
|
||||||
|
|
||||||
val moduleId by extra("zygisksu")
|
val moduleId by extra("zygisksu")
|
||||||
val moduleName by extra("Zygisk on KernelSU")
|
val moduleName by extra("Zygisk Next")
|
||||||
val verName by extra("v4-0.4.0")
|
val verName by extra("v4-0.9.1")
|
||||||
val verCode by extra(gitCommitCount)
|
val verCode by extra(gitCommitCount)
|
||||||
|
val commitHash by extra(gitCommitHash)
|
||||||
|
val minKsuVersion by extra(10940)
|
||||||
|
val minKsudVersion by extra(11424)
|
||||||
|
val maxKsuVersion by extra(20000)
|
||||||
|
val minMagiskVersion by extra(26402)
|
||||||
|
|
||||||
val androidMinSdkVersion by extra(29)
|
val androidMinSdkVersion by extra(26)
|
||||||
val androidTargetSdkVersion by extra(33)
|
val androidTargetSdkVersion by extra(34)
|
||||||
val androidCompileSdkVersion by extra(33)
|
val androidCompileSdkVersion by extra(34)
|
||||||
val androidBuildToolsVersion by extra("33.0.2")
|
val androidBuildToolsVersion by extra("34.0.0")
|
||||||
val androidCompileNdkVersion by extra("25.2.9519653")
|
val androidCompileNdkVersion by extra("26.0.10792818")
|
||||||
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
|
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
|
||||||
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)
|
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)
|
||||||
|
|
||||||
@@ -48,7 +42,7 @@ tasks.register("Delete", Delete::class) {
|
|||||||
|
|
||||||
fun Project.configureBaseExtension() {
|
fun Project.configureBaseExtension() {
|
||||||
extensions.findByType(LibraryExtension::class)?.run {
|
extensions.findByType(LibraryExtension::class)?.run {
|
||||||
namespace = "icu.nullptr.zygisksu"
|
namespace = "icu.nullptr.zygisk.next"
|
||||||
compileSdk = androidCompileSdkVersion
|
compileSdk = androidCompileSdkVersion
|
||||||
ndkVersion = androidCompileNdkVersion
|
ndkVersion = androidCompileNdkVersion
|
||||||
buildToolsVersion = androidBuildToolsVersion
|
buildToolsVersion = androidBuildToolsVersion
|
||||||
|
|||||||
@@ -1,23 +1 @@
|
|||||||
# Project-wide Gradle settings.
|
android.useAndroidX=false
|
||||||
# IDE (e.g. Android Studio) users:
|
|
||||||
# Gradle settings configured through the IDE *will override*
|
|
||||||
# any settings specified in this file.
|
|
||||||
# For more details on how to configure your build environment visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
|
||||||
# This option should only be used with decoupled projects. More details, visit
|
|
||||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
|
||||||
# org.gradle.parallel=true
|
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
|
||||||
# Android operating system, and which are packaged with your app's APK
|
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
|
||||||
android.useAndroidX=true
|
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
|
||||||
kotlin.code.style=official
|
|
||||||
# Enables namespacing of each library's R class so that its R class includes only the
|
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
|
||||||
# thereby reducing the size of the R class for that library
|
|
||||||
android.nonTransitiveRClass=true
|
|
||||||
|
|||||||
9
gradle/libs.versions.toml
Normal file
9
gradle/libs.versions.toml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[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" }
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -1,7 +1,45 @@
|
|||||||
|
import java.nio.file.Paths
|
||||||
|
import org.gradle.internal.os.OperatingSystem
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
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 {
|
android {
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
androidResources = false
|
androidResources = false
|
||||||
@@ -9,8 +47,35 @@ android {
|
|||||||
prefab = true
|
prefab = true
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild.ndkBuild {
|
externalNativeBuild.cmake {
|
||||||
path("src/Android.mk")
|
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
define walk
|
|
||||||
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
|
|
||||||
endef
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := common
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
|
||||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/common))
|
|
||||||
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
|
||||||
LOCAL_STATIC_LIBRARIES := cxx
|
|
||||||
LOCAL_LDLIBS := -llog
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := zygiskloader
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
|
||||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/loader))
|
|
||||||
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
|
||||||
LOCAL_STATIC_LIBRARIES := cxx common
|
|
||||||
LOCAL_LDLIBS := -llog
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
|
||||||
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE := injector
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
|
||||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector))
|
|
||||||
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
|
||||||
LOCAL_STATIC_LIBRARIES := cxx common liblsplt libphmap
|
|
||||||
LOCAL_LDLIBS := -llog
|
|
||||||
include $(BUILD_SHARED_LIBRARY)
|
|
||||||
|
|
||||||
$(call import-module,prefab/cxx)
|
|
||||||
|
|
||||||
include src/external/Android.mk
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
APP_CFLAGS := -Wall -Wextra
|
|
||||||
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
|
|
||||||
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
|
|
||||||
APP_CPPFLAGS := -std=c++20
|
|
||||||
APP_CONLYFLAGS := -std=c18
|
|
||||||
APP_STL := none
|
|
||||||
|
|
||||||
ifneq ($(NDK_DEBUG),1)
|
|
||||||
APP_CFLAGS += -Oz -flto
|
|
||||||
APP_CFLAGS += -Wno-unused -Wno-unused-parameter
|
|
||||||
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
|
||||||
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
|
|
||||||
APP_LDFLAGS += -Wl,--exclude-libs,ALL -flto -Wl,--gc-sections -Wl,--strip-all
|
|
||||||
endif
|
|
||||||
23
loader/src/CMakeLists.txt
Normal file
23
loader/src/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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)
|
||||||
@@ -7,6 +7,14 @@
|
|||||||
#include "socket_utils.h"
|
#include "socket_utils.h"
|
||||||
|
|
||||||
namespace zygiskd {
|
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 Connect(uint8_t retry) {
|
||||||
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
@@ -14,14 +22,17 @@ namespace zygiskd {
|
|||||||
.sun_family = AF_UNIX,
|
.sun_family = AF_UNIX,
|
||||||
.sun_path={0},
|
.sun_path={0},
|
||||||
};
|
};
|
||||||
strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size());
|
auto socket_path = TMP_PATH + kCPSocketName;
|
||||||
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
|
strcpy(addr.sun_path, socket_path.c_str());
|
||||||
|
socklen_t socklen = sizeof(addr);
|
||||||
|
|
||||||
while (retry--) {
|
while (retry--) {
|
||||||
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
|
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
|
||||||
if (r == 0) return fd;
|
if (r == 0) return fd;
|
||||||
LOGW("retrying to connect to zygiskd, sleep 1s");
|
if (retry) {
|
||||||
sleep(1);
|
PLOGE("Retrying to connect to zygiskd, sleep 1s");
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
@@ -48,14 +59,15 @@ namespace zygiskd {
|
|||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ReadNativeBridge() {
|
uint32_t GetProcessFlags(uid_t uid) {
|
||||||
UniqueFd fd = Connect(1);
|
UniqueFd fd = Connect(1);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
PLOGE("ReadNativeBridge");
|
PLOGE("GetProcessFlags");
|
||||||
return "";
|
return 0;
|
||||||
}
|
}
|
||||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge);
|
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags);
|
||||||
return socket_utils::read_string(fd);
|
socket_utils::write_u32(fd, uid);
|
||||||
|
return socket_utils::read_u32(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Module> ReadModules() {
|
std::vector<Module> ReadModules() {
|
||||||
@@ -86,12 +98,13 @@ namespace zygiskd {
|
|||||||
if (socket_utils::read_u8(fd) == 1) {
|
if (socket_utils::read_u8(fd) == 1) {
|
||||||
return fd;
|
return fd;
|
||||||
} else {
|
} else {
|
||||||
|
close(fd);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int GetModuleDir(size_t index) {
|
int GetModuleDir(size_t index) {
|
||||||
int fd = Connect(1);
|
UniqueFd fd = Connect(1);
|
||||||
if (fd == -1) {
|
if (fd == -1) {
|
||||||
PLOGE("GetModuleDir");
|
PLOGE("GetModuleDir");
|
||||||
return -1;
|
return -1;
|
||||||
@@ -100,4 +113,30 @@ namespace zygiskd {
|
|||||||
socket_utils::write_usize(fd, index);
|
socket_utils::write_usize(fd, index);
|
||||||
return socket_utils::recv_fd(fd);
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,8 +44,17 @@ void* DlopenExt(const char* path, int flags) {
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* DlopenMem(int memfd, int flags) {
|
void* DlopenMem(int fd, int flags) {
|
||||||
char path[PATH_MAX];
|
auto info = android_dlextinfo{
|
||||||
sprintf(path, "/proc/self/fd/%d", memfd);
|
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
|
||||||
return DlopenExt(path, flags);
|
.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;
|
||||||
}
|
}
|
||||||
|
|||||||
122
loader/src/common/files.cpp
Normal file
122
loader/src/common/files.cpp
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#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; });
|
||||||
|
}
|
||||||
49
loader/src/common/misc.cpp
Normal file
49
loader/src/common/misc.cpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
@@ -19,12 +19,12 @@ namespace socket_utils {
|
|||||||
read_sz += ret;
|
read_sz += ret;
|
||||||
} while (read_sz != count && ret != 0);
|
} while (read_sz != count && ret != 0);
|
||||||
if (read_sz != count) {
|
if (read_sz != count) {
|
||||||
PLOGE("read (%d != %d)", count, read_sz);
|
PLOGE("read (%zu != %zu)", count, read_sz);
|
||||||
}
|
}
|
||||||
return read_sz;
|
return read_sz;
|
||||||
}
|
}
|
||||||
|
|
||||||
ssize_t xwrite(int fd, const void* buf, size_t count) {
|
size_t xwrite(int fd, const void* buf, size_t count) {
|
||||||
size_t write_sz = 0;
|
size_t write_sz = 0;
|
||||||
ssize_t ret;
|
ssize_t ret;
|
||||||
do {
|
do {
|
||||||
@@ -32,12 +32,12 @@ namespace socket_utils {
|
|||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
if (errno == EINTR) continue;
|
if (errno == EINTR) continue;
|
||||||
PLOGE("write");
|
PLOGE("write");
|
||||||
return ret;
|
return write_sz;
|
||||||
}
|
}
|
||||||
write_sz += ret;
|
write_sz += ret;
|
||||||
} while (write_sz != count && ret != 0);
|
} while (write_sz != count && ret != 0);
|
||||||
if (write_sz != count) {
|
if (write_sz != count) {
|
||||||
PLOGE("write (%d != %d)", count, write_sz);
|
PLOGE("write (%zu != %zu)", count, write_sz);
|
||||||
}
|
}
|
||||||
return write_sz;
|
return write_sz;
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,10 @@ namespace socket_utils {
|
|||||||
return read_exact_or<uint8_t>(fd, 0);
|
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) {
|
size_t read_usize(int fd) {
|
||||||
return read_exact_or<size_t>(fd, 0);
|
return read_exact_or<size_t>(fd, 0);
|
||||||
}
|
}
|
||||||
@@ -110,6 +114,10 @@ namespace socket_utils {
|
|||||||
return write_exact<uint8_t>(fd, 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) {
|
bool write_string(int fd, std::string_view str) {
|
||||||
return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size());
|
return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size());
|
||||||
}
|
}
|
||||||
|
|||||||
20
loader/src/external/Android.mk
vendored
20
loader/src/external/Android.mk
vendored
@@ -1,20 +0,0 @@
|
|||||||
LOCAL_PATH := $(call my-dir)
|
|
||||||
|
|
||||||
# liblsplt.a
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE:= liblsplt
|
|
||||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/liblsplt/lsplt/src/main/jni/include
|
|
||||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
|
||||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
|
|
||||||
LOCAL_CPPFLAGS := -std=c++20
|
|
||||||
LOCAL_STATIC_LIBRARIES := libcxx
|
|
||||||
LOCAL_SRC_FILES := \
|
|
||||||
liblsplt/lsplt/src/main/jni/elf_util.cc \
|
|
||||||
liblsplt/lsplt/src/main/jni/lsplt.cc
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
|
||||||
|
|
||||||
# Header only library
|
|
||||||
include $(CLEAR_VARS)
|
|
||||||
LOCAL_MODULE:= libphmap
|
|
||||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap
|
|
||||||
include $(BUILD_STATIC_LIBRARY)
|
|
||||||
8
loader/src/external/CMakeLists.txt
vendored
Normal file
8
loader/src/external/CMakeLists.txt
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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)
|
||||||
1
loader/src/external/liblsplt
vendored
1
loader/src/external/liblsplt
vendored
Submodule loader/src/external/liblsplt deleted from b254b5b9a5
1
loader/src/external/lsplt
vendored
Submodule
1
loader/src/external/lsplt
vendored
Submodule
Submodule loader/src/external/lsplt added at 5d2b820cf9
1
loader/src/external/parallel-hashmap
vendored
1
loader/src/external/parallel-hashmap
vendored
Submodule loader/src/external/parallel-hashmap deleted from 87ece91c6e
@@ -26,14 +26,40 @@
|
|||||||
#define ZYGISK_API_VERSION 4
|
#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.
|
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.
|
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
|
||||||
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!
|
|
||||||
Example code:
|
Example code:
|
||||||
|
|
||||||
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
static jint (*orig_logger_entry_max)(JNIEnv *env);
|
||||||
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
|
||||||
static void example_handler(int socket) { ... }
|
|
||||||
class ExampleModule : public zygisk::ModuleBase {
|
class ExampleModule : public zygisk::ModuleBase {
|
||||||
public:
|
public:
|
||||||
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
void onLoad(zygisk::Api *api, JNIEnv *env) override {
|
||||||
@@ -51,8 +77,26 @@ private:
|
|||||||
zygisk::Api *api;
|
zygisk::Api *api;
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
};
|
};
|
||||||
|
|
||||||
REGISTER_ZYGISK_MODULE(ExampleModule)
|
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)
|
REGISTER_ZYGISK_COMPANION(example_handler)
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
namespace zygisk {
|
namespace zygisk {
|
||||||
@@ -84,7 +128,7 @@ namespace zygisk {
|
|||||||
|
|
||||||
// This method is called after the app process is specialized.
|
// This method is called after the app process is specialized.
|
||||||
// At this point, the process has all sandbox restrictions enabled for this application.
|
// At this point, the process has all sandbox restrictions enabled for this application.
|
||||||
// This means that this method runs as the same privilege of the app's own code.
|
// This means that this method runs with the same privilege of the app's own code.
|
||||||
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
||||||
|
|
||||||
// This method is called before the system server process is specialized.
|
// This method is called before the system server process is specialized.
|
||||||
@@ -117,6 +161,7 @@ namespace zygisk {
|
|||||||
jobjectArray *const whitelisted_data_info_list;
|
jobjectArray *const whitelisted_data_info_list;
|
||||||
jboolean *const mount_data_dirs;
|
jboolean *const mount_data_dirs;
|
||||||
jboolean *const mount_storage_dirs;
|
jboolean *const mount_storage_dirs;
|
||||||
|
jboolean *const mount_sysprop_overrides;
|
||||||
|
|
||||||
AppSpecializeArgs() = delete;
|
AppSpecializeArgs() = delete;
|
||||||
};
|
};
|
||||||
@@ -219,7 +264,16 @@ namespace zygisk {
|
|||||||
// will be set to nullptr.
|
// will be set to nullptr.
|
||||||
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
|
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
|
||||||
|
|
||||||
// For ELFs loaded in memory matching `inode`, replace function `symbol` with `newFunc`.
|
// 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`.
|
// 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);
|
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||||
|
|
||||||
@@ -243,11 +297,11 @@ void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
|
|||||||
//
|
//
|
||||||
// The function runs in a superuser daemon process and handles a root companion request from
|
// 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,
|
// your module running in a target process. The function has to accept an integer value,
|
||||||
// which is a socket that is connected to the target process.
|
// which is a Unix domain socket that is connected to the target process.
|
||||||
// See Api::connectCompanion() for more info.
|
// See Api::connectCompanion() for more info.
|
||||||
//
|
//
|
||||||
// NOTE: the function can run concurrently on multiple threads.
|
// NOTE: the function can run concurrently on multiple threads.
|
||||||
// Be aware of race conditions if you have a globally shared resource.
|
// Be aware of race conditions if you have globally shared resources.
|
||||||
|
|
||||||
#define REGISTER_ZYGISK_COMPANION(func) \
|
#define REGISTER_ZYGISK_COMPANION(func) \
|
||||||
void zygisk_companion_entry(int client) { func(client); }
|
void zygisk_companion_entry(int client) { func(client); }
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
#else
|
#else
|
||||||
# define LP_SELECT(lp32, lp64) lp32
|
# define LP_SELECT(lp32, lp64) lp32
|
||||||
#endif
|
#endif
|
||||||
constexpr std::string_view kZygiskSocket = LP_SELECT("zygiskd32", "zygiskd64") "socket_placeholder";
|
|
||||||
|
constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock";
|
||||||
|
|
||||||
class UniqueFd {
|
class UniqueFd {
|
||||||
using Fd = int;
|
using Fd = int;
|
||||||
@@ -19,7 +20,7 @@ public:
|
|||||||
|
|
||||||
UniqueFd(Fd fd) : fd_(fd) {}
|
UniqueFd(Fd fd) : fd_(fd) {}
|
||||||
|
|
||||||
~UniqueFd() { close(fd_); }
|
~UniqueFd() { if (fd_ >= 0) close(fd_); }
|
||||||
|
|
||||||
// Disallow copy
|
// Disallow copy
|
||||||
UniqueFd(const UniqueFd&) = delete;
|
UniqueFd(const UniqueFd&) = delete;
|
||||||
@@ -53,21 +54,31 @@ namespace zygiskd {
|
|||||||
enum class SocketAction {
|
enum class SocketAction {
|
||||||
PingHeartBeat,
|
PingHeartBeat,
|
||||||
RequestLogcatFd,
|
RequestLogcatFd,
|
||||||
ReadNativeBridge,
|
GetProcessFlags,
|
||||||
ReadModules,
|
ReadModules,
|
||||||
RequestCompanionSocket,
|
RequestCompanionSocket,
|
||||||
GetModuleDir,
|
GetModuleDir,
|
||||||
|
ZygoteRestart,
|
||||||
|
SystemServerStarted,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void Init(const char *path);
|
||||||
|
|
||||||
|
std::string GetTmpPath();
|
||||||
|
|
||||||
bool PingHeartbeat();
|
bool PingHeartbeat();
|
||||||
|
|
||||||
int RequestLogcatFd();
|
int RequestLogcatFd();
|
||||||
|
|
||||||
std::string ReadNativeBridge();
|
|
||||||
|
|
||||||
std::vector<Module> ReadModules();
|
std::vector<Module> ReadModules();
|
||||||
|
|
||||||
|
uint32_t GetProcessFlags(uid_t uid);
|
||||||
|
|
||||||
int ConnectCompanion(size_t index);
|
int ConnectCompanion(size_t index);
|
||||||
|
|
||||||
int GetModuleDir(size_t index);
|
int GetModuleDir(size_t index);
|
||||||
|
|
||||||
|
void ZygoteRestart();
|
||||||
|
|
||||||
|
void SystemServerStarted();
|
||||||
}
|
}
|
||||||
|
|||||||
56
loader/src/include/files.hpp
Normal file
56
loader/src/include/files.hpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#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));
|
||||||
|
}
|
||||||
@@ -6,19 +6,12 @@
|
|||||||
|
|
||||||
#ifndef LOG_TAG
|
#ifndef LOG_TAG
|
||||||
#if defined(__LP64__)
|
#if defined(__LP64__)
|
||||||
# define LOG_TAG "zygisksu64"
|
# define LOG_TAG "zygisk-core64"
|
||||||
#else
|
#else
|
||||||
# define LOG_TAG "zygisksu32"
|
# define LOG_TAG "zygisk-core32"
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef LOG_DISABLED
|
|
||||||
#define LOGD(...)
|
|
||||||
#define LOGV(...)
|
|
||||||
#define LOGI(...)
|
|
||||||
#define LOGW(...)
|
|
||||||
#define LOGE(...)
|
|
||||||
#else
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||||
@@ -31,7 +24,6 @@
|
|||||||
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, 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 LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||||
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace logging {
|
namespace logging {
|
||||||
void setfd(int fd);
|
void setfd(int fd);
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <list>
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mntent.h>
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdio.h>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include "logging.h"
|
#include "logging.h"
|
||||||
@@ -34,6 +32,10 @@ private:
|
|||||||
using thread_entry = void *(*)(void *);
|
using thread_entry = void *(*)(void *);
|
||||||
int new_daemon_thread(thread_entry entry, void *arg);
|
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>
|
template<typename T, typename Impl>
|
||||||
class stateless_allocator {
|
class stateless_allocator {
|
||||||
public:
|
public:
|
||||||
@@ -49,33 +51,23 @@ public:
|
|||||||
bool operator!=(const stateless_allocator&) { return false; }
|
bool operator!=(const stateless_allocator&) { return false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
template <typename T>
|
||||||
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
|
class reversed_container {
|
||||||
sDIR make_dir(DIR *dp);
|
public:
|
||||||
sFILE make_file(FILE *fp);
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
static inline sDIR open_dir(const char *path) {
|
template <typename T>
|
||||||
return make_dir(opendir(path));
|
reversed_container<T> reversed(T &base) {
|
||||||
}
|
return reversed_container<T>(base);
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
@@ -95,7 +87,9 @@ struct StringCmp {
|
|||||||
*/
|
*/
|
||||||
int parse_int(std::string_view s);
|
int parse_int(std::string_view s);
|
||||||
|
|
||||||
void parse_mnt(const char* file, const std::function<bool(mntent*)>& fn);
|
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>
|
template <typename T>
|
||||||
static inline T align_to(T v, int a) {
|
static inline T align_to(T v, int a) {
|
||||||
@@ -9,16 +9,20 @@ namespace socket_utils {
|
|||||||
|
|
||||||
ssize_t xread(int fd, void *buf, size_t count);
|
ssize_t xread(int fd, void *buf, size_t count);
|
||||||
|
|
||||||
ssize_t xwrite(int fd, const void *buf, size_t count);
|
size_t xwrite(int fd, const void *buf, size_t count);
|
||||||
|
|
||||||
uint8_t read_u8(int fd);
|
uint8_t read_u8(int fd);
|
||||||
|
|
||||||
|
uint32_t read_u32(int fd);
|
||||||
|
|
||||||
size_t read_usize(int fd);
|
size_t read_usize(int fd);
|
||||||
|
|
||||||
std::string read_string(int fd);
|
std::string read_string(int fd);
|
||||||
|
|
||||||
bool write_u8(int fd, uint8_t val);
|
bool write_u8(int fd, uint8_t val);
|
||||||
|
|
||||||
|
bool write_u32(int fd, uint32_t val);
|
||||||
|
|
||||||
int recv_fd(int fd);
|
int recv_fd(int fd);
|
||||||
|
|
||||||
bool write_usize(int fd, size_t val);
|
bool write_usize(int fd, size_t val);
|
||||||
|
|||||||
82
loader/src/injector/art_method.hpp
Normal file
82
loader/src/injector/art_method.hpp
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
#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
|
||||||
@@ -7,29 +7,21 @@ using namespace std;
|
|||||||
|
|
||||||
void *self_handle = nullptr;
|
void *self_handle = nullptr;
|
||||||
|
|
||||||
[[gnu::destructor]] [[maybe_unused]]
|
|
||||||
static void zygisk_cleanup_wait() {
|
|
||||||
if (self_handle) {
|
|
||||||
// Wait 10us to make sure none of our code is executing
|
|
||||||
timespec ts = { .tv_sec = 0, .tv_nsec = 10000L };
|
|
||||||
nanosleep(&ts, nullptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" [[gnu::visibility("default")]]
|
extern "C" [[gnu::visibility("default")]]
|
||||||
void entry(void *handle) {
|
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
|
#ifdef NDEBUG
|
||||||
logging::setfd(zygiskd::RequestLogcatFd());
|
logging::setfd(zygiskd::RequestLogcatFd());
|
||||||
#endif
|
#endif
|
||||||
self_handle = handle;
|
|
||||||
|
|
||||||
LOGD("Load injector successfully");
|
LOGI("Start hooking");
|
||||||
hook_functions();
|
hook_functions();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The following code runs in zygote/app process
|
|
||||||
|
|
||||||
static inline bool should_load_modules(uint32_t flags) {
|
|
||||||
return (flags & UNMOUNT_MASK) != UNMOUNT_MASK &&
|
|
||||||
(flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP;
|
|
||||||
}
|
|
||||||
|
|||||||
292
loader/src/injector/gen_jni_hooks.py
Normal file
292
loader/src/injector/gen_jni_hooks.py
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
#!/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,22 +0,0 @@
|
|||||||
#include <sys/mount.h>
|
|
||||||
|
|
||||||
#include "logging.h"
|
|
||||||
#include "misc.hpp"
|
|
||||||
#include "zygisk.hpp"
|
|
||||||
|
|
||||||
using namespace std::string_view_literals;
|
|
||||||
|
|
||||||
static void lazy_unmount(const char* mountpoint) {
|
|
||||||
if (umount2(mountpoint, MNT_DETACH) != -1)
|
|
||||||
LOGD("Unmounted (%s)", mountpoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
void revert_unmount() {
|
|
||||||
parse_mnt("/proc/self/mounts", [](mntent* mentry) {
|
|
||||||
if (mentry->mnt_fsname == "/data/adb/ksu/modules"sv ||
|
|
||||||
std::string_view(mentry->mnt_opts).find("/data/adb/ksu/modules") != std::string_view::npos) {
|
|
||||||
lazy_unmount(mentry->mnt_fsname);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,27 +4,30 @@
|
|||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <map>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
#include <lsplt.hpp>
|
#include <lsplt.hpp>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
#include "dl.h"
|
#include "dl.h"
|
||||||
#include "daemon.h"
|
#include "daemon.h"
|
||||||
#include "zygisk.hpp"
|
#include "zygisk.hpp"
|
||||||
#include "memory.hpp"
|
|
||||||
#include "module.hpp"
|
#include "module.hpp"
|
||||||
|
#include "files.hpp"
|
||||||
#include "misc.hpp"
|
#include "misc.hpp"
|
||||||
|
|
||||||
using namespace std;
|
#include "art_method.hpp"
|
||||||
using jni_hook::hash_map;
|
|
||||||
using jni_hook::tree_map;
|
|
||||||
using xstring = jni_hook::string;
|
|
||||||
|
|
||||||
static bool unhook_functions();
|
using namespace std;
|
||||||
|
|
||||||
|
static void hook_unloader();
|
||||||
|
static void unhook_functions();
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -34,7 +37,6 @@ enum {
|
|||||||
APP_SPECIALIZE,
|
APP_SPECIALIZE,
|
||||||
SERVER_FORK_AND_SPECIALIZE,
|
SERVER_FORK_AND_SPECIALIZE,
|
||||||
DO_REVERT_UNMOUNT,
|
DO_REVERT_UNMOUNT,
|
||||||
CAN_UNLOAD_ZYGISK,
|
|
||||||
SKIP_FD_SANITIZATION,
|
SKIP_FD_SANITIZATION,
|
||||||
|
|
||||||
FLAG_MAX
|
FLAG_MAX
|
||||||
@@ -46,7 +48,12 @@ void name##_post();
|
|||||||
|
|
||||||
#define MAX_FD_SIZE 1024
|
#define MAX_FD_SIZE 1024
|
||||||
|
|
||||||
struct HookContext {
|
struct ZygiskContext;
|
||||||
|
|
||||||
|
// Current context
|
||||||
|
ZygiskContext *g_ctx;
|
||||||
|
|
||||||
|
struct ZygiskContext {
|
||||||
JNIEnv *env;
|
JNIEnv *env;
|
||||||
union {
|
union {
|
||||||
void *ptr;
|
void *ptr;
|
||||||
@@ -79,8 +86,12 @@ struct HookContext {
|
|||||||
vector<RegisterInfo> register_info;
|
vector<RegisterInfo> register_info;
|
||||||
vector<IgnoreInfo> ignore_info;
|
vector<IgnoreInfo> ignore_info;
|
||||||
|
|
||||||
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0),
|
ZygiskContext(JNIEnv *env, void *args) :
|
||||||
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {}
|
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 */
|
/* Zygisksu changed: Load module fds */
|
||||||
void run_modules_pre();
|
void run_modules_pre();
|
||||||
@@ -91,9 +102,9 @@ struct HookContext {
|
|||||||
DCL_PRE_POST(nativeSpecializeAppProcess)
|
DCL_PRE_POST(nativeSpecializeAppProcess)
|
||||||
DCL_PRE_POST(nativeForkSystemServer)
|
DCL_PRE_POST(nativeForkSystemServer)
|
||||||
|
|
||||||
void unload_zygisk();
|
|
||||||
void sanitize_fds();
|
void sanitize_fds();
|
||||||
bool exempt_fd(int fd);
|
bool exempt_fd(int fd);
|
||||||
|
bool is_child() const { return pid <= 0; }
|
||||||
|
|
||||||
// Compatibility shim
|
// Compatibility shim
|
||||||
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
|
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
|
||||||
@@ -108,103 +119,16 @@ struct HookContext {
|
|||||||
// Global variables
|
// Global variables
|
||||||
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
|
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
|
||||||
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
|
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
|
||||||
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
|
bool should_unmap_zygisk = false;
|
||||||
|
|
||||||
// Current context
|
|
||||||
HookContext *g_ctx;
|
|
||||||
const JNINativeInterface *old_functions = nullptr;
|
|
||||||
JNINativeInterface *new_functions = nullptr;
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
#define HOOK_JNI(method) \
|
|
||||||
if (methods[i].name == #method##sv) { \
|
|
||||||
int j = 0; \
|
|
||||||
for (; j < method##_methods_num; ++j) { \
|
|
||||||
if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \
|
|
||||||
jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \
|
|
||||||
method##_orig = methods[i].fnPtr; \
|
|
||||||
newMethods[i] = method##_methods[j]; \
|
|
||||||
LOGI("replaced %s#" #method "\n", className); \
|
|
||||||
--hook_cnt; \
|
|
||||||
break; \
|
|
||||||
} \
|
|
||||||
} \
|
|
||||||
if (j == method##_methods_num) { \
|
|
||||||
LOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \
|
|
||||||
} \
|
|
||||||
continue; \
|
|
||||||
}
|
|
||||||
|
|
||||||
// JNI method hook definitions, auto generated
|
|
||||||
#include "jni_hooks.hpp"
|
|
||||||
|
|
||||||
#undef HOOK_JNI
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
string get_class_name(JNIEnv *env, jclass clazz) {
|
|
||||||
static auto class_getName = env->GetMethodID(env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;");
|
|
||||||
auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName);
|
|
||||||
const char *name = env->GetStringUTFChars(nameRef, nullptr);
|
|
||||||
string className(name);
|
|
||||||
env->ReleaseStringUTFChars(nameRef, name);
|
|
||||||
std::replace(className.begin(), className.end(), '.', '/');
|
|
||||||
return className;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define DCL_HOOK_FUNC(ret, func, ...) \
|
#define DCL_HOOK_FUNC(ret, func, ...) \
|
||||||
ret (*old_##func)(__VA_ARGS__); \
|
ret (*old_##func)(__VA_ARGS__); \
|
||||||
ret new_##func(__VA_ARGS__)
|
ret new_##func(__VA_ARGS__)
|
||||||
|
|
||||||
jint env_RegisterNatives(
|
|
||||||
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) {
|
|
||||||
auto className = get_class_name(env, clazz);
|
|
||||||
LOGV("JNIEnv->RegisterNatives [%s]\n", className.data());
|
|
||||||
auto newMethods = hookAndSaveJNIMethods(className.data(), methods, numMethods);
|
|
||||||
return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods);
|
|
||||||
}
|
|
||||||
|
|
||||||
DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void* func) {
|
|
||||||
LOGD("androidSetCreateThreadFunc\n");
|
|
||||||
do {
|
|
||||||
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");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
JavaVM *vm = nullptr;
|
|
||||||
jsize num = 0;
|
|
||||||
jint res = get_created_java_vms(&vm, 1, &num);
|
|
||||||
if (res != JNI_OK || vm == nullptr) break;
|
|
||||||
JNIEnv *env = nullptr;
|
|
||||||
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
|
||||||
if (res != JNI_OK || env == nullptr) break;
|
|
||||||
default_new(new_functions);
|
|
||||||
memcpy(new_functions, env->functions, sizeof(*new_functions));
|
|
||||||
new_functions->RegisterNatives = &env_RegisterNatives;
|
|
||||||
|
|
||||||
// Replace the function table in JNIEnv to hook RegisterNatives
|
|
||||||
old_functions = env->functions;
|
|
||||||
env->functions = new_functions;
|
|
||||||
} while (false);
|
|
||||||
old_androidSetCreateThreadFunc(func);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip actual fork and return cached result if applicable
|
// Skip actual fork and return cached result if applicable
|
||||||
DCL_HOOK_FUNC(int, fork) {
|
DCL_HOOK_FUNC(int, fork) {
|
||||||
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
|
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
|
||||||
@@ -219,7 +143,11 @@ DCL_HOOK_FUNC(int, unshare, int flags) {
|
|||||||
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
|
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
|
||||||
(g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) {
|
(g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) {
|
||||||
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
|
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
|
||||||
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 */
|
/* Zygisksu changed: No umount app_process */
|
||||||
@@ -242,53 +170,141 @@ DCL_HOOK_FUNC(void, android_log_close) {
|
|||||||
old_android_log_close();
|
old_android_log_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Last point before process secontext changes
|
// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
|
||||||
DCL_HOOK_FUNC(int, selinux_android_setcontext,
|
// it will return to our code which has been unmapped, causing segmentation fault.
|
||||||
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
|
// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.
|
||||||
if (g_ctx) {
|
DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
|
||||||
g_ctx->flags[CAN_UNLOAD_ZYGISK] = unhook_functions();
|
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 old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
|
|
||||||
|
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
|
#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) {
|
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) {
|
||||||
auto class_map = jni_method_map->find(clz);
|
if (!can_hook_jni) return;
|
||||||
if (class_map == jni_method_map->end()) {
|
auto clazz = env->FindClass(clz);
|
||||||
for (int i = 0; i < numMethods; ++i) {
|
if (clazz == nullptr) {
|
||||||
|
env->ExceptionClear();
|
||||||
|
for (int i = 0; i < numMethods; i++) {
|
||||||
methods[i].fnPtr = nullptr;
|
methods[i].fnPtr = nullptr;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<JNINativeMethod> hooks;
|
vector<JNINativeMethod> hooks;
|
||||||
for (int i = 0; i < numMethods; ++i) {
|
for (int i = 0; i < numMethods; i++) {
|
||||||
auto method_map = class_map->second.find(methods[i].name);
|
auto &nm = methods[i];
|
||||||
if (method_map != class_map->second.end()) {
|
auto mid = env->GetMethodID(clazz, nm.name, nm.signature);
|
||||||
auto it = method_map->second.find(methods[i].signature);
|
bool is_static = false;
|
||||||
if (it != method_map->second.end()) {
|
if (mid == nullptr) {
|
||||||
// Copy the JNINativeMethod
|
env->ExceptionClear();
|
||||||
hooks.push_back(methods[i]);
|
mid = env->GetStaticMethodID(clazz, nm.name, nm.signature);
|
||||||
// Save the original function pointer
|
is_static = true;
|
||||||
methods[i].fnPtr = it->second;
|
|
||||||
// Do not allow double hook, remove method from map
|
|
||||||
method_map->second.erase(it);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// No matching method found, set fnPtr to null
|
if (mid == nullptr) {
|
||||||
methods[i].fnPtr = 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())
|
if (hooks.empty()) return;
|
||||||
return;
|
env->RegisterNatives(clazz, hooks.data(), hooks.size());
|
||||||
|
|
||||||
old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(), static_cast<int>(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)
|
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
|
||||||
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
|
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
|
||||||
// Make sure all pointers are null
|
// Make sure all pointers are null
|
||||||
@@ -327,6 +343,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
|||||||
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
||||||
}
|
}
|
||||||
if (api_version >= 4) {
|
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) {
|
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)
|
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
|
||||||
return;
|
return;
|
||||||
@@ -338,7 +355,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) {
|
void ZygiskContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) {
|
||||||
if (regex == nullptr || symbol == nullptr || fn == nullptr)
|
if (regex == nullptr || symbol == nullptr || fn == nullptr)
|
||||||
return;
|
return;
|
||||||
regex_t re;
|
regex_t re;
|
||||||
@@ -348,7 +365,7 @@ void HookContext::plt_hook_register(const char *regex, const char *symbol, void
|
|||||||
register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});
|
register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
void ZygiskContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
||||||
if (!regex) return;
|
if (!regex) return;
|
||||||
regex_t re;
|
regex_t re;
|
||||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||||
@@ -357,7 +374,7 @@ void HookContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
|||||||
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
|
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::plt_hook_process_regex() {
|
void ZygiskContext::plt_hook_process_regex() {
|
||||||
if (register_info.empty())
|
if (register_info.empty())
|
||||||
return;
|
return;
|
||||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||||
@@ -381,7 +398,7 @@ void HookContext::plt_hook_process_regex() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool HookContext::plt_hook_commit() {
|
bool ZygiskContext::plt_hook_commit() {
|
||||||
{
|
{
|
||||||
mutex_guard lock(hook_info_lock);
|
mutex_guard lock(hook_info_lock);
|
||||||
plt_hook_process_regex();
|
plt_hook_process_regex();
|
||||||
@@ -443,8 +460,7 @@ int sigmask(int how, int signum) {
|
|||||||
return sigprocmask(how, &set, nullptr);
|
return sigprocmask(how, &set, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::fork_pre() {
|
void ZygiskContext::fork_pre() {
|
||||||
g_ctx = this;
|
|
||||||
// Do our own fork before loading any 3rd party code
|
// Do our own fork before loading any 3rd party code
|
||||||
// First block SIGCHLD, unblock after original fork is done
|
// First block SIGCHLD, unblock after original fork is done
|
||||||
sigmask(SIG_BLOCK, SIGCHLD);
|
sigmask(SIG_BLOCK, SIGCHLD);
|
||||||
@@ -466,7 +482,7 @@ void HookContext::fork_pre() {
|
|||||||
allowed_fds[dirfd(dir.get())] = false;
|
allowed_fds[dirfd(dir.get())] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::sanitize_fds() {
|
void ZygiskContext::sanitize_fds() {
|
||||||
if (flags[SKIP_FD_SANITIZATION])
|
if (flags[SKIP_FD_SANITIZATION])
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -522,15 +538,14 @@ void HookContext::sanitize_fds() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::fork_post() {
|
void ZygiskContext::fork_post() {
|
||||||
// Unblock SIGCHLD in case the original method didn't
|
// Unblock SIGCHLD in case the original method didn't
|
||||||
sigmask(SIG_UNBLOCK, SIGCHLD);
|
sigmask(SIG_UNBLOCK, SIGCHLD);
|
||||||
g_ctx = nullptr;
|
g_ctx = nullptr;
|
||||||
unload_zygisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zygisksu changed: Load module fds */
|
/* Zygisksu changed: Load module fds */
|
||||||
void HookContext::run_modules_pre() {
|
void ZygiskContext::run_modules_pre() {
|
||||||
auto ms = zygiskd::ReadModules();
|
auto ms = zygiskd::ReadModules();
|
||||||
auto size = ms.size();
|
auto size = ms.size();
|
||||||
for (size_t i = 0; i < size; i++) {
|
for (size_t i = 0; i < size; i++) {
|
||||||
@@ -551,7 +566,7 @@ void HookContext::run_modules_pre() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::run_modules_post() {
|
void ZygiskContext::run_modules_post() {
|
||||||
flags[POST_SPECIALIZE] = true;
|
flags[POST_SPECIALIZE] = true;
|
||||||
for (const auto &m : modules) {
|
for (const auto &m : modules) {
|
||||||
if (flags[APP_SPECIALIZE]) {
|
if (flags[APP_SPECIALIZE]) {
|
||||||
@@ -564,17 +579,20 @@ void HookContext::run_modules_post() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Zygisksu changed: Load module fds */
|
/* Zygisksu changed: Load module fds */
|
||||||
void HookContext::app_specialize_pre() {
|
void ZygiskContext::app_specialize_pre() {
|
||||||
flags[APP_SPECIALIZE] = true;
|
flags[APP_SPECIALIZE] = true;
|
||||||
run_modules_pre();
|
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 HookContext::app_specialize_post() {
|
void ZygiskContext::app_specialize_post() {
|
||||||
run_modules_post();
|
run_modules_post();
|
||||||
if (info_flags & PROCESS_IS_MAGISK_APP) {
|
|
||||||
setenv("ZYGISK_ENABLED", "1", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanups
|
// Cleanups
|
||||||
env->ReleaseStringUTFChars(args.app->nice_name, process);
|
env->ReleaseStringUTFChars(args.app->nice_name, process);
|
||||||
@@ -582,23 +600,7 @@ void HookContext::app_specialize_post() {
|
|||||||
logging::setfd(-1);
|
logging::setfd(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::unload_zygisk() {
|
bool ZygiskContext::exempt_fd(int fd) {
|
||||||
if (flags[CAN_UNLOAD_ZYGISK]) {
|
|
||||||
// Do NOT call the destructor
|
|
||||||
operator delete(jni_method_map);
|
|
||||||
// Directly unmap the whole memory block
|
|
||||||
jni_hook::memory_block::release();
|
|
||||||
|
|
||||||
// Strip out all API function pointers
|
|
||||||
for (auto &m : modules) {
|
|
||||||
m.clearApi();
|
|
||||||
}
|
|
||||||
|
|
||||||
new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool HookContext::exempt_fd(int fd) {
|
|
||||||
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
|
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
|
||||||
return true;
|
return true;
|
||||||
if (!flags[APP_FORK_AND_SPECIALIZE])
|
if (!flags[APP_FORK_AND_SPECIALIZE])
|
||||||
@@ -609,23 +611,21 @@ bool HookContext::exempt_fd(int fd) {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------
|
// -----------------------------------------------------------------
|
||||||
|
|
||||||
void HookContext::nativeSpecializeAppProcess_pre() {
|
void ZygiskContext::nativeSpecializeAppProcess_pre() {
|
||||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||||
LOGV("pre specialize [%s]\n", process);
|
LOGV("pre specialize [%s]\n", process);
|
||||||
g_ctx = this;
|
|
||||||
// App specialize does not check FD
|
// App specialize does not check FD
|
||||||
flags[SKIP_FD_SANITIZATION] = true;
|
flags[SKIP_FD_SANITIZATION] = true;
|
||||||
app_specialize_pre();
|
app_specialize_pre();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::nativeSpecializeAppProcess_post() {
|
void ZygiskContext::nativeSpecializeAppProcess_post() {
|
||||||
LOGV("post specialize [%s]\n", process);
|
LOGV("post specialize [%s]\n", process);
|
||||||
app_specialize_post();
|
app_specialize_post();
|
||||||
unload_zygisk();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zygisksu changed: No system_server status write back */
|
/* Zygisksu changed: No system_server status write back */
|
||||||
void HookContext::nativeForkSystemServer_pre() {
|
void ZygiskContext::nativeForkSystemServer_pre() {
|
||||||
LOGV("pre forkSystemServer\n");
|
LOGV("pre forkSystemServer\n");
|
||||||
flags[SERVER_FORK_AND_SPECIALIZE] = true;
|
flags[SERVER_FORK_AND_SPECIALIZE] = true;
|
||||||
|
|
||||||
@@ -634,11 +634,12 @@ void HookContext::nativeForkSystemServer_pre() {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
run_modules_pre();
|
run_modules_pre();
|
||||||
|
zygiskd::SystemServerStarted();
|
||||||
|
|
||||||
sanitize_fds();
|
sanitize_fds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::nativeForkSystemServer_post() {
|
void ZygiskContext::nativeForkSystemServer_post() {
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
LOGV("post forkSystemServer\n");
|
LOGV("post forkSystemServer\n");
|
||||||
run_modules_post();
|
run_modules_post();
|
||||||
@@ -646,7 +647,7 @@ void HookContext::nativeForkSystemServer_post() {
|
|||||||
fork_post();
|
fork_post();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::nativeForkAndSpecialize_pre() {
|
void ZygiskContext::nativeForkAndSpecialize_pre() {
|
||||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||||
LOGV("pre forkAndSpecialize [%s]\n", process);
|
LOGV("pre forkAndSpecialize [%s]\n", process);
|
||||||
|
|
||||||
@@ -663,7 +664,7 @@ void HookContext::nativeForkAndSpecialize_pre() {
|
|||||||
sanitize_fds();
|
sanitize_fds();
|
||||||
}
|
}
|
||||||
|
|
||||||
void HookContext::nativeForkAndSpecialize_post() {
|
void ZygiskContext::nativeForkAndSpecialize_post() {
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
LOGV("post forkAndSpecialize [%s]\n", process);
|
LOGV("post forkAndSpecialize [%s]\n", process);
|
||||||
app_specialize_post();
|
app_specialize_post();
|
||||||
@@ -671,6 +672,37 @@ void HookContext::nativeForkAndSpecialize_post() {
|
|||||||
fork_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
|
} // namespace
|
||||||
|
|
||||||
static bool hook_commit() {
|
static bool hook_commit() {
|
||||||
@@ -699,7 +731,6 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_
|
|||||||
void hook_functions() {
|
void hook_functions() {
|
||||||
default_new(plt_hook_list);
|
default_new(plt_hook_list);
|
||||||
default_new(jni_hook_list);
|
default_new(jni_hook_list);
|
||||||
default_new(jni_method_map);
|
|
||||||
|
|
||||||
ino_t android_runtime_inode = 0;
|
ino_t android_runtime_inode = 0;
|
||||||
dev_t android_runtime_dev = 0;
|
dev_t android_runtime_dev = 0;
|
||||||
@@ -713,8 +744,7 @@ void hook_functions() {
|
|||||||
|
|
||||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork);
|
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, unshare);
|
||||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext);
|
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup);
|
||||||
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc);
|
|
||||||
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
|
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
|
||||||
hook_commit();
|
hook_commit();
|
||||||
|
|
||||||
@@ -725,38 +755,32 @@ void hook_functions() {
|
|||||||
plt_hook_list->end());
|
plt_hook_list->end());
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool unhook_functions() {
|
static void hook_unloader() {
|
||||||
bool success = true;
|
ino_t art_inode = 0;
|
||||||
|
dev_t art_dev = 0;
|
||||||
|
|
||||||
// Restore JNIEnv
|
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||||
if (g_ctx->env->functions == new_functions) {
|
if (map.path.ends_with("/libart.so")) {
|
||||||
g_ctx->env->functions = old_functions;
|
art_inode = map.inode;
|
||||||
delete new_functions;
|
art_dev = map.dev;
|
||||||
}
|
break;
|
||||||
|
|
||||||
// Unhook JNI methods
|
|
||||||
for (const auto &[clz, methods] : *jni_hook_list) {
|
|
||||||
if (!methods.empty() && g_ctx->env->RegisterNatives(
|
|
||||||
g_ctx->env->FindClass(clz.data()), methods.data(),
|
|
||||||
static_cast<int>(methods.size())) != 0) {
|
|
||||||
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
|
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete jni_hook_list;
|
|
||||||
|
|
||||||
|
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy);
|
||||||
|
hook_commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unhook_functions() {
|
||||||
// Unhook plt_hook
|
// Unhook plt_hook
|
||||||
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
|
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
|
||||||
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
|
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
|
||||||
LOGE("Failed to register plt_hook [%s]\n", sym);
|
LOGE("Failed to register plt_hook [%s]\n", sym);
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete plt_hook_list;
|
delete plt_hook_list;
|
||||||
if (!hook_commit()) {
|
if (!hook_commit()) {
|
||||||
LOGE("Failed to restore plt_hook\n");
|
LOGE("Failed to restore plt_hook\n");
|
||||||
success = false;
|
should_unmap_zygisk = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|||||||
1116
loader/src/injector/jni_helper.hpp
Normal file
1116
loader/src/injector/jni_helper.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -5,9 +5,7 @@ namespace {
|
|||||||
void *nativeForkAndSpecialize_orig = nullptr;
|
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) {
|
[[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);
|
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_l)>(nativeForkAndSpecialize_orig)(
|
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
|
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir
|
||||||
@@ -18,9 +16,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
[[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) {
|
[[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);
|
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.fds_to_ignore = &fds_to_ignore;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_o)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -32,9 +28,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, 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.fds_to_ignore = &fds_to_ignore;
|
||||||
args.is_child_zygote = &is_child_zygote;
|
args.is_child_zygote = &is_child_zygote;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_p)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -47,9 +41,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
args.fds_to_ignore = &fds_to_ignore;
|
args.fds_to_ignore = &fds_to_ignore;
|
||||||
args.is_child_zygote = &is_child_zygote;
|
args.is_child_zygote = &is_child_zygote;
|
||||||
args.is_top_app = &is_top_app;
|
args.is_top_app = &is_top_app;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_q_alt)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -66,9 +58,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||||
args.mount_data_dirs = &mount_data_dirs;
|
args.mount_data_dirs = &mount_data_dirs;
|
||||||
args.mount_storage_dirs = &mount_storage_dirs;
|
args.mount_storage_dirs = &mount_storage_dirs;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_r)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -76,11 +66,27 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
ctx.nativeForkAndSpecialize_post();
|
ctx.nativeForkAndSpecialize_post();
|
||||||
return ctx.pid;
|
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) {
|
[[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);
|
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(nativeForkAndSpecialize_orig)(
|
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
|
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir
|
||||||
@@ -90,9 +96,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
}
|
}
|
||||||
[[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) {
|
[[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);
|
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -103,9 +107,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
[[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) {
|
[[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);
|
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.fds_to_ignore = &fds_to_ignore;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_o)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -117,9 +119,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, 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.fds_to_ignore = &fds_to_ignore;
|
||||||
args.is_child_zygote = &is_child_zygote;
|
args.is_child_zygote = &is_child_zygote;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkAndSpecialize_pre();
|
ctx.nativeForkAndSpecialize_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_p)>(nativeForkAndSpecialize_orig)(
|
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
|
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
|
||||||
@@ -127,62 +127,64 @@ void *nativeForkAndSpecialize_orig = nullptr;
|
|||||||
ctx.nativeForkAndSpecialize_post();
|
ctx.nativeForkAndSpecialize_post();
|
||||||
return ctx.pid;
|
return ctx.pid;
|
||||||
}
|
}
|
||||||
const JNINativeMethod nativeForkAndSpecialize_methods[] = {
|
std::array nativeForkAndSpecialize_methods = {
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_l
|
(void *) &nativeForkAndSpecialize_l
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_o
|
(void *) &nativeForkAndSpecialize_o
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_p
|
(void *) &nativeForkAndSpecialize_p
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
|
||||||
(void *) &nativeForkAndSpecialize_q_alt
|
(void *) &nativeForkAndSpecialize_q_alt
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"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",
|
"(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
|
(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",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_samsung_m
|
(void *) &nativeForkAndSpecialize_samsung_m
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
|
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
|
||||||
(void *) &nativeForkAndSpecialize_samsung_n
|
(void *) &nativeForkAndSpecialize_samsung_n
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_samsung_o
|
(void *) &nativeForkAndSpecialize_samsung_o
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkAndSpecialize",
|
"nativeForkAndSpecialize",
|
||||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||||
(void *) &nativeForkAndSpecialize_samsung_p
|
(void *) &nativeForkAndSpecialize_samsung_p
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods);
|
|
||||||
|
|
||||||
void *nativeSpecializeAppProcess_orig = nullptr;
|
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) {
|
[[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);
|
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_child_zygote = &is_child_zygote;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeSpecializeAppProcess_pre();
|
ctx.nativeSpecializeAppProcess_pre();
|
||||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q)>(nativeSpecializeAppProcess_orig)(
|
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
|
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir
|
||||||
@@ -193,9 +195,7 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
|||||||
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, 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;
|
args.is_child_zygote = &is_child_zygote;
|
||||||
args.is_top_app = &is_top_app;
|
args.is_top_app = &is_top_app;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeSpecializeAppProcess_pre();
|
ctx.nativeSpecializeAppProcess_pre();
|
||||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q_alt)>(nativeSpecializeAppProcess_orig)(
|
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
|
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
|
||||||
@@ -210,57 +210,71 @@ void *nativeSpecializeAppProcess_orig = nullptr;
|
|||||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||||
args.mount_data_dirs = &mount_data_dirs;
|
args.mount_data_dirs = &mount_data_dirs;
|
||||||
args.mount_storage_dirs = &mount_storage_dirs;
|
args.mount_storage_dirs = &mount_storage_dirs;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeSpecializeAppProcess_pre();
|
ctx.nativeSpecializeAppProcess_pre();
|
||||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_r)>(nativeSpecializeAppProcess_orig)(
|
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
|
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();
|
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) {
|
[[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);
|
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_child_zygote = &is_child_zygote;
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeSpecializeAppProcess_pre();
|
ctx.nativeSpecializeAppProcess_pre();
|
||||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_samsung_q)>(nativeSpecializeAppProcess_orig)(
|
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
|
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();
|
ctx.nativeSpecializeAppProcess_post();
|
||||||
}
|
}
|
||||||
const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
|
std::array nativeSpecializeAppProcess_methods = {
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeSpecializeAppProcess",
|
"nativeSpecializeAppProcess",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||||
(void *) &nativeSpecializeAppProcess_q
|
(void *) &nativeSpecializeAppProcess_q
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeSpecializeAppProcess",
|
"nativeSpecializeAppProcess",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
|
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
|
||||||
(void *) &nativeSpecializeAppProcess_q_alt
|
(void *) &nativeSpecializeAppProcess_q_alt
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeSpecializeAppProcess",
|
"nativeSpecializeAppProcess",
|
||||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
|
"(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
|
(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",
|
"nativeSpecializeAppProcess",
|
||||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||||
(void *) &nativeSpecializeAppProcess_samsung_q
|
(void *) &nativeSpecializeAppProcess_samsung_q
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods);
|
|
||||||
|
|
||||||
void *nativeForkSystemServer_orig = nullptr;
|
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) {
|
[[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);
|
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkSystemServer_pre();
|
ctx.nativeForkSystemServer_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
|
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
|
||||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
|
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
|
||||||
@@ -270,9 +284,7 @@ void *nativeForkSystemServer_orig = nullptr;
|
|||||||
}
|
}
|
||||||
[[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) {
|
[[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);
|
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||||
HookContext ctx;
|
ZygiskContext ctx(env, &args);
|
||||||
ctx.env = env;
|
|
||||||
ctx.args = { &args };
|
|
||||||
ctx.nativeForkSystemServer_pre();
|
ctx.nativeForkSystemServer_pre();
|
||||||
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
|
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
|
||||||
env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities
|
env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities
|
||||||
@@ -280,45 +292,48 @@ void *nativeForkSystemServer_orig = nullptr;
|
|||||||
ctx.nativeForkSystemServer_post();
|
ctx.nativeForkSystemServer_post();
|
||||||
return ctx.pid;
|
return ctx.pid;
|
||||||
}
|
}
|
||||||
const JNINativeMethod nativeForkSystemServer_methods[] = {
|
std::array nativeForkSystemServer_methods = {
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkSystemServer",
|
"nativeForkSystemServer",
|
||||||
"(II[II[[IJJ)I",
|
"(II[II[[IJJ)I",
|
||||||
(void *) &nativeForkSystemServer_l
|
(void *) &nativeForkSystemServer_l
|
||||||
},
|
},
|
||||||
{
|
JNINativeMethod {
|
||||||
"nativeForkSystemServer",
|
"nativeForkSystemServer",
|
||||||
"(II[IIII[[IJJ)I",
|
"(II[IIII[[IJJ)I",
|
||||||
(void *) &nativeForkSystemServer_samsung_q
|
(void *) &nativeForkSystemServer_samsung_q
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);
|
|
||||||
|
|
||||||
unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {
|
|
||||||
unique_ptr<JNINativeMethod[]> newMethods;
|
|
||||||
int clz_id = -1;
|
|
||||||
int hook_cnt = 0;
|
|
||||||
do {
|
|
||||||
if (className == "com/android/internal/os/Zygote"sv) {
|
|
||||||
clz_id = 0;
|
|
||||||
hook_cnt = 3;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (false);
|
|
||||||
if (hook_cnt) {
|
|
||||||
newMethods = make_unique<JNINativeMethod[]>(numMethods);
|
|
||||||
memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);
|
|
||||||
}
|
|
||||||
auto &class_map = (*jni_method_map)[className];
|
|
||||||
for (int i = 0; i < numMethods; ++i) {
|
|
||||||
if (hook_cnt && clz_id == 0) {
|
|
||||||
HOOK_JNI(nativeForkAndSpecialize)
|
|
||||||
HOOK_JNI(nativeSpecializeAppProcess)
|
|
||||||
HOOK_JNI(nativeForkSystemServer)
|
|
||||||
}
|
|
||||||
class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;
|
|
||||||
}
|
|
||||||
return newMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // 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,31 +0,0 @@
|
|||||||
#include "memory.hpp"
|
|
||||||
|
|
||||||
namespace jni_hook {
|
|
||||||
|
|
||||||
// We know our minimum alignment is WORD size (size of pointer)
|
|
||||||
static constexpr size_t ALIGN = sizeof(long);
|
|
||||||
|
|
||||||
// 4MB is more than enough
|
|
||||||
static constexpr size_t CAPACITY = (1 << 22);
|
|
||||||
|
|
||||||
// No need to be thread safe as the initial mmap always happens on the main thread
|
|
||||||
static uint8_t *_area = nullptr;
|
|
||||||
|
|
||||||
static std::atomic<uint8_t *> _curr = nullptr;
|
|
||||||
|
|
||||||
void *memory_block::allocate(size_t sz) {
|
|
||||||
if (!_area) {
|
|
||||||
// Memory will not actually be allocated because physical pages are mapped in on-demand
|
|
||||||
_area = static_cast<uint8_t *>(mmap(
|
|
||||||
nullptr, CAPACITY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
|
|
||||||
_curr = _area;
|
|
||||||
}
|
|
||||||
return _curr.fetch_add(align_to(sz, ALIGN));
|
|
||||||
}
|
|
||||||
|
|
||||||
void memory_block::release() {
|
|
||||||
if (_area)
|
|
||||||
munmap(_area, CAPACITY);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace jni_hook
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <sys/mman.h>
|
|
||||||
|
|
||||||
#pragma clang diagnostic push
|
|
||||||
#include <parallel_hashmap/phmap.h>
|
|
||||||
#pragma clang diagnostic pop
|
|
||||||
|
|
||||||
#include "misc.hpp"
|
|
||||||
|
|
||||||
namespace jni_hook {
|
|
||||||
|
|
||||||
struct memory_block {
|
|
||||||
static void *allocate(size_t sz);
|
|
||||||
static void deallocate(void *, size_t) { /* Monotonic increase */ }
|
|
||||||
static void release();
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class T>
|
|
||||||
using allocator = stateless_allocator<T, memory_block>;
|
|
||||||
|
|
||||||
using string = std::basic_string<char, std::char_traits<char>, allocator<char>>;
|
|
||||||
|
|
||||||
// Use node_hash_map since it will use less memory because we are using a monotonic allocator
|
|
||||||
template<class K, class V>
|
|
||||||
using hash_map = phmap::node_hash_map<K, V,
|
|
||||||
phmap::priv::hash_default_hash<K>,
|
|
||||||
phmap::priv::hash_default_eq<K>,
|
|
||||||
allocator<std::pair<const K, V>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
template<class K, class V>
|
|
||||||
using tree_map = std::map<K, V,
|
|
||||||
std::less<K>,
|
|
||||||
allocator<std::pair<const K, V>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
} // namespace jni_hook
|
|
||||||
|
|
||||||
// Provide heterogeneous lookup for jni_hook::string
|
|
||||||
namespace phmap::priv {
|
|
||||||
template <> struct HashEq<jni_hook::string> : StringHashEqT<char> {};
|
|
||||||
} // namespace phmap::priv
|
|
||||||
@@ -1,44 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void parse_mnt(const char* file, const std::function<bool(mntent*)>& fn) {
|
|
||||||
auto fp = sFILE(setmntent(file, "re"), endmntent);
|
|
||||||
if (fp) {
|
|
||||||
mntent mentry{};
|
|
||||||
char buf[PATH_MAX];
|
|
||||||
while (getmntent_r(fp.get(), &mentry, buf, sizeof(buf))) {
|
|
||||||
if (!fn(&mentry))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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; });
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct HookContext;
|
struct ZygiskContext;
|
||||||
struct ZygiskModule;
|
struct ZygiskModule;
|
||||||
|
|
||||||
struct AppSpecializeArgs_v1;
|
struct AppSpecializeArgs_v1;
|
||||||
@@ -44,6 +44,7 @@ namespace {
|
|||||||
jobjectArray *whitelisted_data_info_list = nullptr;
|
jobjectArray *whitelisted_data_info_list = nullptr;
|
||||||
jboolean *mount_data_dirs = nullptr;
|
jboolean *mount_data_dirs = nullptr;
|
||||||
jboolean *mount_storage_dirs = nullptr;
|
jboolean *mount_storage_dirs = nullptr;
|
||||||
|
jboolean *mount_sysprop_overrides = nullptr;
|
||||||
|
|
||||||
AppSpecializeArgs_v3(
|
AppSpecializeArgs_v3(
|
||||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||||
@@ -111,12 +112,12 @@ namespace {
|
|||||||
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
|
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
|
||||||
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
|
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
|
||||||
|
|
||||||
PROCESS_IS_SYS_UI = (1u << 29),
|
PROCESS_IS_MANAGER = (1u << 28),
|
||||||
DENYLIST_ENFORCING = (1u << 30),
|
PROCESS_ROOT_IS_KSU = (1u << 29),
|
||||||
PROCESS_IS_MAGISK_APP = (1u << 31),
|
PROCESS_ROOT_IS_MAGISK = (1u << 30),
|
||||||
|
PROCESS_IS_SYS_UI = (1u << 31),
|
||||||
|
|
||||||
UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING),
|
PRIVATE_MASK = PROCESS_IS_SYS_UI
|
||||||
PRIVATE_MASK = (PROCESS_IS_SYS_UI | DENYLIST_ENFORCING | PROCESS_IS_MAGISK_APP)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct api_abi_base {
|
struct api_abi_base {
|
||||||
|
|||||||
86
loader/src/injector/unmount.cpp
Normal file
86
loader/src/injector/unmount.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,4 +8,7 @@ extern void *self_handle;
|
|||||||
|
|
||||||
void hook_functions();
|
void hook_functions();
|
||||||
|
|
||||||
void revert_unmount();
|
void revert_unmount_ksu();
|
||||||
|
|
||||||
|
void revert_unmount_magisk();
|
||||||
|
|
||||||
|
|||||||
@@ -1,100 +0,0 @@
|
|||||||
#include <string_view>
|
|
||||||
#include <sys/system_properties.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
#include "daemon.h"
|
|
||||||
#include "dl.h"
|
|
||||||
#include "logging.h"
|
|
||||||
#include "native_bridge_callbacks.h"
|
|
||||||
|
|
||||||
extern "C" [[gnu::visibility("default")]]
|
|
||||||
uint8_t NativeBridgeItf[sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0};
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr auto kZygoteProcesses = {"zygote", "zygote32", "zygote64", "usap32", "usap64"};
|
|
||||||
constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libinjector.so";
|
|
||||||
|
|
||||||
void* sOriginalBridge = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
__used __attribute__((destructor))
|
|
||||||
void Destructor() {
|
|
||||||
if (sOriginalBridge) {
|
|
||||||
dlclose(sOriginalBridge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
__used __attribute__((constructor))
|
|
||||||
void Constructor() {
|
|
||||||
if (getuid() != 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string_view cmdline = getprogname();
|
|
||||||
if (std::none_of(
|
|
||||||
kZygoteProcesses.begin(), kZygoteProcesses.end(),
|
|
||||||
[&](const char* p) { return cmdline == p; }
|
|
||||||
)) {
|
|
||||||
LOGW("Not started as zygote (cmdline=%s)", cmdline.data());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string native_bridge;
|
|
||||||
do {
|
|
||||||
if (!zygiskd::PingHeartbeat()) break;
|
|
||||||
#ifdef NDEBUG
|
|
||||||
logging::setfd(zygiskd::RequestLogcatFd());
|
|
||||||
#endif
|
|
||||||
LOGI("Read native bridge");
|
|
||||||
native_bridge = zygiskd::ReadNativeBridge();
|
|
||||||
|
|
||||||
LOGI("Load injector");
|
|
||||||
auto handle = DlopenExt(kInjector, RTLD_NOW);
|
|
||||||
if (handle == nullptr) {
|
|
||||||
LOGE("Failed to dlopen injector: %s", dlerror());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
auto entry = dlsym(handle, "entry");
|
|
||||||
if (entry == nullptr) {
|
|
||||||
LOGE("Failed to dlsym injector entry: %s", dlerror());
|
|
||||||
dlclose(handle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
reinterpret_cast<void (*)(void*)>(entry)(handle);
|
|
||||||
} while (false);
|
|
||||||
|
|
||||||
do {
|
|
||||||
if (native_bridge.empty() || native_bridge == "0") break;
|
|
||||||
|
|
||||||
LOGI("Load original native bridge: %s", native_bridge.data());
|
|
||||||
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
|
|
||||||
if (sOriginalBridge == nullptr) {
|
|
||||||
LOGE("dlopen failed: %s", dlerror());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
|
|
||||||
if (original_native_bridge_itf == nullptr) {
|
|
||||||
LOGE("dlsym failed: %s", dlerror());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
long sdk = 0;
|
|
||||||
char value[PROP_VALUE_MAX + 1];
|
|
||||||
if (__system_property_get("ro.build.version.sdk", value) > 0) {
|
|
||||||
sdk = strtol(value, nullptr, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto callbacks_size = 0;
|
|
||||||
if (sdk >= __ANDROID_API_R__) {
|
|
||||||
callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>);
|
|
||||||
} else if (sdk == __ANDROID_API_Q__) {
|
|
||||||
callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_Q__>);
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size);
|
|
||||||
} while (false);
|
|
||||||
|
|
||||||
logging::setfd(-1);
|
|
||||||
}
|
|
||||||
55
loader/src/ptracer/main.cpp
Normal file
55
loader/src/ptracer/main.cpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
loader/src/ptracer/main.hpp
Normal file
21
loader/src/ptracer/main.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#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);
|
||||||
608
loader/src/ptracer/monitor.cpp
Normal file
608
loader/src/ptracer/monitor.cpp
Normal file
@@ -0,0 +1,608 @@
|
|||||||
|
#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");
|
||||||
|
}
|
||||||
218
loader/src/ptracer/ptracer.cpp
Normal file
218
loader/src/ptracer/ptracer.cpp
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
435
loader/src/ptracer/utils.cpp
Normal file
435
loader/src/ptracer/utils.cpp
Normal file
@@ -0,0 +1,435 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
124
loader/src/ptracer/utils.hpp
Normal file
124
loader/src/ptracer/utils.hpp
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#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);
|
||||||
2
module/.gitignore
vendored
Normal file
2
module/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
public_key
|
||||||
|
private_key
|
||||||
@@ -1,16 +1,34 @@
|
|||||||
|
import android.databinding.tool.ext.capitalizeUS
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
|
||||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
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 {
|
plugins {
|
||||||
id("com.android.library")
|
alias(libs.plugins.agp.lib)
|
||||||
}
|
}
|
||||||
|
|
||||||
val moduleId: String by rootProject.extra
|
val moduleId: String by rootProject.extra
|
||||||
val moduleName: String by rootProject.extra
|
val moduleName: String by rootProject.extra
|
||||||
val verCode: Int by rootProject.extra
|
val verCode: Int by rootProject.extra
|
||||||
val verName: String 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 {
|
android.buildFeatures {
|
||||||
androidResources = false
|
androidResources = false
|
||||||
@@ -18,23 +36,23 @@ android.buildFeatures {
|
|||||||
}
|
}
|
||||||
|
|
||||||
androidComponents.onVariants { variant ->
|
androidComponents.onVariants { variant ->
|
||||||
val variantLowered = variant.name.toLowerCase()
|
val variantLowered = variant.name.lowercase()
|
||||||
val variantCapped = variant.name.capitalize()
|
val variantCapped = variant.name.capitalizeUS()
|
||||||
val buildTypeLowered = variant.buildType?.toLowerCase()
|
val buildTypeLowered = variant.buildType?.lowercase()
|
||||||
|
|
||||||
val moduleDir = "$buildDir/outputs/module/$variantLowered"
|
val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered")
|
||||||
val zipFileName = "$moduleName-$verName-$verCode-$buildTypeLowered.zip".replace(' ', '-')
|
val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-')
|
||||||
|
|
||||||
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
|
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
|
||||||
group = "module"
|
group = "module"
|
||||||
dependsOn(
|
dependsOn(
|
||||||
":loader:assemble$variantCapped",
|
":loader:assemble$variantCapped",
|
||||||
":zygiskd:cargoBuild",
|
":zygiskd:buildAndStrip",
|
||||||
)
|
)
|
||||||
into(moduleDir)
|
into(moduleDir)
|
||||||
from("${rootProject.projectDir}/README.md")
|
from("${rootProject.projectDir}/README.md")
|
||||||
from("$projectDir/src") {
|
from("$projectDir/src") {
|
||||||
exclude("module.prop", "customize.sh", "daemon.sh")
|
exclude("module.prop", "customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh", "mazoku")
|
||||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||||
}
|
}
|
||||||
from("$projectDir/src") {
|
from("$projectDir/src") {
|
||||||
@@ -42,33 +60,132 @@ androidComponents.onVariants { variant ->
|
|||||||
expand(
|
expand(
|
||||||
"moduleId" to moduleId,
|
"moduleId" to moduleId,
|
||||||
"moduleName" to moduleName,
|
"moduleName" to moduleName,
|
||||||
"versionName" to "$verName ($verCode)",
|
"versionName" to "$verName ($verCode-$commitHash-$variantLowered)",
|
||||||
"versionCode" to verCode,
|
"versionCode" to verCode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
from("$projectDir/src/mazoku")
|
||||||
from("$projectDir/src") {
|
from("$projectDir/src") {
|
||||||
include("customize.sh", "daemon.sh")
|
include("customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh")
|
||||||
val tokens = mapOf(
|
val tokens = mapOf(
|
||||||
"DEBUG" to if (buildTypeLowered == "debug") "true" else "false"
|
"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<ReplaceTokens>("tokens" to tokens)
|
||||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||||
}
|
}
|
||||||
into("bin") {
|
into("bin") {
|
||||||
from(project(":zygiskd").buildDir.path + "/rustJniLibs/android")
|
from(project(":zygiskd").layout.buildDirectory.file("rustJniLibs/android"))
|
||||||
|
include("**/zygiskd")
|
||||||
}
|
}
|
||||||
into("lib") {
|
into("lib") {
|
||||||
from("${project(":loader").buildDir}/intermediates/stripped_native_libs/$variantLowered/out/lib")
|
from(project(":loader").layout.buildDirectory.file("intermediates/stripped_native_libs/$variantLowered/out/lib"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val root = moduleDir.get()
|
||||||
|
|
||||||
doLast {
|
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 {
|
fileTree(moduleDir).visit {
|
||||||
if (isDirectory) return@visit
|
if (isDirectory) return@visit
|
||||||
val md = MessageDigest.getInstance("SHA-256")
|
val md = MessageDigest.getInstance("SHA-256")
|
||||||
file.forEachBlock(4096) { bytes, size ->
|
file.forEachBlock(4096) { bytes, size ->
|
||||||
md.update(bytes, 0, size)
|
md.update(bytes, 0, size)
|
||||||
}
|
}
|
||||||
file(file.path + ".sha256").writeText(org.apache.commons.codec.binary.Hex.encodeHexString(md.digest()))
|
file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,7 +194,7 @@ androidComponents.onVariants { variant ->
|
|||||||
group = "module"
|
group = "module"
|
||||||
dependsOn(prepareModuleFilesTask)
|
dependsOn(prepareModuleFilesTask)
|
||||||
archiveFileName.set(zipFileName)
|
archiveFileName.set(zipFileName)
|
||||||
destinationDirectory.set(file("$buildDir/outputs/release"))
|
destinationDirectory.set(layout.buildDirectory.file("outputs/release").get().asFile)
|
||||||
from(moduleDir)
|
from(moduleDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +223,7 @@ androidComponents.onVariants { variant ->
|
|||||||
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
|
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
|
||||||
group = "module"
|
group = "module"
|
||||||
dependsOn(pushTask)
|
dependsOn(pushTask)
|
||||||
commandLine("adb", "shell", "su", "-c", "KSU=true magisk --install-module /data/local/tmp/$zipFileName")
|
commandLine("adb", "shell", "su", "-c", "magisk --install-module /data/local/tmp/$zipFileName")
|
||||||
}
|
}
|
||||||
|
|
||||||
task<Exec>("installKsuAndReboot$variantCapped") {
|
task<Exec>("installKsuAndReboot$variantCapped") {
|
||||||
|
|||||||
@@ -2,32 +2,60 @@
|
|||||||
SKIPUNZIP=1
|
SKIPUNZIP=1
|
||||||
|
|
||||||
DEBUG=@DEBUG@
|
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" == "true" ]; then
|
if [ "$BOOTMODE" ] && [ "$KSU" ]; then
|
||||||
ui_print "- Installing from KernelSU app"
|
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
|
else
|
||||||
ui_print "*********************************************************"
|
ui_print "*********************************************************"
|
||||||
ui_print "! Install from recovery or Magisk is NOT supported"
|
ui_print "! Install from recovery is not supported"
|
||||||
ui_print "! Please install from KernelSU app"
|
ui_print "! Please install from KernelSU or Magisk app"
|
||||||
abort "*********************************************************"
|
|
||||||
fi
|
|
||||||
|
|
||||||
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
|
||||||
ui_print "- Installing Zygisksu $VERSION"
|
|
||||||
|
|
||||||
# check KernelSU
|
|
||||||
ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)"
|
|
||||||
if [ "$KSU_KERNEL_VER_CODE" -lt 10575 ]; then
|
|
||||||
ui_print "*********************************************************"
|
|
||||||
ui_print "! KernelSU version is too old!"
|
|
||||||
ui_print "! Please update KernelSU to latest version"
|
|
||||||
abort "*********************************************************"
|
abort "*********************************************************"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
|
||||||
|
ui_print "- Installing Zygisk Next $VERSION"
|
||||||
|
|
||||||
# check android
|
# check android
|
||||||
if [ "$API" -lt 29 ]; then
|
if [ "$API" -lt 26 ]; then
|
||||||
ui_print "! Unsupported sdk: $API"
|
ui_print "! Unsupported sdk: $API"
|
||||||
abort "! Minimal supported sdk is 29 (Android 10)"
|
abort "! Minimal supported sdk is 26 (Android 8.0)"
|
||||||
else
|
else
|
||||||
ui_print "- Device sdk: $API"
|
ui_print "- Device sdk: $API"
|
||||||
fi
|
fi
|
||||||
@@ -52,86 +80,69 @@ extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
|
|||||||
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
|
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
|
||||||
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
|
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
|
||||||
|
|
||||||
ui_print "- Checking SELinux patches"
|
if [ "$KSU" ]; then
|
||||||
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
|
ui_print "- Checking SELinux patches"
|
||||||
ui_print "*********************************************************"
|
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
|
||||||
ui_print "! Unable to apply SELinux patches!"
|
ui_print "*********************************************************"
|
||||||
ui_print "! Your kernel may not support SELinux patch fully"
|
ui_print "! Unable to apply SELinux patches!"
|
||||||
abort "*********************************************************"
|
ui_print "! Your kernel may not support SELinux patch fully"
|
||||||
|
abort "*********************************************************"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ui_print "- Extracting module files"
|
ui_print "- Extracting module files"
|
||||||
extract "$ZIPFILE" 'daemon.sh' "$MODPATH"
|
|
||||||
extract "$ZIPFILE" 'module.prop' "$MODPATH"
|
extract "$ZIPFILE" 'module.prop' "$MODPATH"
|
||||||
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
|
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
|
||||||
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
|
extract "$ZIPFILE" 'service.sh' "$MODPATH"
|
||||||
|
extract "$ZIPFILE" 'zygisk-ctl.sh' "$MODPATH"
|
||||||
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
|
extract "$ZIPFILE" 'mazoku' "$MODPATH"
|
||||||
HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true
|
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
|
||||||
|
|
||||||
mkdir "$MODPATH/bin"
|
mkdir "$MODPATH/bin"
|
||||||
mkdir "$MODPATH/system"
|
mkdir "$MODPATH/lib"
|
||||||
[ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib"
|
mkdir "$MODPATH/lib64"
|
||||||
[ "$HAS64BIT" = true ] && mkdir "$MODPATH/system/lib64"
|
mv "$MODPATH/zygisk-ctl.sh" "$MODPATH/bin/zygisk-ctl"
|
||||||
|
|
||||||
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||||
if [ "$HAS32BIT" = true ]; then
|
ui_print "- Extracting x86 libraries"
|
||||||
ui_print "- Extracting x86 libraries"
|
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
|
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true
|
||||||
extract "$ZIPFILE" 'lib/x86/libinjector.so' "$MODPATH/system/lib" true
|
extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'lib/x86/libzygiskloader.so' "$MODPATH/system/lib" true
|
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||||
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$HAS64BIT" = true ]; then
|
ui_print "- Extracting x64 libraries"
|
||||||
ui_print "- Extracting x64 libraries"
|
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
|
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
||||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true
|
||||||
extract "$ZIPFILE" 'lib/x86_64/libinjector.so' "$MODPATH/system/lib64" true
|
extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'lib/x86_64/libzygiskloader.so' "$MODPATH/system/lib64" true
|
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||||
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
|
|
||||||
fi
|
extract "$ZIPFILE" 'machikado.x86' "$MODPATH" true
|
||||||
|
mv "$MODPATH/machikado.x86" "$MODPATH/machikado"
|
||||||
else
|
else
|
||||||
if [ "$HAS32BIT" = true ]; then
|
ui_print "- Extracting arm libraries"
|
||||||
ui_print "- Extracting arm libraries"
|
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
|
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true
|
||||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libinjector.so' "$MODPATH/system/lib" true
|
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygiskloader.so' "$MODPATH/system/lib" true
|
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||||
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$HAS64BIT" = true ]; then
|
ui_print "- Extracting arm64 libraries"
|
||||||
ui_print "- Extracting arm64 libraries"
|
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
|
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
||||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true
|
||||||
extract "$ZIPFILE" 'lib/arm64-v8a/libinjector.so' "$MODPATH/system/lib64" true
|
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||||
extract "$ZIPFILE" 'lib/arm64-v8a/libzygiskloader.so' "$MODPATH/system/lib64" true
|
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||||
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ $DEBUG = false ]; then
|
extract "$ZIPFILE" 'machikado.arm' "$MODPATH" true
|
||||||
ui_print "- Hex patching"
|
mv "$MODPATH/machikado.arm" "$MODPATH/machikado"
|
||||||
SOCKET_PATCH=$(tr -dc 'a-f0-9' </dev/urandom | head -c 18)
|
|
||||||
if [ "$HAS32BIT" = true ]; then
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/bin/zygiskd32"
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib/libinjector.so"
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib/libzygiskloader.so"
|
|
||||||
fi
|
|
||||||
if [ "$HAS64BIT" = true ]; then
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/bin/zygiskd64"
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib64/libinjector.so"
|
|
||||||
sed -i "s/socket_placeholder/$SOCKET_PATCH/g" "$MODPATH/system/lib64/libzygiskloader.so"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ui_print "- Setting permissions"
|
ui_print "- Setting permissions"
|
||||||
chmod 0744 "$MODPATH/daemon.sh"
|
set_perm_recursive "$MODPATH/bin" 0 0 0755 0755
|
||||||
set_perm_recursive "$MODPATH/bin" 0 2000 0755 0755
|
set_perm_recursive "$MODPATH/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0
|
||||||
set_perm_recursive "$MODPATH/system/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
|
||||||
set_perm_recursive "$MODPATH/system/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
|
# 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)
|
HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable)
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/system/bin/sh
|
|
||||||
|
|
||||||
DEBUG=@DEBUG@
|
|
||||||
|
|
||||||
# shellcheck disable=SC2155
|
|
||||||
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
|
|
||||||
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
|
|
||||||
|
|
||||||
log -p i -t "zygisksu" "Start watchdog"
|
|
||||||
resetprop ro.dalvik.vm.native.bridge libzygiskloader.so
|
|
||||||
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1
|
|
||||||
1
module/src/mazoku
Normal file
1
module/src/mazoku
Normal file
@@ -0,0 +1 @@
|
|||||||
|
c—„ˆ]œ‘ „[{Ú‚BÒuÞ5=ÙrEUÕZ„Ê¿èã<¦5ß_oñãMéL•l•¢ÛQ#ÿøœC¾}ù e<>äfjÙ‚/©7³‡Ž(â´g<bþRgÛm±d
|
||||||
@@ -3,4 +3,5 @@ name=${moduleName}
|
|||||||
version=${versionName}
|
version=${versionName}
|
||||||
versionCode=${versionCode}
|
versionCode=${versionCode}
|
||||||
author=Nullptr, 5ec1cff
|
author=Nullptr, 5ec1cff
|
||||||
description=Run Zygisk on KernelSU.
|
description=Standalone implementation of Zygisk.
|
||||||
|
updateJson=https://api.nullptr.icu/android/zygisk-next/static/update.json
|
||||||
|
|||||||
@@ -1,8 +1,47 @@
|
|||||||
#!/system/bin/sh
|
#!/system/bin/sh
|
||||||
|
|
||||||
MODDIR=${0%/*}
|
MODDIR=${0%/*}
|
||||||
|
if [ "$ZYGISK_ENABLED" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
cd $MODDIR
|
cd "$MODDIR"
|
||||||
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
|
|
||||||
|
|
||||||
unshare -m sh -c "./daemon.sh $@&"
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
TMP_PATH=/sbin
|
||||||
|
[ -f /sbin ] || 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,4 +1,6 @@
|
|||||||
allow * tmpfs * *
|
allow * tmpfs * *
|
||||||
|
allow zygote appdomain_tmpfs dir *
|
||||||
|
allow zygote appdomain_tmpfs file *
|
||||||
|
|
||||||
type magisk_file file_type
|
type magisk_file file_type
|
||||||
typeattribute magisk_file mlstrustedobject
|
typeattribute magisk_file mlstrustedobject
|
||||||
@@ -10,3 +12,9 @@ allow * magisk_file lnk_file *
|
|||||||
allow * magisk_file sock_file *
|
allow * magisk_file sock_file *
|
||||||
|
|
||||||
allow system_server system_server process execmem
|
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
|
||||||
|
|||||||
23
module/src/service.sh
Normal file
23
module/src/service.sh
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/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
|
||||||
6
module/src/zygisk-ctl.sh
Normal file
6
module/src/zygisk-ctl.sh
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
MODDIR=${0%/*}/..
|
||||||
|
|
||||||
|
TMP_PATH=/sbin
|
||||||
|
[ -f /sbin ] || TMP_PATH=/debug_ramdisk
|
||||||
|
|
||||||
|
exec $MODDIR/bin/zygisk-ptrace64 ctl $*
|
||||||
@@ -6,10 +6,6 @@ pluginManagement {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
plugins {
|
|
||||||
id("com.android.library") version "7.4.1"
|
|
||||||
id("com.android.application") version "7.4.1"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
@@ -20,7 +16,7 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "ZygiskOnKernelSU"
|
rootProject.name = "ZygiskNext"
|
||||||
include(
|
include(
|
||||||
":loader",
|
":loader",
|
||||||
":module",
|
":module",
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
[build]
|
[build]
|
||||||
target-dir = "build/intermediates/rust"
|
target-dir = "build/intermediates/rust"
|
||||||
|
target = "aarch64-linux-android"
|
||||||
|
|||||||
@@ -3,24 +3,33 @@ name = "zygiskd"
|
|||||||
authors = ["Nullptr"]
|
authors = ["Nullptr"]
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.67"
|
rust-version = "1.69"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
android_logger = "0.12.0"
|
android_logger = "0.13"
|
||||||
anyhow = { version = "1.0.68", features = ["backtrace"] }
|
anyhow = { version = "1.0", features = ["backtrace"] }
|
||||||
clap = { version = "4.1.4", features = ["derive"] }
|
bitflags = { version = "2.3" }
|
||||||
const_format = "0.2.5"
|
const_format = "0.2"
|
||||||
lazy_static = "1.4.0"
|
futures = "0.3"
|
||||||
log = "0.4.17"
|
konst = "0.3"
|
||||||
memfd = "0.6.2"
|
lazy_static = "1.4"
|
||||||
nix = "0.26.2"
|
libc = "0.2"
|
||||||
num_enum = "0.5.9"
|
log = "0.4"
|
||||||
passfd = "0.1.5"
|
memfd = "0.6"
|
||||||
rand = "0.8.5"
|
num_enum = "0.5"
|
||||||
|
passfd = "0.1"
|
||||||
|
proc-maps = "0.3"
|
||||||
|
|
||||||
binder = { path = "src/external/binder_rs/binder" }
|
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] }
|
||||||
|
tokio = { version = "1.28", features = ["full"] }
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
strip = false
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = false
|
||||||
|
debug = true
|
||||||
|
panic = "abort"
|
||||||
opt-level = "z"
|
opt-level = "z"
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
alias(libs.plugins.agp.lib)
|
||||||
id("org.mozilla.rust-android-gradle.rust-android")
|
alias(libs.plugins.rust.android)
|
||||||
}
|
}
|
||||||
|
|
||||||
val verName: String by rootProject.extra
|
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 verCode: Int by rootProject.extra
|
||||||
|
val verName: String by rootProject.extra
|
||||||
|
val commitHash: String by rootProject.extra
|
||||||
|
|
||||||
android.buildFeatures {
|
android.buildFeatures {
|
||||||
androidResources = false
|
androidResources = false
|
||||||
@@ -21,7 +25,43 @@ cargo {
|
|||||||
profile = if (isDebug) "debug" else "release"
|
profile = if (isDebug) "debug" else "release"
|
||||||
exec = { spec, _ ->
|
exec = { spec, _ ->
|
||||||
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
|
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
|
||||||
spec.environment("VERSION_CODE", verCode)
|
spec.environment("MIN_KSU_VERSION", minKsuVersion)
|
||||||
spec.environment("VERSION_NAME", verName)
|
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,47 +1,59 @@
|
|||||||
|
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::ffi::c_void;
|
||||||
use std::os::fd::{FromRawFd, RawFd};
|
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
|
||||||
use std::os::unix::net::UnixStream;
|
use std::os::unix::net::UnixStream;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use anyhow::Result;
|
|
||||||
use nix::libc;
|
|
||||||
use passfd::FdPassingExt;
|
|
||||||
use crate::utils::UnixStreamExt;
|
|
||||||
use crate::dl;
|
|
||||||
|
|
||||||
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
|
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
|
||||||
|
|
||||||
pub fn entry(fd: i32) -> Result<()> {
|
pub fn entry(fd: i32) {
|
||||||
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
|
log::info!("companion entry fd={}", fd);
|
||||||
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
|
||||||
let name = stream.read_string()?;
|
let name = stream.read_string().expect("read name");
|
||||||
let library = stream.recv_fd()?;
|
let library = stream.recv_fd().expect("receive library fd");
|
||||||
let entry = load_module(library)?;
|
let entry = load_module(library).expect("load module");
|
||||||
unsafe { libc::close(library) };
|
unsafe { libc::close(library) };
|
||||||
|
|
||||||
let entry = match entry {
|
let entry = match entry {
|
||||||
Some(entry) => {
|
Some(entry) => {
|
||||||
log::debug!("Companion process created for `{name}`");
|
log::debug!("Companion process created for `{name}`");
|
||||||
stream.write_u8(1)?;
|
stream.write_u8(1).expect("reply 1");
|
||||||
entry
|
entry
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
log::debug!("No companion entry for `{name}`");
|
log::debug!("No companion entry for `{name}`");
|
||||||
stream.write_u8(0)?;
|
stream.write_u8(0).expect("reply 0");
|
||||||
return Ok(());
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let fd = stream.recv_fd()?;
|
if !check_unix_socket(&stream, true) {
|
||||||
log::trace!("New companion request from module `{name}`");
|
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 || {
|
thread::spawn(move || {
|
||||||
|
let st0 = fstat(&stream).expect("failed to stat stream");
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut s = UnixStream::from_raw_fd(fd);
|
entry(stream.as_raw_fd());
|
||||||
match s.write_u8(1) { // Ack
|
}
|
||||||
Ok(_) => entry(fd),
|
// Only close client if it is the same file so we don't
|
||||||
Err(_) => log::warn!("Ack failed?"),
|
// 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);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,48 @@
|
|||||||
use const_format::concatcp;
|
use crate::lp_select;
|
||||||
|
use bitflags::bitflags;
|
||||||
|
use konst::primitive::parse_i32;
|
||||||
|
use konst::unwrap_ctx;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
use num_enum::TryFromPrimitive;
|
use num_enum::TryFromPrimitive;
|
||||||
|
|
||||||
pub const VERSION_NAME: &str = env!("VERSION_NAME");
|
pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION")));
|
||||||
pub const VERSION_CODE: &str = env!("VERSION_CODE");
|
pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION")));
|
||||||
pub const VERSION_FULL: &str = concatcp!(VERSION_NAME, " (", VERSION_CODE, ")");
|
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)]
|
#[cfg(debug_assertions)]
|
||||||
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
|
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
|
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
pub const PATH_MODULES_DIR: &str = "..";
|
||||||
#[macro_export]
|
pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4);
|
||||||
macro_rules! lp_select {
|
pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6);
|
||||||
($lp32:expr, $lp64:expr) => { $lp64 };
|
pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8);
|
||||||
}
|
pub const SYSTEM_SERVER_STARTED: i32 = 10;
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! lp_select {
|
|
||||||
($lp32:expr, $lp64:expr) => { $lp32 };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
|
|
||||||
pub const PROP_SVC_ZYGOTE: &str = "init.svc.zygote";
|
|
||||||
pub const ZYGISK_LOADER: &str = "libzygiskloader.so";
|
|
||||||
|
|
||||||
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
|
|
||||||
|
|
||||||
pub const PATH_MODULE_DIR: &str = "..";
|
|
||||||
pub const PATH_ZYGISKD32: &str = "bin/zygiskd32";
|
|
||||||
pub const PATH_ZYGISKD64: &str = "bin/zygiskd64";
|
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
|
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum DaemonSocketAction {
|
pub enum DaemonSocketAction {
|
||||||
PingHeartbeat,
|
PingHeartbeat,
|
||||||
RequestLogcatFd,
|
RequestLogcatFd,
|
||||||
ReadNativeBridge,
|
GetProcessFlags,
|
||||||
ReadModules,
|
ReadModules,
|
||||||
RequestCompanionSocket,
|
RequestCompanionSocket,
|
||||||
GetModuleDir,
|
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,18 +1,17 @@
|
|||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::ffi::{c_char, c_void};
|
use std::ffi::{c_char, c_void};
|
||||||
use nix::libc;
|
|
||||||
|
|
||||||
const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
|
pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
|
||||||
const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
|
pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
struct AndroidNamespace {
|
pub struct AndroidNamespace {
|
||||||
_unused: [u8; 0],
|
_unused: [u8; 0],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
struct AndroidDlextinfo {
|
pub struct AndroidDlextinfo {
|
||||||
pub flags: u64,
|
pub flags: u64,
|
||||||
pub reserved_addr: *mut c_void,
|
pub reserved_addr: *mut c_void,
|
||||||
pub reserved_size: libc::size_t,
|
pub reserved_size: libc::size_t,
|
||||||
@@ -23,7 +22,7 @@ struct AndroidDlextinfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn android_dlopen_ext(
|
pub fn android_dlopen_ext(
|
||||||
filename: *const c_char,
|
filename: *const c_char,
|
||||||
flags: libc::c_int,
|
flags: libc::c_int,
|
||||||
extinfo: *const AndroidDlextinfo,
|
extinfo: *const AndroidDlextinfo,
|
||||||
@@ -31,13 +30,13 @@ extern "C" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AndroidCreateNamespaceFn = unsafe extern "C" fn(
|
type AndroidCreateNamespaceFn = unsafe extern "C" fn(
|
||||||
*const c_char, // name
|
*const c_char, // name
|
||||||
*const c_char, // ld_library_path
|
*const c_char, // ld_library_path
|
||||||
*const c_char, // default_library_path
|
*const c_char, // default_library_path
|
||||||
u64, // type
|
u64, // type
|
||||||
*const c_char, // permitted_when_isolated_path
|
*const c_char, // permitted_when_isolated_path
|
||||||
*mut AndroidNamespace, // parent
|
*mut AndroidNamespace, // parent
|
||||||
*const c_void, // caller_addr
|
*const c_void, // caller_addr
|
||||||
) -> *mut AndroidNamespace;
|
) -> *mut AndroidNamespace;
|
||||||
|
|
||||||
pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
|
pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
|
||||||
@@ -58,11 +57,15 @@ pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
|
|||||||
libc::RTLD_DEFAULT,
|
libc::RTLD_DEFAULT,
|
||||||
std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(),
|
std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(),
|
||||||
);
|
);
|
||||||
let android_create_namespace_fn: AndroidCreateNamespaceFn = std::mem::transmute(android_create_namespace_fn);
|
let android_create_namespace_fn: AndroidCreateNamespaceFn =
|
||||||
|
std::mem::transmute(android_create_namespace_fn);
|
||||||
let ns = android_create_namespace_fn(
|
let ns = android_create_namespace_fn(
|
||||||
filename, dir, std::ptr::null(),
|
filename,
|
||||||
|
dir,
|
||||||
|
std::ptr::null(),
|
||||||
ANDROID_NAMESPACE_TYPE_SHARED,
|
ANDROID_NAMESPACE_TYPE_SHARED,
|
||||||
std::ptr::null(), std::ptr::null_mut(),
|
std::ptr::null(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
&dlopen as *const _ as *const c_void,
|
&dlopen as *const _ as *const c_void,
|
||||||
);
|
);
|
||||||
if ns != std::ptr::null_mut() {
|
if ns != std::ptr::null_mut() {
|
||||||
@@ -76,7 +79,7 @@ pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
|
|||||||
let result = android_dlopen_ext(filename, flags, &info);
|
let result = android_dlopen_ext(filename, flags, &info);
|
||||||
if result.is_null() {
|
if result.is_null() {
|
||||||
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
|
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
|
||||||
bail!("dlopen failed: {}", e);
|
bail!(e);
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
1
zygiskd/src/external/binder_rs
vendored
1
zygiskd/src/external/binder_rs
vendored
Submodule zygiskd/src/external/binder_rs deleted from 6d958bb94a
@@ -1,31 +1,11 @@
|
|||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
mod companion;
|
mod companion;
|
||||||
mod constants;
|
mod constants;
|
||||||
mod dl;
|
mod dl;
|
||||||
|
mod root_impl;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod watchdog;
|
|
||||||
mod zygiskd;
|
mod zygiskd;
|
||||||
|
|
||||||
use clap::{Subcommand, Parser};
|
use crate::constants::ZKSU_VERSION;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[command(author, version = constants::VERSION_FULL, about, long_about = None)]
|
|
||||||
struct Args {
|
|
||||||
#[command(subcommand)]
|
|
||||||
command: Commands,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
|
||||||
enum Commands {
|
|
||||||
/// Start zygisk watchdog
|
|
||||||
Watchdog,
|
|
||||||
/// Start zygisk daemon
|
|
||||||
Daemon,
|
|
||||||
/// Start zygisk companion
|
|
||||||
Companion { fd: i32 },
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn init_android_logger(tag: &str) {
|
fn init_android_logger(tag: &str) {
|
||||||
android_logger::init_once(
|
android_logger::init_once(
|
||||||
@@ -35,19 +15,31 @@ fn init_android_logger(tag: &str) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
fn main() {
|
||||||
let process = std::env::args().next().unwrap();
|
let process = std::env::args().next().unwrap();
|
||||||
let nice_name = process.split('/').last().unwrap();
|
let nice_name = process.split('/').last().unwrap();
|
||||||
init_android_logger(nice_name);
|
init_android_logger(nice_name);
|
||||||
|
|
||||||
let cli = Args::parse();
|
start();
|
||||||
let result = match cli.command {
|
|
||||||
Commands::Watchdog => watchdog::entry(),
|
|
||||||
Commands::Daemon => zygiskd::entry(),
|
|
||||||
Commands::Companion { fd } => companion::entry(fd),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(e) = &result {
|
|
||||||
log::error!("Crashed: {}\n{}", e, e.backtrace());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
77
zygiskd/src/root_impl/kernelsu.rs
Normal file
77
zygiskd/src/root_impl/kernelsu.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
124
zygiskd/src/root_impl/magisk.rs
Normal file
124
zygiskd/src/root_impl/magisk.rs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
64
zygiskd/src/root_impl/mod.rs
Normal file
64
zygiskd/src/root_impl/mod.rs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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,13 +1,76 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nix::unistd::gettid;
|
use rustix::net::{
|
||||||
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
|
bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix,
|
||||||
use std::os::fd::FromRawFd;
|
SocketType,
|
||||||
use std::os::unix::net::UnixListener;
|
};
|
||||||
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
|
use rustix::path::Arg;
|
||||||
use rand::distributions::{Alphanumeric, DistString};
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn random_string() -> String {
|
#[cfg(target_pointer_width = "64")]
|
||||||
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
|
#[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<()> {
|
pub fn set_socket_create_context(context: &str) -> Result<()> {
|
||||||
@@ -15,22 +78,78 @@ pub fn set_socket_create_context(context: &str) -> Result<()> {
|
|||||||
match fs::write(path, context) {
|
match fs::write(path, context) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let path = format!("/proc/self/task/{}/attr/sockcreate", gettid().as_raw());
|
let path = format!(
|
||||||
|
"/proc/self/task/{}/attr/sockcreate",
|
||||||
|
gettid().as_raw_nonzero()
|
||||||
|
);
|
||||||
fs::write(path, context)?;
|
fs::write(path, context)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_native_bridge() -> String {
|
pub fn get_current_attr() -> Result<String> {
|
||||||
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
|
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<()> {
|
pub fn set_property(name: &str, value: &str) -> Result<()> {
|
||||||
Command::new("resetprop")
|
let name = CString::new(name)?;
|
||||||
.arg(name)
|
let value = CString::new(value)?;
|
||||||
.arg(value)
|
unsafe {
|
||||||
.spawn()?.wait()?;
|
__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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +159,7 @@ pub trait UnixStreamExt {
|
|||||||
fn read_usize(&mut self) -> Result<usize>;
|
fn read_usize(&mut self) -> Result<usize>;
|
||||||
fn read_string(&mut self) -> Result<String>;
|
fn read_string(&mut self) -> Result<String>;
|
||||||
fn write_u8(&mut self, value: u8) -> Result<()>;
|
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_usize(&mut self, value: usize) -> Result<()>;
|
||||||
fn write_string(&mut self, value: &str) -> Result<()>;
|
fn write_string(&mut self, value: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
@@ -75,6 +195,11 @@ impl UnixStreamExt for UnixStream {
|
|||||||
Ok(())
|
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<()> {
|
fn write_usize(&mut self, value: usize) -> Result<()> {
|
||||||
self.write_all(&value.to_ne_bytes())?;
|
self.write_all(&value.to_ne_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -87,12 +212,53 @@ impl UnixStreamExt for UnixStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
|
pub fn unix_listener_from_path(path: &str) -> Result<UnixListener> {
|
||||||
pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
|
let _ = fs::remove_file(path);
|
||||||
let addr = UnixAddr::new_abstract(name.as_bytes())?;
|
let addr = SocketAddrUnix::new(path)?;
|
||||||
let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
|
let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?;
|
||||||
nix::sys::socket::bind(socket, &addr)?;
|
bind_unix(&socket, &addr)?;
|
||||||
nix::sys::socket::listen(socket, 2)?;
|
listen(&socket, 2)?;
|
||||||
let listener = unsafe { UnixListener::from_raw_fd(socket) };
|
chcon(path, "u:object_r:magisk_file:s0")?;
|
||||||
Ok(listener)
|
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,105 +0,0 @@
|
|||||||
use crate::{constants, utils};
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use nix::unistd::{getgid, getuid, Pid};
|
|
||||||
use std::process::{Child, Command};
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::{fs, thread};
|
|
||||||
use std::os::unix::net::UnixListener;
|
|
||||||
use std::time::Duration;
|
|
||||||
use binder::IBinder;
|
|
||||||
use nix::sys::signal::{kill, Signal};
|
|
||||||
|
|
||||||
static mut LOCK: Option<UnixListener> = None;
|
|
||||||
|
|
||||||
pub fn entry() -> Result<()> {
|
|
||||||
log::info!("Start zygisksu watchdog");
|
|
||||||
check_permission()?;
|
|
||||||
ensure_single_instance()?;
|
|
||||||
spawn_daemon()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_permission() -> Result<()> {
|
|
||||||
log::info!("Check permission");
|
|
||||||
let uid = getuid();
|
|
||||||
if uid.as_raw() != 0 {
|
|
||||||
bail!("UID is not 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
let gid = getgid();
|
|
||||||
if gid.as_raw() != 0 {
|
|
||||||
bail!("GID is not 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
let context = fs::read_to_string("/proc/self/attr/current")?;
|
|
||||||
let context = context.trim_end_matches('\0');
|
|
||||||
if context != "u:r:su:s0" && context != "u:r:magisk:s0" {
|
|
||||||
bail!("SELinux context incorrect: {context}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ensure_single_instance() -> Result<()> {
|
|
||||||
log::info!("Ensure single instance");
|
|
||||||
let name = String::from("zygiskwd") + constants::SOCKET_PLACEHOLDER;
|
|
||||||
match utils::abstract_namespace_socket(&name) {
|
|
||||||
Ok(socket) => unsafe { LOCK = Some(socket) },
|
|
||||||
Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?")
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_daemon() -> Result<()> {
|
|
||||||
let mut lives = 5;
|
|
||||||
loop {
|
|
||||||
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
|
|
||||||
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
|
|
||||||
let mut child_ids = vec![];
|
|
||||||
let (sender, receiver) = mpsc::channel();
|
|
||||||
let mut spawn = |mut daemon: Child| {
|
|
||||||
child_ids.push(daemon.id());
|
|
||||||
let sender = sender.clone();
|
|
||||||
thread::spawn(move || {
|
|
||||||
let result = daemon.wait().unwrap();
|
|
||||||
log::error!("Daemon process {} died: {}", daemon.id(), result);
|
|
||||||
drop(daemon);
|
|
||||||
let _ = sender.send(());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
if let Ok(it) = daemon32 { spawn(it) }
|
|
||||||
if let Ok(it) = daemon64 { spawn(it) }
|
|
||||||
|
|
||||||
let mut binder = loop {
|
|
||||||
if receiver.try_recv().is_ok() {
|
|
||||||
bail!("Daemon died before system server ready");
|
|
||||||
}
|
|
||||||
match binder::get_service("activity") {
|
|
||||||
Some(binder) => break binder,
|
|
||||||
None => {
|
|
||||||
log::trace!("System server not ready, wait for 1s...");
|
|
||||||
thread::sleep(Duration::from_secs(1));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
log::info!("System server ready, restore native bridge");
|
|
||||||
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
if receiver.try_recv().is_ok() || binder.ping_binder().is_err() { break; }
|
|
||||||
thread::sleep(Duration::from_secs(1))
|
|
||||||
}
|
|
||||||
for child in child_ids {
|
|
||||||
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
|
|
||||||
}
|
|
||||||
|
|
||||||
lives -= 1;
|
|
||||||
if lives == 0 {
|
|
||||||
bail!("Too many crashes, abort");
|
|
||||||
}
|
|
||||||
|
|
||||||
log::error!("Restarting zygote...");
|
|
||||||
utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?;
|
|
||||||
utils::set_property(constants::PROP_SVC_ZYGOTE, "restart")?;
|
|
||||||
thread::sleep(Duration::from_secs(2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +1,167 @@
|
|||||||
use crate::constants::DaemonSocketAction;
|
use crate::constants::{DaemonSocketAction, ProcessFlags};
|
||||||
use crate::utils::UnixStreamExt;
|
use crate::utils::{check_unix_socket, LateInit, UnixStreamExt};
|
||||||
use crate::{constants, lp_select, utils};
|
use crate::{constants, lp_select, root_impl, utils};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use memfd::Memfd;
|
use log::{debug, error, info, trace, warn};
|
||||||
use nix::{
|
|
||||||
fcntl::{fcntl, FcntlArg, FdFlag},
|
|
||||||
libc::self,
|
|
||||||
};
|
|
||||||
use passfd::FdPassingExt;
|
use passfd::FdPassingExt;
|
||||||
use std::sync::{Arc, Mutex};
|
use rustix::fs::{fcntl_setfd, FdFlags};
|
||||||
use std::thread;
|
|
||||||
use std::ffi::c_char;
|
|
||||||
use std::fs;
|
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::{
|
use std::os::unix::{
|
||||||
net::{UnixListener, UnixStream},
|
net::{UnixListener, UnixStream},
|
||||||
prelude::AsRawFd,
|
prelude::AsRawFd,
|
||||||
};
|
};
|
||||||
use std::os::unix::process::CommandExt;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::{exit, Command};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
|
||||||
struct Module {
|
struct Module {
|
||||||
name: String,
|
name: String,
|
||||||
memfd: Memfd,
|
lib_fd: OwnedFd,
|
||||||
companion: Mutex<Option<UnixStream>>,
|
companion: Mutex<Option<Option<UnixStream>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Context {
|
struct Context {
|
||||||
native_bridge: String,
|
|
||||||
modules: Vec<Module>,
|
modules: Vec<Module>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entry() -> Result<()> {
|
static TMP_PATH: LateInit<String> = LateInit::new();
|
||||||
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
|
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()?;
|
let arch = get_arch()?;
|
||||||
log::debug!("Daemon architecture: {arch}");
|
debug!("Daemon architecture: {arch}");
|
||||||
|
|
||||||
log::info!("Load modules");
|
|
||||||
let modules = load_modules(arch)?;
|
let modules = load_modules(arch)?;
|
||||||
|
|
||||||
let context = Context {
|
{
|
||||||
native_bridge: utils::get_native_bridge(),
|
let mut msg = Vec::<u8>::new();
|
||||||
modules,
|
let info = match root_impl::get_impl() {
|
||||||
};
|
root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk => {
|
||||||
let context = Arc::new(context);
|
msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes());
|
||||||
|
let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect();
|
||||||
log::info!("Create socket");
|
format!(
|
||||||
let listener = create_daemon_socket()?;
|
"Root: {:?},module({}): {}",
|
||||||
|
root_impl::get_impl(),
|
||||||
log::info!("Handle zygote connections");
|
modules.len(),
|
||||||
for stream in listener.incoming() {
|
module_names.join(",")
|
||||||
let stream = stream?;
|
)
|
||||||
let context = Arc::clone(&context);
|
|
||||||
thread::spawn(move || {
|
|
||||||
if let Err(e) = handle_daemon_action(stream, &context) {
|
|
||||||
log::warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
|
|
||||||
}
|
}
|
||||||
});
|
_ => {
|
||||||
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_arch() -> Result<&'static str> {
|
fn get_arch() -> Result<&'static str> {
|
||||||
let output = Command::new("getprop").arg("ro.product.cpu.abi").output()?;
|
let system_arch = utils::get_property("ro.product.cpu.abi")?;
|
||||||
let system_arch = String::from_utf8(output.stdout)?;
|
|
||||||
if system_arch.contains("arm") {
|
if system_arch.contains("arm") {
|
||||||
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"))
|
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
|
||||||
}
|
}
|
||||||
if system_arch.contains("x86") {
|
if system_arch.contains("x86") {
|
||||||
return Ok(lp_select!("x86", "x86_64"))
|
return Ok(lp_select!("x86", "x86_64"));
|
||||||
}
|
}
|
||||||
bail!("Unsupported system architecture: {}", system_arch);
|
bail!("Unsupported system architecture: {}", system_arch);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_modules(arch: &str) -> Result<Vec<Module>> {
|
fn load_modules(arch: &str) -> Result<Vec<Module>> {
|
||||||
let mut modules = Vec::new();
|
let mut modules = Vec::new();
|
||||||
let dir = match fs::read_dir(constants::PATH_MODULE_DIR) {
|
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
|
||||||
Ok(dir) => dir,
|
Ok(dir) => dir,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!("Failed reading modules directory: {}", e);
|
warn!("Failed reading modules directory: {}", e);
|
||||||
return Ok(modules);
|
return Ok(modules);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for entry_result in dir.into_iter() {
|
for entry in dir.into_iter() {
|
||||||
let entry = entry_result?;
|
let entry = entry?;
|
||||||
let name = entry.file_name().into_string().unwrap();
|
let name = entry.file_name().into_string().unwrap();
|
||||||
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
|
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
|
||||||
let disabled = entry.path().join("disable");
|
let disabled = entry.path().join("disable");
|
||||||
if !so_path.exists() || disabled.exists() {
|
if !so_path.exists() || disabled.exists() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log::info!(" Loading module `{name}`...");
|
info!(" Loading module `{name}`...");
|
||||||
let memfd = match create_memfd(&name, &so_path) {
|
let lib_fd = match create_library_fd(&so_path) {
|
||||||
Ok(memfd) => memfd,
|
Ok(fd) => fd,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::warn!(" Failed to create memfd for `{name}`: {e}");
|
warn!(" Failed to create memfd for `{name}`: {e}");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let companion = match spawn_companion(&name, &memfd) {
|
let companion = Mutex::new(None);
|
||||||
Ok(companion) => companion,
|
let module = Module {
|
||||||
Err(e) => {
|
name,
|
||||||
log::warn!(" Failed to spawn companion for `{name}`: {e}");
|
lib_fd,
|
||||||
continue;
|
companion,
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let companion = Mutex::new(companion);
|
|
||||||
let module = Module { name, memfd, companion };
|
|
||||||
modules.push(module);
|
modules.push(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(modules)
|
Ok(modules)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_memfd(name: &str, so_path: &PathBuf) -> Result<Memfd> {
|
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
|
||||||
let opts = memfd::MemfdOptions::default().allow_sealing(true);
|
let opts = memfd::MemfdOptions::default().allow_sealing(true);
|
||||||
let memfd = opts.create(name)?;
|
let memfd = opts.create("jit-cache")?;
|
||||||
|
|
||||||
let file = fs::File::open(so_path)?;
|
let file = fs::File::open(so_path)?;
|
||||||
let mut reader = std::io::BufReader::new(file);
|
let mut reader = std::io::BufReader::new(file);
|
||||||
let mut writer = memfd.as_file();
|
let mut writer = memfd.as_file();
|
||||||
@@ -132,88 +174,147 @@ fn create_memfd(name: &str, so_path: &PathBuf) -> Result<Memfd> {
|
|||||||
seals.insert(memfd::FileSeal::SealSeal);
|
seals.insert(memfd::FileSeal::SealSeal);
|
||||||
memfd.add_seals(&seals)?;
|
memfd.add_seals(&seals)?;
|
||||||
|
|
||||||
Ok(memfd)
|
Ok(OwnedFd::from(memfd.into_file()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_daemon_socket() -> Result<UnixListener> {
|
fn create_daemon_socket() -> Result<UnixListener> {
|
||||||
utils::set_socket_create_context("u:r:zygote:s0")?;
|
utils::set_socket_create_context("u:r:zygote:s0")?;
|
||||||
let prefix = lp_select!("zygiskd32", "zygiskd64");
|
let listener = utils::unix_listener_from_path(&PATH_CP_NAME)?;
|
||||||
let name = String::from(prefix) + constants::SOCKET_PLACEHOLDER;
|
|
||||||
let listener = utils::abstract_namespace_socket(&name)?;
|
|
||||||
log::debug!("Daemon socket: {name}");
|
|
||||||
Ok(listener)
|
Ok(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_companion(name: &str, memfd: &Memfd) -> Result<Option<UnixStream>> {
|
fn spawn_companion(name: &str, lib_fd: RawFd) -> Result<Option<UnixStream>> {
|
||||||
let (mut daemon, companion) = UnixStream::pair()?;
|
let (mut daemon, companion) = UnixStream::pair()?;
|
||||||
// Remove FD_CLOEXEC flag
|
|
||||||
fcntl(companion.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?;
|
|
||||||
|
|
||||||
|
// FIXME: avoid getting self path from arg0
|
||||||
let process = std::env::args().next().unwrap();
|
let process = std::env::args().next().unwrap();
|
||||||
let nice_name = process.split('/').last().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)
|
Command::new(&process)
|
||||||
.arg0(format!("{}-{}", nice_name, name))
|
.arg0(format!("{}-{}", nice_name, name))
|
||||||
.arg("companion")
|
.arg("companion")
|
||||||
.arg(format!("{}", companion.as_raw_fd()))
|
.arg(format!("{}", companion.as_raw_fd()))
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
drop(companion);
|
exit(0)
|
||||||
|
|
||||||
daemon.write_string(name)?;
|
|
||||||
daemon.send_fd(memfd.as_raw_fd())?;
|
|
||||||
match daemon.read_u8()? {
|
|
||||||
0 => Ok(None),
|
|
||||||
1 => Ok(Some(daemon)),
|
|
||||||
_ => bail!("Invalid companion response"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> {
|
fn handle_daemon_action(
|
||||||
let action = stream.read_u8()?;
|
action: DaemonSocketAction,
|
||||||
let action = DaemonSocketAction::try_from(action)?;
|
mut stream: UnixStream,
|
||||||
log::trace!("New daemon action {:?}", action);
|
context: &Context,
|
||||||
|
) -> Result<()> {
|
||||||
match action {
|
match action {
|
||||||
DaemonSocketAction::PingHeartbeat => {
|
DaemonSocketAction::RequestLogcatFd => loop {
|
||||||
// Do nothing
|
let level = match stream.read_u8() {
|
||||||
}
|
Ok(level) => level,
|
||||||
DaemonSocketAction::RequestLogcatFd => {
|
Err(_) => break,
|
||||||
loop {
|
};
|
||||||
let level = match stream.read_u8() {
|
let tag = stream.read_string()?;
|
||||||
Ok(level) => level,
|
let message = stream.read_string()?;
|
||||||
Err(_) => break,
|
utils::log_raw(level as i32, &tag, &message)?;
|
||||||
};
|
},
|
||||||
let tag = stream.read_string()?;
|
DaemonSocketAction::GetProcessFlags => {
|
||||||
let tag = std::ffi::CString::new(tag)?;
|
let uid = stream.read_u32()? as i32;
|
||||||
let message = stream.read_string()?;
|
let mut flags = ProcessFlags::empty();
|
||||||
let message = std::ffi::CString::new(message)?;
|
if root_impl::uid_is_manager(uid) {
|
||||||
unsafe {
|
flags |= ProcessFlags::PROCESS_IS_MANAGER;
|
||||||
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
|
} 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() {
|
||||||
DaemonSocketAction::ReadNativeBridge => {
|
root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
|
||||||
stream.write_string(&context.native_bridge)?;
|
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 => {
|
DaemonSocketAction::ReadModules => {
|
||||||
stream.write_usize(context.modules.len())?;
|
stream.write_usize(context.modules.len())?;
|
||||||
for module in context.modules.iter() {
|
for module in context.modules.iter() {
|
||||||
stream.write_string(&module.name)?;
|
stream.write_string(&module.name)?;
|
||||||
stream.send_fd(module.memfd.as_raw_fd())?;
|
stream.send_fd(module.lib_fd.as_raw_fd())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DaemonSocketAction::RequestCompanionSocket => {
|
DaemonSocketAction::RequestCompanionSocket => {
|
||||||
let index = stream.read_usize()?;
|
let index = stream.read_usize()?;
|
||||||
let module = &context.modules[index];
|
let module = &context.modules[index];
|
||||||
let mut companion = module.companion.lock().unwrap();
|
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() {
|
match companion.as_ref() {
|
||||||
Some(sock) => {
|
Some(Some(sock)) => {
|
||||||
if let Err(_) = sock.send_fd(stream.as_raw_fd()) {
|
if let Err(e) = sock.send_fd(stream.as_raw_fd()) {
|
||||||
log::error!("Companion of module `{}` crashed", module.name);
|
error!(
|
||||||
companion.take();
|
"Failed to send companion fd socket of module `{}`: {}",
|
||||||
|
module.name, e
|
||||||
|
);
|
||||||
stream.write_u8(0)?;
|
stream.write_u8(0)?;
|
||||||
}
|
}
|
||||||
// Ok: Send by companion
|
// Ok: Send by companion
|
||||||
}
|
}
|
||||||
None => {
|
_ => {
|
||||||
stream.write_u8(0)?;
|
stream.write_u8(0)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -221,14 +322,11 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
|
|||||||
DaemonSocketAction::GetModuleDir => {
|
DaemonSocketAction::GetModuleDir => {
|
||||||
let index = stream.read_usize()?;
|
let index = stream.read_usize()?;
|
||||||
let module = &context.modules[index];
|
let module = &context.modules[index];
|
||||||
let dir = format!("{}/{}", constants::PATH_MODULE_DIR, module.name);
|
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
|
||||||
let dir = fs::File::open(dir)?;
|
let dir = fs::File::open(dir)?;
|
||||||
stream.send_fd(dir.as_raw_fd())?;
|
stream.send_fd(dir.as_raw_fd())?;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user