diff --git a/zygiskd/.cargo/config.toml b/zygiskd/.cargo/config.toml deleted file mode 100644 index 10816b1..0000000 --- a/zygiskd/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build] -target-dir = "build/intermediates/rust" -target = "aarch64-linux-android" diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml deleted file mode 100644 index bebacf3..0000000 --- a/zygiskd/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "zygiskd" -authors = ["Nullptr"] -version = "1.0.0" -edition = "2021" -rust-version = "1.69" - -[dependencies] -csv = "1.3.0" -serde = { version = "1.0.130", features = ["derive"] } -android_logger = "0.13" -anyhow = { version = "1.0", features = ["backtrace"] } -bitflags = { version = "2.3" } -const_format = "0.2" -futures = "0.3" -konst = "0.3" -lazy_static = "1.4" -libc = "0.2" -log = "0.4" -memfd = "0.6" -num_enum = "0.5" -passfd = "0.1" -proc-maps = "0.3" - -rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] } -tokio = { version = "1.28", features = ["full"] } - -[profile.dev] -strip = false -panic = "abort" - -[profile.release] -strip = false -debug = true -panic = "abort" -opt-level = "z" -lto = true diff --git a/zygiskd/build.gradle.kts b/zygiskd/build.gradle.kts deleted file mode 100644 index 536393c..0000000 --- a/zygiskd/build.gradle.kts +++ /dev/null @@ -1,70 +0,0 @@ -plugins { - alias(libs.plugins.agp.lib) - alias(libs.plugins.rust.android) -} - -val minAPatchVersion: Int by rootProject.extra -val minKsuVersion: Int by rootProject.extra -val maxKsuVersion: Int by rootProject.extra -val minMagiskVersion: Int by rootProject.extra -val verCode: Int by rootProject.extra -val verName: String by rootProject.extra -val commitHash: String by rootProject.extra - -android.buildFeatures { - androidResources = false - buildConfig = false -} - -cargo { - module = "." - pythonCommand = "python3" - libname = "zygiskd" - targetIncludes = arrayOf("zygiskd") - targets = listOf("arm64", "arm", "x86", "x86_64") - 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("MIN_APATCH_VERSION", minAPatchVersion) - spec.environment("MIN_KSU_VERSION", minKsuVersion) - spec.environment("MAX_KSU_VERSION", maxKsuVersion) - spec.environment("MIN_MAGISK_VERSION", minMagiskVersion) - spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile") - } -} - -afterEvaluate { - task("buildAndStrip") { - dependsOn(":zygiskd:cargoBuild") - val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") } - doLast { - val dir = File(buildDir, "rustJniLibs/android") - val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first() - val binDir = File(prebuilt, "bin") - val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}") - symbolDir.mkdirs() - val suffix = if (prebuilt.name.contains("windows")) ".exe" else "" - val strip = File(binDir, "llvm-strip$suffix") - val objcopy = File(binDir, "llvm-objcopy$suffix") - dir.listFiles()!!.forEach { - if (!it.isDirectory) return@forEach - val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug") - symbolPath.parentFile.mkdirs() - exec { - workingDir = it - commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath) - } - exec { - workingDir = it - commandLine(strip, "--strip-all", "zygiskd") - } - exec { - workingDir = it - commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd") - } - } - } - } -} diff --git a/zygiskd/build.gradlew.kts b/zygiskd/build.gradlew.kts new file mode 100644 index 0000000..697ed96 --- /dev/null +++ b/zygiskd/build.gradlew.kts @@ -0,0 +1,85 @@ +import java.nio.file.Paths +import org.gradle.internal.os.OperatingSystem + +plugins { + alias(libs.plugins.agp.lib) +} + +val verCode: Int by rootProject.extra +val verName: String by rootProject.extra +val commitHash: String by rootProject.extra + +fun Project.findInPath(executable: String, property: String): String? { + val pathEnv = System.getenv("PATH") + return pathEnv.split(File.pathSeparator).map { folder -> + Paths.get("${folder}${File.separator}${executable}${if (OperatingSystem.current().isWindows) ".exe" else ""}") + .toFile() + }.firstOrNull { path -> + path.exists() + }?.absolutePath ?: properties.getOrDefault(property, null) as? String? +} + +val ccachePath by lazy { + project.findInPath("ccache", "ccache.path")?.also { + println("loader: Use ccache: $it") + } +} + +val defaultCFlags = arrayOf( + "-Wall", "-Wextra", + "-fno-rtti", "-fno-exceptions", + "-fno-stack-protector", "-fomit-frame-pointer", + "-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__", + "-O0", "-g" +) + +val releaseFlags = arrayOf( + "-Oz", "-flto", + "-Wno-unused", "-Wno-unused-parameter", + "-fvisibility=hidden", "-fvisibility-inlines-hidden", + "-fno-unwind-tables", "-fno-asynchronous-unwind-tables", + "-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all" +) + +android { + buildFeatures { + androidResources = false + buildConfig = false + prefab = true + } + + externalNativeBuild.cmake { + path("src/CMakeLists.txt") + } + + defaultConfig { + externalNativeBuild.cmake { + arguments += "-DANDROID_STL=none" + arguments += "-DLSPLT_STANDALONE=ON" + cFlags("-std=c18", *defaultCFlags) + cppFlags("-std=c++20", *defaultCFlags) + ccachePath?.let { + arguments += "-DNDK_CCACHE=$it" + } + } + } + + buildTypes { + debug { + externalNativeBuild.cmake { + arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug" + } + } + release { + externalNativeBuild.cmake { + cFlags += releaseFlags + cppFlags += releaseFlags + arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release" + } + } + } +} + +dependencies { + implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") +} diff --git a/zygiskd/src/.gitignore b/zygiskd/src/.gitignore new file mode 100644 index 0000000..8f961c3 --- /dev/null +++ b/zygiskd/src/.gitignore @@ -0,0 +1,2 @@ +zygiskd64 +zygiskd32 \ No newline at end of file diff --git a/zygiskd/src/CMakeLists.txt b/zygiskd/src/CMakeLists.txt new file mode 100644 index 0000000..b3c9ec5 --- /dev/null +++ b/zygiskd/src/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.22.1) +project("zygiskd") + +find_package(cxx REQUIRED CONFIG) + +add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\") + +aux_source_directory(common COMMON_SRC_LIST) +add_library(common STATIC ${COMMON_SRC_LIST}) +target_include_directories(common PRIVATE include) +target_link_libraries(log) + +aux_source_directory(ptracer PTRACER_SRC_LIST) +add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST}) +target_include_directories(libzygisk_ptrace.so PRIVATE include) +target_link_libraries(libzygisk_ptrace.so cxx::cxx log common) diff --git a/zygiskd/src/LICENSE b/zygiskd/src/LICENSE new file mode 100644 index 0000000..bf86410 --- /dev/null +++ b/zygiskd/src/LICENSE @@ -0,0 +1,24 @@ +BSD 2-Clause License + +Copyright (c) 2024, The PerformanC Organization + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/zygiskd/src/Makefile b/zygiskd/src/Makefile new file mode 100644 index 0000000..da67f95 --- /dev/null +++ b/zygiskd/src/Makefile @@ -0,0 +1,33 @@ +CC := ~/Android/Sdk/ndk/27.0.11902837/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang + +# FILES = root_impl/common.c \ +# root_impl/kernelsu.c \ +# companion.c \ +# dl.c \ +# main.c \ +# utils.c \ +# zygiskd.c + +FILES = root_impl/*.c \ + *.c + +CFLAGS = -D_GNU_SOURCE -std=c99 -Wpedantic -Wall -Wextra -Werror -Wformat -Wuninitialized -Wshadow -Wno-zero-length-array -Wno-fixed-enum-extension -Iroot_impl -llog + +all: CFLAGS += -DDEBUG=0 -O3 -flto=thin -Wl,--strip-all +all: + $(CC) $(CFLAGS) $(FILES) -o zygiskd64 + +debug: CFLAGS += -DDEBUG=1 -g -O0 +debug: + $(CC) $(CFLAGS) $(FILES) -o zygiskd64 + +32bit: CFLAGS += -m32 -DDEBUG=0 -O3 -flto=thin -Wl,--strip-all +32bit: + $(CC) $(CFLAGS) $(FILES) -o zygiskd32 + +32bit-debug: CFLAGS += -m32 -DDEBUG=1 -g -O0 +32bit-debug: + $(CC) $(CFLAGS) $(FILES) -o zygiskd32 + +clean: + rm -f zygiskd \ No newline at end of file diff --git a/zygiskd/src/companion.c b/zygiskd/src/companion.c new file mode 100644 index 0000000..b3b8442 --- /dev/null +++ b/zygiskd/src/companion.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "companion.h" +#include "dl.h" +#include "utils.h" + +typedef void (*ZygiskCompanionEntryFn)(int); + +ZygiskCompanionEntryFn load_module(int fd) { + char path[PATH_MAX]; + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + + void *handle = android_dlopen(path, RTLD_NOW); + void *entry = dlsym(handle, "zygisk_companion_entry"); + if (entry == NULL) return NULL; + + return (ZygiskCompanionEntryFn)entry; +} + +void *ExecuteNew(void *arg) { + int fd = *((int *)arg); + + struct stat st0; + if (fstat(fd, &st0) == -1) { + LOGE("Failed to stat client fd\n"); + + free(arg); + + exit(0); + } + entry(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. + struct stat st1; + if (fstat(fd, &st1) == -1) { + LOGE("Failed to stat client fd\n"); + + free(arg); + + exit(0); + } + + if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino) { + close(fd); + } + + free(arg); + + return NULL; +} + + +void entry(int fd) { + LOGI("companion entry fd: |%d|\n", fd); + + char name[256 + 1]; + + /* INFO: Getting stuck here */ + ssize_t ret = read_string(fd, name, sizeof(name) - 1); + if (ret == -1) return; + + name[ret] = '\0'; + + LOGI("Companion process requested for `%s`\n", name); + + int library_fd; + recv_fd(fd, &library_fd); + + LOGI("Library fd: %d\n", library_fd); + + ZygiskCompanionEntryFn entry = load_module(library_fd); + + LOGI("Library loaded\n"); + + close(library_fd); + + LOGI("Library closed\n"); + + if (entry == NULL) { + LOGI("No companion entry for: %s\n", name); + + write(fd, (void *)0, 1); + + return; + } + + LOGI("Companion process created for: %s\n", name); + + uint8_t response = 1; + write(fd, &response, sizeof(response)); + + while (1) { + int client_fd; + recv_fd(fd, &client_fd); + + LOGI("New companion request from module \"%s\" with fd \"%d\"\n", name, client_fd); + + write(fd, &response, sizeof(response)); + + int *client_fd_ptr = malloc(sizeof(int)); + *client_fd_ptr = client_fd; + + LOGI("Creating new thread for companion request\n"); + + pthread_t thread; + pthread_create(&thread, NULL, ExecuteNew, (void *)client_fd_ptr); + pthread_detach(thread); + } +} diff --git a/zygiskd/src/companion.h b/zygiskd/src/companion.h new file mode 100644 index 0000000..1b931e8 --- /dev/null +++ b/zygiskd/src/companion.h @@ -0,0 +1,6 @@ +#ifndef COMPANION_H +#define COMPANION_H + +void entry(int fd); + +#endif /* COMPANION_H */ diff --git a/zygiskd/src/companion.rs b/zygiskd/src/companion.rs deleted file mode 100644 index 8d158a5..0000000 --- a/zygiskd/src/companion.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::dl; -use crate::utils::{check_unix_socket, UnixStreamExt}; -use anyhow::Result; -use passfd::FdPassingExt; -use rustix::fs::fstat; -use std::ffi::c_void; -use std::os::fd::{AsRawFd, FromRawFd, RawFd}; -use std::os::unix::net::UnixStream; -use std::thread; - -type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); - -pub fn entry(fd: i32) { - log::info!("companion entry fd={}", fd); - let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; - let name = stream.read_string().expect("read name"); - let library = stream.recv_fd().expect("receive library fd"); - let entry = load_module(library).expect("load module"); - unsafe { libc::close(library) }; - - let entry = match entry { - Some(entry) => { - log::debug!("Companion process created for `{name}`"); - stream.write_u8(1).expect("reply 1"); - entry - } - None => { - log::debug!("No companion entry for `{name}`"); - stream.write_u8(0).expect("reply 0"); - std::process::exit(0); - } - }; - - loop { - if !check_unix_socket(&stream, true) { - log::info!("Something bad happened in zygiskd, terminate companion"); - std::process::exit(0); - } - let fd = stream.recv_fd().expect("recv fd"); - log::trace!("New companion request from module `{name}` fd=`{fd}`"); - let mut stream = unsafe { UnixStream::from_raw_fd(fd) }; - stream.write_u8(1).expect("reply success"); - thread::spawn(move || { - let st0 = fstat(&stream).expect("failed to stat stream"); - unsafe { - entry(stream.as_raw_fd()); - } - // Only close client if it is the same file so we don't - // accidentally close a re-used file descriptor. - // This check is required because the module companion - // handler could've closed the file descriptor already. - if let Ok(st1) = fstat(&stream) { - if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino { - std::mem::forget(stream); - } - } - }); - } -} - -fn load_module(fd: RawFd) -> Result> { - 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)) - } -} diff --git a/zygiskd/src/constants.h b/zygiskd/src/constants.h new file mode 100644 index 0000000..94a7f5a --- /dev/null +++ b/zygiskd/src/constants.h @@ -0,0 +1,70 @@ +#ifndef CONSTANTS_H +#define CONSTANTS_H + +#include + +#define bool _Bool +#define true 1 +#define false 0 + +// #define MIN_APATCH_VERSION (atoi(getenv("MIN_APATCH_VERSION"))) +// #define MIN_KSU_VERSION (atoi(getenv("MIN_KSU_VERSION"))) +// #define MAX_KSU_VERSION (atoi(getenv("MAX_KSU_VERSION"))) +// #define MIN_MAGISK_VERSION (atoi(getenv("MIN_MAGISK_VERSION"))) +// #define ZKSU_VERSION (getenv("ZKSU_VERSION")) + +#define MIN_APATCH_VERSION 0 +// val minKsudVersion by extra(11425) +// val maxKsuVersion by extra(20000) +#define MIN_KSU_VERSION 11425 +#define MAX_KSU_VERSION 20000 +#define MIN_MAGISK_VERSION 0 +#define ZKSU_VERSION "1.0.0" + +#if DEBUG == false + #define MAX_LOG_LEVEL ANDROID_LOG_VERBOSE +#else + #define MAX_LOG_LEVEL ANDROID_LOG_INFO +#endif + +#if (defined(__LP64__) || defined(_LP64)) + #define lp_select(a, b) b +#else + #define lp_select(a, b) a +#endif + +#define ZYGOTE_INJECTED lp_select(5, 4) +#define DAEMON_SET_INFO lp_select(7, 6) +#define DAEMON_SET_ERROR_INFO lp_select(9, 8) +#define SYSTEM_SERVER_STARTED 10 + +enum DaemonSocketAction { + PingHeartbeat, + RequestLogcatFd, + GetProcessFlags, + GetInfo, + ReadModules, + RequestCompanionSocket, + GetModuleDir, + ZygoteRestart, + SystemServerStarted +}; + +enum ProcessFlags: uint32_t { + PROCESS_GRANTED_ROOT = (1u << 0), + PROCESS_ON_DENYLIST = (1u << 1), + PROCESS_IS_MANAGER = (1u << 28), + PROCESS_ROOT_IS_APATCH = (1u << 27), + PROCESS_ROOT_IS_KSU = (1u << 29), + PROCESS_ROOT_IS_MAGISK = (1u << 30), + PROCESS_IS_SYS_UI = (1u << 31), + PROCESS_IS_SYSUI = (1u << 31) +}; + +enum RootImplState { + Supported, + TooOld, + Abnormal +}; + +#endif /* CONSTANTS_H */ diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs deleted file mode 100644 index 4d8a93f..0000000 --- a/zygiskd/src/constants.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::lp_select; -use bitflags::bitflags; -use konst::primitive::parse_i32; -use konst::unwrap_ctx; -use log::LevelFilter; -use num_enum::TryFromPrimitive; - -pub const MIN_APATCH_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_APATCH_VERSION"))); -pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION"))); -pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION"))); -pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION"))); -pub const ZKSU_VERSION: &str = env!("ZKSU_VERSION"); - -#[cfg(debug_assertions)] -pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; -#[cfg(not(debug_assertions))] -pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; - -pub const PATH_MODULES_DIR: &str = ".."; -pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4); -pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6); -pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8); -pub const SYSTEM_SERVER_STARTED: i32 = 10; - -#[derive(Debug, Eq, PartialEq, TryFromPrimitive)] -#[repr(u8)] -pub enum DaemonSocketAction { - PingHeartbeat, - RequestLogcatFd, - GetProcessFlags, - GetInfo, - ReadModules, - RequestCompanionSocket, - GetModuleDir, - ZygoteRestart, - SystemServerStarted, -} - -// Zygisk process flags -bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct ProcessFlags: u32 { - const PROCESS_GRANTED_ROOT = 1 << 0; - const PROCESS_ON_DENYLIST = 1 << 1; - const PROCESS_IS_MANAGER = 1 << 28; - const PROCESS_ROOT_IS_APATCH = 1 << 27; - const PROCESS_ROOT_IS_KSU = 1 << 29; - const PROCESS_ROOT_IS_MAGISK = 1 << 30; - const PROCESS_IS_SYSUI = 1 << 31; - } -} diff --git a/zygiskd/src/dl.c b/zygiskd/src/dl.c new file mode 100644 index 0000000..eddcf1f --- /dev/null +++ b/zygiskd/src/dl.c @@ -0,0 +1,58 @@ +#include + +#include +#include +#include + +#define ANDROID_NAMESPACE_TYPE_SHARED 0x2 +#define ANDROID_DLEXT_USE_NAMESPACE 0x200 + +struct AndroidNamespace { + u_int8_t _unused[0]; +}; + +struct AndroidDlextinfo { + u_int64_t flags; + void *reserved_addr; + size_t reserved_size; + int relro_fd; + int library_fd; + off64_t library_fd_offset; + struct AndroidNamespace *library_namespace; +}; + +void *android_dlopen_ext(const char *filename, int flags, const struct AndroidDlextinfo *extinfo); + +void *android_dlopen(char *path, u_int32_t flags) { + char *dir = dirname(path); + struct AndroidDlextinfo info = { + .flags = 0, + .reserved_addr = NULL, + .reserved_size = 0, + .relro_fd = 0, + .library_fd = 0, + .library_fd_offset = 0, + .library_namespace = NULL, + }; + + void *android_create_namespace_fn = dlsym(RTLD_DEFAULT, "__loader_android_create_namespace"); + + if (android_create_namespace_fn != NULL) { + void *ns = ((void *(*)(const char *, const char *, const char *, u_int32_t, void *, void *, void *))android_create_namespace_fn)( + path, + dir, + NULL, + ANDROID_NAMESPACE_TYPE_SHARED, + NULL, + NULL, + (void *)&android_dlopen + ); + + if (ns != NULL) { + info.flags = ANDROID_DLEXT_USE_NAMESPACE; + info.library_namespace = ns; + } + } + + return android_dlopen_ext(path, flags, &info); +} diff --git a/zygiskd/src/dl.h b/zygiskd/src/dl.h new file mode 100644 index 0000000..32cd248 --- /dev/null +++ b/zygiskd/src/dl.h @@ -0,0 +1,6 @@ +#ifndef DL_H +#define DL_H + +void *android_dlopen(char *path, u_int32_t flags); + +#endif /* DL_H */ diff --git a/zygiskd/src/dl.rs b/zygiskd/src/dl.rs deleted file mode 100644 index a401fcd..0000000 --- a/zygiskd/src/dl.rs +++ /dev/null @@ -1,85 +0,0 @@ -use anyhow::{bail, Result}; -use std::ffi::{c_char, c_void}; - -pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2; -pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200; - -#[repr(C)] -#[derive(Debug, Copy, Clone)] -pub struct AndroidNamespace { - _unused: [u8; 0], -} - -#[repr(C)] -pub 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" { - pub 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) -} diff --git a/zygiskd/src/main.c b/zygiskd/src/main.c new file mode 100644 index 0000000..a86aa31 --- /dev/null +++ b/zygiskd/src/main.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +#include + +#include "root_impl/common.h" +#include "companion.h" +#include "zygiskd.h" + +#include "utils.h" + +int __android_log_print(int prio, const char *tag, const char *fmt, ...); + +int main(int argc, char *argv[]) { + errno = 0; + /* Initialize android logger */ + LOGI("Initializing zygiskd\n"); + + LOGI("Argc: %d\n", argc); + for (int i = 0; i < argc; i++) { + LOGI("argv[%d] = %s\n", i, argv[i]); + } + + if (argc > 1) { + if (strcmp(argv[1], "companion") == 0) { + if (argc < 3) { + LOGI("Usage: zygiskd companion \n"); + + return 1; + } + + int fd = atoi(argv[2]); + entry(fd); + + return 0; + } + + else if (strcmp(argv[1], "version") == 0) { + LOGI("ReZygisk Daemon %s\n", ZKSU_VERSION); + + return 0; + } + + else if (strcmp(argv[1], "root") == 0) { + root_impls_setup(); + enum RootImpl impl = get_impl(); + + switch (impl) { + case None: { + LOGI("No root implementation found.\n"); + + return 1; + } + + case Multiple: { + LOGI("Multiple root implementations found.\n"); + + return 1; + } + + case KernelSU: { + LOGI("KernelSU root implementation found.\n"); + + return 0; + } + } + + + return 0; + } + + else { + LOGI("Usage: zygiskd [companion|version|root]\n"); + + return 0; + } + } + + switch_mount_namespace((pid_t)1); + root_impls_setup(); + zygiskd_start(); + + return 0; +} diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs deleted file mode 100644 index df042e7..0000000 --- a/zygiskd/src/main.rs +++ /dev/null @@ -1,45 +0,0 @@ -mod companion; -mod constants; -mod dl; -mod root_impl; -mod utils; -mod zygiskd; - -use crate::constants::ZKSU_VERSION; - -fn init_android_logger(tag: &str) { - android_logger::init_once( - android_logger::Config::default() - .with_max_level(constants::MAX_LOG_LEVEL) - .with_tag(tag), - ); -} - -fn start() { - let args: Vec = std::env::args().collect(); - if args.len() == 3 && args[1] == "companion" { - let fd: i32 = args[2].parse().unwrap(); - companion::entry(fd); - return; - } else if args.len() == 2 && args[1] == "version" { - println!("ReZygisk daemon {}", ZKSU_VERSION); - return; - } else if args.len() == 2 && args[1] == "root" { - root_impl::setup(); - println!("root impl: {:?}", root_impl::get_impl()); - return; - } - - utils::switch_mount_namespace(1).expect("switch mnt ns"); - root_impl::setup(); - log::info!("current root impl: {:?}", root_impl::get_impl()); - zygiskd::main().expect("zygiskd main"); -} - -fn main() { - let process = std::env::args().next().unwrap(); - let nice_name = process.split('/').last().unwrap(); - init_android_logger(nice_name); - - start(); -} diff --git a/zygiskd/src/root_impl/apatch.rs b/zygiskd/src/root_impl/apatch.rs deleted file mode 100644 index aeab57f..0000000 --- a/zygiskd/src/root_impl/apatch.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::process::{Command, Stdio}; -use std::fs::File; -use std::io::{self, BufRead, BufReader}; -use serde::Deserialize; -use crate::constants::MIN_APATCH_VERSION; - -pub enum Version { - Supported, - TooOld, - Abnormal, -} - -fn parse_version(output: &str) -> i32 { - let mut version: Option = None; - for line in output.lines() { - if let Some(num) = line.trim().split_whitespace().last() { - if let Ok(v) = num.parse::() { - version = Some(v); - break; - } - } - } - version.unwrap_or_default() -} - -fn read_su_path() -> Result { - let file = File::open("/data/adb/ap/su_path")?; - let mut reader = BufReader::new(file); - let mut su_path = String::new(); - reader.read_line(&mut su_path)?; - Ok(su_path.trim().to_string()) -} - -pub fn get_apatch() -> Option { - let default_su_path = String::from("/system/bin/su"); - let su_path = read_su_path().ok()?; - - let output = Command::new(&su_path) - .arg("-v") - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .output() - .ok()?; - let stdout = String::from_utf8(output.stdout).ok()?; - if !stdout.contains("APatch") { - return None; - } - - let output1 = Command::new("/data/adb/apd") - .arg("-V") - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .output() - .ok()?; - let stdout1 = String::from_utf8(output1.stdout).ok()?; - if su_path == default_su_path { - let version = parse_version(&stdout1); - const MAX_OLD_VERSION: i32 = MIN_APATCH_VERSION - 1; - match version { - 0 => Some(Version::Abnormal), - v if v >= MIN_APATCH_VERSION && v <= 999999 => Some(Version::Supported), - v if v >= 1 && v <= MAX_OLD_VERSION => Some(Version::TooOld), - _ => None, - } - } else { - return None; - } -} - -#[derive(Deserialize)] -#[allow(dead_code)] -struct PackageConfig { - pkg: String, - exclude: i32, - allow: i32, - uid: i32, - to_uid: i32, - sctx: String, -} - -fn read_package_config() -> Result, std::io::Error> { - let file = File::open("/data/adb/ap/package_config")?; - let mut reader = csv::Reader::from_reader(file); - - let mut package_configs = Vec::new(); - for record in reader.deserialize() { - match record { - Ok(config) => package_configs.push(config), - Err(error) => { - log::warn!("Error deserializing record: {}", error); - } - } - } - - Ok(package_configs) -} - -pub fn uid_granted_root(uid: i32) -> bool { - match read_package_config() { - Ok(package_configs) => { - package_configs - .iter() - .find(|config| config.uid == uid) - .map(|config| config.allow == 1) - .unwrap_or(false) - } - Err(err) => { - log::warn!("Error reading package config: {}", err); - return false; - } - } -} - -pub fn uid_should_umount(uid: i32) -> bool { - match read_package_config() { - Ok(package_configs) => { - package_configs - .iter() - .find(|config| config.uid == uid) - .map(|config| { - match config.exclude { - 1 => true, - _ => false, - } - }) - .unwrap_or(false) - } - Err(err) => { - log::warn!("Error reading package configs: {}", err); - false - } - } -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.bmax.apatch") { - return s.st_uid == uid as u32; - } - false -} diff --git a/zygiskd/src/root_impl/common.c b/zygiskd/src/root_impl/common.c new file mode 100644 index 0000000..ddc75c1 --- /dev/null +++ b/zygiskd/src/root_impl/common.c @@ -0,0 +1,54 @@ +#include + +#include "kernelsu.h" + +#include "common.h" + +static enum RootImpl ROOT_IMPL = None; + +void root_impls_setup(void) { + enum RootImplState ksu_version = ksu_get_kernel_su(); + + enum RootImpl impl = None; + + if (ksu_version == Supported) impl = KernelSU; + + ROOT_IMPL = impl; +} + +enum RootImpl get_impl(void) { + return ROOT_IMPL; +} + +bool uid_granted_root(uid_t uid) { + switch (get_impl()) { + case KernelSU: { + return ksu_uid_granted_root(uid); + } + default: { + return false; + } + } +} + +bool uid_should_umount(uid_t uid) { + switch (get_impl()) { + case KernelSU: { + return ksu_uid_should_umount(uid); + } + default: { + return false; + } + } +} + +bool uid_is_manager(uid_t uid) { + switch (get_impl()) { + case KernelSU: { + return ksu_uid_is_manager(uid); + } + default: { + return false; + } + } +} diff --git a/zygiskd/src/root_impl/common.h b/zygiskd/src/root_impl/common.h new file mode 100644 index 0000000..314caf3 --- /dev/null +++ b/zygiskd/src/root_impl/common.h @@ -0,0 +1,22 @@ +#ifndef COMMON_H +#define COMMON_H + +#include "../constants.h" + +enum RootImpl { + None, + Multiple, /* INFO: I know. */ + KernelSU +}; + +void root_impls_setup(void); + +enum RootImpl get_impl(void); + +bool uid_granted_root(uid_t uid); + +bool uid_should_umount(uid_t uid); + +bool uid_is_manager(uid_t uid); + +#endif /* COMMON_H */ diff --git a/zygiskd/src/root_impl/kernelsu.c b/zygiskd/src/root_impl/kernelsu.c new file mode 100644 index 0000000..b8f950c --- /dev/null +++ b/zygiskd/src/root_impl/kernelsu.c @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "../constants.h" +#include "../utils.h" + +#include "kernelsu.h" + +#define KERNEL_SU_OPTION 0xdeadbeef + +#define CMD_GET_VERSION 2 +#define CMD_UID_GRANTED_ROOT 12 +#define CMD_UID_SHOULD_UMOUNT 13 + +enum RootImplState ksu_get_kernel_su(void) { + int version = 0; + prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, 0); + + errno = 0; + + if (version == 0) return Abnormal; + + if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) return Supported; + + if (version >= 1 && version <= MIN_KSU_VERSION - 1) return TooOld; + + return Abnormal; +} + +bool ksu_uid_granted_root(uid_t uid) { + uint32_t result = 0; + bool granted = false; + prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &granted, &result); + + LOGI("ksu_uid_granted_root: %d", granted); + + if (result != KERNEL_SU_OPTION) return false; + + return granted; +} + +bool ksu_uid_should_umount(uid_t uid) { + uint32_t result = 0; + bool umount = false; + prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &umount, &result); + + LOGI("ksu_uid_should_umount: %d", umount); + + if (result != KERNEL_SU_OPTION) return false; + + return umount; +} + +bool ksu_uid_is_manager(uid_t uid) { + + struct stat s; + if (stat("/data/user_de/0/me.weishu.kernelsu", &s) == 0) { + LOGI("ksu_uid_is_manager: %d", uid); + + return s.st_uid == uid; + } + + LOGI("ksu_uid_is_manager: false"); + + return false; +} diff --git a/zygiskd/src/root_impl/kernelsu.h b/zygiskd/src/root_impl/kernelsu.h new file mode 100644 index 0000000..1f2c494 --- /dev/null +++ b/zygiskd/src/root_impl/kernelsu.h @@ -0,0 +1,14 @@ +#ifndef KERNELSU_H +#define KERNELSU_H + +#include "../constants.h" + +enum RootImplState ksu_get_kernel_su(void); + +bool ksu_uid_granted_root(uid_t uid); + +bool ksu_uid_should_umount(uid_t uid); + +bool ksu_uid_is_manager(uid_t uid); + +#endif diff --git a/zygiskd/src/root_impl/kernelsu.rs b/zygiskd/src/root_impl/kernelsu.rs deleted file mode 100644 index da7177e..0000000 --- a/zygiskd/src/root_impl/kernelsu.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::constants::{MAX_KSU_VERSION, MIN_KSU_VERSION}; - -const KERNEL_SU_OPTION: u32 = 0xdeadbeefu32; - -const CMD_GET_VERSION: usize = 2; -const CMD_UID_GRANTED_ROOT: usize = 12; -const CMD_UID_SHOULD_UMOUNT: usize = 13; - -pub enum Version { - Supported, - TooOld, - Abnormal, -} - -pub fn get_kernel_su() -> Option { - let mut version = 0; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_GET_VERSION, - &mut version as *mut i32, - 0, - 0, - ) - }; - const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1; - match version { - 0 => None, - MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported), - 1..=MAX_OLD_VERSION => Some(Version::TooOld), - _ => Some(Version::Abnormal), - } -} - -pub fn uid_granted_root(uid: i32) -> bool { - let mut result: u32 = 0; - let mut granted = false; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_UID_GRANTED_ROOT, - uid, - &mut granted as *mut bool, - &mut result as *mut u32, - ) - }; - if result != KERNEL_SU_OPTION { - log::warn!("uid_granted_root failed"); - } - granted -} - -pub fn uid_should_umount(uid: i32) -> bool { - let mut result: u32 = 0; - let mut umount = false; - unsafe { - libc::prctl( - KERNEL_SU_OPTION as i32, - CMD_UID_SHOULD_UMOUNT, - uid, - &mut umount as *mut bool, - &mut result as *mut u32, - ) - }; - if result != KERNEL_SU_OPTION { - log::warn!("uid_granted_root failed"); - } - umount -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.weishu.kernelsu") { - return s.st_uid == uid as u32; - } - false -} diff --git a/zygiskd/src/root_impl/magisk.rs b/zygiskd/src/root_impl/magisk.rs deleted file mode 100644 index 612a03a..0000000 --- a/zygiskd/src/root_impl/magisk.rs +++ /dev/null @@ -1,124 +0,0 @@ -use std::fs; -use std::os::android::fs::MetadataExt; -use crate::constants::MIN_MAGISK_VERSION; -use std::process::{Command, Stdio}; -use log::info; -use crate::utils::LateInit; - -const MAGISK_OFFICIAL: &str = "com.topjohnwu.magisk"; -const MAGISK_THIRD_PARTIES: &[(&str, &str)] = &[ - ("alpha", "io.github.vvb2060.magisk"), - ("kitsune", "io.github.huskydg.magisk"), -]; - -pub enum Version { - Supported, - TooOld, -} - -static VARIANT: LateInit<&str> = LateInit::new(); - -pub fn get_magisk() -> Option { - if !VARIANT.initiated() { - Command::new("magisk") - .arg("-v") - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|version| { - let third_party = MAGISK_THIRD_PARTIES.iter().find_map(|v| { - version.contains(v.0).then_some(v.1) - }); - VARIANT.init(third_party.unwrap_or(MAGISK_OFFICIAL)); - info!("Magisk variant: {}", *VARIANT); - }); - } - Command::new("magisk") - .arg("-V") - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .and_then(|output| output.trim().parse::().ok()) - .map(|version| { - if version >= MIN_MAGISK_VERSION { - Version::Supported - } else { - Version::TooOld - } - }) -} - -pub fn uid_granted_root(uid: i32) -> bool { - Command::new("magisk") - .arg("--sqlite") - .arg(format!( - "select 1 from policies where uid={uid} and policy=2 limit 1" - )) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.is_empty()) - == Some(false) -} - -pub fn uid_should_umount(uid: i32) -> bool { - let output = Command::new("pm") - .args(["list", "packages", "--uid", &uid.to_string()]) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()); - let line = match output { - Some(line) => line, - None => return false, - }; - let pkg = line - .strip_prefix("package:") - .and_then(|line| line.split(' ').next()); - let pkg = match pkg { - Some(pkg) => pkg, - None => return false, - }; - Command::new("magisk") - .arg("--sqlite") - .arg(format!( - "select 1 from denylist where package_name=\"{pkg}\" limit 1" - )) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.is_empty()) - == Some(false) -} - -// TODO: signature -pub fn uid_is_manager(uid: i32) -> bool { - let output = Command::new("magisk") - .arg("--sqlite") - .arg(format!("select value from strings where key=\"requester\" limit 1")) - .stdout(Stdio::piped()) - .spawn() - .ok() - .and_then(|child| child.wait_with_output().ok()) - .and_then(|output| String::from_utf8(output.stdout).ok()) - .map(|output| output.trim().to_string()); - if let Some(output) = output { - if let Some(manager) = output.strip_prefix("value=") { - return fs::metadata(format!("/data/user_de/0/{}", manager)) - .map(|s| s.st_uid() == uid as u32) - .unwrap_or(false); - } - } - fs::metadata(format!("/data/user_de/0/{}", *VARIANT)) - .map(|s| s.st_uid() == uid as u32) - .unwrap_or(false) -} diff --git a/zygiskd/src/root_impl/mod.rs b/zygiskd/src/root_impl/mod.rs deleted file mode 100644 index 7ff6ff1..0000000 --- a/zygiskd/src/root_impl/mod.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::ptr::addr_of; - -mod kernelsu; -mod magisk; -mod apatch; - -#[derive(Debug)] -pub enum RootImpl { - None, - TooOld, - Abnormal, - Multiple, - KernelSU, - Magisk, - APatch, -} - -static mut ROOT_IMPL: RootImpl = RootImpl::None; - -pub fn setup() { - let apatch_version = apatch::get_apatch(); - let ksu_version = kernelsu::get_kernel_su(); - let magisk_version = magisk::get_magisk(); - - let impl_ = match (apatch_version, ksu_version, magisk_version) { - (None, None, None) => RootImpl::None, - (Some(_), Some(_), Some(_)) => RootImpl::Multiple, - (Some(apatch_version),None, None) => match apatch_version { - apatch::Version::Supported => RootImpl::APatch, - apatch::Version::TooOld => RootImpl::TooOld, - apatch::Version::Abnormal => RootImpl::Abnormal, - }, - (None,Some(ksu_version), None) => match ksu_version { - kernelsu::Version::Supported => RootImpl::KernelSU, - kernelsu::Version::TooOld => RootImpl::TooOld, - kernelsu::Version::Abnormal => RootImpl::Abnormal, - }, - (None, None, Some(magisk_version)) => match magisk_version { - magisk::Version::Supported => RootImpl::Magisk, - magisk::Version::TooOld => RootImpl::TooOld, - }, - _ => RootImpl::None, - }; - unsafe { - ROOT_IMPL = impl_; - } -} - -pub fn get_impl() -> &'static RootImpl { - unsafe { &*addr_of!(ROOT_IMPL) } -} - -pub fn uid_granted_root(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_granted_root(uid), - RootImpl::Magisk => magisk::uid_granted_root(uid), - RootImpl::APatch => apatch::uid_granted_root(uid), - _ => panic!("uid_granted_root: unknown root impl {:?}", get_impl()), - } -} - -pub fn uid_should_umount(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_should_umount(uid), - RootImpl::Magisk => magisk::uid_should_umount(uid), - RootImpl::APatch => apatch::uid_should_umount(uid), - _ => panic!("uid_should_umount: unknown root impl {:?}", get_impl()), - } -} - -pub fn uid_is_manager(uid: i32) -> bool { - match get_impl() { - RootImpl::KernelSU => kernelsu::uid_is_manager(uid), - RootImpl::Magisk => magisk::uid_is_manager(uid), - RootImpl::APatch => apatch::uid_is_manager(uid), - _ => panic!("uid_is_manager: unknown root impl {:?}", get_impl()), - } -} diff --git a/zygiskd/src/utils.c b/zygiskd/src/utils.c new file mode 100644 index 0000000..3984876 --- /dev/null +++ b/zygiskd/src/utils.c @@ -0,0 +1,295 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "utils.h" + +void switch_mount_namespace(pid_t pid) { + char current_path[PATH_MAX]; + if (getcwd(current_path, PATH_MAX) == NULL) { + /* TODO: Improve error messages */ + LOGE("getcwd: %s\n", strerror(errno)); + + return; + } + + /* INFO: We will NEVER achieve PATH_MAX value, but this is for ensurance. */ + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "/proc/%d/ns/mnt", pid); + + FILE *mnt_ns = fopen(path, "r"); + if (mnt_ns == NULL) { + /* TODO: Improve error messages */ + LOGE("fopen: %s\n", strerror(errno)); + + return; + } + + if (setns(fileno(mnt_ns), 0) == -1) { + /* TODO: Improve error messages */ + LOGE("setns: %s\n", strerror(errno)); + + return; + } + + fclose(mnt_ns); + + if (chdir(current_path) == -1) { + /* TODO: Improve error messages */ + LOGE("chdir: %s\n", strerror(errno)); + + return; + } +} + +int __system_property_get(const char *, char *); + +void get_property(const char *name, char *output) { + __system_property_get(name, output); +} + +void set_socket_create_context(const char *context) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "/proc/thread-self/attr/sockcreate"); + + FILE *sockcreate = fopen(path, "w"); + if (sockcreate == NULL) { + LOGE("fopen: %s\n", strerror(errno)); + + return; + } + + if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) { + LOGE("fwrite: %s\n", strerror(errno)); + + return; + } + + fclose(sockcreate); +} + +static void get_current_attr(char *output) { + char path[PATH_MAX]; + snprintf(path, PATH_MAX, "/proc/self/attr/current"); + + FILE *current = fopen(path, "r"); + if (current == NULL) { + LOGE("fopen: %s\n", strerror(errno)); + + return; + } + + if (fgets(output, PATH_MAX, current) == NULL) { + LOGE("fgets: %s\n", strerror(errno)); + + return; + } + + fclose(current); +} + +void unix_datagram_sendto(const char *path, void *buf, size_t len) { + char current_attr[PATH_MAX]; + get_current_attr(current_attr); + + set_socket_create_context(current_attr); + + struct sockaddr_un addr; + addr.sun_family = AF_UNIX; + + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + int socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0); + if (socket_fd == -1) { + LOGE("socket: %s\n", strerror(errno)); + + return; + } + + if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOGE("connect: %s\n", strerror(errno)); + + return; + } + + if (sendto(socket_fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + LOGE("sendto: %s\n", strerror(errno)); + + return; + } + + set_socket_create_context("u:r:zygote:s0"); + + close(socket_fd); +} + +int chcon(const char *path, const char *context) { + char command[PATH_MAX]; + snprintf(command, PATH_MAX, "chcon %s %s", context, path); + + return system(command); +} + +int unix_listener_from_path(char *path) { + if (remove(path) == -1 && errno != ENOENT) { + LOGE("remove: %s\n", strerror(errno)); + + return -1; + } + + int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socket_fd == -1) { + LOGE("socket: %s\n", strerror(errno)); + + return -1; + } + + struct sockaddr_un addr; + + memset(&addr, 0, sizeof(struct sockaddr_un)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1); + + if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) { + LOGE("bind: %s\n", strerror(errno)); + + return -1; + } + + if (listen(socket_fd, 2) == -1) { + LOGE("listen: %s\n", strerror(errno)); + + return -1; + } + + if (chcon(path, "u:object_r:magisk_file:s0") == -1) { + LOGE("chcon: %s\n", strerror(errno)); + + return -1; + } + + return socket_fd; +} + +ssize_t send_fd(int sockfd, int fd) { + char control_buf[CMSG_SPACE(sizeof(int))]; + memset(control_buf, 0, sizeof(control_buf)); + + int cnt = 1; + struct iovec iov = { + .iov_base = &cnt, + .iov_len = sizeof(cnt) + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control_buf, + .msg_controllen = sizeof(control_buf) + }; + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + + memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); + + ssize_t sent_bytes = sendmsg(sockfd, &msg, 0); + if (sent_bytes == -1) { + LOGE("Failed to send fd: %s\n", strerror(errno)); + + return -1; + } + + return sent_bytes; +} + +ssize_t recv_fd(int sockfd, int *fd) { + char control_buf[CMSG_SPACE(sizeof(int))]; + memset(control_buf, 0, sizeof(control_buf)); + + int cnt = 1; + struct iovec iov = { + .iov_base = &cnt, + .iov_len = sizeof(cnt) + }; + + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = control_buf, + .msg_controllen = sizeof(control_buf) + }; + + ssize_t received_bytes = recvmsg(sockfd, &msg, 0); + if (received_bytes == -1) { + LOGE("Failed to read fd: %s\n", strerror(errno)); + + return -1; + } + + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg); + memcpy(fd, CMSG_DATA(cmsg), sizeof(int)); + + return received_bytes; +} + +ssize_t write_string(int fd, const char *str) { + size_t len = strlen(str); + + ssize_t written_bytes = write(fd, &len, sizeof(len)); + if (written_bytes != sizeof(len)) { + LOGE("Failed to write string length: %s\n", strerror(errno)); + + return -1; + } + + written_bytes = write(fd, str, len); + if ((size_t)written_bytes != len) { + LOGE("Failed to write string: Not all bytes were written.\n"); + + return -1; + } + + return written_bytes; +} + +ssize_t read_string(int fd, char *str, size_t len) { + size_t str_len_buf[1]; + + ssize_t read_bytes = read(fd, &str_len_buf, sizeof(str_len_buf)); + if (read_bytes != (ssize_t)sizeof(str_len_buf)) { + LOGE("Failed to read string length: %s\n", strerror(errno)); + + return -1; + } + + size_t str_len = str_len_buf[0]; + + if (str_len > len) { + LOGE("Failed to read string: Buffer is too small (%zu > %zu).\n", str_len, len); + + return -1; + } + + read_bytes = read(fd, str, str_len); + if (read_bytes != (ssize_t)str_len) { + LOGE("Failed to read string: Not all bytes were read (%zd != %zu).\n", read_bytes, str_len); + + return -1; + } + + return read_bytes; +} diff --git a/zygiskd/src/utils.h b/zygiskd/src/utils.h new file mode 100644 index 0000000..b86c869 --- /dev/null +++ b/zygiskd/src/utils.h @@ -0,0 +1,36 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +#include "constants.h" + +#define LOGI(...) \ + __android_log_print(ANDROID_LOG_INFO, lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ + printf(__VA_ARGS__) + +#define LOGE(...) \ + __android_log_print(ANDROID_LOG_INFO , lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \ + printf(__VA_ARGS__) + +void switch_mount_namespace(pid_t pid); + +void get_property(const char *name, char *output); + +void set_socket_create_context(const char *context); + +void unix_datagram_sendto(const char *path, void *buf, size_t len); + +int chcon(const char *path, const char *context); + +int unix_listener_from_path(char *path); + +ssize_t send_fd(int sockfd, int fd); + +ssize_t recv_fd(int sockfd, int *fd); + +ssize_t write_string(int fd, const char *str); + +ssize_t read_string(int fd, char *str, size_t len); + +#endif /* UTILS_H */ diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs deleted file mode 100644 index 772b816..0000000 --- a/zygiskd/src/utils.rs +++ /dev/null @@ -1,264 +0,0 @@ -use anyhow::Result; -use rustix::net::{ - bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix, - SocketType, -}; -use rustix::path::Arg; -use rustix::thread::gettid; -use std::ffi::{c_char, c_void, CStr, CString}; -use std::os::fd::{AsFd, AsRawFd}; -use std::os::unix::net::{UnixListener}; -use std::process::Command; -use std::sync::OnceLock; -use std::{ - fs, - io::{Read, Write}, - os::unix::net::UnixStream, -}; - -#[cfg(target_pointer_width = "64")] -#[macro_export] -macro_rules! lp_select { - ($lp32:expr, $lp64:expr) => { - $lp64 - }; -} -#[cfg(target_pointer_width = "32")] -#[macro_export] -macro_rules! lp_select { - ($lp32:expr, $lp64:expr) => { - $lp32 - }; -} - -#[cfg(debug_assertions)] -#[macro_export] -macro_rules! debug_select { - ($debug:expr, $release:expr) => { - $debug - }; -} -#[cfg(not(debug_assertions))] -#[macro_export] -macro_rules! debug_select { - ($debug:expr, $release:expr) => { - $release - }; -} - -pub struct LateInit { - cell: OnceLock, -} - -impl LateInit { - pub const fn new() -> Self { - LateInit { - cell: OnceLock::new(), - } - } - - pub fn init(&self, value: T) { - assert!(self.cell.set(value).is_ok()) - } - - pub fn initiated(&self) -> bool { - self.cell.get().is_some() - } -} - -impl std::ops::Deref for LateInit { - type Target = T; - fn deref(&self) -> &T { - self.cell.get().unwrap() - } -} - -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_nonzero() - ); - fs::write(path, context)?; - Ok(()) - } - } -} - -pub fn get_current_attr() -> Result { - let s = fs::read("/proc/self/attr/current")?; - Ok(s.to_string_lossy().to_string()) -} - -pub fn chcon(path: &str, context: &str) -> Result<()> { - Command::new("chcon").arg(context).arg(path).status()?; - Ok(()) -} - -pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> { - let tag = CString::new(tag)?; - let message = CString::new(message)?; - unsafe { - __android_log_print(level, tag.as_ptr(), message.as_ptr()); - } - Ok(()) -} - -pub fn get_property(name: &str) -> Result { - let name = CString::new(name)?; - let mut buf = vec![0u8; 92]; - let prop = unsafe { - __system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char); - CStr::from_bytes_until_nul(&buf)? - }; - Ok(prop.to_string_lossy().to_string()) -} - -#[allow(dead_code)] -pub fn set_property(name: &str, value: &str) -> Result<()> { - let name = CString::new(name)?; - let value = CString::new(value)?; - unsafe { - __system_property_set(name.as_ptr(), value.as_ptr()); - } - Ok(()) -} - -#[allow(dead_code)] -pub fn wait_property(name: &str, serial: u32) -> Result { - let name = CString::new(name)?; - let info = unsafe { __system_property_find(name.as_ptr()) }; - let mut serial = serial; - unsafe { - __system_property_wait(info, serial, &mut serial, std::ptr::null()); - } - Ok(serial) -} - -#[allow(dead_code)] -pub fn get_property_serial(name: &str) -> Result { - let name = CString::new(name)?; - let info = unsafe { __system_property_find(name.as_ptr()) }; - Ok(unsafe { __system_property_serial(info) }) -} - -pub fn switch_mount_namespace(pid: i32) -> Result<()> { - let cwd = std::env::current_dir()?; - let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?; - rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?; - std::env::set_current_dir(cwd)?; - Ok(()) -} - -pub trait UnixStreamExt { - fn read_u8(&mut self) -> Result; - fn read_u32(&mut self) -> Result; - fn read_usize(&mut self) -> Result; - fn read_string(&mut self) -> Result; - fn write_u8(&mut self, value: u8) -> Result<()>; - fn write_u32(&mut self, value: u32) -> Result<()>; - fn write_usize(&mut self, value: usize) -> Result<()>; - fn write_string(&mut self, value: &str) -> Result<()>; -} - -impl UnixStreamExt for UnixStream { - fn read_u8(&mut self) -> Result { - let mut buf = [0u8; 1]; - self.read_exact(&mut buf)?; - Ok(buf[0]) - } - - fn read_u32(&mut self) -> Result { - let mut buf = [0u8; 4]; - self.read_exact(&mut buf)?; - Ok(u32::from_ne_bytes(buf)) - } - - fn read_usize(&mut self) -> Result { - let mut buf = [0u8; std::mem::size_of::()]; - self.read_exact(&mut buf)?; - Ok(usize::from_ne_bytes(buf)) - } - - fn read_string(&mut self) -> Result { - 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(()) - } -} - -pub fn unix_listener_from_path(path: &str) -> Result { - let _ = fs::remove_file(path); - let addr = SocketAddrUnix::new(path)?; - let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?; - bind_unix(&socket, &addr)?; - listen(&socket, 2)?; - chcon(path, "u:object_r:magisk_file:s0")?; - Ok(UnixListener::from(socket)) -} - -pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> { - // FIXME: shall we set create context every time? - set_socket_create_context(get_current_attr()?.as_str())?; - let addr = SocketAddrUnix::new(path.as_bytes())?; - let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?; - connect_unix(&socket, &addr)?; - sendto_unix(socket, buf, SendFlags::empty(), &addr)?; - set_socket_create_context("u:r:zygote:s0")?; - Ok(()) -} - -pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool { - unsafe { - let mut pfd = libc::pollfd { - fd: stream.as_raw_fd(), - events: libc::POLLIN, - revents: 0, - }; - let timeout = if block { -1 } else { 0 }; - libc::poll(&mut pfd, 1, timeout); - if pfd.revents & !libc::POLLIN != 0 { - return false; - } - } - return true; -} - -extern "C" { - fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32; - fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32; - fn __system_property_set(name: *const c_char, value: *const c_char) -> u32; - fn __system_property_find(name: *const c_char) -> *const c_void; - fn __system_property_wait( - info: *const c_void, - old_serial: u32, - new_serial: *mut u32, - timeout: *const libc::timespec, - ) -> bool; - fn __system_property_serial(info: *const c_void) -> u32; -} diff --git a/zygiskd/src/zygiskd.c b/zygiskd/src/zygiskd.c new file mode 100644 index 0000000..311bf06 --- /dev/null +++ b/zygiskd/src/zygiskd.c @@ -0,0 +1,690 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "root_impl/common.h" +#include "constants.h" +#include "utils.h" + +struct Module { + char *name; + int lib_fd; + int companion; +}; + +struct Context { + struct Module *modules; + int len; +}; + +enum Architecture { + ARM32, + ARM64, + X86, + X86_64, +}; + +#define PATH_MODULES_DIR "/data/adb/modules" +#define TMP_PATH "/data/adb/rezygisk" +#define CONTROLLER_SOCKET TMP_PATH "/init_monitor" +#define PATH_CP_NAME TMP_PATH "/" lp_select("cp32.sock", "cp64.sock") +#define ZYGISKD_FILE "zygiskd" lp_select("32", "64") +#define ZYGISKD_PATH "/data/adb/modules/zygisksu/bin/zygiskd" lp_select("32", "64") + +#define ASSURE_SIZE_WRITE(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return; \ + } + +#define ASSURE_SIZE_READ(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return; \ + } + +#define ASSURE_SIZE_WRITE_BREAK(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + break; \ + } + +#define ASSURE_SIZE_READ_BREAK(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + break; \ + } + +#define ASSURE_SIZE_WRITE_WR(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return -1; \ + } + +#define ASSURE_SIZE_READ_WR(area_name, subarea_name, sent_size, expected_size) \ + if (sent_size != (ssize_t)(expected_size)) { \ + LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \ + \ + return -1; \ + } + +static enum Architecture get_arch(void) { + char system_arch[32]; + get_property("ro.product.cpu.abi", system_arch); + + if (strstr(system_arch, "arm") != NULL) return lp_select(ARM32, ARM64); + if (strstr(system_arch, "x86") != NULL) return lp_select(X86, X86_64); + + LOGE("Unsupported system architecture: %s\n", system_arch); + exit(1); +} + +int create_library_fd(const char *so_path) { + int memfd = memfd_create("jit-cache-zygisk", MFD_ALLOW_SEALING); + if (memfd == -1) { + perror("memfd_create"); + + return -1; + } + + int so_fd = open(so_path, O_RDONLY); + if (so_fd == -1) { + perror("open"); + close(memfd); + + return -1; + } + + struct stat st; + if (fstat(so_fd, &st) == -1) { + perror("fstat"); + close(so_fd); + close(memfd); + + return -1; + } + + if (sendfile(memfd, so_fd, NULL, st.st_size) == -1) { + perror("sendfile"); + close(so_fd); + close(memfd); + + return -1; + } + + close(so_fd); + + if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) == -1) { + perror("fcntl"); + close(memfd); + + return -1; + } + + return memfd; +} + + +/* WARNING: Dynamic memory based */ +static void load_modules(enum Architecture arch, struct Context *context) { + context->len = 0; + context->modules = malloc(1); + + DIR *dir = opendir(PATH_MODULES_DIR); + if (dir == NULL) { + LOGE("Failed opening modules directory: %s.", PATH_MODULES_DIR); + + return; + } + + char arch_str[32]; + switch (arch) { + case ARM32: { + strcpy(arch_str, "armeabi-v7a"); + + break; + } + case ARM64: { + strcpy(arch_str, "arm64-v8a"); + + break; + } + case X86: { + strcpy(arch_str, "x86"); + + break; + } + case X86_64: { + strcpy(arch_str, "x86_64"); + + break; + } + } + + LOGI("Loading modules for architecture: %s\n", arch_str); + + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type != DT_DIR) continue; /* INFO: Only directories */ + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, "zygisksu") == 0) continue; + + char *name = entry->d_name; + char so_path[PATH_MAX]; + snprintf(so_path, PATH_MAX, "/data/adb/modules/%s/zygisk/%s.so", name, arch_str); + + struct stat st; + if (stat(so_path, &st) == -1) { + errno = 0; + + continue; + } + + char disabled[PATH_MAX]; + snprintf(disabled, PATH_MAX, "/data/adb/modules/%s/disable", name); + + if (stat(disabled, &st) != -1) { + errno = 0; + + continue; + } + + LOGI("Loading module `%s`...\n", name); + int lib_fd = create_library_fd(so_path); + if (lib_fd == -1) { + LOGE("Failed loading module `%s`\n", name); + + continue; + } + + LOGI("Loaded module lib fd: %d\n", lib_fd); + + context->modules = realloc(context->modules, ((context->len + 1) * sizeof(struct Module))); + context->modules[context->len].name = strdup(name); + context->modules[context->len].lib_fd = lib_fd; + context->modules[context->len].companion = -1; + context->len++; + } +} + +static void free_modules(struct Context *context) { + for (int i = 0; i < context->len; i++) { + free(context->modules[i].name); + if (context->modules[i].companion != -1) close(context->modules[i].companion); + } +} + +static int create_daemon_socket(void) { + set_socket_create_context("u:r:zygote:s0"); + + return unix_listener_from_path(PATH_CP_NAME); +} + +static int spawn_companion(char *name, int lib_fd) { + int sockets[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) { + LOGE("Failed creating socket pair.\n"); + + return -1; + } + + int daemon_fd = sockets[0]; + int companion_fd = sockets[1]; + + LOGI("Companion fd: %d\n", companion_fd); + LOGI("Daemon fd: %d\n", daemon_fd); + + pid_t pid = fork(); + LOGI("Forked: %d\n", pid); + if (pid < 0) { + LOGE("Failed forking companion: %s\n", strerror(errno)); + + close(companion_fd); + close(daemon_fd); + + exit(1); + } else if (pid > 0) { + close(companion_fd); + + LOGI("Waiting for companion to start (%d)\n", pid); + + int status = 0; + // waitpid(pid, &status, 0); + + LOGI("Companion exited with status %d\n", status); + + // if (WIFEXITED(status) && WEXITSTATUS(status) == 0) { + if (write_string(daemon_fd, name) == -1) return -1; + + if (send_fd(daemon_fd, lib_fd) == -1) return -1; + + LOGI("Sent module name and lib fd\n"); + + uint8_t response_buf[1]; + ssize_t ret = read(daemon_fd, &response_buf, sizeof(response_buf)); + ASSURE_SIZE_READ_WR("companion", "response", ret, sizeof(response_buf)); + + uint8_t response = response_buf[0]; + + LOGI("Companion response: %hhu\n", response); + + if (response == 0) return -2; + else if (response == 1) return daemon_fd; + else return -2; + // } else { + // LOGE("Exited with status %d\n", status); + + // close(daemon_fd); + + // return -1; + // } + /* INFO: if pid == 0: */ + } else { + LOGI("Companion started (%d)\n", pid); + /* INFO: There is no case where this will fail with a valid fd. */ + fcntl(companion_fd, F_SETFD, 0); + } + + char companion_fd_str[32]; + snprintf(companion_fd_str, 32, "%d", companion_fd); + + LOGI("Executing companion...\n"); + + char *argv[] = { ZYGISKD_FILE, "companion", companion_fd_str, NULL }; + if (execv(ZYGISKD_PATH, argv) == -1) { + LOGE("Failed executing companion: %s\n", strerror(errno)); + + close(companion_fd); + + exit(1); + } + + exit(0); +} + +/* TODO: Is packed attribute really necessary? */ +struct __attribute__((__packed__)) MsgHead { + unsigned int cmd; + int length; + char data[0]; +}; + +void zygiskd_start(void) { + LOGI("Welcome to ReZygisk %s!", ZKSU_VERSION); + + enum Architecture arch = get_arch(); + + struct Context context; + load_modules(arch, &context); + + struct MsgHead *msg = NULL; + size_t msg_sz = 0; + + switch (get_impl()) { + case None: { + /* INFO: Stop, compiler. */ + + break; + } + case Multiple: { + /* INFO: Stop, compiler. */ + + break; + } + + case KernelSU: { + if (context.len == 0) { + msg_sz = sizeof(struct MsgHead) + strlen("Root: KernelSU, Modules: None") + 1; + msg = malloc(msg_sz); + + msg->cmd = DAEMON_SET_INFO; + msg->length = strlen("Root: KernelSU, Modules: None") + 1; + memcpy(msg->data, "Root: KernelSU, Modules: None", strlen("Root: KernelSU, Modules: None")); + } else { + char *module_list = malloc(1); + size_t module_list_len = 0; + + for (int i = 0; i < context.len; i++) { + if (i != context.len - 1) { + module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + strlen(", ") + 1); + memcpy(module_list + module_list_len, context.modules[i].name, strlen(context.modules[i].name)); + + module_list_len += strlen(context.modules[i].name); + + memcpy(module_list + module_list_len, ", ", strlen(", ")); + + module_list_len += strlen(", "); + } else { + module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + 1); + memcpy(module_list + module_list_len, context.modules[i].name, strlen(context.modules[i].name)); + + module_list_len += strlen(context.modules[i].name); + } + } + + msg_sz = sizeof(struct MsgHead) + strlen("Root: KernelSU, Modules: ") + module_list_len + 1; + msg = malloc(msg_sz); + + msg->cmd = DAEMON_SET_INFO; + msg->length = strlen("Root: KernelSU, Modules: ") + module_list_len + 1; + memcpy(msg->data, "Root: KernelSU, Modules: ", strlen("Root: KernelSU, Modules: ")); + memcpy(msg->data + strlen("Root: KernelSU, Modules: "), module_list, module_list_len); + + free(module_list); + } + + break; + } + default: { + msg_sz = sizeof(struct MsgHead) + strlen("Invalid root implementation") + 1; + msg = malloc(msg_sz); + + msg->cmd = DAEMON_SET_ERROR_INFO; + msg->length = strlen("Invalid root implementation") + 1; + memcpy(msg->data, "Invalid root implementation", strlen("Invalid root implementation")); + + break; + } + } + + unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, msg_sz); + + free(msg); + + int socket_fd = create_daemon_socket(); + if (socket_fd == -1) { + LOGE("Failed creating daemon socket\n"); + + return; + } + + while (1) { + int client_fd = accept(socket_fd, NULL, NULL); + if (client_fd == -1) { + LOGE("accept: %s\n", strerror(errno)); + + return; + } + + LOGI("Accepted client: %d\n", client_fd); + + unsigned char buf[1]; + ssize_t len = read(client_fd, buf, sizeof(buf)); + if (len == -1) { + LOGE("read: %s\n", strerror(errno)); + + return; + } else if (len == 0) { + LOGI("Client disconnected\n"); + + return; + } + + LOGI("Action: %hhu\n", (uint8_t)buf[0]); + enum DaemonSocketAction action = (enum DaemonSocketAction)buf[0]; + + switch (action) { + case PingHeartbeat: { + enum DaemonSocketAction msgr = ZYGOTE_INJECTED; + unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction)); + + break; + } + case ZygoteRestart: { + LOGI("Zygote restart\n"); + + for (int i = 0; i < context.len; i++) { + if (context.modules[i].companion != -1) { + close(context.modules[i].companion); + context.modules[i].companion = -1; + } + } + + break; + } + case SystemServerStarted: { + enum DaemonSocketAction msgr = SYSTEM_SERVER_STARTED; + unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction)); + + break; + } + case RequestLogcatFd: { + char level_buf[1]; + ssize_t ret = read(client_fd, &level_buf, sizeof(level_buf)); + ASSURE_SIZE_READ_BREAK("RequestLogcatFd", "level", ret, sizeof(level_buf)); + + char level = level_buf[0]; + + char tag[128 + 1]; + ret = read_string(client_fd, tag, sizeof(tag) - 1); + if (ret == -1) break; + + tag[ret] = '\0'; + + char message[1024]; + ret = read_string(client_fd, message, sizeof(message)); + if (ret == -1) break; + + __android_log_print(level, tag, "%.*s", (int)ret, message); + + break; + } + case GetProcessFlags: { + LOGI("Getting process flags\n"); + + uid_t uid_buf[1]; + ssize_t ret = read(client_fd, &uid_buf, sizeof(uid_buf)); + ASSURE_SIZE_READ_BREAK("GetProcessFlags", "uid", ret, sizeof(uid_buf)); + + uid_t uid = uid_buf[0]; + + LOGI("Checking flags for uid: %d\n", uid); + + uint32_t flags = 0; + if (uid_is_manager(uid)) { + flags |= PROCESS_IS_MANAGER; + } else { + if (uid_granted_root(uid)) { + flags |= PROCESS_GRANTED_ROOT; + } + if (uid_should_umount(uid)) { + flags |= PROCESS_ON_DENYLIST; + } + } + + LOGI("Flags for uid %d: %d\n", uid, flags); + + switch (get_impl()) { + case None: { + break; + } + case Multiple: { + break; + } + case KernelSU: { + flags |= PROCESS_ROOT_IS_KSU; + } + } + + // LOGI("Flags for uid %d: %d\n", uid, flags); + + LOGI("Sending flags\n"); + + ret = write(client_fd, &flags, sizeof(flags)); + // ASSURE_SIZE_WRITE_BREAK("GetProcessFlags", "flags", ret, sizeof(flags)); + + LOGI("Sent flags\n"); + + break; + } + case GetInfo: { + uint32_t flags = 0; + + LOGI("Getting info\n"); + + switch (get_impl()) { + case None: { + break; + } + case Multiple: { + break; + } + case KernelSU: { + flags |= PROCESS_ROOT_IS_KSU; + } + } + + LOGI("Flags: %d\n", flags); + + ssize_t ret = write(client_fd, &flags, sizeof(flags)); + ASSURE_SIZE_WRITE_BREAK("GetInfo", "flags", ret, sizeof(flags)); + + pid_t pid = getpid(); + + LOGI("Getting pid: %d\n", pid); + + ret = write(client_fd, &pid, sizeof(pid)); + ASSURE_SIZE_WRITE_BREAK("GetInfo", "pid", ret, sizeof(pid)); + + LOGI("Sent pid\n"); + + break; + } + case ReadModules: { + LOGI("Reading modules to stream\n"); + + size_t clen = context.len; + ssize_t ret = write(client_fd, &clen, sizeof(clen)); + ASSURE_SIZE_WRITE_BREAK("ReadModules", "len", ret, sizeof(clen)); + + for (int i = 0; i < (int)clen; i++) { + LOGI("Hey, we're talking about: %d\n", i); + LOGI("Writing module `%s` to stream\n", context.modules[i].name); + LOGI("Lib fd: %d\n", context.modules[i].lib_fd); + + size_t name_len = strlen(context.modules[i].name); + + LOGI("Name length: %zu\n", name_len); + ret = write(client_fd, &name_len, sizeof(name_len)); + ASSURE_SIZE_WRITE_BREAK("ReadModules", "name length", ret, sizeof(name_len)); + + LOGI("Writing name: %s\n", context.modules[i].name); + ret = write(client_fd, context.modules[i].name, name_len); + ASSURE_SIZE_WRITE_BREAK("ReadModules", "name", ret, name_len); + + LOGI("Writing lib fd: %d\n", context.modules[i].lib_fd); + if (send_fd(client_fd, context.modules[i].lib_fd) == -1) break; + } + + LOGI("Finished reading modules to stream\n"); + + break; + } + case RequestCompanionSocket: { + LOGI("Requesting companion socket\n"); + + size_t index_buf[1]; + ssize_t ret = read(client_fd, &index_buf, sizeof(index_buf)); + ASSURE_SIZE_READ_BREAK("RequestCompanionSocket", "index", ret, sizeof(index_buf)); + + size_t index = index_buf[0]; + + struct Module *module = &context.modules[index]; + int companion_fd = module->companion; + + if (companion_fd != -1) { + LOGI("Companion for module `%s` already exists\n", module->name); + + if (fcntl(companion_fd, F_GETFD) == -1) { + LOGE("Poll companion for module `%s` crashed\n", module->name); + close(companion_fd); + + module->companion = -1; + } + } + + if (companion_fd == -1) { + LOGI("Spawning companion for `%s`\n", module->name); + + companion_fd = spawn_companion(module->name, module->lib_fd); + + if (companion_fd != -1) { + LOGI("Spawned companion for `%s`\n", module->name); + + module->companion = companion_fd; + + if (send_fd(client_fd, companion_fd) == -1) break; + } else if (companion_fd == -2) { + LOGI("Could not spawn companion for `%s` as it has no entry\n", module->name); + + /* TODO: Avoid duplicated code -- Merge this and the one below. */ + uint8_t response = 0; + ret = write(client_fd, &response, sizeof(response)); + ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(response)); + } else { + LOGE("Failed to spawn companion for `%s`\n", module->name); + + uint8_t response = 0; + ret = write(client_fd, &response, sizeof(response)); + ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(response)); + } + + LOGI("Companion fd: %d\n", companion_fd); + } + + break; + } + case GetModuleDir: { + LOGI("Getting module directory\n"); + + size_t index_buf[1]; + ssize_t ret = read(client_fd, &index_buf, sizeof(index_buf)); + ASSURE_SIZE_READ_BREAK("GetModuleDir", "index", ret, sizeof(index_buf)); + + size_t index = index_buf[0]; + + LOGI("Index: %zu\n", index); + + char dir[PATH_MAX]; + snprintf(dir, PATH_MAX, "%s/%s", PATH_MODULES_DIR, context.modules[index].name); + + LOGI("Module directory: %s\n", dir); + int dir_fd = open(dir, O_RDONLY); + + LOGI("Module directory fd: %d\n", dir_fd); + + if (send_fd(client_fd, dir_fd) == -1) break; + + LOGI("Sent module directory fd\n"); + + break; + } + + + + close(client_fd); + } + + continue; + } + + close(socket_fd); + free_modules(&context); +} diff --git a/zygiskd/src/zygiskd.h b/zygiskd/src/zygiskd.h new file mode 100644 index 0000000..9492a50 --- /dev/null +++ b/zygiskd/src/zygiskd.h @@ -0,0 +1,6 @@ +#ifndef ZYGISKD_H +#define ZYGISKD_H + +void zygiskd_start(void); + +#endif /* ZYGISKD_H */ diff --git a/zygiskd/src/zygiskd.rs b/zygiskd/src/zygiskd.rs deleted file mode 100644 index b0cb45b..0000000 --- a/zygiskd/src/zygiskd.rs +++ /dev/null @@ -1,351 +0,0 @@ -use crate::constants::{DaemonSocketAction, ProcessFlags}; -use crate::utils::{check_unix_socket, LateInit, UnixStreamExt}; -use crate::{constants, lp_select, root_impl, utils}; -use anyhow::{bail, Result}; -use log::{debug, error, info, trace, warn}; -use passfd::FdPassingExt; -use rustix::fs::{fcntl_setfd, FdFlags}; -use std::fs; -use std::io::Error; -use std::ops::Deref; -use std::os::fd::{AsFd, OwnedFd, RawFd}; -use std::os::unix::process::CommandExt; -use std::os::unix::{ - net::{UnixListener, UnixStream}, - prelude::AsRawFd, -}; -use std::path::PathBuf; -use std::process::{exit, Command}; -use std::sync::{Arc, Mutex}; -use std::thread; - -struct Module { - name: String, - lib_fd: OwnedFd, - companion: Mutex>>, -} - -struct Context { - modules: Vec, -} - -static TMP_PATH: LateInit = LateInit::new(); -static CONTROLLER_SOCKET: LateInit = LateInit::new(); -static PATH_CP_NAME: LateInit = LateInit::new(); - -pub fn main() -> Result<()> { - info!("Welcome to ReZygisk ({}) !", constants::ZKSU_VERSION); - - TMP_PATH.init(std::env::var("TMP_PATH")?); - CONTROLLER_SOCKET.init(format!("{}/init_monitor", TMP_PATH.deref())); - PATH_CP_NAME.init(format!( - "{}/{}", - TMP_PATH.deref(), - lp_select!("/cp32.sock", "/cp64.sock") - )); - - let arch = get_arch()?; - debug!("Daemon architecture: {arch}"); - let modules = load_modules(arch)?; - - { - let mut msg = Vec::::new(); - let info = match root_impl::get_impl() { - root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk | root_impl::RootImpl::APatch => { - msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes()); - let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect(); - format!( - "Root: {:?},module({}): {}", - root_impl::get_impl(), - modules.len(), - module_names.join(",") - ) - } - _ => { - msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes()); - format!("Invalid root implementation: {:?}", root_impl::get_impl()) - } - }; - msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes()); - msg.extend_from_slice(info.as_bytes()); - msg.extend_from_slice(&[0u8]); - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, msg.as_slice()) - .expect("failed to send info"); - } - - let context = Context { modules }; - let context = Arc::new(context); - let listener = create_daemon_socket()?; - for stream in listener.incoming() { - let mut stream = stream?; - let context = Arc::clone(&context); - let action = stream.read_u8()?; - let action = DaemonSocketAction::try_from(action)?; - trace!("New daemon action {:?}", action); - match action { - DaemonSocketAction::PingHeartbeat => { - let value = constants::ZYGOTE_INJECTED; - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?; - } - DaemonSocketAction::ZygoteRestart => { - info!("Zygote restarted, clean up companions"); - for module in &context.modules { - let mut companion = module.companion.lock().unwrap(); - companion.take(); - } - } - DaemonSocketAction::SystemServerStarted => { - let value = constants::SYSTEM_SERVER_STARTED; - utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?; - } - _ => { - thread::spawn(move || { - if let Err(e) = handle_daemon_action(action, stream, &context) { - warn!("Error handling daemon action: {}\n{}", e, e.backtrace()); - } - }); - } - } - } - - Ok(()) -} - -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> { - let mut modules = Vec::new(); - let dir = match fs::read_dir(constants::PATH_MODULES_DIR) { - Ok(dir) => dir, - Err(e) => { - warn!("Failed reading modules directory: {}", e); - return Ok(modules); - } - }; - for entry in dir.into_iter() { - let entry = entry?; - let name = entry.file_name().into_string().unwrap(); - let so_path = entry.path().join(format!("zygisk/{arch}.so")); - let disabled = entry.path().join("disable"); - if !so_path.exists() || disabled.exists() { - continue; - } - info!(" Loading module `{name}`..."); - let lib_fd = match create_library_fd(&so_path) { - Ok(fd) => fd, - Err(e) => { - warn!(" Failed to create memfd for `{name}`: {e}"); - continue; - } - }; - let companion = Mutex::new(None); - let module = Module { - name, - lib_fd, - companion, - }; - modules.push(module); - } - - Ok(modules) -} - -fn create_library_fd(so_path: &PathBuf) -> Result { - let opts = memfd::MemfdOptions::default().allow_sealing(true); - let memfd = opts.create("jit-cache-zygisk")?; - 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(OwnedFd::from(memfd.into_file())) -} - -fn create_daemon_socket() -> Result { - utils::set_socket_create_context("u:r:zygote:s0")?; - let listener = utils::unix_listener_from_path(&PATH_CP_NAME)?; - Ok(listener) -} - -fn spawn_companion(name: &str, lib_fd: RawFd) -> Result> { - let (mut daemon, companion) = UnixStream::pair()?; - - // FIXME: avoid getting self path from arg0 - let process = std::env::args().next().unwrap(); - let nice_name = process.split('/').last().unwrap(); - - unsafe { - let pid = libc::fork(); - if pid < 0 { - bail!(Error::last_os_error()); - } else if pid > 0 { - drop(companion); - let mut status: libc::c_int = 0; - libc::waitpid(pid, &mut status, 0); - if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 { - daemon.write_string(name)?; - daemon.send_fd(lib_fd)?; - return match daemon.read_u8()? { - 0 => Ok(None), - 1 => Ok(Some(daemon)), - _ => bail!("Invalid companion response"), - }; - } else { - bail!("exited with status {}", status); - } - } else { - // Remove FD_CLOEXEC flag - fcntl_setfd(companion.as_fd(), FdFlags::empty())?; - } - } - - Command::new(&process) - .arg0(format!("{}-{}", nice_name, name)) - .arg("companion") - .arg(format!("{}", companion.as_raw_fd())) - .spawn()?; - exit(0) -} - -fn handle_daemon_action( - action: DaemonSocketAction, - mut stream: UnixStream, - context: &Context, -) -> Result<()> { - match action { - 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::GetProcessFlags => { - let uid = stream.read_u32()? as i32; - let mut flags = ProcessFlags::empty(); - if root_impl::uid_is_manager(uid) { - flags |= ProcessFlags::PROCESS_IS_MANAGER; - } else { - if root_impl::uid_granted_root(uid) { - flags |= ProcessFlags::PROCESS_GRANTED_ROOT; - } - if root_impl::uid_should_umount(uid) { - flags |= ProcessFlags::PROCESS_ON_DENYLIST; - } - } - match root_impl::get_impl() { - root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU, - root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK, - root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH, - _ => panic!("wrong root impl: {:?}", root_impl::get_impl()), - } - trace!( - "Uid {} granted root: {}", - uid, - flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT) - ); - trace!( - "Uid {} on denylist: {}", - uid, - flags.contains(ProcessFlags::PROCESS_ON_DENYLIST) - ); - stream.write_u32(flags.bits())?; - } - DaemonSocketAction::GetInfo => { - let mut flags = ProcessFlags::empty(); - - match root_impl::get_impl() { - root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU, - root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK, - root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH, - _ => panic!("wrong root impl: {:?}", root_impl::get_impl()), - } - - stream.write_u32(flags.bits())?; - - stream.write_usize(context.modules.len())?; - - for module in context.modules.iter() { - stream.write_string(&module.name)?; - } - } - DaemonSocketAction::ReadModules => { - stream.write_usize(context.modules.len())?; - for module in context.modules.iter() { - stream.write_string(&module.name)?; - stream.send_fd(module.lib_fd.as_raw_fd())?; - } - } - DaemonSocketAction::RequestCompanionSocket => { - let index = stream.read_usize()?; - let module = &context.modules[index]; - let mut companion = module.companion.lock().unwrap(); - if let Some(Some(sock)) = companion.as_ref() { - if !check_unix_socket(sock, false) { - error!("Poll companion for module `{}` crashed", module.name); - companion.take(); - } - } - if companion.is_none() { - match spawn_companion(&module.name, module.lib_fd.as_raw_fd()) { - Ok(c) => { - if c.is_some() { - trace!(" Spawned companion for `{}`", module.name); - } else { - trace!( - " No companion spawned for `{}` because it has not entry", - module.name - ); - } - *companion = Some(c); - } - Err(e) => { - warn!(" Failed to spawn companion for `{}`: {}", module.name, e); - } - }; - } - match companion.as_ref() { - Some(Some(sock)) => { - if let Err(e) = sock.send_fd(stream.as_raw_fd()) { - error!( - "Failed to send companion fd socket of module `{}`: {}", - module.name, e - ); - stream.write_u8(0)?; - } - // Ok: Send by companion - } - _ => { - 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(()) -}