Compare commits

...

10 Commits

Author SHA1 Message Date
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
22 changed files with 249 additions and 227 deletions

3
.gitmodules vendored
View File

@@ -4,3 +4,6 @@
[submodule "loader/src/external/parallel-hashmap"]
path = loader/src/external/parallel-hashmap
url = https://github.com/greg7mdp/parallel-hashmap
[submodule "zygiskd/src/external/binder_rs"]
path = zygiskd/src/external/binder_rs
url = https://github.com/Kernel-SU/binder_rs

View File

@@ -2,31 +2,14 @@
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: 10575
+ Minimal ksud version: 10200
+ Minimal ksud version: 10616
+ Full SELinux patch support (If non-gki kernel)
## Compatibility
Should work with everything except those rely on Magisk internal behaviors.
## Development road map
- [x] [Inject] Basic Zygisk loader
- [x] [Inject] Stabilize injector
- [x] [Inject] Unload
- [x] [Daemon] Linker namespace
- [x] [Daemon] Separate zygiskd process
- [x] [Daemon] Handle 64 bit only devices
- [ ] [Daemon] Handle zygote death
## Running on Magisk
It is possible to run Zygisksu on Magisk with a few steps:
1. `mkdir /data/adb/ksu`
2. `ln -s /data/adb/modules /data/adb/ksu/`

View File

@@ -31,14 +31,14 @@ val gitCommitHash = "git rev-parse --verify --short HEAD".execute()
val moduleId by extra("zygisksu")
val moduleName by extra("Zygisk on KernelSU")
val verName by extra("v4-0.2.0")
val verName by extra("v4-0.4.1")
val verCode by extra(gitCommitCount)
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

