45 Commits

Author SHA1 Message Date
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
Nullptr
209036ad66 Bump to 0.2.0 2023-02-08 20:23:16 +08:00
Nullptr
820d59e285 Refine umount 2023-02-08 20:22:56 +08:00
Nullptr
291599ffc8 Separate companion 2023-02-08 20:17:56 +08:00
Nullptr
f75d15c6f6 Linker namespace 2023-02-08 16:38:39 +08:00
Nullptr
fb1ba93db8 Set MAX_LOG_LEVEL 2023-02-08 15:00:50 +08:00
Nullptr
814476ea7a Log to zygiskd 2023-02-08 14:49:32 +08:00
51 changed files with 1832 additions and 640 deletions

88
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,88 @@
name: CI
on:
workflow_dispatch:
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: 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 target add armv7-linux-androideabi
rustup target add aarch64-linux-android
rustup target add x86_64-linux-android
rustup target add i686-linux-android
- 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-on-KernelSU-v*-release.zip | awk -F '(/|.zip)' '{print $5}'` && echo "releaseName=$releaseName" >> $GITHUB_OUTPUT
debugName=`ls module/build/outputs/release/Zygisk-on-KernelSU-v*-debug.zip | awk -F '(/|.zip)' '{print $5}'` && echo "debugName=$debugName" >> $GITHUB_OUTPUT
unzip module/build/outputs/release/Zygisk-on-KernelSU-v*-release.zip -d zksu-release
unzip module/build/outputs/release/Zygisk-on-KernelSU-v*-debug.zip -d zksu-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/*"

6
.gitmodules vendored
View File

@@ -1,6 +1,6 @@
[submodule "loader/src/external/liblsplt"]
path = loader/src/external/liblsplt
url = https://github.com/LSPosed/LSPlt
[submodule "loader/src/external/lsplt"]
path = loader/src/external/lsplt
url = https://github.com/LSPosed/lsplt
[submodule "loader/src/external/parallel-hashmap"]
path = loader/src/external/parallel-hashmap
url = https://github.com/greg7mdp/parallel-hashmap

View File

@@ -2,33 +2,25 @@
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.
Also works as standalone loader for Magisk on purpose of getting rid of LD_PRELOAD.
## Requirements
+ Minimal KernelSU version: 15
+ Minimal ksud version: 7b32c0e
### General
+ No multiple root implementation installed
### KernelSU
+ Minimal KernelSU version: 10654
+ Minimal ksud version: 10670
+ Kernel has full SELinux patch support
### Magisk
+ Minimal version: 25208
+ Original Zygisk turned off
## Compatibility
- [x] LSPosed
- [x] Storage Isolation
- [ ] IFW Enhance
- [ ] Universal SafetyNet Fix
- [ ] Shamiko
## Development road map
- [x] [Inject] Basic Zygisk loader
- [x] [Inject] Stabilize injector
- [x] [Inject] Unload
- [ ] [Daemon] Separate zygiskd process
- [ ] [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/`
Should work with everything except those rely on Magisk internal behaviors.

View File

@@ -31,14 +31,18 @@ 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.1.0")
val verName by extra("v4-0.6.4")
val verCode by extra(gitCommitCount)
val minKsuVersion by extra(10654)
val minKsudVersion by extra(10670)
val maxKsuVersion by extra(20000)
val minMagiskVersion by extra(25208)
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 androidBuildToolsVersion by extra("33.0.2")
val androidCompileNdkVersion by extra("25.2.9519653")
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)

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

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,7 +1,26 @@
import java.nio.file.Paths
import org.gradle.internal.os.OperatingSystem
plugins {
id("com.android.library")
}
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 ccachePatch by lazy {
project.findInPath("ccache", "ccache.path")?.also {
println("loader: Use ccache: $it")
}
}
android {
buildFeatures {
androidResources = false
@@ -12,6 +31,16 @@ android {
externalNativeBuild.ndkBuild {
path("src/Android.mk")
}
defaultConfig {
externalNativeBuild {
ndkBuild {
ccachePatch?.let {
arguments += "NDK_CCACHE=$it"
}
}
}
}
}
dependencies {

View File

@@ -13,7 +13,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygiskloader
LOCAL_MODULE := zygisk_loader
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)/%=%)
@@ -22,7 +22,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := injector
LOCAL_MODULE := zygisk_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)/%=%)

View File

