diff --git a/loader/src/Android.mk b/loader/src/Android.mk index 2d650e5..2846d1f 100644 --- a/loader/src/Android.mk +++ b/loader/src/Android.mk @@ -13,16 +13,7 @@ LOCAL_LDLIBS := -llog include $(BUILD_STATIC_LIBRARY) include $(CLEAR_VARS) -LOCAL_MODULE := zygisk_loader -LOCAL_C_INCLUDES := $(LOCAL_PATH)/include -FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/loader)) -LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) -LOCAL_STATIC_LIBRARIES := cxx common -LOCAL_LDLIBS := -llog -include $(BUILD_SHARED_LIBRARY) - -include $(CLEAR_VARS) -LOCAL_MODULE := zygisk_injector +LOCAL_MODULE := zygisk LOCAL_C_INCLUDES := $(LOCAL_PATH)/include FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector)) LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp index e0c42d4..cc25fd0 100644 --- a/loader/src/common/daemon.cpp +++ b/loader/src/common/daemon.cpp @@ -8,32 +8,14 @@ namespace zygiskd { - bool sMagicRead = false; - static std::string sSocketName; - - void ReadMagic() { - sMagicRead = true; - char magic[PATH_MAX]{0}; - auto fp = fopen(kZygiskMagic, "r"); - if (fp == nullptr) { - PLOGE("Open magic file"); - return; - } - fgets(magic, PATH_MAX, fp); - fclose(fp); - sSocketName.append(LP_SELECT("zygiskd32", "zygiskd64")).append(magic); - LOGD("Socket name: %s", sSocketName.data()); - } - int Connect(uint8_t retry) { - if (!sMagicRead) ReadMagic(); int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); struct sockaddr_un addr{ .sun_family = AF_UNIX, .sun_path={0}, }; - strcpy(addr.sun_path + 1, sSocketName.data()); - socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1; + strcpy(addr.sun_path, kCPSocketPath); + socklen_t socklen = sizeof(addr); while (retry--) { int r = connect(fd, reinterpret_cast(&addr), socklen); @@ -66,16 +48,6 @@ namespace zygiskd { return fd; } - std::string ReadNativeBridge() { - UniqueFd fd = Connect(1); - if (fd == -1) { - PLOGE("ReadNativeBridge"); - return ""; - } - socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge); - return socket_utils::read_string(fd); - } - uint32_t GetProcessFlags(uid_t uid) { UniqueFd fd = Connect(1); if (fd == -1) { diff --git a/loader/src/include/daemon.h b/loader/src/include/daemon.h index faab188..2a177f3 100644 --- a/loader/src/include/daemon.h +++ b/loader/src/include/daemon.h @@ -11,7 +11,7 @@ # define LP_SELECT(lp32, lp64) lp32 #endif -constexpr auto kZygiskMagic = "/system/zygisk_magic"; +constexpr auto kCPSocketPath = "/dev/zygisk/" LP_SELECT("cp32", "cp64") ".sock"; class UniqueFd { using Fd = int; @@ -54,7 +54,6 @@ namespace zygiskd { enum class SocketAction { PingHeartBeat, RequestLogcatFd, - ReadNativeBridge, GetProcessFlags, ReadModules, RequestCompanionSocket, @@ -65,8 +64,6 @@ namespace zygiskd { int RequestLogcatFd(); - std::string ReadNativeBridge(); - std::vector ReadModules(); uint32_t GetProcessFlags(uid_t uid); diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index 63c20c6..e87bf8e 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -8,12 +8,19 @@ using namespace std; void *self_handle = nullptr; extern "C" [[gnu::visibility("default")]] -void entry(void *handle) { +void entry(void* handle) { + LOGI("Zygisk library injected"); + self_handle = handle; + + if (!zygiskd::PingHeartbeat()) { + LOGE("Zygisk daemon is not running"); + return; + } + #ifdef NDEBUG logging::setfd(zygiskd::RequestLogcatFd()); #endif - self_handle = handle; - LOGD("Load injector successfully"); + LOGD("Start hooking"); hook_functions(); } diff --git a/loader/src/loader/loader.cpp b/loader/src/loader/loader.cpp deleted file mode 100644 index e85bbda..0000000 --- a/loader/src/loader/loader.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include - -#include "daemon.h" -#include "dl.h" -#include "logging.h" -#include "native_bridge_callbacks.h" - -extern "C" [[gnu::visibility("default")]] -uint8_t NativeBridgeItf[sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0}; - -namespace { - constexpr auto kZygoteProcesses = {"zygote", "zygote32", "zygote64", "usap32", "usap64"}; - constexpr auto kInjector = "/system/" LP_SELECT("lib", "lib64") "/libzygisk_injector.so"; - - void* sOriginalBridge = nullptr; -} - -__used __attribute__((destructor)) -void Destructor() { - if (sOriginalBridge) { - dlclose(sOriginalBridge); - } -} - -__used __attribute__((constructor)) -void Constructor() { - if (getuid() != 0) { - return; - } - - std::string_view cmdline = getprogname(); - if (std::none_of( - kZygoteProcesses.begin(), kZygoteProcesses.end(), - [&](const char* p) { return cmdline == p; } - )) { - LOGW("Not started as zygote (cmdline=%s)", cmdline.data()); - return; - } - - std::string native_bridge; - do { - if (!zygiskd::PingHeartbeat()) break; -#ifdef NDEBUG - logging::setfd(zygiskd::RequestLogcatFd()); -#endif - LOGI("Read native bridge"); - native_bridge = zygiskd::ReadNativeBridge(); - - LOGI("Load injector"); - auto handle = DlopenExt(kInjector, RTLD_NOW); - if (handle == nullptr) { - LOGE("Failed to dlopen injector: %s", dlerror()); - break; - } - auto entry = dlsym(handle, "entry"); - if (entry == nullptr) { - LOGE("Failed to dlsym injector entry: %s", dlerror()); - dlclose(handle); - break; - } - reinterpret_cast(entry)(handle); - } while (false); - - do { - if (native_bridge.empty() || native_bridge == "0") break; - - LOGI("Load original native bridge: %s", native_bridge.data()); - sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW); - if (sOriginalBridge == nullptr) { - LOGE("%s", dlerror()); - break; - } - - auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf"); - if (original_native_bridge_itf == nullptr) { - LOGE("%s", dlerror()); - break; - } - - long sdk = 0; - char value[PROP_VALUE_MAX + 1]; - if (__system_property_get("ro.build.version.sdk", value) > 0) { - sdk = strtol(value, nullptr, 10); - } - - auto callbacks_size = 0; - if (sdk >= __ANDROID_API_R__) { - callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>); - } else if (sdk == __ANDROID_API_Q__) { - callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_Q__>); - } - - memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size); - } while (false); - - logging::setfd(-1); -} diff --git a/module/src/customize.sh b/module/src/customize.sh index 4cf9eb9..97bf6f5 100644 --- a/module/src/customize.sh +++ b/module/src/customize.sh @@ -102,58 +102,44 @@ extract "$ZIPFILE" 'service.sh' "$MODPATH" mv "$TMPDIR/sepolicy.rule" "$MODPATH" HAS32BIT=false && [ -d "/system/lib" ] && HAS32BIT=true -HAS64BIT=false && [ -d "/system/lib64" ] && HAS64BIT=true mkdir "$MODPATH/bin" mkdir "$MODPATH/system" +mkdir "$MODPATH/system/lib64" [ "$HAS32BIT" = true ] && mkdir "$MODPATH/system/lib" -[ "$HAS64BIT" = true ] && mkdir "$MODPATH/system/lib64" if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then if [ "$HAS32BIT" = true ]; then ui_print "- Extracting x86 libraries" extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true - mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" - extract "$ZIPFILE" 'lib/x86/libzygisk_injector.so' "$MODPATH/system/lib" true - extract "$ZIPFILE" 'lib/x86/libzygisk_loader.so' "$MODPATH/system/lib" true - ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd" + mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygisk-cp32" + extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/system/lib" true fi - if [ "$HAS64BIT" = true ]; then - ui_print "- Extracting x64 libraries" - extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true - mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" - extract "$ZIPFILE" 'lib/x86_64/libzygisk_injector.so' "$MODPATH/system/lib64" true - extract "$ZIPFILE" 'lib/x86_64/libzygisk_loader.so' "$MODPATH/system/lib64" true - ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd" - fi + ui_print "- Extracting x64 libraries" + extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true + extract "$ZIPFILE" 'lib/x86_64/libzygisk.so' "$MODPATH/system/lib64" true + ln -sf "zygiskd" "$MODPATH/bin/zygisk-wd" + ln -sf "zygiskd" "$MODPATH/bin/zygisk-fuse" + ln -sf "zygiskd" "$MODPATH/bin/zygisk-cp64" else if [ "$HAS32BIT" = true ]; then ui_print "- Extracting arm libraries" extract "$ZIPFILE" 'bin/armeabi-v7a/zygiskd' "$MODPATH/bin" true - mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32" - extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_injector.so' "$MODPATH/system/lib" true - extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_loader.so' "$MODPATH/system/lib" true - ln -sf "zygiskd32" "$MODPATH/bin/zygiskwd" + mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygisk-cp32" + extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/system/lib" true fi - if [ "$HAS64BIT" = true ]; then - ui_print "- Extracting arm64 libraries" - extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true - mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64" - extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_injector.so' "$MODPATH/system/lib64" true - extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_loader.so' "$MODPATH/system/lib64" true - ln -sf "zygiskd64" "$MODPATH/bin/zygiskwd" - fi + ui_print "- Extracting arm64 libraries" + extract "$ZIPFILE" 'bin/arm64-v8a/zygiskd' "$MODPATH/bin" true + extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk.so' "$MODPATH/system/lib64" true + ln -sf "zygiskd" "$MODPATH/bin/zygisk-wd" + ln -sf "zygiskd" "$MODPATH/bin/zygisk-fuse" + ln -sf "zygiskd" "$MODPATH/bin/zygisk-cp64" fi -ui_print "- Generating magic" -MAGIC=$(tr -dc 'a-f0-9' "$MODPATH/system/zygisk_magic" - ui_print "- Setting permissions" -chmod 0744 "$MODPATH/daemon.sh" -set_perm_recursive "$MODPATH/bin" 0 2000 0755 0755 +set_perm_recursive "$MODPATH/bin" 0 0 0755 0755 set_perm_recursive "$MODPATH/system/lib" 0 0 0755 0644 u:object_r:system_lib_file:s0 set_perm_recursive "$MODPATH/system/lib64" 0 0 0755 0644 u:object_r:system_lib_file:s0 diff --git a/module/src/post-fs-data.sh b/module/src/post-fs-data.sh index c71b3a9..614a7fd 100644 --- a/module/src/post-fs-data.sh +++ b/module/src/post-fs-data.sh @@ -6,8 +6,6 @@ if [ "$ZYGISK_ENABLED" ]; then fi cd "$MODDIR" -getprop ro.dalvik.vm.native.bridge > /dev/.native_bridge -resetprop ro.dalvik.vm.native.bridge libzygisk_loader.so if [ "$(which magisk)" ]; then for file in ../*; do @@ -21,3 +19,5 @@ if [ "$(which magisk)" ]; then fi done fi + +sh -c "bin/zygisk-fuse &" diff --git a/module/src/sepolicy.rule b/module/src/sepolicy.rule index 2a9090e..6f16b88 100644 --- a/module/src/sepolicy.rule +++ b/module/src/sepolicy.rule @@ -1,3 +1,5 @@ +deny vold fusectlfs file write + allow * tmpfs * * allow zygote appdomain_tmpfs dir * allow zygote appdomain_tmpfs file * diff --git a/module/src/service.sh b/module/src/service.sh index 69fdc0d..e9c9866 100644 --- a/module/src/service.sh +++ b/module/src/service.sh @@ -8,8 +8,6 @@ if [ "$ZYGISK_ENABLED" ]; then fi cd "$MODDIR" -export NATIVE_BRIDGE=$(cat /dev/.native_bridge) -rm /dev/.native_bridge if [ "$(which magisk)" ]; then for file in ../*; do @@ -26,4 +24,4 @@ fi log -p i -t "zygisksu" "Start watchdog" [ "$DEBUG" = true ] && export RUST_BACKTRACE=1 -exec "bin/zygiskwd" "watchdog" >/dev/null 2>&1 +exec "bin/zygisk-wd" >/dev/null 2>&1 diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index 06b9ccd..956377a 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -9,7 +9,6 @@ rust-version = "1.69" android_logger = "0.13" anyhow = { version = "1.0", features = ["backtrace"] } bitflags = { version = "2.3" } -clap = { version = "4", features = ["derive"] } const_format = "0.2" futures = "0.3" konst = "0.3" @@ -19,11 +18,13 @@ log = "0.4" memfd = "0.6" num_enum = "0.5" passfd = "0.1" -rand = "0.8" +proc-maps = "0.3" +ptrace-do = "0.1" rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] } tokio = { version = "1.28", features = ["full"] } binder = { git = "https://github.com/Kernel-SU/binder_rs", rev = "c9f2b62d6a744fd2264056c638c1b061a6a2932d" } +fuser = { git = "https://github.com/Dr-TSNG/fuser", default-features = false } [profile.release] strip = true diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index 70f82e3..43b6c5d 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -17,15 +17,25 @@ pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace; #[cfg(not(debug_assertions))] pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info; -pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge"; pub const PROP_CTL_RESTART: &str = "ctl.restart"; -pub const ZYGISK_LOADER: &str = "libzygisk_loader.so"; -pub const ZYGISK_MAGIC: &str = "/system/zygisk_magic"; +pub const ZYGISK_LIBRARY: &str = "libzygisk.so"; + +pub const PATH_PCL: &str = "/system/etc/preloaded-classes"; +pub const PATH_SYSTEM_LIB32: &str = "/system/lib"; +pub const PATH_SYSTEM_LIB64: &str = "/system/lib64"; +pub const PATH_WORK_DIR: &str = "/dev/zygisk"; // TODO: Replace with /debug_ramdisk/zygisk +pub const PATH_PROP_OVERLAY: &str = concatcp!(PATH_WORK_DIR, "/module.prop"); +#[cfg(target_pointer_width = "64")] +pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, "/cp64.sock"); +#[cfg(target_pointer_width = "32")] +pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, "/cp32.sock"); +pub const PATH_FUSE_DIR: &str = concatcp!(PATH_WORK_DIR, "/fuse"); +pub const PATH_FUSE_PCL: &str = concatcp!(PATH_FUSE_DIR, "/preloaded-classes"); pub const PATH_MODULES_DIR: &str = ".."; pub const PATH_MODULE_PROP: &str = "module.prop"; -pub const PATH_ZYGISKD32: &str = "bin/zygiskd32"; -pub const PATH_ZYGISKD64: &str = "bin/zygiskd64"; +pub const PATH_CP32_BIN: &str = "bin/zygisk-cp32"; +pub const PATH_CP64_BIN: &str = "bin/zygisk-cp64"; pub const STATUS_LOADED: &str = "😋 Zygisksu is loaded"; pub const STATUS_CRASHED: &str = "❌ Zygiskd has crashed"; @@ -39,7 +49,6 @@ pub const STATUS_ROOT_IMPL_MULTIPLE: &str = "❌ Multiple root implementations i pub enum DaemonSocketAction { PingHeartbeat, RequestLogcatFd, - ReadNativeBridge, GetProcessFlags, ReadModules, RequestCompanionSocket, diff --git a/zygiskd/src/dl.rs b/zygiskd/src/dl.rs index dcc9d48..1b69149 100644 --- a/zygiskd/src/dl.rs +++ b/zygiskd/src/dl.rs @@ -1,17 +1,17 @@ use anyhow::{bail, Result}; use std::ffi::{c_char, c_void}; -const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2; -const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200; +pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2; +pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200; #[repr(C)] #[derive(Debug, Copy, Clone)] -struct AndroidNamespace { +pub struct AndroidNamespace { _unused: [u8; 0], } #[repr(C)] -struct AndroidDlextinfo { +pub struct AndroidDlextinfo { pub flags: u64, pub reserved_addr: *mut c_void, pub reserved_size: libc::size_t, @@ -22,7 +22,7 @@ struct AndroidDlextinfo { } extern "C" { - fn android_dlopen_ext( + pub fn android_dlopen_ext( filename: *const c_char, flags: libc::c_int, extinfo: *const AndroidDlextinfo, diff --git a/zygiskd/src/fuse.rs b/zygiskd/src/fuse.rs new file mode 100644 index 0000000..76593d4 --- /dev/null +++ b/zygiskd/src/fuse.rs @@ -0,0 +1,335 @@ +use std::cmp::min; +use anyhow::{bail, Result}; +use std::ffi::{CString, OsStr}; +use std::{fs, thread}; +use std::sync::{mpsc, Mutex}; +use std::time::{Duration, SystemTime}; +use fuser::{FileAttr, Filesystem, FileType, ReplyAttr, ReplyData, ReplyDirectory, ReplyEntry, ReplyOpen, Request}; +use libc::ENOENT; +use log::{debug, error, info}; +use proc_maps::{get_process_maps, MapRange, Pid}; +use ptrace_do::{RawProcess, TracedProcess}; +use rustix::mount::mount_bind; +use rustix::path::Arg; +use rustix::process::getpid; +use crate::{constants, dl}; +use crate::utils::LateInit; + +pub struct DelegateFilesystem; + +const fn attr(inode: u64, size: u64, kind: FileType) -> FileAttr { + FileAttr { + ino: inode, + size, + blocks: 0, + atime: SystemTime::UNIX_EPOCH, + mtime: SystemTime::UNIX_EPOCH, + ctime: SystemTime::UNIX_EPOCH, + crtime: SystemTime::UNIX_EPOCH, + kind, + perm: 0o644, + nlink: 0, + uid: 0, + gid: 0, + rdev: 0, + blksize: 0, + flags: 0, + } +} + +const ANDROID_LIBC: &str = "bionic/libc.so"; +const ANDROID_LIBDL: &str = "bionic/libdl.so"; + +const INO_DIR: u64 = 1; +const INO_PCL: u64 = 2; + +static ATTR_DIR: FileAttr = attr(INO_DIR, 0, FileType::Directory); +static ATTR_PCL: LateInit = LateInit::new(); + +static PCL_CONTENT: LateInit> = LateInit::new(); + +const ENTRIES: &[(u64, FileType, &str)] = &[ + (INO_DIR, FileType::Directory, "."), + (INO_DIR, FileType::Directory, ".."), + (INO_PCL, FileType::RegularFile, "preloaded-classes"), +]; + +const TTL: Duration = Duration::from_secs(1); + +impl Filesystem for DelegateFilesystem { + fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) { + if parent != INO_DIR { + reply.error(ENOENT); + return; + } + match name.as_str().unwrap() { + "preloaded-classes" => reply.entry(&TTL, &ATTR_PCL, 0), + _ => reply.error(ENOENT), + } + } + + fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) { + match ino { + INO_DIR => reply.attr(&TTL, &ATTR_DIR), + INO_PCL => reply.attr(&TTL, &ATTR_PCL), + _ => reply.error(ENOENT), + } + } + + fn open(&mut self, req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) { + if ino == INO_PCL { + let pid = req.pid(); + let process = format!("/proc/{}/cmdline", pid); + let process = fs::read_to_string(process).unwrap(); + let process = &process[..process.find('\0').unwrap()]; + info!("Process {} is reading preloaded-classes", process); + match process { + // "zygote" => ptrace_zygote(pid, false).unwrap(), + "zygote64" => ptrace_zygote(pid, true).unwrap(), + _ => (), + } + } + reply.opened(0, 0); + } + + fn read(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, size: u32, _flags: i32, _lock_owner: Option, reply: ReplyData) { + let offset = offset as usize; + let size = size as usize; + if ino == INO_PCL { + let len = PCL_CONTENT.len(); + if offset >= len { + reply.data(&[]); + } else { + let end = min(offset + size, len); + reply.data(&PCL_CONTENT[offset..end]); + } + } else { + reply.error(ENOENT); + } + } + + fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) { + if ino != INO_DIR { + reply.error(ENOENT); + return; + } + for (i, entry) in ENTRIES.iter().enumerate().skip(offset as usize) { + if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) { + break; + } + } + reply.ok(); + } +} + +fn find_module_for_pid(pid: Pid, library: &str) -> Result { + let maps = get_process_maps(pid)?; + for map in maps.into_iter() { + if let Some(p) = map.filename() { + if p.as_str()?.contains(library) { + return Ok(map); + } + } + } + bail!("Cannot find module {library} for pid {pid}"); +} + +fn find_remote_procedure( + pid: Pid, + library: &str, + local_addr: usize, +) -> Result { + let local_module = find_module_for_pid(getpid().as_raw_nonzero().get(), library)?; + debug!( + "Identifed local range {library} ({:?}) at {:x}", + local_module.filename(), + local_module.start() + ); + + let remote_module = find_module_for_pid(pid, library)?; + debug!( + "Identifed remote range {library} ({:?}) at {:x}", + remote_module.filename(), + remote_module.start() + ); + + Ok(local_addr - local_module.start() + remote_module.start()) +} + +fn ptrace_zygote(pid: u32, is64: bool) -> Result<()> { + static LAST_32: Mutex = Mutex::new(0); + static LAST_64: Mutex = Mutex::new(0); + + let mut last = match is64 { + true => LAST_64.lock().unwrap(), + false => LAST_32.lock().unwrap(), + }; + if *last == pid { + return Ok(()); + } + *last = pid; + let (sender, receiver) = mpsc::channel::<()>(); + + let worker = move || -> Result<()> { + info!("Injecting into pid {}, is64 = {}", pid, is64); + let lib_dir = match is64 { + true => constants::PATH_SYSTEM_LIB64, + false => constants::PATH_SYSTEM_LIB32, + }; + let zygisk_lib = format!("{}/{}", lib_dir, constants::ZYGISK_LIBRARY); + let lib_dir = CString::new(lib_dir)?; + let zygisk_lib = CString::new(zygisk_lib)?; + let libc_base = find_module_for_pid(pid as i32, ANDROID_LIBC)?.start(); + let mmap_remote = find_remote_procedure( + pid as i32, + ANDROID_LIBC, + libc::mmap as usize, + )?; + let munmap_remote = find_remote_procedure( + pid as i32, + ANDROID_LIBC, + libc::munmap as usize, + )?; + let dlopen_remote = find_remote_procedure( + pid as i32, + ANDROID_LIBDL, + dl::android_dlopen_ext as usize, + )?; + let dlsym_remote = find_remote_procedure( + pid as i32, + ANDROID_LIBDL, + libc::dlsym as usize, + )?; + + let tracer = TracedProcess::attach(RawProcess::new(pid as i32))?; + sender.send(())?; + let frame = tracer.next_frame()?; + debug!("Waited for a frame"); + + // Map a buffer in the remote process + let mmap_params: [usize; 6] = [ + 0, + 0x1000, + (libc::PROT_READ | libc::PROT_WRITE) as usize, + (libc::MAP_ANONYMOUS | libc::MAP_PRIVATE) as usize, + 0, + 0, + ]; + let (regs, mut frame) = frame.invoke_remote( + mmap_remote, + libc_base, + &mmap_params, + )?; + let buf_addr = regs.return_value(); + debug!("Buffer addr: {:x}", buf_addr); + + // Find the address of __loader_android_create_namespace + let sym = CString::new("__loader_android_create_namespace")?; + frame.write_memory(buf_addr, sym.as_bytes_with_nul())?; + let (regs, mut frame) = frame.invoke_remote( + dlsym_remote, + libc_base, + &[libc::RTLD_DEFAULT as usize, buf_addr], + )?; + let android_create_namespace_remote = regs.return_value(); + debug!("__loader_android_create_namespace addr: {:x}", android_create_namespace_remote); + + // Create a linker namespace for remote process + frame.write_memory(buf_addr, zygisk_lib.as_bytes_with_nul())?; + frame.write_memory(buf_addr + 0x100, lib_dir.as_bytes_with_nul())?; + let ns_params: [usize; 7] = [ + buf_addr, // name + buf_addr + 0x100, // ld_library_path + 0, // default_library_path + dl::ANDROID_NAMESPACE_TYPE_SHARED as usize, // type + 0, // permitted_when_isolated_path + 0, // parent + dlopen_remote, // caller_addr + ]; + let (regs, mut frame) = frame.invoke_remote( + android_create_namespace_remote, + libc_base, + &ns_params, + )?; + let ns_addr = regs.return_value(); + debug!("Linker namespace addr: {:x}", ns_addr); + + // Load zygisk into remote process + let info = dl::AndroidDlextinfo { + flags: dl::ANDROID_DLEXT_USE_NAMESPACE, + reserved_addr: std::ptr::null_mut(), + reserved_size: 0, + relro_fd: 0, + library_fd: 0, + library_fd_offset: 0, + library_namespace: ns_addr as *mut _, + }; + let info = unsafe { + std::slice::from_raw_parts( + &info as *const _ as *const u8, + std::mem::size_of::(), + ) + }; + frame.write_memory(buf_addr + 0x200, info)?; + let (regs, mut frame) = frame.invoke_remote( + dlopen_remote, + libc_base, + &[buf_addr, libc::RTLD_NOW as usize, buf_addr + 0x200], + )?; + let handle = regs.return_value(); + debug!("Load zygisk into remote process: {:x}", handle); + + let entry = CString::new("entry")?; + frame.write_memory(buf_addr, entry.as_bytes_with_nul())?; + let (regs, frame) = frame.invoke_remote( + dlsym_remote, + libc_base, + &[handle, buf_addr], + )?; + let entry = regs.return_value(); + debug!("Call zygisk entry: {:x}", entry); + let (_, frame) = frame.invoke_remote( + entry, + libc_base, + &[handle], + )?; + + // Cleanup + let _ = frame.invoke_remote( + munmap_remote, + libc_base, + &[buf_addr], + )?; + debug!("Cleaned up"); + Ok(()) + }; + + thread::spawn(move || { + if let Err(e) = worker() { + error!("Crashed: {:?}", e); + } + }); + + receiver.recv()?; + Ok(()) +} + +pub fn main() -> Result<()> { + fs::create_dir(constants::PATH_WORK_DIR)?; + fs::create_dir(constants::PATH_FUSE_DIR)?; + PCL_CONTENT.init(fs::read(constants::PATH_PCL)?); + ATTR_PCL.init(attr(INO_PCL, PCL_CONTENT.len() as u64, FileType::RegularFile)); + let options = [ + fuser::MountOption::FSName(String::from("zygisk")), + fuser::MountOption::AllowOther, + fuser::MountOption::RO, + ]; + let session = fuser::spawn_mount2( + DelegateFilesystem, + constants::PATH_FUSE_DIR, + &options, + )?; + mount_bind(constants::PATH_FUSE_PCL, constants::PATH_PCL)?; + session.join(); + bail!("Fuse mount exited unexpectedly"); +} diff --git a/zygiskd/src/magic.rs b/zygiskd/src/magic.rs deleted file mode 100644 index d126d02..0000000 --- a/zygiskd/src/magic.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::fs; -use anyhow::Result; -use crate::constants; -use crate::utils::LateInit; - -pub static MAGIC: LateInit = LateInit::new(); -pub static PATH_TMP_DIR: LateInit = LateInit::new(); -pub static PATH_TMP_PROP: LateInit = LateInit::new(); - -pub fn setup() -> Result<()> { - let name = fs::read_to_string(constants::ZYGISK_MAGIC)?; - let path_tmp_dir = format!("/dev/{}", name); - let path_tmp_prop = format!("{}/module.prop", path_tmp_dir); - - MAGIC.init(name); - PATH_TMP_DIR.init(path_tmp_dir); - PATH_TMP_PROP.init(path_tmp_prop); - Ok(()) -} diff --git a/zygiskd/src/main.rs b/zygiskd/src/main.rs index 3ee2ae2..c8b5d4c 100644 --- a/zygiskd/src/main.rs +++ b/zygiskd/src/main.rs @@ -3,29 +3,13 @@ mod constants; mod dl; -mod magic; +mod fuse; mod root_impl; mod utils; mod watchdog; mod zygiskd; use anyhow::Result; -use clap::{Subcommand, Parser}; - -#[derive(Parser, Debug)] -#[command(author, version = constants::VERSION_FULL, about, long_about = None)] -struct Args { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand, Debug)] -enum Commands { - /// Start zygisk watchdog - Watchdog, - /// Start zygisk daemon - Daemon, -} fn init_android_logger(tag: &str) { @@ -36,14 +20,15 @@ fn init_android_logger(tag: &str) { ); } -async fn start() -> Result<()> { +async fn start(name: &str) -> Result<()> { root_impl::setup(); - magic::setup()?; - let cli = Args::parse(); - match cli.command { - Commands::Watchdog => watchdog::entry().await?, - Commands::Daemon => zygiskd::entry()?, - }; + match name.trim_start_matches("zygisk-") { + "wd" => watchdog::main().await?, + "fuse" => fuse::main()?, + "cp32" => zygiskd::main()?, + "cp64" => zygiskd::main()?, + _ => println!("Available commands: wd, fuse, cp"), + } Ok(()) } @@ -53,7 +38,7 @@ async fn main() { let nice_name = process.split('/').last().unwrap(); init_android_logger(nice_name); - if let Err(e) = start().await { + if let Err(e) = start(nice_name).await { log::error!("Crashed: {}\n{}", e, e.backtrace()); } } diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs index 6354776..9d8a379 100644 --- a/zygiskd/src/utils.rs +++ b/zygiskd/src/utils.rs @@ -1,9 +1,9 @@ use anyhow::Result; -use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command}; +use std::{fs, io::{Read, Write}, os::unix::net::UnixStream}; use std::ffi::c_char; use std::os::unix::net::UnixListener; +use std::process::Command; use std::sync::OnceLock; -use rand::distributions::{Alphanumeric, DistString}; use rustix::net::{AddressFamily, bind_unix, listen, socket, SocketAddrUnix, SocketType}; use rustix::thread::gettid; @@ -50,10 +50,6 @@ impl std::ops::Deref for LateInit { } } -pub fn random_string() -> String { - Alphanumeric.sample_string(&mut rand::thread_rng(), 8) -} - pub fn set_socket_create_context(context: &str) -> Result<()> { let path = "/proc/thread-self/attr/sockcreate"; match fs::write(path, context) { @@ -66,15 +62,16 @@ pub fn set_socket_create_context(context: &str) -> Result<()> { } } -pub fn get_native_bridge() -> String { - std::env::var("NATIVE_BRIDGE").unwrap_or_default() +pub fn chcon(path: &str, context: &str) -> Result<()> { + Command::new("chcon").arg(context).arg(path).status()?; + Ok(()) } pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> { let tag = std::ffi::CString::new(tag)?; let message = std::ffi::CString::new(message)?; unsafe { - __android_log_print(level as i32, tag.as_ptr(), message.as_ptr()); + __android_log_print(level, tag.as_ptr(), message.as_ptr()); } Ok(()) } @@ -89,10 +86,11 @@ pub fn get_property(name: &str) -> Result { } pub fn set_property(name: &str, value: &str) -> Result<()> { - Command::new("resetprop") - .arg(name) - .arg(value) - .spawn()?.wait()?; + let name = std::ffi::CString::new(name)?; + let value = std::ffi::CString::new(value)?; + unsafe { + __system_property_set(name.as_ptr(), value.as_ptr()); + } Ok(()) } @@ -155,15 +153,18 @@ impl UnixStreamExt for UnixStream { } } -pub fn abstract_namespace_socket(name: &str) -> Result { - let addr = SocketAddrUnix::new_abstract_name(name.as_bytes())?; +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)) } 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; } diff --git a/zygiskd/src/watchdog.rs b/zygiskd/src/watchdog.rs index 62f478d..310d760 100644 --- a/zygiskd/src/watchdog.rs +++ b/zygiskd/src/watchdog.rs @@ -1,39 +1,46 @@ -use crate::{constants, magic, root_impl, utils}; +use crate::{constants, root_impl, utils}; use anyhow::{bail, Result}; use std::fs; use std::future::Future; use std::io::{BufRead, BufReader, Write}; -use std::os::unix::net::UnixListener; use std::pin::Pin; use std::time::Duration; use binder::IBinder; use futures::stream::FuturesUnordered; use futures::StreamExt; +use log::info; use rustix::mount::mount_bind; use rustix::process::{getgid, getuid, kill_process, Pid, Signal}; use tokio::process::{Child, Command}; use crate::utils::LateInit; -static LOCK: LateInit = LateInit::new(); static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new(); -pub async fn entry() -> Result<()> { - log::info!("Start zygisksu watchdog"); +pub async fn main() -> Result<()> { + let result = run().await; + set_prop_hint(constants::STATUS_CRASHED)?; + result +} + +async fn run() -> Result<()> { + info!("Start zygisksu watchdog"); check_permission()?; - ensure_single_instance()?; mount_prop().await?; if check_and_set_hint()? == false { log::warn!("Requirements not met, exiting"); - utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?; return Ok(()); } - let end = spawn_daemon().await; - set_prop_hint(constants::STATUS_CRASHED)?; - end + spawn_daemon().await?; + Ok(()) +} + +fn spawn_fuse() -> Result<()> { + Command::new("bin/zygisk-fuse").spawn()?; + Ok(()) } fn check_permission() -> Result<()> { - log::info!("Check permission"); + info!("Check permission"); let uid = getuid(); if !uid.is_root() { bail!("UID is not 0"); @@ -53,16 +60,6 @@ fn check_permission() -> Result<()> { Ok(()) } -fn ensure_single_instance() -> Result<()> { - log::info!("Ensure single instance"); - let name = format!("zygiskwd{}", magic::MAGIC.as_str()); - match utils::abstract_namespace_socket(&name) { - Ok(socket) => LOCK.init(socket), - Err(e) => bail!("Failed to acquire lock: {e}. Maybe another instance is running?") - } - Ok(()) -} - async fn mount_prop() -> Result<()> { let module_prop = if let root_impl::RootImpl::Magisk = root_impl::get_impl() { let magisk_path = Command::new("magisk").arg("--path").output().await?; @@ -74,7 +71,7 @@ async fn mount_prop() -> Result<()> { } else { constants::PATH_MODULE_PROP.to_string() }; - log::info!("Mount {module_prop}"); + info!("Mount {module_prop}"); let module_prop_file = fs::File::open(&module_prop)?; let mut section = 0; let mut sections: [String; 2] = [String::new(), String::new()]; @@ -93,15 +90,13 @@ async fn mount_prop() -> Result<()> { } PROP_SECTIONS.init(sections); - fs::create_dir(magic::PATH_TMP_DIR.as_str())?; - fs::File::create(magic::PATH_TMP_PROP.as_str())?; - - mount_bind(magic::PATH_TMP_PROP.as_str(), &module_prop)?; + fs::File::create(constants::PATH_PROP_OVERLAY)?; + mount_bind(constants::PATH_PROP_OVERLAY, &module_prop)?; Ok(()) } fn set_prop_hint(hint: &str) -> Result<()> { - let mut file = fs::File::create(magic::PATH_TMP_PROP.as_str())?; + let mut file = fs::File::create(constants::PATH_PROP_OVERLAY)?; file.write_all(PROP_SECTIONS[0].as_bytes())?; file.write_all(b"[")?; file.write_all(hint.as_bytes())?; @@ -131,8 +126,8 @@ async fn spawn_daemon() -> Result<()> { let mut futures = FuturesUnordered::>>>>::new(); let mut child_ids = vec![]; - let daemon32 = Command::new(constants::PATH_ZYGISKD32).arg("daemon").spawn(); - let daemon64 = Command::new(constants::PATH_ZYGISKD64).arg("daemon").spawn(); + let daemon32 = Command::new(constants::PATH_CP32_BIN).spawn(); + let daemon64 = Command::new(constants::PATH_CP64_BIN).spawn(); async fn spawn_daemon(mut daemon: Child) -> Result<()> { let result = daemon.wait().await?; log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result); @@ -158,8 +153,7 @@ async fn spawn_daemon() -> Result<()> { }; }; - log::info!("System server ready, restore native bridge"); - utils::set_property(constants::PROP_NATIVE_BRIDGE, &utils::get_native_bridge())?; + info!("System server ready"); loop { if binder.ping_binder().is_err() { break; } @@ -185,7 +179,6 @@ async fn spawn_daemon() -> Result<()> { } log::error!("Restarting zygote..."); - utils::set_property(constants::PROP_NATIVE_BRIDGE, constants::ZYGISK_LOADER)?; utils::set_property(constants::PROP_CTL_RESTART, "zygote")?; } } diff --git a/zygiskd/src/zygiskd.rs b/zygiskd/src/zygiskd.rs index 184e92c..1153009 100644 --- a/zygiskd/src/zygiskd.rs +++ b/zygiskd/src/zygiskd.rs @@ -1,7 +1,7 @@ use std::ffi::c_void; use crate::constants::{DaemonSocketAction, ProcessFlags}; use crate::utils::UnixStreamExt; -use crate::{constants, dl, lp_select, magic, root_impl, utils}; +use crate::{constants, dl, lp_select, root_impl, utils}; use anyhow::{bail, Result}; use passfd::FdPassingExt; use std::sync::Arc; @@ -14,7 +14,7 @@ use std::os::unix::{ }; use std::path::PathBuf; use rustix::fs::fstat; -use rustix::process::Signal; +use rustix::process::{set_parent_process_death_signal, Signal}; type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); @@ -25,12 +25,11 @@ struct Module { } struct Context { - native_bridge: String, modules: Vec, } -pub fn entry() -> Result<()> { - rustix::process::set_parent_process_death_signal(Some(Signal::Kill))?; +pub fn main() -> Result<()> { + set_parent_process_death_signal(Some(Signal::Kill))?; let arch = get_arch()?; log::debug!("Daemon architecture: {arch}"); @@ -39,7 +38,6 @@ pub fn entry() -> Result<()> { let modules = load_modules(arch)?; let context = Context { - native_bridge: utils::get_native_bridge(), modules, }; let context = Arc::new(context); @@ -131,10 +129,8 @@ fn create_library_fd(so_path: &PathBuf) -> Result { fn create_daemon_socket() -> Result { utils::set_socket_create_context("u:r:zygote:s0")?; - let prefix = lp_select!("zygiskd32", "zygiskd64"); - let name = format!("{}{}", prefix, magic::MAGIC.as_str()); - let listener = utils::abstract_namespace_socket(&name)?; - log::debug!("Daemon socket: {name}"); + log::debug!("Daemon socket: {}", constants::PATH_CP_SOCKET); + let listener = utils::unix_listener_from_path(constants::PATH_CP_SOCKET)?; Ok(listener) } @@ -170,9 +166,6 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> utils::log_raw(level as i32, &tag, &message)?; } } - DaemonSocketAction::ReadNativeBridge => { - stream.write_string(&context.native_bridge)?; - } DaemonSocketAction::GetProcessFlags => { let uid = stream.read_u32()? as i32; let mut flags = ProcessFlags::empty();