@@ -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-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@@ -18,7 +18,9 @@ static void zygisk_cleanup_wait() {
extern "C" [[gnu::visibility("default")]]
void entry(void *handle) {
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
self_handle = handle;
LOGD("Load injector successfully");

View File

@@ -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
@@ -225,58 +251,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
// -----------------------------------------------------------------
@@ -312,7 +286,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)
@@ -501,11 +475,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;
@@ -739,35 +713,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);
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() {
@@ -776,17 +731,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

@@ -43,9 +43,9 @@ void Constructor() {
std::string native_bridge;
do {
if (!zygiskd::PingHeartbeat()) break;
#ifdef NDEBUG
logging::setfd(zygiskd::RequestLogcatFd());
#endif
LOGI("Read native bridge");
native_bridge = zygiskd::ReadNativeBridge();

View File

@@ -106,7 +106,7 @@ androidComponents.onVariants { variant ->
val installMagiskTask = task<Exec>("installMagisk$variantCapped") {
group = "module"
dependsOn(pushTask)
commandLine("adb", "shell", "su", "-c", "KSU=true magisk --install-module /data/local/tmp/$zipFileName")
commandLine("adb", "shell", "su", "-c", "magisk --install-module /data/local/tmp/$zipFileName")
}
task<Exec>("installKsuAndReboot$variantCapped") {

View File

@@ -1,31 +1,39 @@
# shellcheck disable=SC2034
SKIPUNZIP=1
if [ $BOOTMODE ] && [ "$KSU" == "true" ]; then
DEBUG=@DEBUG@
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 10575 ]; then
ui_print "*********************************************************"
ui_print "! KernelSU version is too old!"
ui_print "! Please update KernelSU to latest version"
abort "*********************************************************"
fi
elif [ "$BOOTMODE" ] && [ "$MAGISK_VER_CODE" ]; then
ui_print "- Installing from Magisk app"
if [ "$MAGISK_VER_CODE" -lt 25000 ]; 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"
# check KernelSU
ui_print "- KernelSU version: $KSU_VER ($KSU_VER_CODE)"
if [ "$KSU_VER_CODE" -lt 10200 ]; then
ui_print "*********************************************************"
ui_print "! KernelSU version is too old!"
ui_print "! Please update KernelSU to latest version"
abort "*********************************************************"
fi
# 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
@@ -50,20 +58,24 @@ extract "$ZIPFILE" 'customize.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'verify.sh' "$TMPDIR/.vunzip"
extract "$ZIPFILE" 'sepolicy.rule' "$TMPDIR"
ui_print "- Checking SELinux patches"
if ! /data/adb/ksud sepolicy check "$TMPDIR/sepolicy.rule"; then
ui_print "*********************************************************"
ui_print "! Unable to apply SELinux patches!"
ui_print "! Your kernel may not support SELinux patch fully"
abort "*********************************************************"
if [ "$KSU" ]; then
ui_print "- Checking SELinux patches"
if ! check_sepolicy "$TMPDIR/sepolicy.rule"; then
ui_print "*********************************************************"
ui_print "! Unable to apply SELinux patches!"
ui_print "! Your kernel may not support SELinux patch fully"
abort "*********************************************************"
fi
fi
ui_print "- Extracting module files"
extract "$ZIPFILE" 'daemon.sh' "$MODPATH"
extract "$ZIPFILE" 'module.prop' "$MODPATH"
extract "$ZIPFILE" 'post-fs-data.sh' "$MODPATH"
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
extract "$ZIPFILE" 'service.sh' "$MODPATH"
if [ "$KSU" ]; then
extract "$ZIPFILE" 'sepolicy.rule' "$MODPATH"
fi
HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true
HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true
@@ -111,17 +123,19 @@ else
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"
if [ $DEBUG = false ]; then
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
fi
ui_print "- Setting permissions"

View File

@@ -1,7 +1,6 @@
#!/system/bin/sh
DEBUG=@DEBUG@
MODDIR=${0%/*}
# shellcheck disable=SC2155
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
@@ -9,4 +8,4 @@ export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
log -p i -t "zygisksu" "Start watchdog"
resetprop ro.dalvik.vm.native.bridge libzygiskloader.so
exec "$MODDIR/bin/zygiskwd" "watchdog" >/dev/null 2>&1
exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1

View File

@@ -1,5 +1,24 @@
#!/system/bin/sh
MODDIR=${0%/*}
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(getprop ro.dalvik.vm.native.bridge)
unshare -m sh -c "$MODDIR/daemon.sh $@&"
if [ $(which magisk) ] && [ ".." -ef "/data/adb/modules" ]; 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
unshare -m sh -c "./daemon.sh $@&"

View File

@@ -1 +1,21 @@
#!/system/bin/sh
MODDIR=${0%/*}
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
cd "$MODDIR"
if [ $(which magisk) ] && [ ".." -ef "/data/adb/modules" ]; 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

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.1"
id("com.android.application") version "7.4.1"
}
}

View File

@@ -10,6 +10,7 @@ android_logger = "0.12.0"
anyhow = { version = "1.0.68", features = ["backtrace"] }
clap = { version = "4.1.4", features = ["derive"] }
const_format = "0.2.5"
lazy_static = "1.4.0"
log = "0.4.17"
memfd = "0.6.2"
nix = "0.26.2"
@@ -17,6 +18,8 @@ num_enum = "0.5.9"
passfd = "0.1.5"
rand = "0.8.5"
binder = { path = "src/external/binder_rs/binder" }
[profile.release]
strip = true
opt-level = "z"

View File

@@ -20,6 +20,7 @@ cargo {
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)
}

View File

@@ -23,14 +23,14 @@ macro_rules! lp_select {
}
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
pub const PROP_SVC_ZYGOTE: &str = "init.svc.zygote";
pub const ZYGISK_LOADER: &str = "libzygiskloader.so";
pub const SOCKET_PLACEHOLDER: &str = "socket_placeholder";
pub const PATH_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 PATH_MODULE_DIR: &str = "..";
pub const PATH_ZYGISKD32: &str = "bin/zygiskd32";
pub const PATH_ZYGISKD64: &str = "bin/zygiskd64";
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]

View File

@@ -43,7 +43,7 @@ fn main() {
let cli = Args::parse();
let result = match cli.command {
Commands::Watchdog => watchdog::entry(),
Commands::Daemon => zygiskd::entry(lp_select!(false, true)),
Commands::Daemon => zygiskd::entry(),
Commands::Companion { fd } => companion::entry(fd),
};

View File

@@ -1,4 +1,3 @@
use crate::constants;
use anyhow::Result;
use nix::unistd::gettid;
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
@@ -27,10 +26,10 @@ pub fn get_native_bridge() -> String {
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
}
pub fn restore_native_bridge() -> Result<()> {
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(())
}

View File

@@ -1,13 +1,15 @@
use crate::constants;
use crate::{constants, 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::os::unix::net::UnixListener;
use std::time::Duration;
use binder::IBinder;
use nix::sys::signal::{kill, Signal};
static mut LOCK_FILE: Option<fs::File> = None;
static mut LOCK: Option<UnixListener> = None;
pub fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
@@ -30,7 +32,6 @@ 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}");
}
@@ -40,38 +41,65 @@ fn check_permission() -> 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 = String::from("zygiskwd") + constants::SOCKET_PLACEHOLDER;
match utils::abstract_namespace_socket(&name) {
Ok(socket) => unsafe { LOCK = Some(socket) },
Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?")
}
Ok(())
}
fn spawn_daemon() -> Result<()> {
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let (sender, receiver) = mpsc::channel();
let spawn = |mut daemon: Child| {
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
sender.send(()).unwrap();
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let _ = receiver.recv();
bail!("Daemon process died");
let mut lives = 5;
loop {
let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn();
let mut child_ids = vec![];
let (sender, receiver) = mpsc::channel();
let mut spawn = |mut daemon: Child| {
child_ids.push(daemon.id());
let sender = sender.clone();
thread::spawn(move || {
let result = daemon.wait().unwrap();
log::error!("Daemon process {} died: {}", daemon.id(), result);
drop(daemon);
let _ = sender.send(());
});
};
if let Ok(it) = daemon32 { spawn(it) }
if let Ok(it) = daemon64 { spawn(it) }
let mut binder = loop {
if receiver.try_recv().is_ok() {
bail!("Daemon died before system server ready");
}
match binder::get_service("activity") {
Some(binder) => break binder,
None => {
log::trace!("System server not ready, wait for 1s...");
thread::sleep(Duration::from_secs(1));
}
};
};
log::info!("System server ready, restore native bridge");
utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?;
loop {
if receiver.try_recv().is_ok() || binder.ping_binder().is_err() { break; }
thread::sleep(Duration::from_secs(1))
}
for child in child_ids {
let _ = kill(Pid::from_raw(child as i32), Signal::SIGKILL);
}
lives -= 1;
if lives == 0 {
bail!("Too many crashes, abort");
}
log::error!("Restarting zygote...");
utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?;
utils::set_property(constants::PROP_SVC_ZYGOTE, "restart")?;
thread::sleep(Duration::from_secs(2));
}
}

View File

@@ -1,6 +1,6 @@
use crate::constants::DaemonSocketAction;
use crate::utils::{restore_native_bridge, UnixStreamExt};
use crate::{constants, utils};
use crate::utils::UnixStreamExt;
use crate::{constants, lp_select, utils};
use anyhow::{bail, Result};
use memfd::Memfd;
use nix::{
@@ -31,10 +31,10 @@ struct Context {
modules: Vec<Module>,
}
pub fn entry(is64: bool) -> Result<()> {
pub fn entry() -> Result<()> {
unsafe { libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGKILL) };
let arch = get_arch(is64)?;
let arch = get_arch()?;
log::debug!("Daemon architecture: {arch}");
log::info!("Load modules");
@@ -47,7 +47,7 @@ pub fn entry(is64: bool) -> Result<()> {
let context = Arc::new(context);
log::info!("Create socket");
let listener = create_daemon_socket(is64)?;
let listener = create_daemon_socket()?;
log::info!("Handle zygote connections");
for stream in listener.incoming() {
@@ -63,23 +63,21 @@ pub fn entry(is64: bool) -> Result<()> {
Ok(())
}
fn get_arch(is64: bool) -> Result<&'static str> {
fn get_arch() -> 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),
if system_arch.contains("arm") {
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"))
}
if system_arch.contains("x86") {
return Ok(lp_select!("x86", "x86_64"))
}
bail!("Unsupported system architecture: {}", system_arch);
}
fn load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_KSU_MODULE_DIR) {
let dir = match fs::read_dir(constants::PATH_MODULE_DIR) {
Ok(dir) => dir,
Err(e) => {
log::warn!("Failed reading modules directory: {}", e);
@@ -137,10 +135,10 @@ fn create_memfd(name: &str, so_path: &PathBuf) -> Result<Memfd> {
Ok(memfd)
}
fn create_daemon_socket(is64: bool) -> Result<UnixListener> {
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let suffix = if is64 { "zygiskd64" } else { "zygiskd32" };
let name = String::from(suffix) + constants::SOCKET_PLACEHOLDER;
let prefix = lp_select!("zygiskd32", "zygiskd64");
let name = String::from(prefix) + constants::SOCKET_PLACEHOLDER;
let listener = utils::abstract_namespace_socket(&name)?;
log::debug!("Daemon socket: {name}");
Ok(listener)
@@ -175,7 +173,7 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
log::trace!("New daemon action {:?}", action);
match action {
DaemonSocketAction::PingHeartbeat => {
restore_native_bridge()?;
// Do nothing
}
DaemonSocketAction::RequestLogcatFd => {
loop {
@@ -223,7 +221,7 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_KSU_MODULE_DIR, module.name);
let dir = format!("{}/{}", constants::PATH_MODULE_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}