Compare commits

...

144 Commits

Author SHA1 Message Date
Nullptr
32931cf3a8 Bump to 0.9.0 2024-01-03 21:48:42 +08:00
5ec1cff
9a649e0f00 remove redundant sepolicy 2024-01-03 16:10:33 +08:00
5ec1cff
ded1af2757 fix unmount module.prop 2024-01-02 23:53:42 +08:00
5ec1cff
7e1f85caf5 change minimal version codes 2024-01-02 23:49:31 +08:00
5ec1cff
711a3e98d0 no need to unshare 2024-01-02 23:49:19 +08:00
5ec1cff
cbd0f0f0c3 refine mount prop 2024-01-02 23:39:26 +08:00
5ec1cff
977bd7753d write empty machikado if no key available 2024-01-02 19:51:29 +08:00
5ec1cff
b041a4fca0 always use memfd for lib 2024-01-02 19:34:34 +08:00
5ec1cff
d5c896e966 check manager app 2024-01-02 19:33:27 +08:00
5ec1cff
5c8a67657c use /debug_ramdisk 2024-01-02 18:03:17 +08:00
5ec1cff
28f77fa5e2 machikado mazoku 2024-01-02 15:10:03 +08:00
5ec1cff
914c54cdd0 extract both 64 and 32 2024-01-02 11:13:52 +08:00
5ec1cff
8463574e2d temporary move magic to /data/adb/zygisksu 2024-01-02 00:46:30 +08:00
5ec1cff
07c29016a5 unmount ksu tmpfs 2024-01-01 00:12:54 +08:00
5ec1cff
cd714527e9 remove unnecessary jni hook logs 2023-12-31 14:27:19 +08:00
5ec1cff
0741a35c8f Update monitor.cpp 2023-12-22 09:28:40 +08:00
5ec1cff
dce2937ba0 don't panic in handle_daemon_action 2023-12-20 11:47:43 +08:00
Nullptr
92ac6d4f1a Update README.md 2023-12-14 15:44:38 +08:00
Nullptr
535379fe45 Merge pull request #73 from 5ec1cff/master 2023-12-12 16:01:56 +08:00
5ec1cff
1999ef734c fix ci upload (https://core.telegram.org/bots/api#markdownv2-style) test \_*[]()~`>#+-=|{}.! 2023-12-12 13:20:25 +08:00
5ec1cff
12f57d6e8c log if jni hook failed 2023-12-12 13:12:58 +08:00
5ec1cff
49886d9485 Fixes for Android 14 QPR2 B2 (https://github.com/topjohnwu/Magisk/pull/7620) 2023-12-12 13:05:10 +08:00
5ec1cff
056900406e separate error info and normal info of daemons 2023-12-12 12:35:13 +08:00
5ec1cff
370bb0863b Indicate root implementation 2023-12-12 12:18:32 +08:00
5ec1cff
75495dee38 Indicate wrong root implementation 2023-12-12 12:18:19 +08:00
5ec1cff
feb34c701d show loaded modules 2023-12-12 12:12:42 +08:00
5ec1cff
483987b7e6 fix companion exec failed 2023-12-12 11:24:53 +08:00
5ec1cff
b45d55e83d change some logs level to V 2023-12-12 10:50:11 +08:00
5ec1cff
f101ffb95f add emoji to status 2023-12-12 10:35:26 +08:00
5ec1cff
faf074b990 don't show inject status if not tracing 2023-12-12 00:50:49 +08:00
5ec1cff
db60c3185e show injection status 2023-12-12 00:43:30 +08:00
5ec1cff
993b18752b daemon launched by monitor & show daemon status in module.prop 2023-12-11 23:43:32 +08:00
5ec1cff
ad8510dee0 show root impl 2023-12-11 20:24:50 +08:00
5ec1cff
5d95f627bf don't use Result on handle_daemon_action 2023-12-11 20:10:28 +08:00
5ec1cff
99048699fc rename uploaded symbol filename 2023-12-11 16:13:33 +08:00
5ec1cff
ba1f90cba8 Upload CI builds to telegram 2023-12-11 15:42:26 +08:00
5ec1cff
0e2bc75966 Add version info to logs 2023-12-11 11:40:19 +08:00
5ec1cff
a88d7a5bb8 fix CI 2023-12-08 20:40:32 +08:00
5ec1cff
d66dba265c upload zygiskd debug symbols 2023-12-08 20:32:40 +08:00
5ec1cff
4f81f09a67 randomize init control socket & add shortcut (/data/adb/modules/zygisksu/bin/zygisk-ctl) 2023-12-08 20:20:59 +08:00
5ec1cff
c4163c91b9 generate debug info for release 2023-12-08 19:53:38 +08:00
5ec1cff
0254623166 fix check_unix_socket 2023-12-08 19:47:06 +08:00
5ec1cff
b34015b5f0 strip by ourselves 2023-12-08 19:46:40 +08:00
5ec1cff
bcb65c4bd9 run companion in standalone process 2023-12-08 17:19:26 +08:00
5ec1cff
9c34c671fa restart zygiskd when zygote restart 2023-12-08 11:41:40 +08:00
5ec1cff
9e48f18247 Randomize tmp path 2023-11-27 10:23:03 +08:00
5ec1cff
c205893dd2 set max retry count to 5 2023-11-14 00:10:20 +08:00
5ec1cff
5aea51ce03 Revert "run zygisk-cp on post-fs-data"
This reverts commit 47a17d2079.
2023-11-13 22:15:20 +08:00
5ec1cff
c59a7887ca refine tracing logic 2023-11-13 21:59:12 +08:00
5ec1cff
47a17d2079 run zygisk-cp on post-fs-data 2023-11-13 21:51:51 +08:00
5ec1cff
fd9f8799a1 refine tracing logic 2023-11-13 21:51:37 +08:00
5ec1cff
fd6a454275 fix compatibility on lower android version 2023-11-13 15:04:23 +08:00
5ec1cff
3e541ece3a prevent from bootloop 2023-11-12 21:38:45 +08:00
5ec1cff
36dcec0264 fix wrong entry break addr on arm32 2023-11-12 21:27:40 +08:00
5ec1cff
889a44cd0d inject non-stop signal by default 2023-11-12 21:10:40 +08:00
5ec1cff
0ac9bb819b add controller command 2023-11-12 21:00:18 +08:00
5ec1cff
040643337d init monitor 2023-11-12 20:34:45 +08:00
5ec1cff
ba8c313122 Merge branch 'art2' into new-pt
# Conflicts:
#	loader/src/Android.mk
#	loader/src/external/Android.mk
#	loader/src/injector/hook.cpp
#	module/src/customize.sh
#	zygiskd/Cargo.toml
#	zygiskd/src/utils.rs
#	zygiskd/src/watchdog.rs
2023-11-08 15:33:15 +08:00
5ec1cff
070aa5f1a1 fix pure 64 bit support 2023-11-06 10:05:41 +08:00
5ec1cff
add1c65626 make compiler happy 2023-11-04 16:39:21 +08:00
5ec1cff
ac53ef11a3 refactor jni hooks 2023-11-04 16:39:10 +08:00
5ec1cff
1c79932cae comment out useless code 2023-11-03 18:20:52 +08:00
5ec1cff
5e2a211ca4 min api is 26 2023-11-03 18:19:04 +08:00
5ec1cff
f958e57af6 Get rid of binder-rs & Refine watchdog 2023-11-03 18:12:50 +08:00
5ec1cff
f5e0a418c9 WIP 2023-11-03 11:41:28 +08:00
5ec1cff
f78c217552 init-sigstop-based ptrace zygisk 2023-10-31 18:36:01 +08:00
5ec1cff
c249ebe22c use cmake instead of ndk-build 2023-10-30 21:25:04 +08:00
Nullptr
47e515e2fc Update CI 2023-10-24 17:22:34 +08:00
Nullptr
44918d332e Rename project to Zygisk Next 2023-10-24 17:18:59 +08:00
Nullptr
cbf5920d02 Bump to 0.8.1 2023-10-24 16:07:21 +08:00
5ec1cff
a2af28dc6f add 32 bit support back & fix x86-64 support & fix jni hook restore (#59)
* Add back 32 bit support & some fix

* fix system server crash on android 10

* Refine code

---------

Co-authored-by: Nullptr <noreply@nullptr.icu>
2023-10-24 15:27:21 +08:00
Nullptr
a956dba77d Show crash info correctly 2023-10-23 22:23:25 +08:00
Nullptr
abbca19c82 Implement uid_should_umount for magisk 2023-10-22 00:23:12 +08:00
Nullptr
4587e39964 Bump to 0.8.0 2023-10-21 16:26:54 +08:00
Nullptr
9df4fb64cd Fix magisk unmount 2023-10-21 16:25:00 +08:00
Nullptr
9f28e0a7ad Update CI and README.md 2023-10-21 16:08:35 +08:00
Nullptr
8a80586fb8 Fix magisk loading 2023-10-21 13:51:01 +08:00
Nullptr
218659dcbf Fix module loading 2023-10-20 23:27:40 +08:00
Nullptr
8c0d5b5395 Drop 32 bit support 2023-10-20 19:23:40 +08:00
Nullptr
9051f59bf6 Fix hook 2023-10-20 15:24:42 +08:00
Nullptr
5f2dd50703 Replace native bridge with fuse + ptrace 2023-10-20 14:04:44 +08:00
5ec1cff
77cb323506 remove dead code (#53) 2023-10-19 01:47:53 +08:00
Nullptr
2b41a8336c Use rustix to replace nix 2023-10-19 01:33:47 +08:00
Nullptr
e730ccd9b2 Update deps 2023-10-19 01:20:50 +08:00
LoveSy
db47f03728 New way to unload zygisk (#51)
* use old binder_rs

* New way to unload zygisk (3a4fe53)

Co-authored-by: LoveSy <shana@zju.edu.cn>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>

---------

Co-authored-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>
2023-10-15 11:11:05 +08:00
Nullptr
42503e7cfe Update issue template 2023-08-13 09:54:50 +08:00
Nullptr
14c920c553 Update dependencies 2023-08-02 08:24:07 +08:00
Nullptr
ceaa2d431c Add updateJson 2023-06-27 16:18:52 +08:00
5ec1cff
ccb5764b72 Receive the reply_ok to prevent from dmesg flooding (#35) 2023-06-21 23:06:47 +08:00
Nullptr
e8958e94b6 Bump to 0.7.1 2023-06-19 11:08:41 +08:00
5ec1cff
0338cdb0ed Update kernelsu.rs (#34) 2023-06-18 16:42:54 +08:00
Nullptr
99e653c576 Revert "Fix #27: bool on kernel is 4 bytes, while rust's is 1 byte. This causes the stack to be covered, triggering a ub."
This reverts commit 7c27c32861.
2023-06-18 15:47:12 +08:00
Nullptr
7c27c32861 Fix #27: bool on kernel is 4 bytes, while rust's is 1 byte. This causes the stack to be covered, triggering a ub. 2023-06-13 23:32:43 +08:00
Nullptr
bea5ed47b8 Bump to 0.7.0 2023-06-04 11:16:03 +08:00
Nullptr
954a712089 Use app profile 2023-06-04 01:31:12 +08:00
Nullptr
f6195ddb43 Don't spawn new process for companion 2023-05-21 20:14:36 +08:00
Nullptr
8b5e9db347 Make watchdog async 2023-05-21 19:38:24 +08:00
Nullptr
a04f636ac4 Refine code 2023-05-19 19:14:54 +08:00
5ec1cff
00f0a6e3fa several changes (#21)
* umount for ksu 10763

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add crashdump sepolicy

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Add more information about debug

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* daemonize zygiskd companion

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* create zygiskd if crash

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* injector: use ANDROID_DLEXT_USE_LIBRARY_FD to load module

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* zygiskd: use file as module fd instead of memfd on debug build

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* use OwnedFd

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* dlopen: no need to create ns

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-05-19 18:28:56 +08:00
Nullptr
f5bf82fa93 Set memfd name to module name on debug 2023-04-23 09:15:53 +08:00
Nullptr
33c4ea3c62 Bump to 0.6.5 2023-04-21 16:19:12 +08:00
Nullptr
3eee57eb8f Fix incorrect ksu denylist meaning 2023-04-21 08:40:11 +08:00
5ec1cff
250b4b2f8c umount for ksu 10763 (#20)
Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-04-20 13:36:08 +08:00
Howard Wu
3772e23473 Add issue templates (#19) 2023-04-16 14:41:56 +08:00
Nullptr
8c5acf1ebe Always add sepolicy 2023-04-14 10:43:18 +08:00
Nullptr
9d0858be7c Bump to 0.6.4 2023-03-23 19:55:15 +08:00
Nullptr
b7bed4ad35 Fix pltHookCommit 2023-03-23 19:54:45 +08:00
Nullptr
80b19c4412 Bump to 0.6.3 2023-03-21 23:08:11 +08:00
Nullptr
a6f455218f No inline for root_impl 2023-03-21 22:55:14 +08:00
Nullptr
87cf885070 No hex patch 2023-03-20 17:36:59 +08:00
5ec1cff
b775d28c23 Add CI (#14)
* CI

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Update gradle.properties

* add rustup targets (#1)

* Update ci.yml

* Update ci.yml

* Update ci.yml

* Use ccache and rust-cache

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
Co-authored-by: Mufanc <47652878+Mufanc@users.noreply.github.com>
2023-03-16 17:26:29 +08:00
5ec1cff
bf72296d33 Fix revert umount sepolicy (#12)
* Fix revert umount sepolicy

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>

* Update sepolicy.rule

---------

Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
2023-03-13 18:10:30 +08:00
Nullptr
9d648d9aa4 Bump to 0.6.2 2023-03-03 18:20:50 +08:00
Nullptr
843086f6f3 Add more sepolicy 2023-03-03 18:20:50 +08:00
Nullptr
49e3ac9d7a Fix dependency bug: OnceCell crashes on 32 bit 2023-03-03 18:20:36 +08:00
Nullptr
446ed92f26 Change module dir and bump to 0.6.1 2023-03-01 12:42:12 +08:00
Nullptr
2e9cbf79a7 Bump to 0.6.0 2023-02-28 21:32:13 +08:00
Nullptr
cce8e6686f Implement uid_on_allowlist for Magisk 2023-02-28 20:48:32 +08:00
Nullptr
ff2658f2de Rename libs 2023-02-28 20:48:20 +08:00
Nullptr
f465cbf810 Refactor to better support Magisk 2023-02-28 19:50:41 +08:00
Nullptr
09b6673ab0 Implement revert_unmount_magisk 2023-02-28 16:03:41 +08:00
Nullptr
5f8eb4af09 No submodule 2023-02-28 12:37:58 +08:00
Nullptr
8affc8f991 Fix zygote restart & Show zygisksu status on module.prop 2023-02-26 11:54:52 +08:00
Nullptr
ec8475bca5 Better unmount refine 2023-02-25 13:59:37 +08:00
Nullptr
9ff1e27a7d Unmount everything under ksu loop 2023-02-25 11:00:35 +08:00
Nullptr
6d9cc560cc Bump to 0.5.1 2023-02-24 22:24:45 +08:00
Nullptr
f395cfb490 Fix stupid remount bug 2023-02-24 16:43:53 +08:00
Nullptr
03575edd96 Bump to 0.5.0 2023-02-24 09:34:48 +08:00
Nullptr
915749e59b Never allow multiple root implementation 2023-02-24 09:31:44 +08:00
Nullptr
d08b415577 Require ksud version 2023-02-24 08:57:24 +08:00
Nullptr
f27aed5068 Change memfd name to jit-cache 2023-02-20 16:35:02 +08:00
Nullptr
5365ab1f12 Check correct KernelSU version 2023-02-20 16:28:15 +08:00
Nullptr
b99d042002 Implement GetProcessFlags for KernelSU 2023-02-19 13:29:36 +08:00
Nullptr
57d3d8a0ba Refine unmount 2023-02-18 19:14:15 +08:00
Nullptr
e69aa5c527 Bump to 0.4.1 2023-02-17 21:38:55 +08:00
Nullptr
9b5eb1bac7 Support Magisk out of box 2023-02-17 21:08:19 +08:00
Nullptr
c8ad933388 Bump to 0.4.0 2023-02-15 13:19:12 +08:00
Nullptr
baf444228d Handle zygote death 2023-02-15 13:15:35 +08:00
Nullptr
5c00071fed Use relative path 2023-02-15 11:06:46 +08:00
Nullptr
fc9bc3b28f Refine code 2023-02-14 10:14:46 +08:00
Nullptr
8cac525aa9 Update dependencies 2023-02-14 10:02:40 +08:00
Nullptr
2bcb36ab4a Remove vtable hook 2023-02-10 23:18:27 +08:00
Nullptr
150be54ff0 Bump to 0.3.0 2023-02-09 12:40:19 +08:00
Nullptr
ab9ce993eb Fix race 2023-02-09 12:37:18 +08:00
77 changed files with 5259 additions and 1210 deletions

73
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View 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
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View 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
View 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
View 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
View 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

9
.gitmodules vendored
View File

@@ -1,6 +1,3 @@
[submodule "loader/src/external/liblsplt"]
path = loader/src/external/liblsplt
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 "loader/src/external/lsplt"]
path = loader/src/external/lsplt
url = https://github.com/LSPosed/lsplt

View File

@@ -1,32 +1,26 @@
# Zygisk on KernelSU
# Zygisk Next
Zygisk loader for KernelSU, allowing Zygisk modules to run without Magisk environment.
Warning: The current version of Zygisksu is UNSTABLE. You may suffer boot loop or even data loss so use with caution.
Standalone implementation of Zygisk, providing Zygisk API support for KernelSU and a replacement of Magisk's built-in Zygisk.
## Requirements
+ Minimal KernelSU version: 10575
+ Minimal ksud version: 10200
+ Full SELinux patch support (If non-gki kernel)
### General
+ No multiple root implementation installed
### KernelSU
+ Minimal KernelSU version: 10940
+ Minimal ksud version: 11412
+ Kernel has full SELinux patch support
### Magisk
+ Minimal version: 26402
+ Built-in Zygisk turned off
## 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
- [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
- [ ] [Daemon] Handle zygote death
## Running on Magisk
It is possible to run Zygisksu on Magisk with a few steps:
1. `mkdir /data/adb/ksu`
2. `ln -s /data/adb/modules /data/adb/ksu/`
Zygisk Next only guarantees the same behavior of Zygisk API, but will NOT ensure Magisk's internal features.

View File

@@ -2,18 +2,7 @@ import com.android.build.gradle.LibraryExtension
import java.io.ByteArrayOutputStream
plugins {
id("com.android.application") 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")
}
alias(libs.plugins.agp.lib) apply false
}
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 moduleId by extra("zygisksu")
val moduleName by extra("Zygisk on KernelSU")
val verName by extra("v4-0.2.0")
val moduleName by extra("Zygisk Next")
val verName by extra("v4-0.9.0")
val verCode by extra(gitCommitCount)
val commitHash by extra(gitCommitHash)
val minKsuVersion by extra(10940)
val minKsudVersion by extra(11412)
val maxKsuVersion by extra(20000)
val minMagiskVersion by extra(26402)
val androidMinSdkVersion by extra(29)
val androidTargetSdkVersion by extra(33)
val androidCompileSdkVersion by extra(33)
val androidBuildToolsVersion by extra("33.0.1")
val androidCompileNdkVersion by extra("25.1.8937393")
val androidMinSdkVersion by extra(26)
val androidTargetSdkVersion by extra(34)
val androidCompileSdkVersion by extra(34)
val androidBuildToolsVersion by extra("34.0.0")
val androidCompileNdkVersion by extra("26.0.10792818")
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)
@@ -48,7 +42,7 @@ tasks.register("Delete", Delete::class) {
fun Project.configureBaseExtension() {
extensions.findByType(LibraryExtension::class)?.run {
namespace = "icu.nullptr.zygisksu"
namespace = "icu.nullptr.zygisk.next"
compileSdk = androidCompileSdkVersion
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion

View File

@@ -20,4 +20,4 @@ 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
android.nonTransitiveRClass=true

View File

@@ -0,0 +1,9 @@
[versions]
agp = "8.1.2"
kotlin = "1.9.10"
[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" }

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
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
zipStorePath=wrapper/dists

View File

@@ -1,7 +1,45 @@
import java.nio.file.Paths
import org.gradle.internal.os.OperatingSystem
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 {
buildFeatures {
androidResources = false
@@ -9,8 +47,35 @@ android {
prefab = true
}
externalNativeBuild.ndkBuild {
path("src/Android.mk")
externalNativeBuild.cmake {
path("src/CMakeLists.txt")
}
defaultConfig {
externalNativeBuild.cmake {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLT_STANDALONE=ON"
cFlags("-std=c18", *defaultCFlags)
cppFlags("-std=c++20", *defaultCFlags)
ccachePath?.let {
arguments += "-DNDK_CCACHE=$it"
}
}
}
buildTypes {
debug {
externalNativeBuild.cmake {
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug"
}
}
release {
externalNativeBuild.cmake {
cFlags += releaseFlags
cppFlags += releaseFlags
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release"
}
}
}
}

View File

@@ -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

View File

@@ -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
View 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)

View File

@@ -7,6 +7,11 @@
#include "socket_utils.h"
namespace zygiskd {
static std::string zygisk_path;
void Init(const char *path) {
LOGI("zygisk path set to %s", path);
zygisk_path = path;
}
int Connect(uint8_t retry) {
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
@@ -14,14 +19,17 @@ namespace zygiskd {
.sun_family = AF_UNIX,
.sun_path={0},
};
strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size());
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
auto socket_path = zygisk_path + kCPSocketName;
strcpy(addr.sun_path, socket_path.c_str());
socklen_t socklen = sizeof(addr);
while (retry--) {
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
if (r == 0) return fd;
LOGW("retrying to connect to zygiskd, sleep 1s");
sleep(1);
if (retry) {
PLOGE("Retrying to connect to zygiskd, sleep 1s");
sleep(1);
}
}
close(fd);
@@ -48,14 +56,15 @@ namespace zygiskd {
return fd;
}
std::string ReadNativeBridge() {
uint32_t GetProcessFlags(uid_t uid) {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("ReadNativeBridge");
return "";
PLOGE("GetProcessFlags");
return 0;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge);
return socket_utils::read_string(fd);
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags);
socket_utils::write_u32(fd, uid);
return socket_utils::read_u32(fd);
}
std::vector<Module> ReadModules() {
@@ -86,12 +95,13 @@ namespace zygiskd {
if (socket_utils::read_u8(fd) == 1) {
return fd;
} else {
close(fd);
return -1;
}
}
int GetModuleDir(size_t index) {
int fd = Connect(1);
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("GetModuleDir");
return -1;
@@ -100,4 +110,30 @@ namespace zygiskd {
socket_utils::write_usize(fd, index);
return socket_utils::recv_fd(fd);
}
void ZygoteRestart() {
UniqueFd fd = Connect(1);
if (fd == -1) {
if (errno == ENOENT) {
LOGD("Could not notify ZygoteRestart (maybe it hasn't been created)");
} else {
PLOGE("Could not notify ZygoteRestart");
}
return;
}
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart)) {
PLOGE("Failed to request ZygoteRestart");
}
}
void SystemServerStarted() {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("Failed to report system server started");
} else {
if (!socket_utils::write_u8(fd, (uint8_t) SocketAction::SystemServerStarted)) {
PLOGE("Failed to report system server started");
}
}
}
}

View File

@@ -44,8 +44,17 @@ void* DlopenExt(const char* path, int flags) {
return handle;
}
void* DlopenMem(int memfd, int flags) {
char path[PATH_MAX];
sprintf(path, "/proc/self/fd/%d", memfd);
return DlopenExt(path, flags);
void* DlopenMem(int fd, int flags) {
auto info = android_dlextinfo{
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd
};
auto* handle = android_dlopen_ext("/jit-cache", flags, &info);
if (handle) {
LOGV("dlopen fd %d: %p", fd, handle);
} else {
LOGE("dlopen fd %d: %s", fd, dlerror());
}
return handle;
}

122
loader/src/common/files.cpp Normal file
View 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; });
}

View 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;
}

View File

@@ -19,12 +19,12 @@ namespace socket_utils {
read_sz += ret;
} while (read_sz != count && ret != 0);
if (read_sz != count) {
PLOGE("read (%d != %d)", count, read_sz);
PLOGE("read (%zu != %zu)", count, 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;
ssize_t ret;
do {
@@ -32,12 +32,12 @@ namespace socket_utils {
if (ret < 0) {
if (errno == EINTR) continue;
PLOGE("write");
return ret;
return write_sz;
}
write_sz += ret;
} while (write_sz != count && ret != 0);
if (write_sz != count) {
PLOGE("write (%d != %d)", count, write_sz);
PLOGE("write (%zu != %zu)", count, write_sz);
}
return write_sz;
}
@@ -90,6 +90,10 @@ namespace socket_utils {
return read_exact_or<uint8_t>(fd, 0);
}
uint32_t read_u32(int fd) {
return read_exact_or<uint32_t>(fd, 0);
}
size_t read_usize(int fd) {
return read_exact_or<size_t>(fd, 0);
}
@@ -110,6 +114,10 @@ namespace socket_utils {
return write_exact<uint8_t>(fd, val);
}
bool write_u32(int fd, uint32_t val) {
return write_exact<uint32_t>(fd, val);
}
bool write_string(int fd, std::string_view str) {
return write_usize(fd, str.size()) && str.size() == xwrite(fd, str.data(), str.size());
}

View File

@@ -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
View 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/lsplt vendored Submodule

View File

@@ -26,14 +26,40 @@
#define ZYGISK_API_VERSION 4
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
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:
static jint (*orig_logger_entry_max)(JNIEnv *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 {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
@@ -51,8 +77,26 @@ private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
@@ -84,7 +128,7 @@ namespace zygisk {
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs 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) {}
// This method is called before the system server process is specialized.
@@ -117,6 +161,7 @@ namespace zygisk {
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
jboolean *const mount_sysprop_overrides;
AppSpecializeArgs() = delete;
};
@@ -219,7 +264,16 @@ namespace zygisk {
// will be set to nullptr.
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`.
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
// 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.
//
// 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) \
void zygisk_companion_entry(int client) { func(client); }

View File

@@ -10,7 +10,9 @@
#else
# define LP_SELECT(lp32, lp64) lp32
#endif
constexpr std::string_view kZygiskSocket = LP_SELECT("zygiskd32", "zygiskd64") "socket_placeholder";
constexpr auto kCPSocketName = "/" LP_SELECT("cp32", "cp64") ".sock";
#define TMP_PATH "/debug_ramdisk/zygisksu"
class UniqueFd {
using Fd = int;
@@ -19,7 +21,7 @@ public:
UniqueFd(Fd fd) : fd_(fd) {}
~UniqueFd() { close(fd_); }
~UniqueFd() { if (fd_ >= 0) close(fd_); }
// Disallow copy
UniqueFd(const UniqueFd&) = delete;
@@ -53,21 +55,29 @@ namespace zygiskd {
enum class SocketAction {
PingHeartBeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
};
void Init(const char *path = TMP_PATH);
bool PingHeartbeat();
int RequestLogcatFd();
std::string ReadNativeBridge();
std::vector<Module> ReadModules();
uint32_t GetProcessFlags(uid_t uid);
int ConnectCompanion(size_t index);
int GetModuleDir(size_t index);
void ZygoteRestart();
void SystemServerStarted();
}

View 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));
}

View File

@@ -6,19 +6,12 @@
#ifndef LOG_TAG
#if defined(__LP64__)
# define LOG_TAG "zygisksu64"
# define LOG_TAG "zygisk-core64"
#else
# define LOG_TAG "zygisksu32"
# define LOG_TAG "zygisk-core32"
#endif
#endif
#ifdef LOG_DISABLED
#define LOGD(...)
#define LOGV(...)
#define LOGI(...)
#define LOGW(...)
#define LOGE(...)
#else
#ifndef NDEBUG
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, 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 LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
#endif
namespace logging {
void setfd(int fd);

View File

@@ -1,11 +1,9 @@
#pragma once
#include <dirent.h>
#include <functional>
#include <list>
#include <memory>
#include <mntent.h>
#include <pthread.h>
#include <stdio.h>
#include <string>
#include <string_view>
#include "logging.h"
@@ -34,6 +32,10 @@ private:
using thread_entry = void *(*)(void *);
int new_daemon_thread(thread_entry entry, void *arg);
static inline bool str_contains(std::string_view s, std::string_view ss) {
return s.find(ss) != std::string_view::npos;
}
template<typename T, typename Impl>
class stateless_allocator {
public:
@@ -49,33 +51,23 @@ public:
bool operator!=(const stateless_allocator&) { return false; }
};
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);
template <typename T>
class reversed_container {
public:
reversed_container(T &base) : base(base) {}
decltype(std::declval<T>().rbegin()) begin() { return base.rbegin(); }
decltype(std::declval<T>().crbegin()) begin() const { return base.crbegin(); }
decltype(std::declval<T>().crbegin()) cbegin() const { return base.crbegin(); }
decltype(std::declval<T>().rend()) end() { return base.rend(); }
decltype(std::declval<T>().crend()) end() const { return base.crend(); }
decltype(std::declval<T>().crend()) cend() const { return base.crend(); }
private:
T &base;
};
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));
template <typename T>
reversed_container<T> reversed(T &base) {
return reversed_container<T>(base);
}
template<class T>
@@ -95,7 +87,9 @@ struct StringCmp {
*/
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>
static inline T align_to(T v, int a) {

View File

@@ -9,16 +9,20 @@ namespace socket_utils {
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);
uint32_t read_u32(int fd);
size_t read_usize(int fd);
std::string read_string(int fd);
bool write_u8(int fd, uint8_t val);
bool write_u32(int fd, uint32_t val);
int recv_fd(int fd);
bool write_usize(int fd, size_t val);

View 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

View File

@@ -7,27 +7,21 @@ using namespace std;
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")]]
void entry(void *handle) {
logging::setfd(zygiskd::RequestLogcatFd());
void entry(void* handle) {
LOGI("Zygisk library injected, version %s", ZKSU_VERSION);
self_handle = handle;
LOGD("Load injector successfully");
zygiskd::Init();
if (!zygiskd::PingHeartbeat()) {
LOGE("Zygisk daemon is not running");
return;
}
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
LOGI("Start hooking");
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;
}

View 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));
}
""")

View File

@@ -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;
});
}

View File

@@ -4,27 +4,30 @@
#include <regex.h>
#include <bitset>
#include <list>
#include <map>
#include <array>
#include <lsplt.hpp>
#include <fcntl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include "dl.h"
#include "daemon.h"
#include "zygisk.hpp"
#include "memory.hpp"
#include "module.hpp"
#include "files.hpp"
#include "misc.hpp"
using namespace std;
using jni_hook::hash_map;
using jni_hook::tree_map;
using xstring = jni_hook::string;
#include "art_method.hpp"
static bool unhook_functions();
using namespace std;
static void hook_unloader();
static void unhook_functions();
namespace {
@@ -34,7 +37,6 @@ enum {
APP_SPECIALIZE,
SERVER_FORK_AND_SPECIALIZE,
DO_REVERT_UNMOUNT,
CAN_UNLOAD_ZYGISK,
SKIP_FD_SANITIZATION,
FLAG_MAX
@@ -46,7 +48,12 @@ void name##_post();
#define MAX_FD_SIZE 1024
struct HookContext {
struct ZygiskContext;
// Current context
ZygiskContext *g_ctx;
struct ZygiskContext {
JNIEnv *env;
union {
void *ptr;
@@ -79,8 +86,12 @@ struct HookContext {
vector<RegisterInfo> register_info;
vector<IgnoreInfo> ignore_info;
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0),
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {}
ZygiskContext(JNIEnv *env, void *args) :
env(env), args{args}, process(nullptr), pid(-1), info_flags(0),
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {
g_ctx = this;
}
~ZygiskContext();
/* Zygisksu changed: Load module fds */
void run_modules_pre();
@@ -91,9 +102,9 @@ struct HookContext {
DCL_PRE_POST(nativeSpecializeAppProcess)
DCL_PRE_POST(nativeForkSystemServer)
void unload_zygisk();
void sanitize_fds();
bool exempt_fd(int fd);
bool is_child() const { return pid <= 0; }
// Compatibility shim
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
@@ -108,77 +119,16 @@ struct HookContext {
// Global variables
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
// Current context
HookContext *g_ctx;
const JNINativeInterface *old_functions;
JNINativeInterface *new_functions;
bool should_unmap_zygisk = false;
} // 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 {
jclass gClassRef;
jmethodID class_getName;
string get_class_name(JNIEnv *env, jclass clazz) {
if (!gClassRef) {
jclass cls = env->FindClass("java/lang/Class");
gClassRef = (jclass) env->NewGlobalRef(cls);
env->DeleteLocalRef(cls);
class_getName = env->GetMethodID(gClassRef, "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, ...) \
ret (*old_##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(int, jniRegisterNativeMethods,
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) {
LOGV("jniRegisterNativeMethods [%s]\n", className);
auto newMethods = hookAndSaveJNIMethods(className, methods, numMethods);
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
}
// Skip actual fork and return cached result if applicable
DCL_HOOK_FUNC(int, fork) {
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
@@ -193,7 +143,11 @@ DCL_HOOK_FUNC(int, unshare, int flags) {
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
(g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) {
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
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 */
@@ -216,105 +170,141 @@ DCL_HOOK_FUNC(void, android_log_close) {
old_android_log_close();
}
// Last point before process secontext changes
DCL_HOOK_FUNC(int, selinux_android_setcontext,
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
if (g_ctx) {
g_ctx->flags[CAN_UNLOAD_ZYGISK] = unhook_functions();
// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
// it will return to our code which has been unmapped, causing segmentation fault.
// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.
DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
int res = old_pthread_attr_destroy((pthread_attr_t *)target);
// Only perform unloading on the main thread
if (gettid() != getpid())
return res;
LOGV("pthread_attr_destroy\n");
if (should_unmap_zygisk) {
unhook_functions();
if (should_unmap_zygisk) {
// Because both `pthread_attr_destroy` and `dlclose` have the same function signature,
// we can use `musttail` to let the compiler reuse our stack frame and thus
// `dlclose` will directly return to the caller of `pthread_attr_destroy`.
[[clang::musttail]] return dlclose(self_handle);
}
}
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
return res;
}
// -----------------------------------------------------------------
void initialize_jni_hook();
// The original android::AppRuntime virtual table
void **gAppRuntimeVTable;
// This method is a trampoline for hooking JNIEnv->RegisterNatives
void onVmCreated(void *self, JNIEnv* env) {
LOGD("AppRuntime::onVmCreated\n");
// Restore virtual table
auto new_table = *reinterpret_cast<void***>(self);
*reinterpret_cast<void***>(self) = gAppRuntimeVTable;
delete[] new_table;
new_functions = new JNINativeInterface();
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;
}
template<int N>
void vtable_entry(void *self, JNIEnv* env) {
// The first invocation will be onVmCreated. It will also restore the vtable.
onVmCreated(self, env);
// Call original function
reinterpret_cast<decltype(&onVmCreated)>(gAppRuntimeVTable[N])(self, env);
}
/* Zygisksu changed: AndroidRuntime setArgv0 before native bridge loaded */
void hookVirtualTable(void *self) {
LOGD("hook AndroidRuntime virtual table\n");
// We don't know which entry is onVmCreated, so overwrite every one
// We also don't know the size of the vtable, but 8 is more than enough
auto new_table = new void*[8];
new_table[0] = reinterpret_cast<void*>(&vtable_entry<0>);
new_table[1] = reinterpret_cast<void*>(&vtable_entry<1>);
new_table[2] = reinterpret_cast<void*>(&vtable_entry<2>);
new_table[3] = reinterpret_cast<void*>(&vtable_entry<3>);
new_table[4] = reinterpret_cast<void*>(&vtable_entry<4>);
new_table[5] = reinterpret_cast<void*>(&vtable_entry<5>);
new_table[6] = reinterpret_cast<void*>(&vtable_entry<6>);
new_table[7] = reinterpret_cast<void*>(&vtable_entry<7>);
// Swizzle C++ vtable to hook virtual function
gAppRuntimeVTable = *reinterpret_cast<void***>(self);
*reinterpret_cast<void***>(self) = new_table;
DCL_HOOK_FUNC(char *, strdup, const char *s) {
if (s == "com.android.internal.os.ZygoteInit"sv) {
LOGV("strdup %s\n", s);
initialize_jni_hook();
}
return old_strdup(s);
}
#undef DCL_HOOK_FUNC
// -----------------------------------------------------------------
static bool can_hook_jni = false;
static jint MODIFIER_NATIVE = 0;
static jmethodID member_getModifiers = nullptr;
void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) {
auto class_map = jni_method_map->find(clz);
if (class_map == jni_method_map->end()) {
for (int i = 0; i < numMethods; ++i) {
if (!can_hook_jni) return;
auto clazz = env->FindClass(clz);
if (clazz == nullptr) {
env->ExceptionClear();
for (int i = 0; i < numMethods; i++) {
methods[i].fnPtr = nullptr;
}
return;
}
vector<JNINativeMethod> hooks;
for (int i = 0; i < numMethods; ++i) {
auto method_map = class_map->second.find(methods[i].name);
if (method_map != class_map->second.end()) {
auto it = method_map->second.find(methods[i].signature);
if (it != method_map->second.end()) {
// Copy the JNINativeMethod
hooks.push_back(methods[i]);
// Save the original function pointer
methods[i].fnPtr = it->second;
// Do not allow double hook, remove method from map
method_map->second.erase(it);
continue;
}
for (int i = 0; i < numMethods; i++) {
auto &nm = methods[i];
auto mid = env->GetMethodID(clazz, nm.name, nm.signature);
bool is_static = false;
if (mid == nullptr) {
env->ExceptionClear();
mid = env->GetStaticMethodID(clazz, nm.name, nm.signature);
is_static = true;
}
// No matching method found, set fnPtr to null
methods[i].fnPtr = nullptr;
if (mid == nullptr) {
env->ExceptionClear();
nm.fnPtr = nullptr;
continue;
}
auto method = lsplant::JNI_ToReflectedMethod(env, clazz, mid, is_static);
auto modifier = lsplant::JNI_CallIntMethod(env, method, member_getModifiers);
if ((modifier & MODIFIER_NATIVE) == 0) {
nm.fnPtr = nullptr;
continue;
}
auto artMethod = lsplant::art::ArtMethod::FromReflectedMethod(env, method);
hooks.push_back(nm);
auto orig = artMethod->GetData();
LOGV("replaced %s %s orig %p", clz, nm.name, orig);
nm.fnPtr = orig;
}
if (hooks.empty())
return;
old_jniRegisterNativeMethods(env, clz, hooks.data(), hooks.size());
if (hooks.empty()) return;
env->RegisterNatives(clazz, hooks.data(), hooks.size());
}
// JNI method hook definitions, auto generated
#include "jni_hooks.hpp"
void initialize_jni_hook() {
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
if (!get_created_java_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror());
break;
}
get_created_java_vms = reinterpret_cast<decltype(get_created_java_vms)>(dlsym(h, "JNI_GetCreatedJavaVMs"));
dlclose(h);
break;
}
if (!get_created_java_vms) {
LOGW("JNI_GetCreatedJavaVMs not found\n");
return;
}
}
JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_java_vms(&vm, 1, &num);
if (res != JNI_OK || vm == nullptr) return;
JNIEnv *env = nullptr;
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) return;
auto classMember = lsplant::JNI_FindClass(env, "java/lang/reflect/Member");
if (classMember != nullptr) member_getModifiers = lsplant::JNI_GetMethodID(env, classMember, "getModifiers", "()I");
auto classModifier = lsplant::JNI_FindClass(env, "java/lang/reflect/Modifier");
if (classModifier != nullptr) {
auto fieldId = lsplant::JNI_GetStaticFieldID(env, classModifier, "NATIVE", "I");
if (fieldId != nullptr) MODIFIER_NATIVE = lsplant::JNI_GetStaticIntField(env, classModifier, fieldId);
}
if (member_getModifiers == nullptr || MODIFIER_NATIVE == 0) return;
if (!lsplant::art::ArtMethod::Init(env)) {
LOGE("failed to init ArtMethod");
return;
}
can_hook_jni = true;
do_hook_zygote(env);
}
// -----------------------------------------------------------------
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
// Make sure all pointers are null
@@ -353,6 +343,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
}
if (api_version >= 4) {
api->v4.pltHookCommit = lsplt::CommitHook;
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
return;
@@ -364,7 +355,7 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
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)
return;
regex_t re;
@@ -374,7 +365,7 @@ void HookContext::plt_hook_register(const char *regex, const char *symbol, void
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;
regex_t re;
if (regcomp(&re, regex, REG_NOSUB) != 0)
@@ -383,7 +374,7 @@ void HookContext::plt_hook_exclude(const char *regex, const char *symbol) {
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
}
void HookContext::plt_hook_process_regex() {
void ZygiskContext::plt_hook_process_regex() {
if (register_info.empty())
return;
for (auto &map : lsplt::MapInfo::Scan()) {
@@ -407,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);
plt_hook_process_regex();
@@ -469,8 +460,7 @@ int sigmask(int how, int signum) {
return sigprocmask(how, &set, nullptr);
}
void HookContext::fork_pre() {
g_ctx = this;
void ZygiskContext::fork_pre() {
// Do our own fork before loading any 3rd party code
// First block SIGCHLD, unblock after original fork is done
sigmask(SIG_BLOCK, SIGCHLD);
@@ -492,7 +482,7 @@ void HookContext::fork_pre() {
allowed_fds[dirfd(dir.get())] = false;
}
void HookContext::sanitize_fds() {
void ZygiskContext::sanitize_fds() {
if (flags[SKIP_FD_SANITIZATION])
return;
@@ -501,11 +491,11 @@ void HookContext::sanitize_fds() {
if (exempted_fds.empty())
return nullptr;
jintArray array = env->NewIntArray(off + exempted_fds.size());
jintArray array = env->NewIntArray(static_cast<int>(off + exempted_fds.size()));
if (array == nullptr)
return nullptr;
env->SetIntArrayRegion(array, off, exempted_fds.size(), exempted_fds.data());
env->SetIntArrayRegion(array, off, static_cast<int>(exempted_fds.size()), exempted_fds.data());
for (int fd : exempted_fds) {
if (fd >= 0 && fd < MAX_FD_SIZE) {
allowed_fds[fd] = true;
@@ -548,15 +538,14 @@ void HookContext::sanitize_fds() {
}
}
void HookContext::fork_post() {
void ZygiskContext::fork_post() {
// Unblock SIGCHLD in case the original method didn't
sigmask(SIG_UNBLOCK, SIGCHLD);
g_ctx = nullptr;
unload_zygisk();
}
/* Zygisksu changed: Load module fds */
void HookContext::run_modules_pre() {
void ZygiskContext::run_modules_pre() {
auto ms = zygiskd::ReadModules();
auto size = ms.size();
for (size_t i = 0; i < size; i++) {
@@ -577,7 +566,7 @@ void HookContext::run_modules_pre() {
}
}
void HookContext::run_modules_post() {
void ZygiskContext::run_modules_post() {
flags[POST_SPECIALIZE] = true;
for (const auto &m : modules) {
if (flags[APP_SPECIALIZE]) {
@@ -590,17 +579,20 @@ void HookContext::run_modules_post() {
}
/* Zygisksu changed: Load module fds */
void HookContext::app_specialize_pre() {
void ZygiskContext::app_specialize_pre() {
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();
if (info_flags & PROCESS_IS_MAGISK_APP) {
setenv("ZYGISK_ENABLED", "1", 1);
}
// Cleanups
env->ReleaseStringUTFChars(args.app->nice_name, process);
@@ -608,23 +600,7 @@ void HookContext::app_specialize_post() {
logging::setfd(-1);
}
void HookContext::unload_zygisk() {
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) {
bool ZygiskContext::exempt_fd(int fd) {
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
return true;
if (!flags[APP_FORK_AND_SPECIALIZE])
@@ -635,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);
LOGV("pre specialize [%s]\n", process);
g_ctx = this;
// App specialize does not check FD
flags[SKIP_FD_SANITIZATION] = true;
app_specialize_pre();
}
void HookContext::nativeSpecializeAppProcess_post() {
void ZygiskContext::nativeSpecializeAppProcess_post() {
LOGV("post specialize [%s]\n", process);
app_specialize_post();
unload_zygisk();
}
/* Zygisksu changed: No system_server status write back */
void HookContext::nativeForkSystemServer_pre() {
void ZygiskContext::nativeForkSystemServer_pre() {
LOGV("pre forkSystemServer\n");
flags[SERVER_FORK_AND_SPECIALIZE] = true;
@@ -660,11 +634,12 @@ void HookContext::nativeForkSystemServer_pre() {
return;
run_modules_pre();
zygiskd::SystemServerStarted();
sanitize_fds();
}
void HookContext::nativeForkSystemServer_post() {
void ZygiskContext::nativeForkSystemServer_post() {
if (pid == 0) {
LOGV("post forkSystemServer\n");
run_modules_post();
@@ -672,7 +647,7 @@ void HookContext::nativeForkSystemServer_post() {
fork_post();
}
void HookContext::nativeForkAndSpecialize_pre() {
void ZygiskContext::nativeForkAndSpecialize_pre() {
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
LOGV("pre forkAndSpecialize [%s]\n", process);
@@ -689,7 +664,7 @@ void HookContext::nativeForkAndSpecialize_pre() {
sanitize_fds();
}
void HookContext::nativeForkAndSpecialize_post() {
void ZygiskContext::nativeForkAndSpecialize_post() {
if (pid == 0) {
LOGV("post forkAndSpecialize [%s]\n", process);
app_specialize_post();
@@ -697,6 +672,37 @@ void HookContext::nativeForkAndSpecialize_post() {
fork_post();
}
ZygiskContext::~ZygiskContext() {
// This global pointer points to a variable on the stack.
// Set this to nullptr to prevent leaking local variable.
// This also disables most plt hooked functions.
g_ctx = nullptr;
if (!is_child())
return;
should_unmap_zygisk = true;
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && env->RegisterNatives(
env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
should_unmap_zygisk = false;
}
}
delete jni_hook_list;
jni_hook_list = nullptr;
// Strip out all API function pointers
for (auto &m : modules) {
m.clearApi();
}
hook_unloader();
}
} // namespace
static bool hook_commit() {
@@ -725,7 +731,6 @@ static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_
void hook_functions() {
default_new(plt_hook_list);
default_new(jni_hook_list);
default_new(jni_method_map);
ino_t android_runtime_inode = 0;
dev_t android_runtime_dev = 0;
@@ -739,72 +744,43 @@ void hook_functions() {
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, jniRegisterNativeMethods);
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_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit();
// Remove unhooked methods
plt_hook_list->erase(
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
[](auto &t) { return *std::get<3>(t) == nullptr;}),
[](auto &t) { return *std::get<3>(t) == nullptr;}),
plt_hook_list->end());
/* Zygisksu changed: AndroidRuntime setArgv0 before native bridge loaded */
if (old_jniRegisterNativeMethods == nullptr) {
do {
LOGD("jniRegisterNativeMethods not hooked, using fallback\n");
constexpr char sig[] = "_ZN7android14AndroidRuntime10getRuntimeEv";
auto *GetRuntime = (void*(*)()) dlsym(RTLD_DEFAULT, sig);
if (GetRuntime == nullptr) {
LOGE("GetRuntime is nullptr");
break;
}
hookVirtualTable(GetRuntime());
} while (false);
// We still need old_jniRegisterNativeMethods as other code uses it
// android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char*, const JNINativeMethod*, int)
constexpr char sig[] = "_ZN7android14AndroidRuntime21registerNativeMethodsEP7_JNIEnvPKcPK15JNINativeMethodi";
*(void **) &old_jniRegisterNativeMethods = dlsym(RTLD_DEFAULT, sig);
}
}
static bool unhook_functions() {
bool success = true;
static void hook_unloader() {
ino_t art_inode = 0;
dev_t art_dev = 0;
// Restore JNIEnv
if (g_ctx->env->functions == new_functions) {
g_ctx->env->functions = old_functions;
if (gClassRef) {
g_ctx->env->DeleteGlobalRef(gClassRef);
gClassRef = nullptr;
class_getName = nullptr;
for (auto &map : lsplt::MapInfo::Scan()) {
if (map.path.ends_with("/libart.so")) {
art_inode = map.inode;
art_dev = map.dev;
break;
}
}
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && old_jniRegisterNativeMethods(
g_ctx->env, clz.data(), methods.data(), 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
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
LOGE("Failed to register plt_hook [%s]\n", sym);
success = false;
}
}
delete plt_hook_list;
if (!hook_commit()) {
LOGE("Failed to restore plt_hook\n");
success = false;
should_unmap_zygisk = false;
}
return success;
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,7 @@ namespace {
void *nativeForkAndSpecialize_orig = nullptr;
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_l)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir
@@ -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) {
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;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_o)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir
@@ -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);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_p)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir
@@ -47,9 +41,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
args.is_top_app = &is_top_app;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_q_alt)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app
@@ -66,9 +58,7 @@ void *nativeForkAndSpecialize_orig = nullptr;
args.whitelisted_data_info_list = &whitelisted_data_info_list;
args.mount_data_dirs = &mount_data_dirs;
args.mount_storage_dirs = &mount_storage_dirs;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_r)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs
@@ -76,11 +66,27 @@ void *nativeForkAndSpecialize_orig = nullptr;
ctx.nativeForkAndSpecialize_post();
return ctx.pid;
}
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
args.is_top_app = &is_top_app;
args.pkg_data_info_list = &pkg_data_info_list;
args.whitelisted_data_info_list = &whitelisted_data_info_list;
args.mount_data_dirs = &mount_data_dirs;
args.mount_storage_dirs = &mount_storage_dirs;
args.mount_sysprop_overrides = &mount_sysprop_overrides;
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_u)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides
);
ctx.nativeForkAndSpecialize_post();
return ctx.pid;
}
[[clang::no_stack_protector]] jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir
@@ -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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4
@@ -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) {
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;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_o)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir
@@ -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);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_p)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir
@@ -127,62 +127,64 @@ void *nativeForkAndSpecialize_orig = nullptr;
ctx.nativeForkAndSpecialize_post();
return ctx.pid;
}
const JNINativeMethod nativeForkAndSpecialize_methods[] = {
{
std::array nativeForkAndSpecialize_methods = {
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_l
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_o
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_p
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
(void *) &nativeForkAndSpecialize_q_alt
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
(void *) &nativeForkAndSpecialize_r
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)I",
(void *) &nativeForkAndSpecialize_u
},
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_samsung_m
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
(void *) &nativeForkAndSpecialize_samsung_n
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_samsung_o
},
{
JNINativeMethod {
"nativeForkAndSpecialize",
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
(void *) &nativeForkAndSpecialize_samsung_p
},
};
constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods);
void *nativeSpecializeAppProcess_orig = nullptr;
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir
@@ -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);
args.is_child_zygote = &is_child_zygote;
args.is_top_app = &is_top_app;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q_alt)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app
@@ -210,57 +210,71 @@ void *nativeSpecializeAppProcess_orig = nullptr;
args.whitelisted_data_info_list = &whitelisted_data_info_list;
args.mount_data_dirs = &mount_data_dirs;
args.mount_storage_dirs = &mount_storage_dirs;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_r)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs
);
ctx.nativeSpecializeAppProcess_post();
}
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_u(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs, jboolean mount_sysprop_overrides) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.is_child_zygote = &is_child_zygote;
args.is_top_app = &is_top_app;
args.pkg_data_info_list = &pkg_data_info_list;
args.whitelisted_data_info_list = &whitelisted_data_info_list;
args.mount_data_dirs = &mount_data_dirs;
args.mount_storage_dirs = &mount_storage_dirs;
args.mount_sysprop_overrides = &mount_sysprop_overrides;
ZygiskContext ctx(env, &args);
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_u)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs, mount_sysprop_overrides
);
ctx.nativeSpecializeAppProcess_post();
}
[[clang::no_stack_protector]] void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_samsung_q)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir
);
ctx.nativeSpecializeAppProcess_post();
}
const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
{
std::array nativeSpecializeAppProcess_methods = {
JNINativeMethod {
"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
(void *) &nativeSpecializeAppProcess_q
},
{
JNINativeMethod {
"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
(void *) &nativeSpecializeAppProcess_q_alt
},
{
JNINativeMethod {
"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
(void *) &nativeSpecializeAppProcess_r
},
{
JNINativeMethod {
"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZZ)V",
(void *) &nativeSpecializeAppProcess_u
},
JNINativeMethod {
"nativeSpecializeAppProcess",
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
(void *) &nativeSpecializeAppProcess_samsung_q
},
};
constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods);
void *nativeForkSystemServer_orig = nullptr;
[[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkSystemServer_pre();
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
@@ -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) {
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
HookContext ctx;
ctx.env = env;
ctx.args = { &args };
ZygiskContext ctx(env, &args);
ctx.nativeForkSystemServer_pre();
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities
@@ -280,45 +292,48 @@ void *nativeForkSystemServer_orig = nullptr;
ctx.nativeForkSystemServer_post();
return ctx.pid;
}
const JNINativeMethod nativeForkSystemServer_methods[] = {
{
std::array nativeForkSystemServer_methods = {
JNINativeMethod {
"nativeForkSystemServer",
"(II[II[[IJJ)I",
(void *) &nativeForkSystemServer_l
},
{
JNINativeMethod {
"nativeForkSystemServer",
"(II[IIII[[IJJ)I",
(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
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));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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; });
}

View File

@@ -5,7 +5,7 @@
namespace {
struct HookContext;
struct ZygiskContext;
struct ZygiskModule;
struct AppSpecializeArgs_v1;
@@ -44,6 +44,7 @@ namespace {
jobjectArray *whitelisted_data_info_list = nullptr;
jboolean *mount_data_dirs = nullptr;
jboolean *mount_storage_dirs = nullptr;
jboolean *mount_sysprop_overrides = nullptr;
AppSpecializeArgs_v3(
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
@@ -111,12 +112,12 @@ namespace {
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
PROCESS_IS_SYS_UI = (1u << 29),
DENYLIST_ENFORCING = (1u << 30),
PROCESS_IS_MAGISK_APP = (1u << 31),
PROCESS_IS_MANAGER = (1u << 28),
PROCESS_ROOT_IS_KSU = (1u << 29),
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 | DENYLIST_ENFORCING | PROCESS_IS_MAGISK_APP)
PRIVATE_MASK = PROCESS_IS_SYS_UI
};
struct api_abi_base {

View 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 /debug_ramdisk
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());
}
}

View File

@@ -8,4 +8,7 @@ extern void *self_handle;
void hook_functions();
void revert_unmount();
void revert_unmount_ksu();
void revert_unmount_magisk();

View File

@@ -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;
logging::setfd(zygiskd::RequestLogcatFd());
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);
}

View 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) {
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::Init();
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;
}
}

View 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);

View 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[] = TMP_PATH "/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;
}
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},
};
strcpy(addr.sun_path, 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 event) 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 &loop, uint32_t event) 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 {
LOGE("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 = TMP_PATH "/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},
};
strcpy(addr.sun_path, 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 %ld != %ld\n", nsend, sizeof(cmd));
exit(1);
}
printf("command sent\n");
}

View File

@@ -0,0 +1,212 @@
#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 "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 = reinterpret_cast<uintptr_t *>(regs.REG_SP);
LOGD("kernel argument %p %s", arg, get_addr_mem_region(map, arg).c_str());
int argc;
auto argv = reinterpret_cast<char **>(reinterpret_cast<uintptr_t *>(arg) + 1);
LOGD("argv %p", argv);
read_proc(pid, arg, &argc, sizeof(argc));
LOGD("argc %d", argc);
auto envp = argv + argc + 1;
LOGD("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);
LOGD("auxv %p %s", auxv, get_addr_mem_region(map, auxv).c_str());
auto v = auxv;
void *entry_addr = nullptr;
void *addr_of_entry_addr = nullptr;
while (true) {
ElfW(auxv_t) buf;
read_proc(pid, (uintptr_t *) v, &buf, sizeof(buf));
if (buf.a_type == AT_ENTRY) {
entry_addr = reinterpret_cast<void *>(buf.a_un.a_val);
addr_of_entry_addr = reinterpret_cast<char *>(v) + offsetof(ElfW(auxv_t), a_un);
LOGD("entry address %p %s (v=%p, entry_addr=%p)", entry_addr,
get_addr_mem_region(map, entry_addr).c_str(), v, addr_of_entry_addr);
break;
}
if (buf.a_type == AT_NULL) break;
v++;
}
if (entry_addr == nullptr) {
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 ((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, &regs, 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);
LOGD("dlerror len %ld", dlerror_len);
if (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, magic)
args.clear();
args.push_back(remote_handle);
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 = TMP_PATH;
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;
}

View 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) {
LOGD("write to remote addr %p 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 (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 (l != len) {
LOGW("not fully read: %zu, excepted %zu", l, len);
}
return l;
}
bool get_regs(int pid, struct user_regs_struct &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_GETREGS, pid, 0, &regs) == -1) {
PLOGE("getregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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 &regs) {
#if defined(__x86_64__) || defined(__i386__)
if (ptrace(PTRACE_SETREGS, pid, 0, &regs) == -1) {
PLOGE("setregs");
return false;
}
#elif defined(__aarch64__) || defined(__arm__)
struct iovec iov = {
.iov_base = &regs,
.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, void *addr) {
for (auto &map: info) {
if (map.start <= (uintptr_t) addr && map.end > (uintptr_t) 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 &regs, long preserve) {
regs.REG_SP = (regs.REG_SP - preserve) & ~0xf;
}
void *push_string(int pid, struct user_regs_struct &regs, const char *str) {
auto len = strlen(str) + 1;
regs.REG_SP -= len;
align_stack(regs);
auto addr = reinterpret_cast<uintptr_t *>(regs.REG_SP);
if (!write_proc(pid, addr, str, len)) {
LOGE("failed to write string %s", str);
}
LOGD("pushed string %p", addr);
return addr;
}
uintptr_t remote_call(int pid, struct user_regs_struct &regs, uintptr_t func_addr, uintptr_t return_addr,
std::vector<long> &args) {
align_stack(regs);
LOGD("call %d args", args.size());
for (auto &a: args) {
LOGD("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 (int 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 (int 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 (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;
}

View 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 &regs);
bool set_regs(int pid, struct user_regs_struct &regs);
std::string get_addr_mem_region(std::vector<MapInfo> &info, void *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 &regs, long preserve = 0);
void *push_string(int pid, struct user_regs_struct &regs, const char *str);
uintptr_t remote_call(int pid, struct user_regs_struct &regs, 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
View File

@@ -0,0 +1,2 @@
public_key
private_key

View File

@@ -3,14 +3,31 @@ import org.apache.tools.ant.filters.ReplaceTokens
import org.apache.tools.ant.filters.FixCrLfFilter
import org.apache.commons.codec.binary.Hex
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.security.KeyFactory
import java.security.KeyPairGenerator
import java.security.Signature
import java.security.interfaces.EdECPrivateKey
import java.security.interfaces.EdECPublicKey
import java.security.spec.EdECPrivateKeySpec
import java.security.spec.NamedParameterSpec
import java.util.TreeSet
plugins {
id("com.android.library")
alias(libs.plugins.agp.lib)
}
val moduleId: String by rootProject.extra
val moduleName: String by rootProject.extra
val verCode: Int by rootProject.extra
val verName: String by rootProject.extra
val minKsuVersion: Int by rootProject.extra
val minKsudVersion: Int by rootProject.extra
val maxKsuVersion: Int by rootProject.extra
val minMagiskVersion: Int by rootProject.extra
val commitHash: String by rootProject.extra
android.buildFeatures {
androidResources = false
@@ -22,19 +39,19 @@ androidComponents.onVariants { variant ->
val variantCapped = variant.name.capitalize()
val buildTypeLowered = variant.buildType?.toLowerCase()
val moduleDir = "$buildDir/outputs/module/$variantLowered"
val zipFileName = "$moduleName-$verName-$verCode-$buildTypeLowered.zip".replace(' ', '-')
val moduleDir = layout.buildDirectory.dir("outputs/module/$variantLowered")
val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-')
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
group = "module"
dependsOn(
":loader:assemble$variantCapped",
":zygiskd:cargoBuild",
":zygiskd:buildAndStrip",
)
into(moduleDir)
from("${rootProject.projectDir}/README.md")
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"))
}
from("$projectDir/src") {
@@ -42,33 +59,131 @@ androidComponents.onVariants { variant ->
expand(
"moduleId" to moduleId,
"moduleName" to moduleName,
"versionName" to "$verName ($verCode)",
"versionCode" to verCode,
"versionName" to "$verName ($verCode-$commitHash-$variantLowered)",
"versionCode" to verCode
)
}
from("$projectDir/src/mazoku")
from("$projectDir/src") {
include("customize.sh", "daemon.sh")
include("customize.sh", "post-fs-data.sh", "service.sh", "zygisk-ctl.sh")
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<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
into("bin") {
from(project(":zygiskd").buildDir.path + "/rustJniLibs/android")
include("**/zygiskd")
}
into("lib") {
from("${project(":loader").buildDir}/intermediates/stripped_native_libs/$variantLowered/out/lib")
}
val root = moduleDir.get()
doLast {
if (file("private_key").exists()) {
println("=== machikado intergity signing ===")
val privateKey = file("private_key").readBytes()
val publicKey = file("public_key").readBytes()
val namedSpec = NamedParameterSpec("ed25519")
val privKeySpec = EdECPrivateKeySpec(namedSpec, privateKey)
val kf = KeyFactory.getInstance("ed25519")
val privKey = kf.generatePrivate(privKeySpec);
val sig = Signature.getInstance("ed25519")
fun File.sha(realFile: File? = null) {
val path = this.path.replace("\\", "/")
sig.update(this.name.toByteArray())
sig.update(0) // null-terminated string
val real = realFile ?: this
val buffer = ByteBuffer.allocate(8)
.order(ByteOrder.LITTLE_ENDIAN)
.putLong(real.length())
.array()
sig.update(buffer)
println("sha $path ${real.length()}")
real.forEachBlock { bytes, size ->
sig.update(bytes, 0, size)
}
}
fun getSign(name: String, abi32: String, abi64: String) {
println("getSign for $name $abi32 $abi64")
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 {
root.file("machikado.arm").asFile.createNewFile()
root.file("machikado.x86").asFile.createNewFile()
}
fileTree(moduleDir).visit {
if (isDirectory) return@visit
val md = MessageDigest.getInstance("SHA-256")
file.forEachBlock(4096) { bytes, size ->
md.update(bytes, 0, size)
}
file(file.path + ".sha256").writeText(org.apache.commons.codec.binary.Hex.encodeHexString(md.digest()))
file(file.path + ".sha256").writeText(Hex.encodeHexString(md.digest()))
}
}
}
@@ -106,7 +221,7 @@ androidComponents.onVariants { variant ->
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
group = "module"
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") {

View File

@@ -1,31 +1,61 @@
# shellcheck disable=SC2034
SKIPUNZIP=1
if [ $BOOTMODE ] && [ "$KSU" == "true" ]; then
DEBUG=@DEBUG@
MIN_KSU_VERSION=@MIN_KSU_VERSION@
MIN_KSUD_VERSION=@MIN_KSUD_VERSION@
MAX_KSU_VERSION=@MAX_KSU_VERSION@
MIN_MAGISK_VERSION=@MIN_MAGISK_VERSION@
if [ "$BOOTMODE" ] && [ "$KSU" ]; then
ui_print "- Installing from KernelSU app"
ui_print "- KernelSU version: $KSU_KERNEL_VER_CODE (kernel) + $KSU_VER_CODE (ksud)"
if ! [ "$KSU_KERNEL_VER_CODE" ] || [ "$KSU_KERNEL_VER_CODE" -lt "$MIN_KSU_VERSION" ]; then
ui_print "*********************************************************"
ui_print "! KernelSU version is too old!"
ui_print "! Please update KernelSU to latest version"
abort "*********************************************************"
elif [ "$KSU_KERNEL_VER_CODE" -ge "$MAX_KSU_VERSION" ]; then
ui_print "*********************************************************"
ui_print "! KernelSU version abnormal!"
ui_print "! Please integrate KernelSU into your kernel"
ui_print " as submodule instead of copying the source code"
abort "*********************************************************"
fi
if ! [ "$KSU_VER_CODE" ] || [ "$KSU_VER_CODE" -lt "$MIN_KSUD_VERSION" ]; then
ui_print "*********************************************************"
ui_print "! ksud version is too old!"
ui_print "! Please update KernelSU Manager to latest version"
abort "*********************************************************"
fi
if [ "$(which magisk)" ]; then
ui_print "*********************************************************"
ui_print "! Multiple root implementation is NOT supported!"
ui_print "! Please uninstall Magisk before installing Zygisk Next"
abort "*********************************************************"
fi
elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then
ui_print "- Installing from Magisk app"
if [ "$MAGISK_VER_CODE" -lt "$MIN_MAGISK_VERSION" ]; then
ui_print "*********************************************************"
ui_print "! Magisk version is too old!"
ui_print "! Please update Magisk to latest version"
abort "*********************************************************"
fi
else
ui_print "*********************************************************"
ui_print "! Install from recovery or Magisk is NOT supported"
ui_print "! Please install from KernelSU app"
abort "*********************************************************"
fi
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
ui_print "- Installing Zygisksu $VERSION"
# check KernelSU
ui_print "- KernelSU version: $KSU_VER ($KSU_VER_CODE)"
if [ "$KSU_VER_CODE" -lt 10200 ]; then
ui_print "*********************************************************"
ui_print "! KernelSU version is too old!"
ui_print "! Please update KernelSU to latest version"
ui_print "! Install from recovery is not supported"
ui_print "! Please install from KernelSU or Magisk app"
abort "*********************************************************"
fi
VERSION=$(grep_prop version "${TMPDIR}/module.prop")
ui_print "- Installing Zygisk Next $VERSION"
# check android
if [ "$API" -lt 29 ]; then
if [ "$API" -lt 26 ]; then
ui_print "! Unsupported sdk: $API"
abort "! Minimal supported sdk is 29 (Android 10.0)"
abort "! Minimal supported sdk is 26 (Android 8.0)"
else
ui_print "- Device sdk: $API"
fi
@@ -50,85 +80,69 @@ extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
ui_print "- Checking SELinux patches"
if ! /data/adb/ksud sepolicy check "$TMPDIR/sepolicy.rule"; then
ui_print "*********************************************************"
ui_print "! Unable to apply SELinux patches!"
ui_print "! Your kernel may not support SELinux patch fully"
abort "*********************************************************"
if [ "$KSU" ]; then
ui_print "- Checking SELinux patches"
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
ui_print "*********************************************************"
ui_print "! Unable to apply SELinux patches!"
ui_print "! Your kernel may not support SELinux patch fully"
abort "*********************************************************"
fi
fi
ui_print "- Extracting module files"
extract "$ZIPFILE" 'daemon.sh' "$MODPATH"
extract "$ZIPFILE" 'module.prop' "$MODPATH"
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
extract "$ZIPFILE" 'service.sh' "$MODPATH"
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true
extract "$ZIPFILE" 'zygisk-ctl.sh' "$MODPATH"
extract "$ZIPFILE" 'mazoku' "$MODPATH"
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
mkdir "$MODPATH/bin"
mkdir "$MODPATH/system"
[ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib"
[ "$HAS64BIT" = true ] && mkdir "$MODPATH/system/lib64"
mkdir "$MODPATH/lib"
mkdir "$MODPATH/lib64"
mv "$MODPATH/zygisk-ctl.sh" "$MODPATH/bin/zygisk-ctl"
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
if [ "$HAS32BIT" = true ]; then
ui_print "- Extracting x86 libraries"
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/x86/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygiskloader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
ui_print "- Extracting x86 libraries"
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true
extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
if [ "$HAS64BIT" = true ]; then
ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/x86_64/libinjector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygiskloader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
extract "$ZIPFILE" 'machikado.x86' "$MODPATH" true
mv "$MODPATH/machikado.x86" "$MODPATH/machikado"
else
if [ "$HAS32BIT" = true ]; then
ui_print "- Extracting arm libraries"
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/armeabi-v7a/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygiskloader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
ui_print "- Extracting arm libraries"
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
if [ "$HAS64BIT" = true ]; then
ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/arm64-v8a/libinjector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygiskloader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
fi
ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
ui_print "- Hex patching"
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"
extract "$ZIPFILE" 'machikado.arm' "$MODPATH" true
mv "$MODPATH/machikado.arm" "$MODPATH/machikado"
fi
ui_print "- Setting permissions"
chmod 0744 "$MODPATH/daemon.sh"
set_perm_recursive "$MODPATH/bin" 0 2000 0755 0755
set_perm_recursive "$MODPATH/system/lib" 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
set_perm_recursive "$MODPATH/bin" 0 0 0755 0755
set_perm_recursive "$MODPATH/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0
set_perm_recursive "$MODPATH/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0
# If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control
HUAWEI_MAPLE_ENABLED=$(grep_prop ro.maple.enable)

View File

@@ -1,12 +0,0 @@
#!/system/bin/sh
DEBUG=@DEBUG@
MODDIR=${0%/*}
# 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 "$MODDIR/bin/zygiskwd" "watchdog" >/dev/null 2>&1

1
module/src/mazoku Normal file
View File

@@ -0,0 +1 @@
c<EFBFBD><EFBFBD><EFBFBD>]<5D><> <20>[<11>B<EFBFBD>u<EFBFBD>5=<3D>rEU<45>Z<>ʿ<EFBFBD><CABF><<3C>5<EFBFBD>_o<><6F>M<>L<>l<EFBFBD><6C><EFBFBD>Q#<23><><EFBFBD>C<EFBFBD>}<7D> e<><65>fjق/<2F>7<EFBFBD><11><>(<28><>g<b<>Rg<07>m<EFBFBD>d

View File

@@ -3,4 +3,5 @@ name=${moduleName}
version=${versionName}
versionCode=${versionCode}
author=Nullptr, 5ec1cff
description=Run Zygisk on KernelSU.
description=Standalone implementation of Zygisk.
updateJson=https://api.nullptr.icu/android/zygisk-next/static/update.json

View File

@@ -1,5 +1,46 @@
#!/system/bin/sh
MODDIR=${0%/*}
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
unshare -m sh -c "$MODDIR/daemon.sh $@&"
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
cd "$MODDIR"
if [ "$(which magisk)" ]; then
for file in ../*; do
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
if [ -f "$file/post-fs-data.sh" ]; then
cd "$file"
log -p i -t "zygisk-sh" "Manually trigger post-fs-data.sh for $file"
sh "$(realpath ./post-fs-data.sh)"
cd "$MODDIR"
fi
fi
done
fi
create_sys_perm() {
mkdir -p $1
chmod 555 $1
chcon u:object_r:system_file:s0 $1
}
TMP_PATH="/debug_ramdisk/zygisksu"
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 &

View File

@@ -1,4 +1,6 @@
allow * tmpfs * *
allow zygote appdomain_tmpfs dir *
allow zygote appdomain_tmpfs file *
type magisk_file file_type
typeattribute magisk_file mlstrustedobject
@@ -10,3 +12,9 @@ allow * magisk_file lnk_file *
allow * magisk_file sock_file *
allow system_server system_server process execmem
allow zygote adb_data_file dir search
allow zygote mnt_vendor_file dir search
allow zygote system_file dir mounton
allow zygote labeledfs filesystem mount
allow zygote fs_type filesystem unmount
allow zygote zygote process execmem

View File

@@ -1 +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

2
module/src/zygisk-ctl.sh Normal file
View File

@@ -0,0 +1,2 @@
MODDIR=${0%/*}/..
exec $MODDIR/bin/zygisk-ptrace64 ctl $*

View File

@@ -6,10 +6,6 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
plugins {
id("com.android.library") version "7.4.0"
id("com.android.application") version "7.4.0"
}
}
dependencyResolutionManagement {
@@ -20,7 +16,7 @@ dependencyResolutionManagement {
}
}
rootProject.name = "ZygiskOnKernelSU"
rootProject.name = "ZygiskNext"
include(
":loader",
":module",

View File

@@ -1,2 +1,3 @@
[build]
target-dir = "build/intermediates/rust"
target = "aarch64-linux-android"

View File

@@ -3,21 +3,33 @@ name = "zygiskd"
authors = ["Nullptr"]
version = "1.0.0"
edition = "2021"
rust-version = "1.67"
rust-version = "1.69"
[dependencies]
android_logger = "0.12.0"
anyhow = { version = "1.0.68", features = ["backtrace"] }
clap = { version = "4.1.4", features = ["derive"] }
const_format = "0.2.5"
log = "0.4.17"
memfd = "0.6.2"
nix = "0.26.2"
num_enum = "0.5.9"
passfd = "0.1.5"
rand = "0.8.5"
android_logger = "0.13"
anyhow = { version = "1.0", features = ["backtrace"] }
bitflags = { version = "2.3" }
const_format = "0.2"
futures = "0.3"
konst = "0.3"
lazy_static = "1.4"
libc = "0.2"
log = "0.4"
memfd = "0.6"
num_enum = "0.5"
passfd = "0.1"
proc-maps = "0.3"
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] }
tokio = { version = "1.28", features = ["full"] }
[profile.dev]
strip = false
panic = "abort"
[profile.release]
strip = true
strip = false
debug = true
panic = "abort"
opt-level = "z"
lto = true

View File

@@ -1,10 +1,14 @@
plugins {
id("com.android.library")
id("org.mozilla.rust-android-gradle.rust-android")
alias(libs.plugins.agp.lib)
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 verName: String by rootProject.extra
val commitHash: String by rootProject.extra
android.buildFeatures {
androidResources = false
@@ -20,7 +24,44 @@ cargo {
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
profile = if (isDebug) "debug" else "release"
exec = { spec, _ ->
spec.environment("VERSION_CODE", verCode)
spec.environment("VERSION_NAME", verName)
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
spec.environment("MIN_KSU_VERSION", minKsuVersion)
spec.environment("MAX_KSU_VERSION", maxKsuVersion)
spec.environment("MIN_MAGISK_VERSION", minMagiskVersion)
spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile")
}
}
afterEvaluate {
task<Task>("buildAndStrip") {
dependsOn(":zygiskd:cargoBuild")
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
doLast {
val dir = File(buildDir, "rustJniLibs/android")
val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first()
val binDir = File(prebuilt, "bin")
val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}")
symbolDir.mkdirs()
val suffix = if (prebuilt.name.contains("windows")) ".exe" else ""
val strip = File(binDir, "llvm-strip$suffix")
val objcopy = File(binDir, "llvm-objcopy$suffix")
dir.listFiles()!!.forEach {
if (!it.isDirectory) return@forEach
val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug")
symbolPath.parentFile.mkdirs()
exec {
workingDir = it
commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath)
}
exec {
workingDir = it
commandLine(strip, "--strip-all", "zygiskd")
}
exec {
workingDir = it
commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd")
}
}
}
}
}

View File

@@ -1,47 +1,58 @@
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::thread;
use anyhow::Result;
use nix::libc;
use passfd::FdPassingExt;
use crate::utils::UnixStreamExt;
use rustix::fs::fstat;
use tokio::io::AsyncWriteExt;
use crate::utils::{check_unix_socket, UnixStreamExt};
use crate::dl;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
pub fn entry(fd: i32) -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
pub fn entry(fd: i32) {
log::info!("companion entry fd={}", fd);
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
let name = stream.read_string()?;
let library = stream.recv_fd()?;
let entry = load_module(library)?;
let name = stream.read_string().expect("read name");
let library = stream.recv_fd().expect("receive library fd");
let entry = load_module(library).expect("load module");
unsafe { libc::close(library) };
let entry = match entry {
Some(entry) => {
log::debug!("Companion process created for `{name}`");
stream.write_u8(1)?;
stream.write_u8(1).expect("reply 1");
entry
}
None => {
log::debug!("No companion entry for `{name}`");
stream.write_u8(0)?;
return Ok(());
stream.write_u8(0).expect("reply 0");
return ();
}
};
loop {
let fd = stream.recv_fd()?;
log::trace!("New companion request from module `{name}`");
if !check_unix_socket(&stream, true) {
log::info!("Something bad happened in zygiskd, terminate companion");
std::process::exit(0);
}
let fd = stream.recv_fd().expect("recv fd");
log::trace!("New companion request from module `{name}` fd=`{fd}`");
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
stream.write_u8(1).expect("reply success");
thread::spawn(move || {
unsafe {
let mut s = UnixStream::from_raw_fd(fd);
match s.write_u8(1) { // Ack
Ok(_) => entry(fd),
Err(_) => log::warn!("Ack failed?"),
let st0 = fstat(&stream).expect("failed to stat stream");
unsafe { entry(stream.as_raw_fd()); }
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if let Ok(st1) = fstat(&stream) {
if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino {
std::mem::forget(stream);
}
};
}
});
}
}

View File

@@ -1,44 +1,57 @@
use bitflags::bitflags;
use const_format::concatcp;
use konst::primitive::parse_i32;
use konst::unwrap_ctx;
use log::LevelFilter;
use num_enum::TryFromPrimitive;
use crate::lp_select;
pub const VERSION_NAME: &str = env!("VERSION_NAME");
pub const VERSION_CODE: &str = env!("VERSION_CODE");
pub const VERSION_FULL: &str = concatcp!(VERSION_NAME, " (", VERSION_CODE, ")");
pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION")));
pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION")));
pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION")));
pub const ZKSU_VERSION: &'static str = env!("ZKSU_VERSION");
#[cfg(debug_assertions)]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
#[cfg(not(debug_assertions))]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
#[cfg(target_pointer_width = "64")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp64 };
}
#[cfg(target_pointer_width = "32")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp32 };
}
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
pub const PATH_MODULES_DIR: &str = "..";
pub const PATH_MODULE_PROP: &str = "module.prop";
pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4);
pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6);
pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8);
pub const SYSTEM_SERVER_STARTED: i32 = 10;
pub const TMP_DIR: &str = "/debug_ramdisk/zygisksu";
pub const CONTROLLER_SOCKET: &str = concatcp!(TMP_DIR, "/init_monitor");
pub const PATH_CP_NAME: &str = concatcp!(TMP_DIR, lp_select!("/cp32.sock", "/cp64.sock"));
pub const PATH_KSU_MODULE_DIR: &str = "/data/adb/ksu/modules";
pub const PATH_ZYGISKSU_DIR: &str = concatcp!(PATH_KSU_MODULE_DIR, "/zygisksu");
pub const PATH_ZYGISKD32: &str = concatcp!(PATH_ZYGISKSU_DIR, "/bin/zygiskd32");
pub const PATH_ZYGISKD64: &str = concatcp!(PATH_ZYGISKSU_DIR, "/bin/zygiskd64");
pub const PATH_DAEMON_LOCK: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd.lock");
pub const MAX_RESTART_COUNT: i32 = 5;
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum DaemonSocketAction {
PingHeartbeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
}
// Zygisk process flags
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProcessFlags: u32 {
const PROCESS_GRANTED_ROOT = 1 << 0;
const PROCESS_ON_DENYLIST = 1 << 1;
const PROCESS_IS_MANAGER = 1 << 28;
const PROCESS_ROOT_IS_KSU = 1 << 29;
const PROCESS_ROOT_IS_MAGISK = 1 << 30;
const PROCESS_IS_SYSUI = 1 << 31;
}
}

View File

@@ -1,18 +1,17 @@
use anyhow::{bail, Result};
use std::ffi::{c_char, c_void};
use nix::libc;
const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct AndroidNamespace {
pub struct AndroidNamespace {
_unused: [u8; 0],
}
#[repr(C)]
struct AndroidDlextinfo {
pub struct AndroidDlextinfo {
pub flags: u64,
pub reserved_addr: *mut c_void,
pub reserved_size: libc::size_t,
@@ -23,7 +22,7 @@ struct AndroidDlextinfo {
}
extern "C" {
fn android_dlopen_ext(
pub fn android_dlopen_ext(
filename: *const c_char,
flags: libc::c_int,
extinfo: *const AndroidDlextinfo,
@@ -76,7 +75,7 @@ pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
let result = android_dlopen_ext(filename, flags, &info);
if result.is_null() {
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
bail!("dlopen failed: {}", e);
bail!(e);
}
Ok(result)
}

View File

@@ -1,31 +1,12 @@
#![allow(dead_code)]
mod companion;
mod constants;
mod dl;
mod root_impl;
mod utils;
mod watchdog;
mod zygiskd;
mod companion;
use clap::{Subcommand, Parser};
#[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 },
}
use std::future::Future;
use crate::constants::ZKSU_VERSION;
fn init_android_logger(tag: &str) {
android_logger::init_once(
@@ -35,19 +16,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() {
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name);
let cli = Args::parse();
let result = match cli.command {
Commands::Watchdog => watchdog::entry(),
Commands::Daemon => zygiskd::entry(lp_select!(false, true)),
Commands::Companion { fd } => companion::entry(fd),
};
if let Err(e) = &result {
log::error!("Crashed: {}\n{}", e, e.backtrace());
}
start();
}

View 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
}

View File

@@ -0,0 +1,96 @@
use std::process::{Command, Stdio};
use crate::constants::MIN_MAGISK_VERSION;
pub enum Version {
Supported,
TooOld,
}
pub fn get_magisk() -> Option<Version> {
let version: Option<i32> = 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().ok());
version.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
// TODO: magisk random package name
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=") {
if let Ok(s) = rustix::fs::stat(format!("/data/user_de/0/{}", manager)) {
return s.st_uid == uid as u32;
} else {
return false;
}
}
}
if let Ok(s) = rustix::fs::stat("/data/user_de/0/com.topjohnwu.magisk") {
if s.st_uid == uid as u32 {
return true;
}
}
if let Ok(s) = rustix::fs::stat("/data/user_de/0/io.github.vvb2060.magisk") {
if s.st_uid == uid as u32 {
return true;
}
}
false
}

View File

@@ -0,0 +1,66 @@
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()),
}
}

View File

@@ -1,14 +1,56 @@
use crate::constants;
use anyhow::Result;
use nix::unistd::gettid;
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
use std::os::fd::FromRawFd;
use std::os::unix::net::UnixListener;
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
use rand::distributions::{Alphanumeric, DistString};
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream};
use std::ffi::{c_char, c_void, CStr, CString};
use std::os::fd::{AsFd, AsRawFd};
use std::os::unix::net::{UnixDatagram, UnixListener};
use std::process::Command;
use std::sync::OnceLock;
use bitflags::Flags;
use rustix::net::{AddressFamily, bind_unix, connect_unix, listen, SendFlags, sendto_unix, socket, SocketAddrUnix, SocketType};
use rustix::path::Arg;
use rustix::thread::gettid;
pub fn random_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
#[cfg(target_pointer_width = "64")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp64 };
}
#[cfg(target_pointer_width = "32")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => { $lp32 };
}
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => { $debug };
}
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => { $release };
}
pub struct LateInit<T> {
cell: OnceLock<T>,
}
impl<T> LateInit<T> {
pub const fn new() -> Self {
LateInit { cell: OnceLock::new() }
}
pub fn init(&self, value: T) {
assert!(self.cell.set(value).is_ok())
}
}
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<()> {
@@ -16,22 +58,78 @@ pub fn set_socket_create_context(context: &str) -> Result<()> {
match fs::write(path, context) {
Ok(_) => Ok(()),
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)?;
Ok(())
}
}
}
pub fn get_native_bridge() -> String {
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
pub fn get_current_attr() -> Result<String> {
let s = fs::read("/proc/self/attr/current")?;
Ok(s.to_string_lossy().to_string())
}
pub fn restore_native_bridge() -> Result<()> {
Command::new("resetprop")
.arg(constants::PROP_NATIVE_BRIDGE)
.arg(get_native_bridge())
.spawn()?.wait()?;
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())
}
pub fn set_property(name: &str, value: &str) -> Result<()> {
let name = CString::new(name)?;
let value = CString::new(value)?;
unsafe {
__system_property_set(name.as_ptr(), value.as_ptr());
}
Ok(())
}
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)
}
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(())
}
@@ -41,6 +139,7 @@ pub trait UnixStreamExt {
fn read_usize(&mut self) -> Result<usize>;
fn read_string(&mut self) -> Result<String>;
fn write_u8(&mut self, value: u8) -> Result<()>;
fn write_u32(&mut self, value: u32) -> Result<()>;
fn write_usize(&mut self, value: usize) -> Result<()>;
fn write_string(&mut self, value: &str) -> Result<()>;
}
@@ -76,6 +175,11 @@ impl UnixStreamExt for UnixStream {
Ok(())
}
fn write_u32(&mut self, value: u32) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_usize(&mut self, value: usize) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
@@ -88,12 +192,48 @@ impl UnixStreamExt for UnixStream {
}
}
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
let addr = UnixAddr::new_abstract(name.as_bytes())?;
let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
nix::sys::socket::bind(socket, &addr)?;
nix::sys::socket::listen(socket, 2)?;
let listener = unsafe { UnixListener::from_raw_fd(socket) };
Ok(listener)
pub fn unix_listener_from_path(path: &str) -> Result<UnixListener> {
let _ = fs::remove_file(path);
let addr = SocketAddrUnix::new(path)?;
let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?;
bind_unix(&socket, &addr)?;
listen(&socket, 2)?;
chcon(path, "u:object_r:magisk_file:s0")?;
Ok(UnixListener::from(socket))
}
pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> {
// FIXME: shall we set create context every time?
set_socket_create_context(get_current_attr()?.as_str())?;
let addr = SocketAddrUnix::new(path.as_bytes())?;
let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?;
connect_unix(&socket, &addr)?;
sendto_unix(socket, buf, SendFlags::empty(), &addr)?;
set_socket_create_context("u:r:zygote:s0")?;
Ok(())
}
pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool {
unsafe {
let mut pfd = libc::pollfd {
fd: stream.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let timeout = if block { -1 } else { 0 };
libc::poll(&mut pfd, 1, timeout);
if pfd.revents & !libc::POLLIN != 0 {
return false;
}
}
return true;
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32;
fn __system_property_set(name: *const c_char, value: *const c_char) -> u32;
fn __system_property_find(name: *const c_char) -> *const c_void;
fn __system_property_wait(info: *const c_void, old_serial: u32, new_serial: *mut u32, timeout: *const libc::timespec) -> bool;
fn __system_property_serial(info: *const c_void) -> u32;
}

View File

@@ -1,77 +0,0 @@
use crate::constants;
use anyhow::{bail, Result};
use nix::fcntl::{flock, FlockArg};
use nix::unistd::{getgid, getuid};
use std::os::unix::prelude::AsRawFd;
use std::process::{Child, Command};
use std::sync::mpsc;
use std::{fs, thread};
static mut LOCK_FILE: Option<fs::File> = 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');
//TODO: remove magisk context after debug finished
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 metadata = fs::metadata(constants::PATH_ZYGISKSU_DIR);
if metadata.is_err() || !metadata.unwrap().is_dir() {
bail!("Zygisksu is not installed");
}
unsafe {
match fs::File::create(constants::PATH_DAEMON_LOCK) {
Ok(file) => LOCK_FILE = Some(file),
Err(e) => bail!("Failed to open lock file: {e}"),
};
let fd = LOCK_FILE.as_ref().unwrap().as_raw_fd();
if let Err(e) = flock(fd, FlockArg::LockExclusiveNonblock) {
bail!("Failed to acquire lock: {e}. Maybe another instance is running?");
}
}
Ok(())
}
fn spawn_daemon() -> Result<()> {
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let (sender, receiver) = mpsc::channel();
let spawn = |mut daemon: Child| {
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
sender.send(()).unwrap();
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let _ = receiver.recv();
bail!("Daemon process died");
}

View File

@@ -1,93 +1,124 @@
use crate::constants::DaemonSocketAction;
use crate::utils::{restore_native_bridge, UnixStreamExt};
use crate::{constants, utils};
use crate::constants::{DaemonSocketAction, ProcessFlags};
use crate::utils::{check_unix_socket, UnixStreamExt};
use crate::{constants, dl, lp_select, root_impl, utils};
use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
fcntl::{fcntl, FcntlArg, FdFlag},
libc::self,
};
use passfd::FdPassingExt;
use std::sync::{Arc, Mutex};
use std::thread;
use std::ffi::c_char;
use std::fs;
use std::io::Error;
use std::os::fd::{OwnedFd, RawFd};
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::Command;
use std::process::{Command, exit};
use log::info;
use std::os::unix::process::CommandExt;
use bitflags::Flags;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
struct Module {
name: String,
memfd: Memfd,
companion: Mutex<Option<UnixStream>>,
lib_fd: OwnedFd,
companion: Mutex<Option<Option<UnixStream>>>,
}
struct Context {
native_bridge: String,
modules: Vec<Module>,
}
pub fn entry(is64: bool) -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
pub fn main() -> Result<()> {
log::info!("Welcome to Zygisk Next ({}) !", constants::ZKSU_VERSION);
let arch = get_arch(is64)?;
let arch = get_arch()?;
log::debug!("Daemon architecture: {arch}");
log::info!("Load modules");
let modules = load_modules(arch)?;
{
let mut msg = Vec::<u8>::new();
let info = match root_impl::get_impl() {
root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk => {
msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes());
let module_names: Vec<_> = modules.iter()
.map(|m| m.name.as_str()).collect();
format!("Root: {:?},module({}): {}", root_impl::get_impl(), modules.len(), module_names.join(","))
}
_ => {
msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes());
format!("Invalid root implementation: {:?}", root_impl::get_impl())
}
};
msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes());
msg.extend_from_slice(info.as_bytes());
msg.extend_from_slice(&[0u8]);
utils::unix_datagram_sendto(constants::CONTROLLER_SOCKET, msg.as_slice()).expect("failed to send info");
}
let context = Context {
native_bridge: utils::get_native_bridge(),
modules,
};
let context = Arc::new(context);
log::info!("Create socket");
let listener = create_daemon_socket(is64)?;
log::info!("Handle zygote connections");
let listener = create_daemon_socket()?;
for stream in listener.incoming() {
let stream = stream?;
let mut 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());
let action = stream.read_u8()?;
let action = DaemonSocketAction::try_from(action)?;
log::trace!("New daemon action {:?}", action);
match action {
DaemonSocketAction::PingHeartbeat => {
let value = constants::ZYGOTE_INJECTED;
utils::unix_datagram_sendto(constants::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(constants::CONTROLLER_SOCKET, &value.to_le_bytes())?;
}
_ => {
thread::spawn(move || {
if let Err(e) = handle_daemon_action(action, stream, &context) {
log::warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
}
});
}
}
}
Ok(())
}
fn get_arch(is64: bool) -> Result<&'static str> {
let output = Command::new("getprop").arg("ro.product.cpu.abi").output()?;
let system_arch = String::from_utf8(output.stdout)?;
let is_arm = system_arch.contains("arm");
let is_x86 = system_arch.contains("x86");
match (is_arm, is_x86, is64) {
(true, _, false) => Ok("armeabi-v7a"),
(true, _, true) => Ok("arm64-v8a"),
(_, true, false) => Ok("x86"),
(_, true, true) => Ok("x86_64"),
_ => bail!("Unsupported system architecture: {}", system_arch),
fn get_arch() -> Result<&'static str> {
let system_arch = utils::get_property("ro.product.cpu.abi")?;
if system_arch.contains("arm") {
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
}
if system_arch.contains("x86") {
return Ok(lp_select!("x86", "x86_64"));
}
bail!("Unsupported system architecture: {}", system_arch);
}
fn load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_KSU_MODULE_DIR) {
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
Ok(dir) => dir,
Err(e) => {
log::warn!("Failed reading modules directory: {}", e);
return Ok(modules);
}
};
for entry_result in dir.into_iter() {
let entry = entry_result?;
for entry in dir.into_iter() {
let entry = entry?;
let name = entry.file_name().into_string().unwrap();
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
let disabled = entry.path().join("disable");
@@ -95,33 +126,24 @@ fn load_modules(arch: &str) -> Result<Vec<Module>> {
continue;
}
log::info!(" Loading module `{name}`...");
let memfd = match create_memfd(&name, &so_path) {
Ok(memfd) => memfd,
let lib_fd = match create_library_fd(&so_path) {
Ok(fd) => fd,
Err(e) => {
log::warn!(" Failed to create memfd for `{name}`: {e}");
continue;
}
};
let companion = match spawn_companion(&name, &memfd) {
Ok(companion) => companion,
Err(e) => {
log::warn!(" Failed to spawn companion for `{name}`: {e}");
continue;
}
};
let companion = Mutex::new(companion);
let module = Module { name, memfd, companion };
let companion = Mutex::new(None);
let module = Module { name, lib_fd, companion };
modules.push(module);
}
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 memfd = opts.create(name)?;
let memfd = opts.create("jit-cache")?;
let file = fs::File::open(so_path)?;
let mut reader = std::io::BufReader::new(file);
let mut writer = memfd.as_file();
@@ -134,49 +156,57 @@ fn create_memfd(name: &str, so_path: &PathBuf) -> Result<Memfd> {
seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?;
Ok(memfd)
Ok(OwnedFd::from(memfd.into_file()))
}
fn create_daemon_socket(is64: bool) -> Result<UnixListener> {
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let suffix = if is64 { "zygiskd64" } else { "zygiskd32" };
let name = String::from(suffix) + constants::SOCKET_PLACEHOLDER;
let listener = utils::abstract_namespace_socket(&name)?;
log::debug!("Daemon socket: {name}");
let listener = utils::unix_listener_from_path(constants::PATH_CP_NAME)?;
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()?;
// 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 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
unsafe { libc::fcntl(companion.as_raw_fd() as libc::c_int, libc::F_SETFD, 0i32); };
}
}
Command::new(&process)
.arg0(format!("{}-{}", nice_name, name))
.arg("companion")
.arg(format!("{}", companion.as_raw_fd()))
.spawn()?;
drop(companion);
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"),
}
exit(0)
}
fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> {
let action = stream.read_u8()?;
let action = DaemonSocketAction::try_from(action)?;
log::trace!("New daemon action {:?}", action);
fn handle_daemon_action(action: DaemonSocketAction, mut stream: UnixStream, context: &Context) -> Result<()> {
match action {
DaemonSocketAction::PingHeartbeat => {
restore_native_bridge()?;
}
DaemonSocketAction::RequestLogcatFd => {
loop {
let level = match stream.read_u8() {
@@ -184,38 +214,73 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
Err(_) => break,
};
let tag = stream.read_string()?;
let tag = std::ffi::CString::new(tag)?;
let message = stream.read_string()?;
let message = std::ffi::CString::new(message)?;
unsafe {
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
}
utils::log_raw(level as i32, &tag, &message)?;
}
}
DaemonSocketAction::ReadNativeBridge => {
stream.write_string(&context.native_bridge)?;
DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32;
let mut flags = ProcessFlags::empty();
if root_impl::uid_is_manager(uid) {
flags |= ProcessFlags::PROCESS_IS_MANAGER;
} else {
if root_impl::uid_granted_root(uid) {
flags |= ProcessFlags::PROCESS_GRANTED_ROOT;
}
if root_impl::uid_should_umount(uid) {
flags |= ProcessFlags::PROCESS_ON_DENYLIST;
}
}
match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK,
_ => panic!("wrong root impl: {:?}", root_impl::get_impl()),
}
log::trace!("Uid {} granted root: {}", uid, flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT));
log::trace!("Uid {} on denylist: {}", uid, flags.contains(ProcessFlags::PROCESS_ON_DENYLIST));
stream.write_u32(flags.bits())?;
}
DaemonSocketAction::ReadModules => {
stream.write_usize(context.modules.len())?;
for module in context.modules.iter() {
stream.write_string(&module.name)?;
stream.send_fd(module.memfd.as_raw_fd())?;
stream.send_fd(module.lib_fd.as_raw_fd())?;
}
}
DaemonSocketAction::RequestCompanionSocket => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let mut companion = module.companion.lock().unwrap();
if let Some(Some(sock)) = companion.as_ref() {
if !check_unix_socket(sock, false) {
log::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() {
log::trace!(" Spawned companion for `{}`", module.name);
} else {
log::trace!(" No companion spawned for `{}` because it has not entry", module.name);
}
*companion = Some(c);
},
Err(e) => {
log::warn!(" Failed to spawn companion for `{}`: {}", module.name, e);
}
};
}
match companion.as_ref() {
Some(sock) => {
if let Err(_) = sock.send_fd(stream.as_raw_fd()) {
log::error!("Companion of module `{}` crashed", module.name);
companion.take();
Some(Some(sock)) => {
if let Err(e) = sock.send_fd(stream.as_raw_fd()) {
log::error!("Failed to send companion fd socket of module `{}`: {}", module.name, e);
stream.write_u8(0)?;
}
// Ok: Send by companion
}
None => {
_ => {
stream.write_u8(0)?;
}
}
@@ -223,14 +288,11 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_KSU_MODULE_DIR, module.name);
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
_ => {}
}
Ok(())
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
}