Compare commits

...

17 Commits

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

* New way to unload zygisk (3a4fe53)

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

---------

Co-authored-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
Co-authored-by: 残页 <31466456+canyie@users.noreply.github.com>
2023-10-15 11:11:05 +08:00
Nullptr
42503e7cfe Update issue template 2023-08-13 09:54:50 +08:00
Nullptr
14c920c553 Update dependencies 2023-08-02 08:24:07 +08:00
Nullptr
ceaa2d431c Add updateJson 2023-06-27 16:18:52 +08:00
5ec1cff
ccb5764b72 Receive the reply_ok to prevent from dmesg flooding (#35) 2023-06-21 23:06:47 +08:00
36 changed files with 755 additions and 599 deletions

View File

@@ -7,12 +7,13 @@ body:
attributes:
value: |
Thanks for reporting issues of Zygisk on KernelSU!
To make it easier for us to help you please enter detailed information below.
Note: We will **NEVER** handle any issue related to root detection or writable system.
感谢给 Zygisk on KernelSU 汇报问题!
为了使我们更好地帮助你,请提供以下信息。
为了防止重复汇报,标题请务必使用英文。
请注意:我们**不会**处理任何有关 root 检测和 system 分区可写相关的问题。
- type: textarea
attributes:
label: Steps to reproduce/复现步骤

View File

@@ -3,6 +3,12 @@ description: Suggest an idea./提出建议
labels: [enhancement]
title: "[Feature Request] Short description/简单描述"
body:
- type: markdown
attributes:
value: |
Note: We will **NEVER** handle any issue related to root detection or writable system.
请注意:我们**不会**处理任何有关 root 检测和 system 分区可写相关的问题。
- type: textarea
attributes:
label: Is your feature request related to a problem?/你的请求是否与某个问题相关?

View File

@@ -43,10 +43,9 @@ jobs:
- name: Setup Rust
run: |
rustup target add armv7-linux-androideabi
rustup override set nightly
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

View File

@@ -9,6 +9,7 @@ Also works as standalone loader for Magisk.
### General
+ No multiple root implementation installed
+ SELinux enforcing: We now rely on SELinux to prevent `vold` from aborting our fuse connection
### KernelSU
@@ -18,7 +19,7 @@ Also works as standalone loader for Magisk.
### Magisk
+ Minimal version: 25208
+ Minimal version: 26300
+ Original Zygisk turned off
## Compatibility

View File

@@ -2,18 +2,7 @@ import com.android.build.gradle.LibraryExtension
import java.io.ByteArrayOutputStream
plugins {
id("com.android.application") apply false
id("com.android.library") apply false
}
buildscript {
repositories {
maven("https://plugins.gradle.org/m2/")
}
dependencies {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.4.0.202211300538-r")
classpath("org.mozilla.rust-android-gradle:plugin:0.9.3")
}
alias(libs.plugins.agp.lib) apply false
}
fun String.execute(currentWorkingDir: File = file("./")): String {
@@ -31,18 +20,19 @@ 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.7.1")
val verName by extra("v4-0.8.0")
val verCode by extra(gitCommitCount)
val commitHash by extra(gitCommitHash)
val minKsuVersion by extra(10940)
val minKsudVersion by extra(10942)
val maxKsuVersion by extra(20000)
val minMagiskVersion by extra(25208)
val minMagiskVersion by extra(26300)
val androidMinSdkVersion by extra(29)
val androidTargetSdkVersion by extra(33)
val androidCompileSdkVersion by extra(33)
val androidBuildToolsVersion by extra("33.0.2")
val androidCompileNdkVersion by extra("25.2.9519653")
val androidTargetSdkVersion by extra(34)
val androidCompileSdkVersion by extra(34)
val androidBuildToolsVersion by extra("34.0.0")
val androidCompileNdkVersion by extra("26.0.10792818")
val androidSourceCompatibility by extra(JavaVersion.VERSION_11)
val androidTargetCompatibility by extra(JavaVersion.VERSION_11)

View File

@@ -0,0 +1,9 @@
[versions]
agp = "8.1.2"
kotlin = "1.9.10"
[plugins]
agp-lib = { id = "com.android.library", version.ref = "agp" }
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
lsplugin-jgit = { id = "org.lsposed.lsplugin.jgit", version = "1.1" }
rust-android = { id = "org.mozilla.rust-android-gradle.rust-android", version = "0.9.3" }

Binary file not shown.

View File

@@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -2,7 +2,7 @@ import java.nio.file.Paths
import org.gradle.internal.os.OperatingSystem
plugins {
id("com.android.library")
alias(libs.plugins.agp.lib)
}
fun Project.findInPath(executable: String, property: String): String? {
@@ -35,6 +35,7 @@ android {
defaultConfig {
externalNativeBuild {
ndkBuild {
abiFilters("arm64-v8a", "x86_64")
ccachePatch?.let {
arguments += "NDK_CCACHE=$it"
}

View File

@@ -13,16 +13,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
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)/%=%)
LOCAL_STATIC_LIBRARIES := cxx common
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygisk_injector
LOCAL_MODULE := zygisk
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,32 +8,14 @@
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},
};
strcpy(addr.sun_path + 1, sSocketName.data());
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
strcpy(addr.sun_path, kCPSocketPath);
socklen_t socklen = sizeof(addr);
while (retry--) {
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
@@ -66,16 +48,6 @@ namespace zygiskd {
return fd;
}
std::string ReadNativeBridge() {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("ReadNativeBridge");
return "";
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge);
return socket_utils::read_string(fd);
}
uint32_t GetProcessFlags(uid_t uid) {
UniqueFd fd = Connect(1);
if (fd == -1) {

View File

@@ -5,13 +5,7 @@
#include <unistd.h>
#include <vector>
#if defined(__LP64__)
# define LP_SELECT(lp32, lp64) lp64
#else
# define LP_SELECT(lp32, lp64) lp32
#endif
constexpr auto kZygiskMagic = "/system/zygisk_magic";
constexpr auto kCPSocketPath = "/dev/zygisk/cp.sock";
class UniqueFd {
using Fd = int;
@@ -54,7 +48,6 @@ namespace zygiskd {
enum class SocketAction {
PingHeartBeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,
@@ -65,8 +58,6 @@ namespace zygiskd {
int RequestLogcatFd();
std::string ReadNativeBridge();
std::vector<Module> ReadModules();
uint32_t GetProcessFlags(uid_t uid);

View File

@@ -5,11 +5,7 @@
#include <string.h>
#ifndef LOG_TAG
#if defined(__LP64__)
# define LOG_TAG "zygisksu64"
#else
# define LOG_TAG "zygisksu32"
#endif
#define LOG_TAG "zygisk-core"
#endif
#ifdef LOG_DISABLED

View File

@@ -7,22 +7,20 @@ using namespace std;
void *self_handle = nullptr;
[[gnu::destructor]] [[maybe_unused]]
static void zygisk_cleanup_wait() {
if (self_handle) {
// Wait 10us to make sure none of our code is executing
timespec ts = { .tv_sec = 0, .tv_nsec = 10000L };
nanosleep(&ts, nullptr);
}
}
extern "C" [[gnu::visibility("default")]]
void entry(void *handle) {
void entry(void* handle) {
LOGI("Zygisk library injected");
self_handle = handle;
if (!zygiskd::PingHeartbeat()) {
LOGE("Zygisk daemon is not running");
return;
}
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
self_handle = handle;
LOGD("Load injector successfully");
LOGD("Start hooking");
hook_functions();
}

View File

@@ -24,7 +24,8 @@ using jni_hook::hash_map;
using jni_hook::tree_map;
using xstring = jni_hook::string;
static bool unhook_functions();
static void hook_unloader();
static void unhook_functions();
namespace {
@@ -34,7 +35,6 @@ enum {
APP_SPECIALIZE,
SERVER_FORK_AND_SPECIALIZE,
DO_REVERT_UNMOUNT,
CAN_UNLOAD_ZYGISK,
SKIP_FD_SANITIZATION,
FLAG_MAX
@@ -46,6 +46,11 @@ void name##_post();
#define MAX_FD_SIZE 1024
struct HookContext;
// Current context
HookContext *g_ctx;
struct HookContext {
JNIEnv *env;
union {
@@ -80,7 +85,8 @@ struct HookContext {
vector<IgnoreInfo> ignore_info;
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0),
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {}
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; }
~HookContext();
/* Zygisksu changed: Load module fds */
void run_modules_pre();
@@ -91,9 +97,9 @@ struct HookContext {
DCL_PRE_POST(nativeSpecializeAppProcess)
DCL_PRE_POST(nativeForkSystemServer)
void unload_zygisk();
void sanitize_fds();
bool exempt_fd(int fd);
bool is_child() const { return pid <= 0; }
// Compatibility shim
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
@@ -109,9 +115,8 @@ struct HookContext {
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
bool should_unmap_zygisk = false;
// Current context
HookContext *g_ctx;
const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions = nullptr;
@@ -153,10 +158,6 @@ string get_class_name(JNIEnv *env, jclass clazz) {
return className;
}
#define DCL_HOOK_FUNC(ret, func, ...) \
ret (*old_##func)(__VA_ARGS__); \
ret new_##func(__VA_ARGS__)
jint env_RegisterNatives(
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) {
auto className = get_class_name(env, clazz);
@@ -165,46 +166,50 @@ jint env_RegisterNatives(
return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods);
}
DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void* func) {
LOGD("androidSetCreateThreadFunc\n");
do {
auto get_created_java_vms = reinterpret_cast<jint (*)(JavaVM **, jsize, jsize *)>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
if (!get_created_java_vms) {
for (auto &map: lsplt::MapInfo::Scan()) {
if (!map.path.ends_with("/libnativehelper.so")) continue;
void *h = dlopen(map.path.data(), RTLD_LAZY);
if (!h) {
LOGW("cannot dlopen libnativehelper.so: %s\n", dlerror());
break;
}
get_created_java_vms = reinterpret_cast<decltype(get_created_java_vms)>(dlsym(h, "JNI_GetCreatedJavaVMs"));
dlclose(h);
break;
}
if (!get_created_java_vms) {
LOGW("JNI_GetCreatedJavaVMs not found\n");
void replace_jni_methods() {
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;
}
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;
if (!get_created_java_vms) {
LOGW("JNI_GetCreatedJavaVMs not found\n");
return;
}
}
JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_java_vms(&vm, 1, &num);
if (res != JNI_OK || vm == nullptr) return;
JNIEnv *env = nullptr;
res = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
if (res != JNI_OK || env == nullptr) return;
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);
// Replace the function table in JNIEnv to hook RegisterNatives
old_functions = env->functions;
env->functions = new_functions;
// Re-run register_com_android_internal_os_Zygote to hook JNI methods
auto register_zygote = dlsym(RTLD_DEFAULT, "_ZN7android39register_com_android_internal_os_ZygoteEP7_JNIEnv");
reinterpret_cast<void (*)(JNIEnv *)>(register_zygote)(env);
}
#define DCL_HOOK_FUNC(ret, func, ...) \
ret (*old_##func)(__VA_ARGS__); \
ret new_##func(__VA_ARGS__)
// Skip actual fork and return cached result if applicable
DCL_HOOK_FUNC(int, fork) {
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
@@ -246,13 +251,28 @@ DCL_HOOK_FUNC(void, android_log_close) {
old_android_log_close();
}
// Last point before process secontext changes
DCL_HOOK_FUNC(int, selinux_android_setcontext,
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
if (g_ctx) {
g_ctx->flags[CAN_UNLOAD_ZYGISK] = unhook_functions();
// We cannot directly call `dlclose` to unload ourselves, otherwise when `dlclose` returns,
// it will return to our code which has been unmapped, causing segmentation fault.
// Instead, we hook `pthread_attr_destroy` which will be called when VM daemon threads start.
DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) {
int res = old_pthread_attr_destroy((pthread_attr_t *)target);
// Only perform unloading on the main thread
if (gettid() != getpid())
return res;
LOGD("pthread_attr_destroy\n");
if (should_unmap_zygisk) {
unhook_functions();
if (should_unmap_zygisk) {
// Because both `pthread_attr_destroy` and `dlclose` have the same function signature,
// we can use `musttail` to let the compiler reuse our stack frame and thus
// `dlclose` will directly return to the caller of `pthread_attr_destroy`.
[[clang::musttail]] return dlclose(self_handle);
}
}
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
return res;
}
#undef DCL_HOOK_FUNC
@@ -449,7 +469,6 @@ int sigmask(int how, int signum) {
}
void HookContext::fork_pre() {
g_ctx = this;
// Do our own fork before loading any 3rd party code
// First block SIGCHLD, unblock after original fork is done
sigmask(SIG_BLOCK, SIGCHLD);
@@ -531,7 +550,6 @@ void HookContext::fork_post() {
// Unblock SIGCHLD in case the original method didn't
sigmask(SIG_UNBLOCK, SIGCHLD);
g_ctx = nullptr;
unload_zygisk();
}
/* Zygisksu changed: Load module fds */
@@ -585,22 +603,6 @@ void HookContext::app_specialize_post() {
logging::setfd(-1);
}
void HookContext::unload_zygisk() {
if (flags[CAN_UNLOAD_ZYGISK]) {
// Do NOT call the destructor
operator delete(jni_method_map);
// Directly unmap the whole memory block
jni_hook::memory_block::release();
// Strip out all API function pointers
for (auto &m : modules) {
m.clearApi();
}
new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
}
}
bool HookContext::exempt_fd(int fd) {
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
return true;
@@ -615,7 +617,6 @@ bool HookContext::exempt_fd(int fd) {
void HookContext::nativeSpecializeAppProcess_pre() {
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
LOGV("pre specialize [%s]\n", process);
g_ctx = this;
// App specialize does not check FD
flags[SKIP_FD_SANITIZATION] = true;
app_specialize_pre();
@@ -624,7 +625,6 @@ void HookContext::nativeSpecializeAppProcess_pre() {
void HookContext::nativeSpecializeAppProcess_post() {
LOGV("post specialize [%s]\n", process);
app_specialize_post();
unload_zygisk();
}
/* Zygisksu changed: No system_server status write back */
@@ -674,6 +674,43 @@ void HookContext::nativeForkAndSpecialize_post() {
fork_post();
}
HookContext::~HookContext() {
// This global pointer points to a variable on the stack.
// Set this to nullptr to prevent leaking local variable.
// This also disables most plt hooked functions.
g_ctx = nullptr;
if (!is_child())
return;
should_unmap_zygisk = true;
// Restore JNIEnv
if (env->functions == new_functions) {
env->functions = old_functions;
delete new_functions;
}
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && env->RegisterNatives(
env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
should_unmap_zygisk = false;
}
}
delete jni_hook_list;
jni_hook_list = nullptr;
// Strip out all API function pointers
for (auto &m : modules) {
m.clearApi();
}
hook_unloader();
}
} // namespace
static bool hook_commit() {
@@ -716,8 +753,6 @@ 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, selinux_android_setcontext);
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();
@@ -726,40 +761,36 @@ void hook_functions() {
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
[](auto &t) { return *std::get<3>(t) == nullptr;}),
plt_hook_list->end());
replace_jni_methods();
}
static bool unhook_functions() {
bool success = true;
static void hook_unloader() {
ino_t art_inode = 0;
dev_t art_dev = 0;
// Restore JNIEnv
if (g_ctx->env->functions == new_functions) {
g_ctx->env->functions = old_functions;
delete new_functions;
}
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && g_ctx->env->RegisterNatives(
g_ctx->env->FindClass(clz.data()), methods.data(),
static_cast<int>(methods.size())) != 0) {
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
success = false;
for (auto &map : lsplt::MapInfo::Scan()) {
if (map.path.ends_with("/libart.so")) {
art_inode = map.inode;
art_dev = map.dev;
break;
}
}
delete jni_hook_list;
PLT_HOOK_REGISTER(art_dev, art_inode, pthread_attr_destroy);
hook_commit();
}
static void unhook_functions() {
// Unhook plt_hook
for (const auto &[dev, inode, sym, old_func] : *plt_hook_list) {
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
LOGE("Failed to register plt_hook [%s]\n", sym);
success = false;
}
}
delete plt_hook_list;
if (!hook_commit()) {
LOGE("Failed to restore plt_hook\n");
success = false;
should_unmap_zygisk = false;
}
return success;
}

View File

@@ -11,13 +11,16 @@ using namespace std::string_view_literals;
namespace {
constexpr auto MODULE_DIR = "/data/adb/modules";
constexpr auto KSU_OVERLAY_SOURCE = "KSU";
constexpr auto ZYGISK_FUSE_SOURCE = "zygisk";
const std::vector<std::string> KSU_PARTITIONS{"/system", "/vendor", "/product", "/system_ext", "/odm", "/oem"};
void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1) {
LOGD("Unmounted (%s)", mountpoint);
} else {
#ifndef NDEBUG
PLOGE("Unmount (%s)", mountpoint);
#endif
}
}
}
@@ -34,7 +37,7 @@ void revert_unmount_ksu() {
ksu_loop = info.source;
continue;
}
// Unmount everything on /data/adb except ksu module dir
// Unmount everything mounted to /data/adb
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
@@ -44,6 +47,10 @@ void revert_unmount_ksu() {
&& std::find(KSU_PARTITIONS.begin(), KSU_PARTITIONS.end(), info.target) != KSU_PARTITIONS.end()) {
targets.emplace_back(info.target);
}
// Unmount fuse
if (info.type == "fuse" && info.source == ZYGISK_FUSE_SOURCE) {
targets.emplace_back(info.target);
}
}
for (auto& info: parse_mount_info("self")) {
// Unmount everything from ksu loop except ksu module dir
@@ -68,6 +75,14 @@ void revert_unmount_magisk() {
info.root.starts_with("/adb/modules")) { // bind mount from data partition
targets.push_back(info.target);
}
// Unmount everything mounted to /data/adb
if (info.target.starts_with("/data/adb")) {
targets.emplace_back(info.target);
}
// Unmount fuse
if (info.type == "fuse" && info.source == ZYGISK_FUSE_SOURCE) {
targets.emplace_back(info.target);
}
}
for (auto& s: reversed(targets)) {

View File

@@ -1,100 +0,0 @@
#include <string_view>
#include <sys/system_properties.h>
#include <unistd.h>
#include <array>
#include "daemon.h"
#include "dl.h"
#include "logging.h"
#include "native_bridge_callbacks.h"
extern "C" [[gnu::visibility("default")]]
uint8_t NativeBridgeItf[sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0};
namespace {
constexpr auto kZygoteProcesses = {"zygote", "zygote32", "zygote64", "usap32", "usap64"};
constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libzygisk_injector.so";
void* sOriginalBridge = nullptr;
}
__used __attribute__((destructor))
void Destructor() {
if (sOriginalBridge) {
dlclose(sOriginalBridge);
}
}
__used __attribute__((constructor))
void Constructor() {
if (getuid() != 0) {
return;
}
std::string_view cmdline = getprogname();
if (std::none_of(
kZygoteProcesses.begin(), kZygoteProcesses.end(),
[&](const char* p) { return cmdline == p; }
)) {
LOGW("Not started as zygote (cmdline=%s)", cmdline.data());
return;
}
std::string native_bridge;
do {
if (!zygiskd::PingHeartbeat()) break;
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
LOGI("Read native bridge");
native_bridge = zygiskd::ReadNativeBridge();
LOGI("Load injector");
auto handle = DlopenExt(kInjector, RTLD_NOW);
if (handle == nullptr) {
LOGE("Failed to dlopen injector: %s", dlerror());
break;
}
auto entry = dlsym(handle, "entry");
if (entry == nullptr) {
LOGE("Failed to dlsym injector entry: %s", dlerror());
dlclose(handle);
break;
}
reinterpret_cast<void (*)(void*)>(entry)(handle);
} while (false);
do {
if (native_bridge.empty() || native_bridge == "0") break;
LOGI("Load original native bridge: %s", native_bridge.data());
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
if (sOriginalBridge == nullptr) {
LOGE("%s", dlerror());
break;
}
auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
if (original_native_bridge_itf == 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 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

@@ -4,7 +4,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
import org.apache.tools.ant.filters.FixCrLfFilter
plugins {
id("com.android.library")
alias(libs.plugins.agp.lib)
}
val moduleId: String by rootProject.extra
@@ -15,6 +15,7 @@ val minKsuVersion: Int by rootProject.extra
val minKsudVersion: Int by rootProject.extra
val maxKsuVersion: Int by rootProject.extra
val minMagiskVersion: Int by rootProject.extra
val commitHash: String by rootProject.extra
android.buildFeatures {
androidResources = false
@@ -27,7 +28,7 @@ androidComponents.onVariants { variant ->
val buildTypeLowered = variant.buildType?.toLowerCase()
val moduleDir = "$buildDir/outputs/module/$variantLowered"
val zipFileName = "$moduleName-$verName-$verCode-$buildTypeLowered.zip".replace(' ', '-')
val zipFileName = "$moduleName-$verName-$verCode-$commitHash-$buildTypeLowered.zip".replace(' ', '-')
val prepareModuleFilesTask = task<Sync>("prepareModuleFiles$variantCapped") {
group = "module"
@@ -46,8 +47,8 @@ androidComponents.onVariants { variant ->
expand(
"moduleId" to moduleId,
"moduleName" to moduleName,
"versionName" to "$verName ($verCode-$variantLowered)",
"versionCode" to verCode,
"versionName" to "$verName ($verCode-$commitHash-$variantLowered)",
"versionCode" to verCode
)
}
from("$projectDir/src") {

View File

@@ -61,7 +61,7 @@ else
fi
# check architecture
if [ "$ARCH" != "arm" ] && [ "$ARCH" != "arm64" ] && [ "$ARCH" != "x86" ] && [ "$ARCH" != "x64" ]; then
if [ "$ARCH" != "arm64" ] && [ "$ARCH" != "x64" ]; then
abort "! Unsupported platform: $ARCH"
else
ui_print "- Device platform: $ARCH"
@@ -101,60 +101,28 @@ extract "$ZIPFILE" 'post-fs-data.sh' "$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
mkdir "$MODPATH/bin"
mkdir "$MODPATH/system"
[ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib"
[ "$HAS64BIT" = true ] && mkdir "$MODPATH/system/lib64"
mkdir "$MODPATH/system/lib64"
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
if [ "$HAS32BIT" = true ]; then
ui_print "- Extracting x86 libraries"
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/x86/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
if [ "$HAS64BIT" = true ]; then
ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/x86_64/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
if [ "$ARCH" = "x64" ]; then
ui_print "- Extracting x64 libraries"
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd" "$MODPATH/bin/zygisk-wd"
ln -sf "zygiskd" "$MODPATH/bin/zygisk-fuse"
ln -sf "zygiskd" "$MODPATH/bin/zygisk-cp"
else
if [ "$HAS32BIT" = true ]; then
ui_print "- Extracting arm libraries"
extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
extract "$ZIPFILE" 'lib/armeabi-v7a/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
if [ "$HAS64BIT" = true ]; then
ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
extract "$ZIPFILE" 'lib/arm64-v8a/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
ui_print "- Extracting arm64 libraries"
extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/system/lib64" true
ln -sf "zygiskd" "$MODPATH/bin/zygisk-wd"
ln -sf "zygiskd" "$MODPATH/bin/zygisk-fuse"
ln -sf "zygiskd" "$MODPATH/bin/zygisk-cp"
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"
set_perm_recursive "$MODPATH/bin" 0 2000 0755 0755
set_perm_recursive "$MODPATH/system/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0
set_perm_recursive "$MODPATH/bin" 0 0 0755 0755
set_perm_recursive "$MODPATH/system/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0
# If Huawei's Maple is enabled, system_server is created with a special way which is out of Zygisk's control

View File

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

View File

@@ -6,8 +6,6 @@ if [ "$ZYGISK_ENABLED" ]; then
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
@@ -21,3 +19,6 @@ if [ "$(which magisk)" ]; then
fi
done
fi
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
unshare -m sh -c "bin/zygisk-fuse &"

View File

@@ -1,3 +1,5 @@
deny vold fusectlfs file write
allow * tmpfs * *
allow zygote appdomain_tmpfs dir *
allow zygote appdomain_tmpfs file *

View File

@@ -8,8 +8,6 @@ if [ "$ZYGISK_ENABLED" ]; then
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(cat /dev/.native_bridge)
rm /dev/.native_bridge
if [ "$(which magisk)" ]; then
for file in ../*; do
@@ -24,6 +22,5 @@ if [ "$(which magisk)" ]; then
done
fi
log -p i -t "zygisksu" "Start watchdog"
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1
unshare -m sh -c "bin/zygisk-wd &"

View File

@@ -6,10 +6,6 @@ pluginManagement {
mavenCentral()
gradlePluginPortal()
}
plugins {
id("com.android.library") version "7.4.2"
id("com.android.application") version "7.4.2"
}
}
dependencyResolutionManagement {

View File

@@ -9,21 +9,22 @@ rust-version = "1.69"
android_logger = "0.13"
anyhow = { version = "1.0", features = ["backtrace"] }
bitflags = { version = "2.3" }
clap = { version = "4", features = ["derive"] }
const_format = "0.2"
futures = "0.3"
konst = "0.3"
lazy_static = "1.4"
libc = "0.2"
log = "0.4"
memfd = "0.6"
nix = { version = "0.26", features = ["process","poll"] }
num_enum = "0.5"
once_cell = "1.17"
passfd = "0.1"
rand = "0.8"
proc-maps = "0.3"
ptrace-do = "0.1"
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] }
tokio = { version = "1.28", features = ["full"] }
binder = { git = "https://github.com/Kernel-SU/binder_rs" }
binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" }
fuser = { git = "https://github.com/Dr-TSNG/fuser", default-features = false }
[profile.release]
strip = true

View File

@@ -1,6 +1,6 @@
plugins {
id("com.android.library")
id("org.mozilla.rust-android-gradle.rust-android")
alias(libs.plugins.agp.lib)
alias(libs.plugins.rust.android)
}
val verName: String by rootProject.extra
@@ -18,7 +18,7 @@ cargo {
module = "."
libname = "zygiskd"
targetIncludes = arrayOf("zygiskd")
targets = listOf("arm64", "arm", "x86", "x86_64")
targets = listOf("arm64", "x86_64")
targetDirectory = "build/intermediates/rust"
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
profile = if (isDebug) "debug" else "release"

View File

@@ -17,15 +17,20 @@ 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 ZYGISK_LIBRARY: &str = "libzygisk.so";
pub const PATH_PCL: &str = "/system/etc/preloaded-classes";
pub const PATH_SYSTEM_LIB: &str = "/system/lib64";
pub const PATH_WORK_DIR: &str = "/dev/zygisk"; // TODO: Replace with /debug_ramdisk/zygisk
pub const PATH_PROP_OVERLAY: &str = concatcp!(PATH_WORK_DIR, "/module.prop");
pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, "/cp.sock");
pub const PATH_FUSE_DIR: &str = concatcp!(PATH_WORK_DIR, "/fuse");
pub const PATH_FUSE_PCL: &str = concatcp!(PATH_FUSE_DIR, "/preloaded-classes");
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_CP_BIN: &str = "bin/zygisk-cp";
pub const STATUS_LOADED: &str = "😋 Zygisksu is loaded";
pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed";
@@ -39,7 +44,6 @@ pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations i
pub enum DaemonSocketAction {
PingHeartbeat,
RequestLogcatFd,
ReadNativeBridge,
GetProcessFlags,
ReadModules,
RequestCompanionSocket,

View File

@@ -1,18 +1,17 @@
use anyhow::{bail, Result};
use std::ffi::{c_char, c_void};
use nix::libc;
const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
struct AndroidNamespace {
pub struct AndroidNamespace {
_unused: [u8; 0],
}
#[repr(C)]
struct AndroidDlextinfo {
pub struct AndroidDlextinfo {
pub flags: u64,
pub reserved_addr: *mut c_void,
pub reserved_size: libc::size_t,
@@ -23,7 +22,7 @@ struct AndroidDlextinfo {
}
extern "C" {
fn android_dlopen_ext(
pub fn android_dlopen_ext(
filename: *const c_char,
flags: libc::c_int,
extinfo: *const AndroidDlextinfo,

328
zygiskd/src/fuse.rs Normal file
View File

@@ -0,0 +1,328 @@
use std::cmp::min;
use anyhow::{bail, Result};
use std::ffi::{CString, OsStr};
use std::{fs, thread};
use std::sync::{mpsc, Mutex};
use std::time::{Duration, SystemTime};
use fuser::{FileAttr, Filesystem, FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyOpen, Request};
use libc::ENOENT;
use log::{debug, error, info};
use proc_maps::{get_process_maps, MapRange, Pid};
use ptrace_do::{RawProcess, TracedProcess};
use rustix::mount::mount_bind;
use rustix::path::Arg;
use rustix::process::getpid;
use crate::{constants, dl};
use crate::utils::LateInit;
pub struct DelegateFilesystem;
const fn attr(inode: u64, size: u64, kind: FileType) -> FileAttr {
FileAttr {
ino: inode,
size,
blocks: 0,
atime: SystemTime::UNIX_EPOCH,
mtime: SystemTime::UNIX_EPOCH,
ctime: SystemTime::UNIX_EPOCH,
crtime: SystemTime::UNIX_EPOCH,
kind,
perm: 0o644,
nlink: 0,
uid: 0,
gid: 0,
rdev: 0,
blksize: 0,
flags: 0,
}
}
const ANDROID_LIBC: &str = "bionic/libc.so";
const ANDROID_LIBDL: &str = "bionic/libdl.so";
const INO_DIR: u64 = 1;
const INO_PCL: u64 = 2;
static ATTR_DIR: FileAttr = attr(INO_DIR, 0, FileType::Directory);
static ATTR_PCL: LateInit<FileAttr> = LateInit::new();
static PCL_CONTENT: LateInit<Vec<u8>> = LateInit::new();
const ENTRIES: &[(u64, FileType, &str)] = &[
(INO_DIR, FileType::Directory, "."),
(INO_DIR, FileType::Directory, ".."),
(INO_PCL, FileType::RegularFile, "preloaded-classes"),
];
const TTL: Duration = Duration::from_secs(1);
impl Filesystem for DelegateFilesystem {
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
if parent != INO_DIR {
reply.error(ENOENT);
return;
}
match name.as_str().unwrap() {
"preloaded-classes" => reply.entry(&TTL, &ATTR_PCL, 0),
_ => reply.error(ENOENT),
}
}
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
match ino {
INO_DIR => reply.attr(&TTL, &ATTR_DIR),
INO_PCL => reply.attr(&TTL, &ATTR_PCL),
_ => reply.error(ENOENT),
}
}
fn open(&mut self, req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) {
if ino == INO_PCL {
let pid = req.pid();
let process = format!("/proc/{}/cmdline", pid);
let process = fs::read_to_string(process).unwrap();
let process = &process[..process.find('\0').unwrap()];
info!("Process {} is reading preloaded-classes", process);
if process == "zygote64" {
ptrace_zygote(pid).unwrap();
}
}
reply.opened(0, 0);
}
fn read(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option<u64>, reply: ReplyData) {
let offset = offset as usize;
let size = size as usize;
if ino == INO_PCL {
let len = PCL_CONTENT.len();
if offset >= len {
reply.data(&[]);
} else {
let end = min(offset + size, len);
reply.data(&PCL_CONTENT[offset..end]);
}
} else {
reply.error(ENOENT);
}
}
fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) {
if ino != INO_DIR {
reply.error(ENOENT);
return;
}
for (i, entry) in ENTRIES.iter().enumerate().skip(offset as usize) {
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
break;
}
}
reply.ok();
}
}
fn find_module_for_pid(pid: Pid, library: &str) -> Result<MapRange> {
let maps = get_process_maps(pid)?;
for map in maps.into_iter() {
if let Some(p) = map.filename() {
if p.as_str()?.contains(library) {
return Ok(map);
}
}
}
bail!("Cannot find module {library} for pid {pid}");
}
fn find_remote_procedure(
pid: Pid,
library: &str,
local_addr: usize,
) -> Result<usize> {
let local_module = find_module_for_pid(getpid().as_raw_nonzero().get(), library)?;
debug!(
"Identifed local range {library} ({:?}) at {:x}",
local_module.filename(),
local_module.start()
);
let remote_module = find_module_for_pid(pid, library)?;
debug!(
"Identifed remote range {library} ({:?}) at {:x}",
remote_module.filename(),
remote_module.start()
);
Ok(local_addr - local_module.start() + remote_module.start())
}
fn ptrace_zygote(pid: u32) -> Result<()> {
static LAST: Mutex<u32> = Mutex::new(0);
let mut last = LAST.lock().unwrap();
if *last == pid {
return Ok(());
}
*last = pid;
let (sender, receiver) = mpsc::channel::<()>();
let worker = move || -> Result<()> {
info!("Injecting into pid {}", pid);
let zygisk_lib = format!("{}/{}", constants::PATH_SYSTEM_LIB, constants::ZYGISK_LIBRARY);
let lib_dir = CString::new(constants::PATH_SYSTEM_LIB)?;
let zygisk_lib = CString::new(zygisk_lib)?;
let libc_base = find_module_for_pid(pid as i32, ANDROID_LIBC)?.start();
let mmap_remote = find_remote_procedure(
pid as i32,
ANDROID_LIBC,
libc::mmap as usize,
)?;
let munmap_remote = find_remote_procedure(
pid as i32,
ANDROID_LIBC,
libc::munmap as usize,
)?;
let dlopen_remote = find_remote_procedure(
pid as i32,
ANDROID_LIBDL,
dl::android_dlopen_ext as usize,
)?;
let dlsym_remote = find_remote_procedure(
pid as i32,
ANDROID_LIBDL,
libc::dlsym as usize,
)?;
let tracer = TracedProcess::attach(RawProcess::new(pid as i32))?;
sender.send(())?;
let frame = tracer.next_frame()?;
debug!("Waited for a frame");
// Map a buffer in the remote process
let mmap_params: [usize; 6] = [
0,
0x1000,
(libc::PROT_READ | libc::PROT_WRITE) as usize,
(libc::MAP_ANONYMOUS | libc::MAP_PRIVATE) as usize,
0,
0,
];
let (regs, mut frame) = frame.invoke_remote(
mmap_remote,
libc_base,
&mmap_params,
)?;
let buf_addr = regs.return_value();
debug!("Buffer addr: {:x}", buf_addr);
// Find the address of __loader_android_create_namespace
let sym = CString::new("__loader_android_create_namespace")?;
frame.write_memory(buf_addr, sym.as_bytes_with_nul())?;
let (regs, mut frame) = frame.invoke_remote(
dlsym_remote,
libc_base,
&[libc::RTLD_DEFAULT as usize, buf_addr],
)?;
let android_create_namespace_remote = regs.return_value();
debug!("__loader_android_create_namespace addr: {:x}", android_create_namespace_remote);
// Create a linker namespace for remote process
frame.write_memory(buf_addr, zygisk_lib.as_bytes_with_nul())?;
frame.write_memory(buf_addr + 0x100, lib_dir.as_bytes_with_nul())?;
let ns_params: [usize; 7] = [
buf_addr, // name
buf_addr + 0x100, // ld_library_path
0, // default_library_path
dl::ANDROID_NAMESPACE_TYPE_SHARED as usize, // type
0, // permitted_when_isolated_path
0, // parent
dlopen_remote, // caller_addr
];
let (regs, mut frame) = frame.invoke_remote(
android_create_namespace_remote,
libc_base,
&ns_params,
)?;
let ns_addr = regs.return_value();
debug!("Linker namespace addr: {:x}", ns_addr);
// Load zygisk into remote process
let info = dl::AndroidDlextinfo {
flags: dl::ANDROID_DLEXT_USE_NAMESPACE,
reserved_addr: std::ptr::null_mut(),
reserved_size: 0,
relro_fd: 0,
library_fd: 0,
library_fd_offset: 0,
library_namespace: ns_addr as *mut _,
};
let info = unsafe {
std::slice::from_raw_parts(
&info as *const _ as *const u8,
std::mem::size_of::<dl::AndroidDlextinfo>(),
)
};
frame.write_memory(buf_addr + 0x200, info)?;
let (regs, mut frame) = frame.invoke_remote(
dlopen_remote,
libc_base,
&[buf_addr, libc::RTLD_NOW as usize, buf_addr + 0x200],
)?;
let handle = regs.return_value();
debug!("Load zygisk into remote process: {:x}", handle);
let entry = CString::new("entry")?;
frame.write_memory(buf_addr, entry.as_bytes_with_nul())?;
let (regs, frame) = frame.invoke_remote(
dlsym_remote,
libc_base,
&[handle, buf_addr],
)?;
let entry = regs.return_value();
debug!("Call zygisk entry: {:x}", entry);
let (_, frame) = frame.invoke_remote(
entry,
libc_base,
&[handle],
)?;
// Cleanup
let _ = frame.invoke_remote(
munmap_remote,
libc_base,
&[buf_addr],
)?;
debug!("Cleaned up");
Ok(())
};
thread::spawn(move || {
if let Err(e) = worker() {
error!("Crashed: {:?}", e);
}
});
receiver.recv()?;
Ok(())
}
pub fn main() -> Result<()> {
info!("Start zygisk fuse");
fs::create_dir(constants::PATH_WORK_DIR)?;
fs::create_dir(constants::PATH_FUSE_DIR)?;
PCL_CONTENT.init(fs::read(constants::PATH_PCL)?);
ATTR_PCL.init(attr(INO_PCL, PCL_CONTENT.len() as u64, FileType::RegularFile));
let options = [
fuser::MountOption::FSName(String::from("zygisk")),
fuser::MountOption::AllowOther,
fuser::MountOption::RO,
];
let session = fuser::spawn_mount2(
DelegateFilesystem,
constants::PATH_FUSE_DIR,
&options,
)?;
mount_bind(constants::PATH_FUSE_PCL, constants::PATH_PCL)?;
match session.guard.join() {
Err(e) => bail!("Fuse mount crashed: {:?}", e),
_ => bail!("Fuse mount exited unexpectedly"),
}
}

View File

@@ -1,19 +0,0 @@
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,30 +1,16 @@
#![feature(exclusive_range_pattern)]
#![allow(dead_code)]
mod constants;
mod dl;
mod magic;
mod fuse;
mod root_impl;
mod utils;
mod watchdog;
mod zygiskd;
use std::future::Future;
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,
}
fn init_android_logger(tag: &str) {
@@ -35,24 +21,29 @@ fn init_android_logger(tag: &str) {
);
}
async fn start() -> Result<()> {
fn async_start<F: Future>(future: F) -> F::Output {
let async_runtime = tokio::runtime::Runtime::new().unwrap();
async_runtime.block_on(future)
}
fn start(name: &str) -> Result<()> {
utils::switch_mount_namespace(1)?;
root_impl::setup();
magic::setup()?;
let cli = Args::parse();
match cli.command {
Commands::Watchdog => watchdog::entry().await?,
Commands::Daemon => zygiskd::entry()?,
};
match name.trim_start_matches("zygisk-") {
"wd" => async_start(watchdog::main())?,
"fuse" => fuse::main()?,
"cp" => zygiskd::main()?,
_ => println!("Available commands: wd, fuse, cp"),
}
Ok(())
}
#[tokio::main]
async fn main() {
fn main() {
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name);
if let Err(e) = start().await {
if let Err(e) = start(nice_name) {
log::error!("Crashed: {}\n{}", e, e.backtrace());
}
}

View File

@@ -1,7 +1,6 @@
use nix::libc::prctl;
use crate::constants::{MIN_KSU_VERSION, MAX_KSU_VERSION};
use crate::constants::{MAX_KSU_VERSION, MIN_KSU_VERSION};
const KERNEL_SU_OPTION: i32 = 0xdeadbeefu32 as i32;
const KERNEL_SU_OPTION: u32 = 0xdeadbeefu32;
const CMD_GET_VERSION: usize = 2;
const CMD_UID_GRANTED_ROOT: usize = 12;
@@ -15,23 +14,55 @@ pub enum Version {
pub fn get_kernel_su() -> Option<Version> {
let mut version = 0;
unsafe { prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &mut version as *mut i32, 0, 0) };
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_GET_VERSION,
&mut version as *mut i32,
0,
0,
)
};
match version {
0 => None,
MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported),
1..=MIN_KSU_VERSION => Some(Version::TooOld),
_ => Some(Version::Abnormal)
1..MIN_KSU_VERSION => Some(Version::TooOld),
_ => Some(Version::Abnormal),
}
}
pub fn uid_granted_root(uid: i32) -> bool {
let mut result: u32 = 0;
let mut granted = false;
unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &mut granted as *mut bool, 0) };
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_UID_GRANTED_ROOT,
uid,
&mut granted as *mut bool,
&mut result as *mut u32,
)
};
if result != KERNEL_SU_OPTION {
log::warn!("uid_granted_root failed");
}
granted
}
pub fn uid_should_umount(uid: i32) -> bool {
let mut result: u32 = 0;
let mut umount = false;
unsafe { prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &mut umount as *mut bool, 0) };
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_UID_SHOULD_UMOUNT,
uid,
&mut umount as *mut bool,
&mut result as *mut u32,
)
};
if result != KERNEL_SU_OPTION {
log::warn!("uid_granted_root failed");
}
umount
}

View File

@@ -24,23 +24,40 @@ pub fn get_magisk() -> Option<Version> {
}
pub fn uid_granted_root(uid: i32) -> bool {
let output: Option<String> = Command::new("magisk")
Command::new("magisk")
.arg("--sqlite")
.arg("select uid from policies where policy=2")
.arg(format!("select 1 from policies where uid={uid} and policy=2 limit 1"))
.stdout(Stdio::piped())
.spawn().ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| output.is_empty()) == Some(false)
}
pub fn uid_should_umount(uid: i32) -> bool {
let output = Command::new("pm")
.args(["list", "packages", "--uid", &uid.to_string()])
.stdout(Stdio::piped())
.spawn().ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok());
let lines = match &output {
Some(output) => output.lines(),
let line = match output {
Some(line) => line,
None => return false,
};
lines.into_iter().any(|line| {
line.trim().strip_prefix("uid=").and_then(|uid| uid.parse().ok()) == Some(uid)
})
}
pub fn uid_should_umount(uid: i32) -> bool {
// TODO: uid_should_umount
return false;
let pkg = line
.strip_prefix("package:")
.and_then(|line| line.split(' ').next());
let pkg = match pkg {
Some(pkg) => pkg,
None => return false,
};
Command::new("magisk")
.arg("--sqlite")
.arg(format!("select 1 from denylist where package_name=\"{pkg}\" limit 1"))
.stdout(Stdio::piped())
.spawn().ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| output.is_empty()) == Some(false)
}

View File

@@ -1,23 +1,12 @@
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::{fs, io::{Read, Write}, os::unix::net::UnixStream};
use std::ffi::{c_char, CStr, CString};
use std::os::fd::AsFd;
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 };
}
use std::process::Command;
use std::sync::OnceLock;
use rustix::net::{AddressFamily, bind_unix, listen, socket, SocketAddrUnix, SocketType};
use rustix::thread::gettid;
#[cfg(debug_assertions)]
#[macro_export]
@@ -31,12 +20,12 @@ macro_rules! debug_select {
}
pub struct LateInit<T> {
cell: OnceCell<T>,
cell: OnceLock<T>,
}
impl<T> LateInit<T> {
pub const fn new() -> Self {
LateInit { cell: OnceCell::new() }
LateInit { cell: OnceLock::new() }
}
pub fn init(&self, value: T) {
@@ -51,49 +40,56 @@ impl<T> std::ops::Deref for LateInit<T> {
}
}
pub fn random_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
}
pub fn set_socket_create_context(context: &str) -> Result<()> {
let path = "/proc/thread-self/attr/sockcreate";
match fs::write(path, context) {
Ok(_) => Ok(()),
Err(_) => {
let path = format!("/proc/self/task/{}/attr/sockcreate", gettid().as_raw());
let path = format!("/proc/self/task/{}/attr/sockcreate", gettid().as_raw_nonzero());
fs::write(path, context)?;
Ok(())
}
}
}
pub fn get_native_bridge() -> String {
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
pub fn chcon(path: &str, context: &str) -> Result<()> {
Command::new("chcon").arg(context).arg(path).status()?;
Ok(())
}
pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> {
let tag = std::ffi::CString::new(tag)?;
let message = std::ffi::CString::new(message)?;
let tag = CString::new(tag)?;
let message = CString::new(message)?;
unsafe {
__android_log_print(level as i32, tag.as_ptr(), message.as_ptr());
__android_log_print(level, tag.as_ptr(), message.as_ptr());
}
Ok(())
}
pub fn get_property(name: &str) -> Result<String> {
let name = std::ffi::CString::new(name)?;
let name = CString::new(name)?;
let mut buf = vec![0u8; 92];
unsafe {
let prop = unsafe {
__system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char);
}
Ok(String::from_utf8(buf)?)
CStr::from_bytes_until_nul(&buf)?
};
Ok(prop.to_string_lossy().to_string())
}
pub fn set_property(name: &str, value: &str) -> Result<()> {
Command::new("resetprop")
.arg(name)
.arg(value)
.spawn()?.wait()?;
let name = CString::new(name)?;
let value = CString::new(value)?;
unsafe {
__system_property_set(name.as_ptr(), value.as_ptr());
}
Ok(())
}
pub fn switch_mount_namespace(pid: i32) -> Result<()> {
let cwd = std::env::current_dir()?;
let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?;
rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?;
std::env::set_current_dir(cwd)?;
Ok(())
}
@@ -156,17 +152,18 @@ impl UnixStreamExt for UnixStream {
}
}
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
let addr = UnixAddr::new_abstract(name.as_bytes())?;
let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
nix::sys::socket::bind(socket, &addr)?;
nix::sys::socket::listen(socket, 2)?;
let listener = unsafe { UnixListener::from_raw_fd(socket) };
Ok(listener)
pub fn unix_listener_from_path(path: &str) -> Result<UnixListener> {
let _ = fs::remove_file(path);
let addr = SocketAddrUnix::new(path)?;
let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?;
bind_unix(&socket, &addr)?;
listen(&socket, 2)?;
chcon(path, "u:object_r:magisk_file:s0")?;
Ok(UnixListener::from(socket))
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32;
fn __system_property_set(name: *const c_char, value: *const c_char) -> u32;
}

View File

@@ -1,49 +1,53 @@
use crate::{constants, magic, root_impl, utils};
use crate::{constants, root_impl, utils};
use anyhow::{bail, Result};
use nix::unistd::{getgid, getuid, Pid};
use std::{fs, io};
use std::ffi::CString;
use std::fs;
use std::future::Future;
use std::io::{BufRead, Write};
use std::os::unix::net::UnixListener;
use std::io::{BufRead, BufReader, Write};
use std::pin::Pin;
use std::time::Duration;
use binder::IBinder;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use nix::errno::Errno;
use nix::libc;
use nix::sys::signal::{kill, Signal};
use log::{debug, error, info};
use rustix::mount::mount_bind;
use rustix::process::{getgid, getuid, kill_process, Pid, Signal};
use tokio::process::{Child, Command};
use crate::utils::LateInit;
static LOCK: LateInit<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub async fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
pub async fn main() -> Result<()> {
let result = run().await;
set_prop_hint(constants::STATUS_CRASHED)?;
result
}
async fn run() -> Result<()> {
info!("Start zygisk watchdog");
check_permission()?;
ensure_single_instance()?;
mount_prop().await?;
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().await;
set_prop_hint(constants::STATUS_CRASHED)?;
end
spawn_daemon().await?;
Ok(())
}
fn spawn_fuse() -> Result<()> {
Command::new("bin/zygisk-fuse").spawn()?;
Ok(())
}
fn check_permission() -> Result<()> {
log::info!("Check permission");
info!("Check permission");
let uid = getuid();
if uid.as_raw() != 0 {
if !uid.is_root() {
bail!("UID is not 0");
}
let gid = getgid();
if gid.as_raw() != 0 {
if !gid.is_root() {
bail!("GID is not 0");
}
@@ -56,32 +60,11 @@ fn check_permission() -> Result<()> {
Ok(())
}
fn ensure_single_instance() -> Result<()> {
log::info!("Ensure single instance");
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(())
}
async 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().await?;
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()
};
log::info!("Mount {module_prop}");
let module_prop_file = fs::File::open(&module_prop)?;
let module_prop_file = fs::File::open(constants::PATH_MODULE_PROP)?;
let mut section = 0;
let mut sections: [String; 2] = [String::new(), String::new()];
let lines = io::BufReader::new(module_prop_file).lines();
let lines = BufReader::new(module_prop_file).lines();
for line in lines {
let line = line?;
if line.starts_with("description=") {
@@ -96,26 +79,14 @@ async fn mount_prop() -> Result<()> {
}
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)?;
}
info!("Mount {} -> {}", constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP);
fs::File::create(constants::PATH_PROP_OVERLAY)?;
mount_bind(constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP)?;
Ok(())
}
fn set_prop_hint(hint: &str) -> Result<()> {
let mut file = fs::File::create(magic::PATH_TMP_PROP.as_str())?;
let mut file = fs::File::create(constants::PATH_PROP_OVERLAY)?;
file.write_all(PROP_SECTIONS[0].as_bytes())?;
file.write_all(b"[")?;
file.write_all(hint.as_bytes())?;
@@ -143,23 +114,14 @@ async fn spawn_daemon() -> Result<()> {
let mut lives = 5;
loop {
let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output=Result<()>>>>>::new();
let mut child_ids = vec![];
let daemon = Command::new(constants::PATH_CP_BIN).spawn()?;
let daemon_pid = daemon.id().unwrap();
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
async fn spawn_daemon(mut daemon: Child) -> Result<()> {
async fn daemon_holder(mut daemon: Child) -> Result<()> {
let result = daemon.wait().await?;
log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result);
Ok(())
}
if let Ok(it) = daemon32 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
}
if let Ok(it) = daemon64 {
child_ids.push(it.id().unwrap());
futures.push(Box::pin(spawn_daemon(it)));
bail!("Daemon process {} died: {}", daemon.id().unwrap(), result);
}
futures.push(Box::pin(daemon_holder(daemon)));
async fn binder_listener() -> Result<()> {
let mut binder = loop {
@@ -172,34 +134,29 @@ async fn spawn_daemon() -> Result<()> {
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
info!("System server ready");
loop {
if binder.ping_binder().is_err() { break; }
tokio::time::sleep(Duration::from_secs(1)).await;
}
log::error!("System server died");
Ok(())
bail!("System server died");
}
futures.push(Box::pin(binder_listener()));
if let Err(e) = futures.next().await.unwrap() {
log::error!("{}", e);
error!("{}", e);
}
for child in child_ids {
log::debug!("Killing child process {}", child);
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
}
debug!("Killing child process {}", daemon_pid);
let _ = kill_process(Pid::from_raw(daemon_pid as i32).unwrap(), Signal::Kill);
lives -= 1;
if lives == 0 {
bail!("Too many crashes, abort");
}
log::error!("Restarting zygote...");
utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?;
error!("Restarting zygote...");
utils::set_property(constants::PROP_CTL_RESTART, "zygote")?;
}
}

View File

@@ -1,21 +1,20 @@
use std::ffi::c_void;
use crate::constants::{DaemonSocketAction, ProcessFlags};
use crate::utils::UnixStreamExt;
use crate::{constants, dl, lp_select, magic, root_impl, utils};
use anyhow::{bail, Result};
use crate::{constants, dl, root_impl, utils};
use anyhow::Result;
use passfd::FdPassingExt;
use std::sync::Arc;
use std::thread;
use std::fs;
use std::os::fd::{IntoRawFd, OwnedFd};
use std::os::fd::OwnedFd;
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::path::PathBuf;
use nix::libc;
use nix::sys::stat::fstat;
use nix::unistd::close;
use rustix::fs::fstat;
use rustix::process::{set_parent_process_death_signal, Signal};
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
@@ -26,21 +25,20 @@ struct Module {
}
struct Context {
native_bridge: String,
modules: Vec<Module>,
}
pub fn entry() -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
pub fn main() -> Result<()> {
log::info!("Start zygisk companion");
set_parent_process_death_signal(Some(Signal::Kill))?;
let arch = get_arch()?;
let arch = utils::get_property("ro.product.cpu.abi")?;
log::debug!("Daemon architecture: {arch}");
log::info!("Load modules");
let modules = load_modules(arch)?;
let modules = load_modules(&arch)?;
let context = Context {
native_bridge: utils::get_native_bridge(),
modules,
};
let context = Arc::new(context);
@@ -62,17 +60,6 @@ pub fn entry() -> Result<()> {
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) {
@@ -132,10 +119,8 @@ fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
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}");
log::debug!("Daemon socket: {}", constants::PATH_CP_SOCKET);
let listener = utils::unix_listener_from_path(constants::PATH_CP_SOCKET)?;
Ok(listener)
}
@@ -171,9 +156,6 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
utils::log_raw(level as i32, &tag, &message)?;
}
}
DaemonSocketAction::ReadNativeBridge => {
stream.write_string(&context.native_bridge)?;
}
DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32;
let mut flags = ProcessFlags::empty();
@@ -209,16 +191,15 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
}
Some(companion) => {
stream.write_u8(1)?;
let fd = stream.into_raw_fd();
let st0 = fstat(fd)?;
unsafe { companion(fd); }
let st0 = fstat(&stream)?;
unsafe { companion(stream.as_raw_fd()); }
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if let Ok(st1) = fstat(fd) {
if st0.st_dev == st1.st_dev && st0.st_ino == st1.st_ino {
close(fd)?;
if let Ok(st1) = fstat(&stream) {
if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino {
std::mem::forget(stream);
}
}
}