@@ -8,19 +8,37 @@
namespace zygiskd {
bool sMagicRead = false;
static std::string sSocketName;
void ReadMagic() {
sMagicRead = true;
char magic[PATH_MAX]{0};
auto fp = fopen(kZygiskMagic, "r");
if (fp == nullptr) {
PLOGE("Open magic file");
return;
}
fgets(magic, PATH_MAX, fp);
fclose(fp);
sSocketName.append(LP_SELECT("zygiskd32", "zygiskd64")).append(magic);
LOGD("Socket name: %s", sSocketName.data());
}
int Connect(uint8_t retry) {
if (!sMagicRead) ReadMagic();
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
struct sockaddr_un addr{
.sun_family = AF_UNIX,
.sun_path={0},
};
strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size());
strcpy(addr.sun_path + 1, sSocketName.data());
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
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");
LOGW("Retrying to connect to zygiskd, sleep 1s");
sleep(1);
}
@@ -29,7 +47,6 @@ namespace zygiskd {
}
bool PingHeartbeat() {
LOGD("Daemon socket: %s", kZygiskSocket.data());
UniqueFd fd = Connect(5);
if (fd == -1) {
PLOGE("Connect to zygiskd");
@@ -39,6 +56,16 @@ namespace zygiskd {
return true;
}
int RequestLogcatFd() {
int fd = Connect(1);
if (fd == -1) {
PLOGE("RequestLogcatFd");
return -1;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestLogcatFd);
return fd;
}
std::string ReadNativeBridge() {
UniqueFd fd = Connect(1);
if (fd == -1) {
@@ -49,6 +76,17 @@ namespace zygiskd {
return socket_utils::read_string(fd);
}
uint32_t GetProcessFlags(uid_t uid) {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("GetProcessFlags");
return 0;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetProcessFlags);
socket_utils::write_u32(fd, uid);
return socket_utils::read_u32(fd);
}
std::vector<Module> ReadModules() {
std::vector<Module> modules;
UniqueFd fd = Connect(1);
@@ -89,10 +127,6 @@ namespace zygiskd {
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir);
socket_utils::write_usize(fd, index);
if (socket_utils::read_u8(fd) == 1) {
return fd;
} else {
return -1;
}
return socket_utils::recv_fd(fd);
}
}

View File

@@ -0,0 +1,36 @@
#include <android/log.h>
#include <unistd.h>
#include "logging.h"
#include "socket_utils.h"
namespace logging {
static int logfd = -1;
void setfd(int fd) {
close(logfd);
logfd = fd;
}
int getfd() {
return logfd;
}
void log(int prio, const char* tag, const char* fmt, ...) {
if (logfd == -1) {
va_list ap;
va_start(ap, fmt);
__android_log_vprint(prio, tag, fmt, ap);
va_end(ap);
} else {
char buf[BUFSIZ];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
socket_utils::write_u8(logfd, prio);
socket_utils::write_string(logfd, tag);
socket_utils::write_string(logfd, buf);
}
}
}

View File

@@ -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,14 @@ 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());
}
int recv_fd(int sockfd) {
char cmsgbuf[CMSG_SPACE(sizeof(int))];

View File

@@ -3,18 +3,19 @@ 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_C_INCLUDES := $(LOCAL_PATH)/lsplt/lsplt/src/main/jni/include
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden -DLOG_DISABLED
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
lsplt/lsplt/src/main/jni/elf_util.cc \
lsplt/lsplt/src/main/jni/lsplt.cc
include $(BUILD_STATIC_LIBRARY)
# Header only library
include $(CLEAR_VARS)
LOCAL_MODULE:= libphmap
LOCAL_CFLAGS := -Wno-unused-value
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap
include $(BUILD_STATIC_LIBRARY)

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.
@@ -219,7 +263,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 +296,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,8 @@
#else
# define LP_SELECT(lp32, lp64) lp32
#endif
constexpr std::string_view kZygiskSocket = LP_SELECT("zygiskd32", "zygiskd64") "socket_placeholder";
constexpr auto kZygiskMagic = "/system/zygisk_magic";
class UniqueFd {
using Fd = int;
@@ -52,7 +53,9 @@ namespace zygiskd {
enum class SocketAction {
PingHeartBeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
@@ -60,10 +63,14 @@ namespace zygiskd {
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);

View File

@@ -20,15 +20,24 @@
#define LOGE(...)
#else
#ifndef NDEBUG
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...) logging::log(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGV(...) logging::log(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#else
#define LOGD(...)
#define LOGV(...)
#endif
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define LOGI(...) logging::log(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) logging::log(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) logging::log(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGF(...) logging::log(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
#endif
namespace logging {
void setfd(int fd);
int getfd();
[[gnu::format(printf, 3, 4)]]
void log(int prio, const char* tag, const char* fmt, ...);
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <string_view>
#include "logging.h"
@@ -12,13 +13,19 @@ namespace socket_utils {
uint8_t read_u8(int fd);
uint32_t read_u32(int fd);
size_t read_usize(int fd);
std::string read_string(int fd);
bool write_u8(int fd, uint8_t val);
bool write_u32(int fd, uint32_t val);
int recv_fd(int fd);
bool write_usize(int fd, size_t val);
bool write_string(int fd, std::string_view str);
}

View File

@@ -1,3 +1,4 @@
#include "daemon.h"
#include "logging.h"
#include "zygisk.hpp"
#include "module.hpp"
@@ -17,14 +18,11 @@ static void zygisk_cleanup_wait() {
extern "C" [[gnu::visibility("default")]]
void entry(void *handle) {
LOGD("Load injector successfully");
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
self_handle = handle;
LOGD("Load injector successfully");
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,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,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

@@ -1,23 +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);
}
#define OVERLAY_MNT(dir) (mentry->mnt_type == "overlay"sv && std::string_view(mentry->mnt_dir).starts_with("/" #dir))
void revert_unmount() {
parse_mnt("/proc/self/mounts", [](mntent* mentry) {
if (OVERLAY_MNT("system") || OVERLAY_MNT("vendor") || OVERLAY_MNT("product") || OVERLAY_MNT("system_ext")) {
lazy_unmount(mentry->mnt_fsname);
}
return true;
});
}

View File

@@ -17,7 +17,7 @@
#include "zygisk.hpp"
#include "memory.hpp"
#include "module.hpp"
#include "misc.hpp"
#include "files.hpp"
using namespace std;
using jni_hook::hash_map;
@@ -112,8 +112,8 @@ 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;
const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions = nullptr;
} // namespace
@@ -143,15 +143,8 @@ if (methods[i].name == #method##sv) {
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;");
}
static auto class_getName = env->GetMethodID(env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;");
auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName);
const char *name = env->GetStringUTFChars(nameRef, nullptr);
string className(name);
@@ -172,11 +165,44 @@ jint env_RegisterNatives(
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);
DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void* func) {
LOGD("androidSetCreateThreadFunc\n");
do {
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
if (!get_created_java_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror());
break;
}
get_created_java_vms = reinterpret_cast<decltype(get_created_java_vms)>(dlsym(h, "JNI_GetCreatedJavaVMs"));
dlclose(h);
break;
}
if (!get_created_java_vms) {
LOGW("JNI_GetCreatedJavaVMs not found\n");
break;
}
}
JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_java_vms(&vm, 1, &num);
if (res != JNI_OK || vm == nullptr) break;
JNIEnv *env = nullptr;
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) break;
default_new(new_functions);
memcpy(new_functions, env->functions, sizeof(*new_functions));
new_functions->RegisterNatives = &env_RegisterNatives;
// Replace the function table in JNIEnv to hook RegisterNatives
old_functions = env->functions;
env->functions = new_functions;
} while (false);
old_androidSetCreateThreadFunc(func);
}
// Skip actual fork and return cached result if applicable
@@ -193,7 +219,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 */
@@ -204,7 +234,17 @@ DCL_HOOK_FUNC(int, unshare, int flags) {
return res;
}
/* Zygisksu changed: No android_log_close hook */
// Close logd_fd if necessary to prevent crashing
// For more info, check comments in zygisk_log_write
DCL_HOOK_FUNC(void, android_log_close) {
if (g_ctx == nullptr) {
// Happens during un-managed fork like nativeForkApp, nativeForkUsap
logging::setfd(-1);
} else if (!g_ctx->flags[SKIP_FD_SANITIZATION]) {
logging::setfd(-1);
}
old_android_log_close();
}
// Last point before process secontext changes
DCL_HOOK_FUNC(int, selinux_android_setcontext,
@@ -215,58 +255,6 @@ DCL_HOOK_FUNC(int, selinux_android_setcontext,
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
}
// -----------------------------------------------------------------
// 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;
}
#undef DCL_HOOK_FUNC
// -----------------------------------------------------------------
@@ -302,7 +290,7 @@ void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods
if (hooks.empty())
return;
old_jniRegisterNativeMethods(env, clz, hooks.data(), hooks.size());
old_functions->RegisterNatives(env, env->FindClass(clz), hooks.data(), static_cast<int>(hooks.size()));
}
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
@@ -343,6 +331,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;
@@ -491,11 +480,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;
@@ -582,20 +571,18 @@ void HookContext::run_modules_post() {
/* Zygisksu changed: Load module fds */
void HookContext::app_specialize_pre() {
flags[APP_SPECIALIZE] = true;
info_flags = zygiskd::GetProcessFlags(g_ctx->args.app->uid);
run_modules_pre();
}
void HookContext::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);
g_ctx = nullptr;
/* Zygisksu changed: No android_log_close */
logging::setfd(-1);
}
void HookContext::unload_zygisk() {
@@ -668,7 +655,9 @@ void HookContext::nativeForkAndSpecialize_pre() {
flags[APP_FORK_AND_SPECIALIZE] = true;
/* Zygisksu changed: No args.app->fds_to_ignore check since we are Android 10+ */
flags[SKIP_FD_SANITIZATION] = true;
if (logging::getfd() != -1) {
exempted_fds.push_back(logging::getfd());
}
fork_pre();
if (pid == 0) {
@@ -727,35 +716,16 @@ 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);
/* Zygisksu changed: No android_log_close hook */
PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
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() {
@@ -764,17 +734,14 @@ static bool unhook_functions() {
// 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;
}
delete new_functions;
}
// 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) {
if (!methods.empty() && g_ctx->env->RegisterNatives(
g_ctx->env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
success = false;
}

View File

@@ -23,22 +23,27 @@ int parse_int(std::string_view s) {
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;
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;
}
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; });
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

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

@@ -111,12 +111,11 @@ 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_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,122 @@
#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";
struct overlay_backup {
std::string target;
std::string vfs_option;
std::string fs_option;
};
void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1) {
LOGD("Unmounted (%s)", mountpoint);
} else {
PLOGE("Unmount (%s)", mountpoint);
}
}
}
#define PARSE_OPT(name, flag) \
if (opt == (name)) { \
flags |= (flag); \
return true; \
}
void revert_unmount_ksu() {
std::string ksu_loop;
std::vector<std::string> targets;
std::list<overlay_backup> backups;
// 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 on /data/adb except ksu module dir
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
// Unmount ksu overlays
if (info.type == "overlay") {
if (str_contains(info.fs_option, MODULE_DIR)) {
targets.emplace_back(info.target);
} else {
auto backup = overlay_backup{
.target = info.target,
.vfs_option = info.vfs_option,
.fs_option = info.fs_option,
};
backups.emplace_back(backup);
}
}
}
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());
}
// Affirm unmounted system overlays
for (auto& info: parse_mount_info("self")) {
if (info.type == "overlay") {
backups.remove_if([&](overlay_backup& mnt) {
return mnt.target == info.target && mnt.fs_option == info.fs_option;
});
}
}
// Restore system overlays
for (auto& mnt: backups) {
auto opts = split_str(mnt.vfs_option, ",");
opts.splice(opts.end(), split_str(mnt.fs_option, ","));
unsigned long flags = 0;
opts.remove_if([&](auto& opt) {
PARSE_OPT(MNTOPT_RO, MS_RDONLY)
PARSE_OPT(MNTOPT_NOSUID, MS_NOSUID)
PARSE_OPT("relatime", MS_RELATIME)
return false;
});
auto mnt_data = join_str(opts, ",");
if (mount("overlay", mnt.target.data(), "overlay", flags, mnt_data.data()) != -1) {
LOGD("Remounted (%s)", mnt.target.data());
} else {
PLOGE("Remount (%s, %s)", mnt.target.data(), mnt.fs_option.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);
}
}
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

@@ -13,7 +13,7 @@ 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";
constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libzygisk_injector.so";
void* sOriginalBridge = nullptr;
}
@@ -42,9 +42,10 @@ void Constructor() {
std::string native_bridge;
do {
LOGD("Ping heartbeat");
if (!zygiskd::PingHeartbeat()) break;
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
LOGI("Read native bridge");
native_bridge = zygiskd::ReadNativeBridge();
@@ -63,32 +64,37 @@ void Constructor() {
reinterpret_cast<void (*)(void*)>(entry)(handle);
} while (false);
if (native_bridge.empty() || native_bridge == "0") return;
LOGI("Load original native bridge: %s", native_bridge.data());
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
if (sOriginalBridge == nullptr) {
LOGE("dlopen failed: %s", dlerror());
return;
}
do {
if (native_bridge.empty() || native_bridge == "0") break;
auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
if (original_native_bridge_itf == nullptr) {
LOGE("dlsym failed: %s", dlerror());
return;
}
LOGI("Load original native bridge: %s", native_bridge.data());
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
if (sOriginalBridge == nullptr) {
LOGE("%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* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
if (original_native_bridge_itf == nullptr) {
LOGE("%s", dlerror());
break;
}
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__>);
}
long sdk = 0;
char value[PROP_VALUE_MAX + 1];
if (__system_property_get("ro.build.version.sdk", value) > 0) {
sdk = strtol(value, nullptr, 10);
}
memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size);
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

@@ -11,6 +11,10 @@ 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
android.buildFeatures {
androidResources = false
@@ -34,7 +38,7 @@ androidComponents.onVariants { variant ->
into(moduleDir)
from("${rootProject.projectDir}/README.md")
from("$projectDir/src") {
exclude("module.prop", "customize.sh", "daemon.sh")
exclude("module.prop", "customize.sh", "service.sh")
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
from("$projectDir/src") {
@@ -47,10 +51,13 @@ androidComponents.onVariants { variant ->
)
}
from("$projectDir/src") {
include("customize.sh", "daemon.sh")
include("customize.sh", "service.sh")
val tokens = mapOf(
"ZYGISK_API" to (verCode / 1000).toString(),
"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"))
@@ -107,7 +114,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,27 +1,61 @@
# shellcheck disable=SC2034
SKIPUNZIP=1
ZYGISK_API="@ZYGISK_API@"
DEBUG=@DEBUG@
MIN_KSU_VERSION=@MIN_KSU_VERSION@
MIN_KSUD_VERSION=@MIN_KSUD_VERSION@
MAX_KSU_VERSION=@MAX_KSU_VERSION@
MIN_MAGISK_VERSION=@MIN_MAGISK_VERSION@
if [ $BOOTMODE ] && [ "$KSU" == "true" ]; then
if [ "$BOOTMODE" ] && [ "$KSU" ]; then
ui_print "- Installing from KernelSU app"
ui_print "- 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 Zygisksu"
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 "*********************************************************"
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 Zygisksu $VERSION (ZYGISK API $ZYGISK_API)"
# check KernelSU
# ui_print "- KernelSU version: $KSU_VER ($KSU_VER_CODE)"
ui_print "- Installing Zygisksu $VERSION"
# check android
if [ "$API" -lt 29 ]; then
ui_print "! Unsupported sdk: $API"
abort "! Minimal supported sdk is 29 (Android 10.0)"
abort "! Minimal supported sdk is 29 (Android 10)"
else
ui_print "- Device sdk: $API"
fi
@@ -39,18 +73,32 @@ if [ ! -f "$TMPDIR/verify.sh" ]; then
ui_print "*********************************************************"
ui_print "! Unable to extract verify.sh!"
ui_print "! This zip may be corrupted, please try downloading again"
abort "*********************************************************"
abort "*********************************************************"
fi
. "$TMPDIR/verify.sh"
extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
if [ "$KSU" ]; then
ui_print "- Checking SELinux patches"
if [ "$(getprop ro.product.first_api_level)" -lt 31 ]; then
echo "allow zygote appdomain_tmpfs file *" >> "$TMPDIR/sepolicy.rule"
echo "allow zygote appdomain_tmpfs dir *" >> "$TMPDIR/sepolicy.rule"
fi
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"
mv "$TMPDIR/sepolicy.rule" "$MODPATH"
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true
@@ -65,8 +113,8 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
ui_print "- Extracting x86 libraries"
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/x86/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygiskloader.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygisk_injector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/x86/libzygisk_loader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
@@ -74,8 +122,8 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; 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
extract "$ZIPFILE" 'lib/x86_64/libzygisk_injector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/x86_64/libzygisk_loader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
else
@@ -83,8 +131,8 @@ else
ui_print "- Extracting arm libraries"
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/armeabi-v7a/libinjector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygiskloader.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_injector.so' "$MODPATH/system/lib" true
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_loader.so' "$MODPATH/system/lib" true
ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd"
fi
@@ -92,24 +140,15 @@ else
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
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_injector.so' "$MODPATH/system/lib64" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_loader.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd"
fi
fi
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"
fi
ui_print "- Generating magic"
MAGIC=$(tr -dc 'a-f0-9' </dev/urandom | head -c 18)
echo -n "$MAGIC" > "$MODPATH/system/zygisk_magic"
ui_print "- Setting permissions"
chmod 0744 "$MODPATH/daemon.sh"

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" >/dev/null 2>&1

View File

@@ -1,5 +1,23 @@
#!/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"
getprop ro.dalvik.vm.native.bridge > /dev/.native_bridge
resetprop ro.dalvik.vm.native.bridge libzygisk_loader.so
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 "zygisksu" "Manually trigger post-fs-data.sh for $file"
sh "$(realpath ./post-fs-data.sh)"
cd "$MODDIR"
fi
fi
done
fi

View File

@@ -10,3 +10,8 @@ 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

View File

@@ -1 +1,29 @@
#!/system/bin/sh
DEBUG=@DEBUG@
MODDIR=${0%/*}
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(cat /dev/.native_bridge)
rm /dev/.native_bridge
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 "zygisksu" "Manually trigger service.sh for $file"
sh "$(realpath ./service.sh)"
cd "$MODDIR"
fi
fi
done
fi
log -p i -t "zygisksu" "Start watchdog"
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1

View File

@@ -7,8 +7,8 @@ pluginManagement {
gradlePluginPortal()
}
plugins {
id("com.android.library") version "7.4.0"
id("com.android.application") version "7.4.0"
id("com.android.library") version "7.4.2"
id("com.android.application") version "7.4.2"
}
}

View File

@@ -6,16 +6,22 @@ edition = "2021"
rust-version = "1.67"
[dependencies]
android_logger = "0.12.0"
android_logger = "0.13.0"
anyhow = { version = "1.0.68", features = ["backtrace"] }
clap = { version = "4.1.4", features = ["derive"] }
const_format = "0.2.5"
konst = "0.3.4"
lazy_static = "1.4.0"
log = "0.4.17"
memfd = "0.6.2"
nix = "0.26.2"
num_enum = "0.5.9"
once_cell = "1.17.1"
passfd = "0.1.5"
rand = "0.8.5"
binder = { git = "https://github.com/Kernel-SU/binder_rs" }
[profile.release]
strip = true
opt-level = "z"

View File

@@ -3,6 +3,12 @@ plugins {
id("org.mozilla.rust-android-gradle.rust-android")
}
val verName: String by rootProject.extra
val verCode: Int by rootProject.extra
val minKsuVersion: Int by rootProject.extra
val maxKsuVersion: Int by rootProject.extra
val minMagiskVersion: Int by rootProject.extra
android.buildFeatures {
androidResources = false
buildConfig = false
@@ -16,4 +22,12 @@ cargo {
targetDirectory = "build/intermediates/rust"
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
profile = if (isDebug) "debug" else "release"
exec = { spec, _ ->
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
spec.environment("VERSION_CODE", verCode)
spec.environment("VERSION_NAME", verName)
spec.environment("MIN_KSU_VERSION", minKsuVersion)
spec.environment("MAX_KSU_VERSION", maxKsuVersion)
spec.environment("MIN_MAGISK_VERSION", minMagiskVersion)
}
}

61
zygiskd/src/companion.rs Normal file
View File

@@ -0,0 +1,61 @@
use std::ffi::c_void;
use std::os::fd::{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 crate::dl;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
pub fn entry(fd: i32) -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
let name = stream.read_string()?;
let library = stream.recv_fd()?;
let entry = load_module(library)?;
unsafe { libc::close(library) };
let entry = match entry {
Some(entry) => {
log::debug!("Companion process created for `{name}`");
stream.write_u8(1)?;
entry
}
None => {
log::debug!("No companion entry for `{name}`");
stream.write_u8(0)?;
return Ok(());
}
};
loop {
let fd = stream.recv_fd()?;
log::trace!("New companion request from module `{name}`");
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?"),
}
};
});
}
}
fn load_module(fd: RawFd) -> Result<Option<ZygiskCompanionEntryFn>> {
unsafe {
let path = format!("/proc/self/fd/{fd}");
let handle = dl::dlopen(&path, libc::RTLD_NOW)?;
let symbol = std::ffi::CString::new("zygisk_companion_entry")?;
let entry = libc::dlsym(handle, symbol.as_ptr());
if entry.is_null() {
return Ok(None);
}
let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry);
Ok(Some(fnptr))
}
}

View File

@@ -1,22 +1,53 @@
use const_format::concatcp;
use konst::primitive::parse_i32;
use konst::unwrap_ctx;
use log::LevelFilter;
use num_enum::TryFromPrimitive;
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")));
#[cfg(debug_assertions)]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
#[cfg(not(debug_assertions))]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
pub const PROP_CTL_RESTART: &str = "ctl.restart";
pub const ZYGISK_LOADER: &str = "libzygisk_loader.so";
pub const ZYGISK_MAGIC: &str = "/system/zygisk_magic";
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
pub const PATH_MODULES_DIR: &str = "..";
pub const PATH_MODULE_PROP: &str = "module.prop";
pub const PATH_ZYGISKD32: &str = "bin/zygiskd32";
pub const PATH_ZYGISKD64: &str = "bin/zygiskd64";
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 STATUS_LOADED: &str = "😋 Zygisksu is loaded";
pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed";
pub const STATUS_ROOT_IMPL_NONE: &str = "❌ Unknown root implementation";
pub const STATUS_ROOT_IMPL_TOO_OLD: &str = "❌ Root implementation version too old";
pub const STATUS_ROOT_IMPL_ABNORMAL: &str = "❌ Abnormal root implementation version";
pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations installed";
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum DaemonSocketAction {
PingHeartbeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
}
// Zygisk process flags
pub const PROCESS_GRANTED_ROOT: u32 = 1 << 0;
pub const PROCESS_ON_DENYLIST: u32 = 1 << 1;
pub const PROCESS_ROOT_IS_KSU: u32 = 1 << 29;
pub const PROCESS_ROOT_IS_MAGISK: u32 = 1 << 30;
pub const PROCESS_IS_SYSUI: u32 = 1 << 31;

82
zygiskd/src/dl.rs Normal file
View File

@@ -0,0 +1,82 @@
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;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct AndroidNamespace {
_unused: [u8; 0],
}
#[repr(C)]
struct AndroidDlextinfo {
pub flags: u64,
pub reserved_addr: *mut c_void,
pub reserved_size: libc::size_t,
pub relro_fd: libc::c_int,
pub library_fd: libc::c_int,
pub library_fd_offset: libc::off64_t,
pub library_namespace: *mut AndroidNamespace,
}
extern "C" {
fn android_dlopen_ext(
filename: *const c_char,
flags: libc::c_int,
extinfo: *const AndroidDlextinfo,
) -> *mut c_void;
}
type AndroidCreateNamespaceFn = unsafe extern "C" fn(
*const c_char, // name
*const c_char, // ld_library_path
*const c_char, // default_library_path
u64, // type
*const c_char, // permitted_when_isolated_path
*mut AndroidNamespace, // parent
*const c_void, // caller_addr
) -> *mut AndroidNamespace;
pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
let filename = std::ffi::CString::new(path)?;
let filename = filename.as_ptr() as *mut _;
let dir = libc::dirname(filename);
let mut info = AndroidDlextinfo {
flags: 0,
reserved_addr: std::ptr::null_mut(),
reserved_size: 0,
relro_fd: 0,
library_fd: 0,
library_fd_offset: 0,
library_namespace: std::ptr::null_mut(),
};
let android_create_namespace_fn = libc::dlsym(
libc::RTLD_DEFAULT,
std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(),
);
let android_create_namespace_fn: AndroidCreateNamespaceFn = std::mem::transmute(android_create_namespace_fn);
let ns = android_create_namespace_fn(
filename, dir, std::ptr::null(),
ANDROID_NAMESPACE_TYPE_SHARED,
std::ptr::null(), std::ptr::null_mut(),
&dlopen as *const _ as *const c_void,
);
if ns != std::ptr::null_mut() {
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
info.library_namespace = ns;
log::debug!("Open {} with namespace {:p}", path, ns);
} else {
log::debug!("Cannot create namespace for {}", path);
};
let result = android_dlopen_ext(filename, flags, &info);
if result.is_null() {
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
bail!(e);
}
Ok(result)
}

19
zygiskd/src/magic.rs Normal file
View File

@@ -0,0 +1,19 @@
use std::fs;
use anyhow::Result;
use crate::constants;
use crate::utils::LateInit;
pub static MAGIC: LateInit<String> = LateInit::new();
pub static PATH_TMP_DIR: LateInit<String> = LateInit::new();
pub static PATH_TMP_PROP: LateInit<String> = LateInit::new();
pub fn setup() -> Result<()> {
let name = fs::read_to_string(constants::ZYGISK_MAGIC)?;
let path_tmp_dir = format!("/dev/{}", name);
let path_tmp_prop = format!("{}/module.prop", path_tmp_dir);
MAGIC.init(name);
PATH_TMP_DIR.init(path_tmp_dir);
PATH_TMP_PROP.init(path_tmp_prop);
Ok(())
}

View File

@@ -1,51 +1,61 @@
#![allow(dead_code)]
mod companion;
mod constants;
mod dl;
mod magic;
mod root_impl;
mod utils;
mod watchdog;
mod zygisk;
mod zygiskd;
use anyhow::Result;
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 anyhow::{bail, Result};
use log::LevelFilter;
use nix::libc;
fn init_android_logger(tag: &str) {
android_logger::init_once(
android_logger::Config::default()
.with_max_level(LevelFilter::Trace)
.with_max_level(constants::MAX_LOG_LEVEL)
.with_tag(tag),
);
}
fn entry() -> Result<()> {
let process = std::env::args().next().unwrap();
let process = process.split('/').last().unwrap();
init_android_logger(process);
match process {
"zygiskwd" => {
log::info!("Start zygisksu watchdog");
watchdog::check_permission()?;
watchdog::ensure_single_instance()?;
watchdog::spawn_daemon()?;
}
"zygiskd32" => {
log::info!("Start zygiskd32");
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); }
zygisk::start(false)?;
loop {}
}
"zygiskd64" => {
log::info!("Start zygiskd64");
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL); }
zygisk::start(true)?;
}
_ => bail!("Unexpected process name: {process}")
}
fn start() -> Result<()> {
root_impl::setup();
magic::setup()?;
let cli = Args::parse();
match cli.command {
Commands::Watchdog => watchdog::entry()?,
Commands::Daemon => zygiskd::entry()?,
Commands::Companion { fd } => companion::entry(fd)?,
};
Ok(())
}
fn main() {
if let Err(e) = entry() {
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name);
if let Err(e) = start() {
log::error!("Crashed: {}\n{}", e, e.backtrace());
}
}

View File

@@ -0,0 +1,43 @@
use nix::libc::prctl;
use crate::constants::{MIN_KSU_VERSION, MAX_KSU_VERSION};
const KERNEL_SU_OPTION: i32 = 0xdeadbeefu32 as i32;
const CMD_GET_VERSION: usize = 2;
const CMD_GET_ALLOW_LIST: usize = 5;
const CMD_GET_DENY_LIST: usize = 6;
pub enum Version {
Supported,
TooOld,
Abnormal,
}
pub fn get_kernel_su() -> Option<Version> {
let mut version = 0;
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &mut version as *mut i32) };
match version {
0 => None,
MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported),
1..=MIN_KSU_VERSION => Some(Version::TooOld),
_ => Some(Version::Abnormal)
}
}
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_ALLOW_LIST, uids.as_mut_ptr(), &mut size as *mut u32) };
uids.resize(size as usize, 0);
uids.contains(&uid)
}
#[inline(never)]
pub fn uid_on_denylist(uid: i32) -> bool {
let mut size = 1024u32;
let mut uids = vec![0; size as usize];
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_DENY_LIST, uids.as_mut_ptr(), &mut size as *mut u32) };
uids.resize(size as usize, 0);
uids.contains(&uid)
}

View File

@@ -0,0 +1,48 @@
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
}
})
}
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
let output: Option<String> = Command::new("magisk")
.arg("--sqlite")
.arg("select uid from policies where policy=2")
.stdout(Stdio::piped())
.spawn().ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok());
let lines = match &output {
Some(output) => output.lines(),
None => return false,
};
lines.into_iter().any(|line| {
line.trim().strip_prefix("uid=").and_then(|uid| uid.parse().ok()) == Some(uid)
})
}
#[inline(never)]
pub fn uid_on_denylist(uid: i32) -> bool {
// TODO: uid_on_denylist
return false;
}

View File

@@ -0,0 +1,61 @@
mod kernelsu;
mod magisk;
pub enum RootImpl {
None,
TooOld,
Abnormal,
Multiple,
KernelSU,
Magisk,
}
// FIXME: OnceCell bugs on 32 bit
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 }
}
// FIXME: Without #[inline(never)], this function will lag forever
#[inline(never)]
pub fn uid_on_allowlist(uid: i32) -> bool {
match get_impl() {
RootImpl::KernelSU => kernelsu::uid_on_allowlist(uid),
RootImpl::Magisk => magisk::uid_on_allowlist(uid),
_ => unreachable!(),
}
}
#[inline(never)]
pub fn uid_on_denylist(uid: i32) -> bool {
match get_impl() {
RootImpl::KernelSU => kernelsu::uid_on_denylist(uid),
RootImpl::Magisk => magisk::uid_on_denylist(uid),
_ => unreachable!(),
}
}

View File

@@ -1,12 +1,45 @@
use crate::constants;
use anyhow::Result;
use nix::unistd::gettid;
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
use std::ffi::c_char;
use std::os::fd::FromRawFd;
use std::os::unix::net::UnixListener;
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
use once_cell::sync::OnceCell;
use rand::distributions::{Alphanumeric, DistString};
#[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 struct LateInit<T> {
cell: OnceCell<T>,
}
impl<T> LateInit<T> {
pub const fn new() -> Self {
LateInit { cell: OnceCell::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 random_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
}
@@ -27,10 +60,28 @@ pub fn get_native_bridge() -> String {
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
}
pub fn restore_native_bridge() -> Result<()> {
pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> {
let tag = std::ffi::CString::new(tag)?;
let message = std::ffi::CString::new(message)?;
unsafe {
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
}
Ok(())
}
pub fn get_property(name: &str) -> Result<String> {
let name = std::ffi::CString::new(name)?;
let mut buf = vec![0u8; 92];
unsafe {
__system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char);
}
Ok(String::from_utf8(buf)?)
}
pub fn set_property(name: &str, value: &str) -> Result<()> {
Command::new("resetprop")
.arg(constants::PROP_NATIVE_BRIDGE)
.arg(get_native_bridge())
.arg(name)
.arg(value)
.spawn()?.wait()?;
Ok(())
}
@@ -39,8 +90,11 @@ pub trait UnixStreamExt {
fn read_u8(&mut self) -> Result<u8>;
fn read_u32(&mut self) -> Result<u32>;
fn read_usize(&mut self) -> Result<usize>;
fn read_string(&mut self) -> Result<String>;
fn write_u8(&mut self, value: u8) -> Result<()>;
fn write_u32(&mut self, value: u32) -> Result<()>;
fn write_usize(&mut self, value: usize) -> Result<()>;
fn write_string(&mut self, value: &str) -> Result<()>;
}
impl UnixStreamExt for UnixStream {
@@ -62,15 +116,33 @@ impl UnixStreamExt for UnixStream {
Ok(usize::from_ne_bytes(buf))
}
fn read_string(&mut self) -> Result<String> {
let len = self.read_usize()?;
let mut buf = vec![0u8; len];
self.read_exact(&mut buf)?;
Ok(String::from_utf8(buf)?)
}
fn write_u8(&mut self, value: u8) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_u32(&mut self, value: u32) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_usize(&mut self, value: usize) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_string(&mut self, value: &str) -> Result<()> {
self.write_usize(value.len())?;
self.write_all(value.as_bytes())?;
Ok(())
}
}
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
@@ -82,3 +154,8 @@ pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
let listener = unsafe { UnixListener::from_raw_fd(socket) };
Ok(listener)
}
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;
}

View File

@@ -1,15 +1,38 @@
use crate::constants;
use crate::{constants, magic, root_impl, utils};
use anyhow::{bail, Result};
use nix::fcntl::{flock, FlockArg};
use nix::unistd::{getgid, getuid};
use std::os::unix::prelude::AsRawFd;
use nix::unistd::{getgid, getuid, Pid};
use std::process::{Child, Command};
use std::sync::mpsc;
use std::{fs, thread};
use std::{fs, io, thread};
use std::ffi::CString;
use std::io::{BufRead, Write};
use std::os::unix::net::UnixListener;
use std::time::Duration;
use binder::IBinder;
use nix::errno::Errno;
use nix::libc;
use nix::sys::signal::{kill, Signal};
use crate::utils::LateInit;
static mut LOCK_FILE: Option<fs::File> = None;
static LOCK: LateInit<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub fn check_permission() -> Result<()> {
pub fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
check_permission()?;
ensure_single_instance()?;
mount_prop()?;
if check_and_set_hint()? == false {
log::warn!("Requirements not met, exiting");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
return Ok(());
}
let end = spawn_daemon();
set_prop_hint(constants::STATUS_CRASHED)?;
end
}
fn check_permission() -> Result<()> {
log::info!("Check permission");
let uid = getuid();
if uid.as_raw() != 0 {
@@ -23,7 +46,6 @@ pub fn check_permission() -> Result<()> {
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}");
}
@@ -31,40 +53,139 @@ pub fn check_permission() -> Result<()> {
Ok(())
}
pub fn ensure_single_instance() -> Result<()> {
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?");
}
let name = format!("zygiskwd{}", magic::MAGIC.as_str());
match utils::abstract_namespace_socket(&name) {
Ok(socket) => LOCK.init(socket),
Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?")
}
Ok(())
}
pub fn spawn_daemon() -> Result<()> {
let daemon32 = Command::new(constants::PATH_ZYGISKD32).spawn()?;
let daemon64 = Command::new(constants::PATH_ZYGISKD64).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();
});
fn mount_prop() -> Result<()> {
let module_prop = if let root_impl::RootImpl::Magisk = root_impl::get_impl() {
let magisk_path = Command::new("magisk").arg("--path").output()?;
let mut magisk_path = String::from_utf8(magisk_path.stdout)?;
magisk_path.pop(); // Removing '\n'
let cwd = std::env::current_dir()?;
let dir = cwd.file_name().unwrap().to_string_lossy();
format!("{magisk_path}/.magisk/modules/{dir}/{}", constants::PATH_MODULE_PROP)
} else {
constants::PATH_MODULE_PROP.to_string()
};
spawn(daemon32);
spawn(daemon64);
let _ = receiver.recv();
bail!("Daemon process died");
log::info!("Mount {module_prop}");
let module_prop_file = fs::File::open(&module_prop)?;
let mut section = 0;
let mut sections: [String; 2] = [String::new(), String::new()];
let lines = io::BufReader::new(module_prop_file).lines();
for line in lines {
let line = line?;
if line.starts_with("description=") {
sections[0].push_str("description=");
sections[1].push_str(line.trim_start_matches("description="));
sections[1].push('\n');
section = 1;
} else {
sections[section].push_str(&line);
sections[section].push('\n');
}
}
PROP_SECTIONS.init(sections);
fs::create_dir(magic::PATH_TMP_DIR.as_str())?;
fs::File::create(magic::PATH_TMP_PROP.as_str())?;
// FIXME: sys_mount cannot be compiled on 32 bit
unsafe {
let r = libc::mount(
CString::new(magic::PATH_TMP_PROP.as_str())?.as_ptr(),
CString::new(module_prop)?.as_ptr(),
std::ptr::null(),
libc::MS_BIND,
std::ptr::null(),
);
Errno::result(r)?;
}
Ok(())
}
fn set_prop_hint(hint: &str) -> Result<()> {
let mut file = fs::File::create(magic::PATH_TMP_PROP.as_str())?;
file.write_all(PROP_SECTIONS[0].as_bytes())?;
file.write_all(b"[")?;
file.write_all(hint.as_bytes())?;
file.write_all(b"] ")?;
file.write_all(PROP_SECTIONS[1].as_bytes())?;
Ok(())
}
fn check_and_set_hint() -> Result<bool> {
let root_impl = root_impl::get_impl();
match root_impl {
root_impl::RootImpl::None => set_prop_hint(constants::STATUS_ROOT_IMPL_NONE)?,
root_impl::RootImpl::TooOld => set_prop_hint(constants::STATUS_ROOT_IMPL_TOO_OLD)?,
root_impl::RootImpl::Abnormal => set_prop_hint(constants::STATUS_ROOT_IMPL_ABNORMAL)?,
root_impl::RootImpl::Multiple => set_prop_hint(constants::STATUS_ROOT_IMPL_MULTIPLE)?,
_ => {
set_prop_hint(constants::STATUS_LOADED)?;
return Ok(true);
}
}
Ok(false)
}
fn spawn_daemon() -> Result<()> {
let mut lives = 5;
loop {
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let mut child_ids = vec![];
let (sender, receiver) = mpsc::channel();
let mut spawn = |mut daemon: Child| {
child_ids.push(daemon.id());
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
let _ = sender.send(());
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let mut binder = loop {
if receiver.try_recv().is_ok() {
bail!("Daemon died before system server ready");
}
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
thread::sleep(Duration::from_secs(1));
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if receiver.try_recv().is_ok() || binder.ping_binder().is_err() { break; }
thread::sleep(Duration::from_secs(1))
}
for child in child_ids {
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
}
lives -= 1;
if lives == 0 {
bail!("Too many crashes, abort");
}
log::error!("Restarting zygote...");
utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?;
utils::set_property(constants::PROP_CTL_RESTART, "zygote")?;
}
}

View File

@@ -1,227 +0,0 @@
use crate::constants::DaemonSocketAction;
use crate::utils::{restore_native_bridge, UnixStreamExt};
use crate::{constants, utils};
use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
libc::{self, dlsym},
unistd::getppid,
};
use passfd::FdPassingExt;
use std::io::Write;
use std::sync::Arc;
use std::thread;
use std::ffi::c_void;
use std::fs;
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::path::PathBuf;
use std::process::Command;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
struct Module {
name: String,
memfd: Memfd,
companion_entry: Option<ZygiskCompanionEntryFn>,
}
struct Context {
native_bridge: String,
modules: Vec<Module>,
}
pub fn start(is64: bool) -> Result<()> {
check_parent()?;
let arch = get_arch(is64)?;
log::debug!("Daemon architecture: {arch}");
log::info!("Load modules");
let modules = load_modules(arch)?;
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");
for stream in listener.incoming() {
let stream = stream?;
let context = Arc::clone(&context);
thread::spawn(move || {
if let Err(e) = handle_daemon_action(stream, &context) {
log::warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
}
});
}
Ok(())
}
fn check_parent() -> Result<()> {
let parent = fs::read_to_string(format!("/proc/{}/cmdline", getppid().as_raw()))?;
let parent = parent.split('/').last().unwrap().trim_end_matches('\0');
if parent != "zygiskwd" {
bail!("Daemon is not started by watchdog: {parent}");
}
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 load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_KSU_MODULE_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?;
let name = entry.file_name().into_string().unwrap();
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
let disabled = entry.path().join("disable");
if !so_path.exists() || disabled.exists() {
continue;
}
log::info!(" Loading module `{name}`...");
let memfd = match create_memfd(&name, &so_path) {
Ok(memfd) => memfd,
Err(e) => {
log::warn!(" Failed to create memfd for `{name}`: {e}");
continue;
}
};
let companion_entry = match preload_module(&memfd) {
Ok(entry) => entry,
Err(e) => {
log::warn!(" Failed to preload `{name}`: {e}");
continue;
}
};
let module = Module {
name,
memfd,
companion_entry,
};
modules.push(module);
}
Ok(modules)
}
fn create_memfd(name: &str, so_path: &PathBuf) -> Result<Memfd> {
let opts = memfd::MemfdOptions::default().allow_sealing(true);
let memfd = opts.create(name)?;
let file = fs::File::open(so_path)?;
let mut reader = std::io::BufReader::new(file);
let mut writer = memfd.as_file();
std::io::copy(&mut reader, &mut writer)?;
let mut seals = memfd::SealsHashSet::new();
seals.insert(memfd::FileSeal::SealShrink);
seals.insert(memfd::FileSeal::SealGrow);
seals.insert(memfd::FileSeal::SealWrite);
seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?;
Ok(memfd)
}
fn preload_module(memfd: &Memfd) -> Result<Option<ZygiskCompanionEntryFn>> {
unsafe {
let path = format!("/proc/self/fd/{}", memfd.as_raw_fd());
let filename = std::ffi::CString::new(path)?;
let handle = libc::dlopen(filename.as_ptr(), libc::RTLD_LAZY);
if handle.is_null() {
let e = std::ffi::CStr::from_ptr(libc::dlerror())
.to_string_lossy()
.into_owned();
bail!("dlopen failed: {}", e);
}
let symbol = std::ffi::CString::new("zygisk_companion_entry")?;
let entry = dlsym(handle, symbol.as_ptr());
if entry.is_null() {
return Ok(None);
}
let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry);
Ok(Some(fnptr))
}
}
fn create_daemon_socket(is64: bool) -> 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}");
Ok(listener)
}
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);
match action {
DaemonSocketAction::PingHeartbeat => {
restore_native_bridge()?;
}
DaemonSocketAction::ReadNativeBridge => {
stream.write_usize(context.native_bridge.len())?;
stream.write_all(context.native_bridge.as_bytes())?;
}
DaemonSocketAction::ReadModules => {
stream.write_usize(context.modules.len())?;
for module in context.modules.iter() {
stream.write_usize(module.name.len())?;
stream.write_all(module.name.as_bytes())?;
stream.send_fd(module.memfd.as_raw_fd())?;
}
}
DaemonSocketAction::RequestCompanionSocket => {
let index = stream.read_usize()?;
let module = &context.modules[index];
log::debug!("New companion request from module {}", module.name);
// FIXME: Spawn a new process
match module.companion_entry {
Some(entry) => {
stream.write_u8(1)?;
unsafe { entry(stream.as_raw_fd()); }
}
None => {
stream.write_u8(0)?;
}
}
}
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_KSU_MODULE_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
}
Ok(())
}

240
zygiskd/src/zygiskd.rs Normal file
View File

@@ -0,0 +1,240 @@
use crate::constants::DaemonSocketAction;
use crate::utils::UnixStreamExt;
use crate::{constants, lp_select, magic, 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::fs;
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::os::unix::process::CommandExt;
use std::path::PathBuf;
use std::process::Command;
struct Module {
name: String,
memfd: Memfd,
companion: Mutex<Option<UnixStream>>,
}
struct Context {
native_bridge: String,
modules: Vec<Module>,
}
pub fn entry() -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
let arch = get_arch()?;
log::debug!("Daemon architecture: {arch}");
log::info!("Load modules");
let modules = load_modules(arch)?;
let context = Context {
native_bridge: utils::get_native_bridge(),
modules,
};
let context = Arc::new(context);
log::info!("Create socket");
let listener = create_daemon_socket()?;
log::info!("Handle zygote connections");
for stream in listener.incoming() {
let stream = stream?;
let context = Arc::clone(&context);
thread::spawn(move || {
if let Err(e) = handle_daemon_action(stream, &context) {
log::warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
}
});
}
Ok(())
}
fn get_arch() -> Result<&'static str> {
let system_arch = utils::get_property("ro.product.cpu.abi")?;
if system_arch.contains("arm") {
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
}
if system_arch.contains("x86") {
return Ok(lp_select!("x86", "x86_64"));
}
bail!("Unsupported system architecture: {}", system_arch);
}
fn load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
Ok(dir) => dir,
Err(e) => {
log::warn!("Failed reading modules directory: {}", e);
return Ok(modules);
}
};
for entry_result in dir.into_iter() {
let entry = entry_result?;
let name = entry.file_name().into_string().unwrap();
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
let disabled = entry.path().join("disable");
if !so_path.exists() || disabled.exists() {
continue;
}
log::info!(" Loading module `{name}`...");
let memfd = match create_memfd(&so_path) {
Ok(memfd) => memfd,
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 };
modules.push(module);
}
Ok(modules)
}
fn create_memfd(so_path: &PathBuf) -> Result<Memfd> {
let opts = memfd::MemfdOptions::default().allow_sealing(true);
let memfd = opts.create("jit-cache")?;
let file = fs::File::open(so_path)?;
let mut reader = std::io::BufReader::new(file);
let mut writer = memfd.as_file();
std::io::copy(&mut reader, &mut writer)?;
let mut seals = memfd::SealsHashSet::new();
seals.insert(memfd::FileSeal::SealShrink);
seals.insert(memfd::FileSeal::SealGrow);
seals.insert(memfd::FileSeal::SealWrite);
seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?;
Ok(memfd)
}
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let prefix = lp_select!("zygiskd32", "zygiskd64");
let name = format!("{}{}", prefix, magic::MAGIC.as_str());
let listener = utils::abstract_namespace_socket(&name)?;
log::debug!("Daemon socket: {name}");
Ok(listener)
}
fn spawn_companion(name: &str, memfd: &Memfd) -> Result<Option<UnixStream>> {
let (mut daemon, companion) = UnixStream::pair()?;
// Remove FD_CLOEXEC flag
fcntl(companion.as_raw_fd(), FcntlArg::F_SETFD(FdFlag::empty()))?;
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
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"),
}
}
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);
match action {
DaemonSocketAction::PingHeartbeat => {
// Do nothing
}
DaemonSocketAction::RequestLogcatFd => {
loop {
let level = match stream.read_u8() {
Ok(level) => level,
Err(_) => break,
};
let tag = stream.read_string()?;
let message = stream.read_string()?;
utils::log_raw(level as i32, &tag, &message)?;
}
}
DaemonSocketAction::ReadNativeBridge => {
stream.write_string(&context.native_bridge)?;
}
DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32;
let mut flags = 0u32;
if root_impl::uid_on_allowlist(uid) {
flags |= constants::PROCESS_GRANTED_ROOT;
}
if root_impl::uid_on_denylist(uid) {
flags |= constants::PROCESS_ON_DENYLIST;
}
match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= constants::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= constants::PROCESS_ROOT_IS_MAGISK,
_ => unreachable!(),
}
// TODO: PROCESS_IS_SYSUI?
stream.write_u32(flags)?;
}
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())?;
}
}
DaemonSocketAction::RequestCompanionSocket => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let mut companion = module.companion.lock().unwrap();
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();
stream.write_u8(0)?;
}
// Ok: Send by companion
}
None => {
stream.write_u8(0)?;
}
}
}
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
}
Ok(())
}