From 70024cc082861e13d90ff16bbc52bdf33ee89e11 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Sun, 29 Jan 2023 22:38:00 +0800 Subject: [PATCH] Add injector --- loader/build.gradle.kts | 17 +++ loader/src/Android.mk | 33 +++++ loader/src/Application.mk | 14 +++ loader/src/common/daemon.cpp | 79 ++++++++++++ loader/src/common/dl.cpp | 51 ++++++++ loader/src/common/socket_utils.cpp | 114 +++++++++++++++++ loader/src/include/daemon.h | 69 ++++++++++ loader/src/include/dl.h | 7 ++ loader/src/include/logging.h | 30 +++++ loader/src/include/native_bridge_callbacks.h | 33 +++++ loader/src/include/socket_utils.h | 20 +++ loader/src/loader/loader.cpp | 96 ++++++++++++++ settings.gradle.kts | 1 + zygiskd/Cargo.toml | 1 + zygiskd/src/constants.rs | 5 + zygiskd/src/utils.rs | 26 +++- zygiskd/src/zygisk.rs | 126 +++++++++---------- 17 files changed, 653 insertions(+), 69 deletions(-) create mode 100644 loader/build.gradle.kts create mode 100644 loader/src/Android.mk create mode 100644 loader/src/Application.mk create mode 100644 loader/src/common/daemon.cpp create mode 100644 loader/src/common/dl.cpp create mode 100644 loader/src/common/socket_utils.cpp create mode 100644 loader/src/include/daemon.h create mode 100644 loader/src/include/dl.h create mode 100644 loader/src/include/logging.h create mode 100644 loader/src/include/native_bridge_callbacks.h create mode 100644 loader/src/include/socket_utils.h create mode 100644 loader/src/loader/loader.cpp diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts new file mode 100644 index 0000000..f05a864 --- /dev/null +++ b/loader/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + id("com.android.library") +} + +android { + buildFeatures { + prefab = true + } + + externalNativeBuild.ndkBuild { + path("src/Android.mk") + } +} + +dependencies { + implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") +} diff --git a/loader/src/Android.mk b/loader/src/Android.mk new file mode 100644 index 0000000..2202e1e --- /dev/null +++ b/loader/src/Android.mk @@ -0,0 +1,33 @@ +LOCAL_PATH := $(call my-dir) +define walk + $(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e))) +endef + +include $(CLEAR_VARS) +LOCAL_MODULE := common +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/common)) +LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) +LOCAL_STATIC_LIBRARIES := cxx +LOCAL_LDLIBS := -llog +include $(BUILD_STATIC_LIBRARY) + +include $(CLEAR_VARS) +LOCAL_MODULE := zygiskloader +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 := injector +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include +FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector)) +LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%) +LOCAL_STATIC_LIBRARIES := cxx common +LOCAL_LDLIBS := -llog +include $(BUILD_SHARED_LIBRARY) + +$(call import-module,prefab/cxx) diff --git a/loader/src/Application.mk b/loader/src/Application.mk new file mode 100644 index 0000000..b15ac15 --- /dev/null +++ b/loader/src/Application.mk @@ -0,0 +1,14 @@ +APP_CFLAGS := -Wall -Wextra +APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer +APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__ +APP_CPPFLAGS := -std=c++20 +APP_CONLYFLAGS := -std=c18 +APP_STL := none + +ifneq ($(NDK_DEBUG),1) +APP_CFLAGS += -Oz -flto +APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror +APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden +APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables +APP_LDFLAGS += -Wl,--exclude-libs,ALL -flto -Wl,--gc-sections -Wl,--strip-all +endif diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp new file mode 100644 index 0000000..b12a116 --- /dev/null +++ b/loader/src/common/daemon.cpp @@ -0,0 +1,79 @@ +#include +#include +#include + +#include "daemon.h" +#include "dl.h" +#include "socket_utils.h" + +namespace zygiskd { + + UniqueFd Connect(uint8_t retry) { + UniqueFd fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); + struct sockaddr_un addr{ + .sun_family = AF_UNIX, + .sun_path={0}, + }; + strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size()); + socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1; + + while (retry--) { + int r = connect(fd, reinterpret_cast(&addr), socklen); + if (r != -1) return r; + LOGW("retrying to connect to zygiskd, sleep 1s"); + sleep(1); + } + return -1; + } + + bool PingHeartbeat() { + auto fd = Connect(5); + if (fd == -1) { + PLOGE("Connect to zygiskd"); + return false; + } + return true; + } + + std::string ReadNativeBridge() { + auto fd = Connect(1); + if (fd == -1) { + PLOGE("ReadNativeBridge"); + return ""; + } + socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge); + return socket_utils::read_string(fd); + } + + UniqueFd ReadInjector() { + auto fd = Connect(1); + if (fd == -1) { + PLOGE("ReadInjector"); + return -1; + } + socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadInjector); + return socket_utils::recv_fd(fd); + } + + std::vector ReadModules() { + std::vector modules; + auto fd = Connect(1); + if (fd == -1) { + PLOGE("ReadModules"); + return modules; + } + socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules); + size_t len = socket_utils::read_usize(fd); + for (size_t i = 0; i < len; i++) { + std::string name = socket_utils::read_string(fd); + UniqueFd module_fd = socket_utils::recv_fd(fd); + auto handle = DlopenMem(module_fd, RTLD_NOW); + if (handle == nullptr) { + LOGW("Failed to dlopen module %s: %s", name.data(), dlerror()); + continue; + } + modules.emplace_back(name, handle); + } + return modules; + } +} diff --git a/loader/src/common/dl.cpp b/loader/src/common/dl.cpp new file mode 100644 index 0000000..4f85cb0 --- /dev/null +++ b/loader/src/common/dl.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include + +#include "dl.h" +#include "logging.h" + +extern "C" [[gnu::weak]] struct android_namespace_t* +//NOLINTNEXTLINE +__loader_android_create_namespace([[maybe_unused]] const char* name, + [[maybe_unused]] const char* ld_library_path, + [[maybe_unused]] const char* default_library_path, + [[maybe_unused]] uint64_t type, + [[maybe_unused]] const char* permitted_when_isolated_path, + [[maybe_unused]] android_namespace_t* parent, + [[maybe_unused]] const void* caller_addr); + +void* DlopenExt(const char* path, int flags) { + auto info = android_dlextinfo{}; + auto* dir = dirname(path); + auto* ns = &__loader_android_create_namespace == nullptr ? nullptr : + __loader_android_create_namespace(path, dir, nullptr, + 2, /* ANDROID_NAMESPACE_TYPE_SHARED */ + nullptr, nullptr, + reinterpret_cast(&DlopenExt)); + if (ns) { + info.flags = ANDROID_DLEXT_USE_NAMESPACE; + info.library_namespace = ns; + + LOGD("Open %s with namespace %p", path, ns); + } else { + LOGD("Cannot create namespace for %s", path); + } + + auto* handle = android_dlopen_ext(path, flags, &info); + if (handle) { + LOGD("dlopen %s: %p", path, handle); + } else { + LOGE("dlopen %s: %s", path, dlerror()); + } + return handle; +} + +void* DlopenMem(int memfd, int flags) { + char path[PATH_MAX]; + sprintf(path, "/proc/self/fd/%d", memfd); + return DlopenExt(path, flags); +} diff --git a/loader/src/common/socket_utils.cpp b/loader/src/common/socket_utils.cpp new file mode 100644 index 0000000..0d445b9 --- /dev/null +++ b/loader/src/common/socket_utils.cpp @@ -0,0 +1,114 @@ +#include +#include +#include + +#include "socket_utils.h" + +namespace socket_utils { + + ssize_t xread(int fd, void* buf, size_t count) { + size_t read_sz = 0; + ssize_t ret; + do { + ret = read(fd, (std::byte*) buf + read_sz, count - read_sz); + if (ret < 0) { + if (errno == EINTR) continue; + PLOGE("read"); + return ret; + } + read_sz += ret; + } while (read_sz != count && ret != 0); + if (read_sz != count) { + PLOGE("read (%d != %d)", count, read_sz); + } + return read_sz; + } + + ssize_t xwrite(int fd, const void* buf, size_t count) { + size_t write_sz = 0; + ssize_t ret; + do { + ret = write(fd, (std::byte*) buf + write_sz, count - write_sz); + if (ret < 0) { + if (errno == EINTR) continue; + PLOGE("write"); + return ret; + } + write_sz += ret; + } while (write_sz != count && ret != 0); + if (write_sz != count) { + PLOGE("write (%d != %d)", count, write_sz); + } + return write_sz; + } + + ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) { + int rec = recvmsg(sockfd, msg, flags); + if (rec < 0) PLOGE("recvmsg"); + return rec; + } + + void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) { + iovec iov = { + .iov_base = &cnt, + .iov_len = sizeof(cnt), + }; + msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = bufsz + }; + + xrecvmsg(sockfd, &msg, MSG_WAITALL); + cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); + + if (msg.msg_controllen != bufsz || + cmsg == nullptr || + cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) || + cmsg->cmsg_level != SOL_SOCKET || + cmsg->cmsg_type != SCM_RIGHTS) { + return nullptr; + } + + return CMSG_DATA(cmsg); + } + + template + inline T read_exact(int fd) { + T res; + return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : -1; + } + + template + inline bool write_exact(int fd, T val) { + return sizeof(T) == xwrite(fd, &val, sizeof(T)); + } + + size_t read_usize(int fd) { + return read_exact(fd); + } + + std::string read_string(int fd) { + auto len = read_exact(fd); + char buf[len + 1]; + buf[len] = '\0'; + xread(fd, buf, len); + return buf; + } + + bool write_u8(int fd, uint8_t val) { + return write_exact(fd, val); + } + + int recv_fd(int sockfd) { + char cmsgbuf[CMSG_SPACE(sizeof(int))]; + + void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1); + if (data == nullptr) return -1; + + int result; + memcpy(&result, data, sizeof(int)); + return result; + } +} diff --git a/loader/src/include/daemon.h b/loader/src/include/daemon.h new file mode 100644 index 0000000..26d7c7e --- /dev/null +++ b/loader/src/include/daemon.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#if defined(__LP64__) +# define LP_SELECT(lp32, lp64) lp64 +#else +# define LP_SELECT(lp32, lp64) lp32 +#endif + +constexpr std::string_view kZygiskSocket = LP_SELECT("zygisk32", "zygisk64") "placeholder123456"; + +class UniqueFd { + using Fd = int; +public: + UniqueFd() = default; + + UniqueFd(Fd fd) : fd_(fd) {} + + ~UniqueFd() { close(fd_); } + + // Disallow copy + UniqueFd(const UniqueFd&) = delete; + + UniqueFd& operator=(const UniqueFd&) = delete; + + // Allow move + UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); } + + UniqueFd& operator=(UniqueFd&& other) { + std::swap(fd_, other.fd_); + return *this; + } + + // Implict cast to Fd + operator const Fd&() const { return fd_; } + +private: + Fd fd_ = -1; +}; + +namespace zygiskd { + + struct Module { + std::string name; + void* handle; + + inline explicit Module(std::string name, void* handle) + : name(name), handle(handle) {} + }; + + enum class SocketAction { + HeartBeat, + ReadNativeBridge, + ReadInjector, + ReadModules, + RequestCompanionSocket, + }; + + bool PingHeartbeat(); + + std::string ReadNativeBridge(); + + UniqueFd ReadInjector(); + + std::vector ReadModules(); +} diff --git a/loader/src/include/dl.h b/loader/src/include/dl.h new file mode 100644 index 0000000..25dd6c6 --- /dev/null +++ b/loader/src/include/dl.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void *DlopenExt(const char *path, int flags); + +void *DlopenMem(int memfd, int flags); diff --git a/loader/src/include/logging.h b/loader/src/include/logging.h new file mode 100644 index 0000000..fe3fdb4 --- /dev/null +++ b/loader/src/include/logging.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#ifndef LOG_TAG +#define LOG_TAG "zygisksu" +#endif + +#ifdef LOG_DISABLED +#define LOGD(...) +#define LOGV(...) +#define LOGI(...) +#define LOGW(...) +#define LOGE(...) +#else +#ifndef NDEBUG +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) +#else +#define LOGD(...) +#define LOGV(...) +#endif +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) +#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) +#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) +#endif diff --git a/loader/src/include/native_bridge_callbacks.h b/loader/src/include/native_bridge_callbacks.h new file mode 100644 index 0000000..60ff4be --- /dev/null +++ b/loader/src/include/native_bridge_callbacks.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +template +struct NativeBridgeCallbacks; + +template<> +struct NativeBridgeCallbacks<__ANDROID_API_Q__> { + [[maybe_unused]] uint32_t version; + [[maybe_unused]] void *initialize; + [[maybe_unused]] void *loadLibrary; + [[maybe_unused]] void *getTrampoline; + [[maybe_unused]] void *isSupported; + [[maybe_unused]] void *getAppEnv; + [[maybe_unused]] void *isCompatibleWith; + [[maybe_unused]] void *getSignalHandler; + [[maybe_unused]] void *unloadLibrary; + [[maybe_unused]] void *getError; + [[maybe_unused]] void *isPathSupported; + [[maybe_unused]] void *initAnonymousNamespace; + [[maybe_unused]] void *createNamespace; + [[maybe_unused]] void *linkNamespaces; + [[maybe_unused]] void *loadLibraryExt; + [[maybe_unused]] void *getVendorNamespace; + [[maybe_unused]] void *getExportedNamespace; +}; + +template<> +struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> { + [[maybe_unused]] void *preZygoteFork; +}; diff --git a/loader/src/include/socket_utils.h b/loader/src/include/socket_utils.h new file mode 100644 index 0000000..ae6321d --- /dev/null +++ b/loader/src/include/socket_utils.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +#include "logging.h" + +namespace socket_utils { + + ssize_t xread(int fd, void *buf, size_t count); + + ssize_t xwrite(int fd, const void *buf, size_t count); + + size_t read_usize(int fd); + + std::string read_string(int fd); + + bool write_u8(int fd, uint8_t val); + + int recv_fd(int fd); +} diff --git a/loader/src/loader/loader.cpp b/loader/src/loader/loader.cpp new file mode 100644 index 0000000..342535f --- /dev/null +++ b/loader/src/loader/loader.cpp @@ -0,0 +1,96 @@ +#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 std::array kZygoteProcesses = {"zygote", "zygote32""zygote64", "usap32", "usap64"}; + + 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::any_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 { + LOGD("Ping heartbeat"); + if (!zygiskd::PingHeartbeat()) break; + + LOGI("Read native bridge"); + native_bridge = zygiskd::ReadNativeBridge(); + + LOGI("Load injector"); + auto injector = zygiskd::ReadInjector(); + if (injector != -1) break; + + auto handle = DlopenMem(injector, 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()); + break; + } + reinterpret_cast(entry)(); + } while (false); + + if (native_bridge.empty()) return; + LOGI("Load original native bridge: %s", native_bridge.data()); + sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW); + if (sOriginalBridge == nullptr) { + LOGE("dlopen failed: %s", dlerror()); + return; + } + + auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf"); + if (original_native_bridge_itf == nullptr) { + LOGE("dlsym failed: %s", dlerror()); + return; + } + + 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); +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a808014..d6c9796 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ dependencyResolutionManagement { rootProject.name = "ZygiskOnKernelSU" include( + ":loader", ":module", ":zygiskd", ) diff --git a/zygiskd/Cargo.toml b/zygiskd/Cargo.toml index b24c03a..17c2811 100644 --- a/zygiskd/Cargo.toml +++ b/zygiskd/Cargo.toml @@ -14,6 +14,7 @@ memfd = "0.6.2" nix = "0.26.2" num_enum = "0.5.9" passfd = "0.1.5" +rand = "0.8.5" [profile.release] strip = true diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index 3e7f780..32da0eb 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -3,17 +3,22 @@ use num_enum::TryFromPrimitive; pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge"; +pub const SOCKET_PLACEHOLDER: &str = "placeholder123456"; + pub const PATH_KSU_MODULE_DIR: &str = "/data/adb/ksu/modules"; pub const PATH_ZYGISKSU_DIR: &str = concatcp!(PATH_KSU_MODULE_DIR, "/zygisksu"); pub const PATH_ZYGISKWD: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskwd"); pub const PATH_ZYGISKD32: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd32"); pub const PATH_ZYGISKD64: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd64"); +pub const PATH_INJECTOR: &str = concatcp!(PATH_ZYGISKSU_DIR, "/libinjector.so"); pub const PATH_DAEMON_LOCK: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd.lock"); #[derive(Debug, Eq, PartialEq, TryFromPrimitive)] #[repr(u8)] pub enum DaemonSocketAction { + PingHeartbeat, ReadNativeBridge, + ReadInjector, ReadModules, RequestCompanionSocket, } diff --git a/zygiskd/src/utils.rs b/zygiskd/src/utils.rs index 5f2f959..fff8e15 100644 --- a/zygiskd/src/utils.rs +++ b/zygiskd/src/utils.rs @@ -2,6 +2,14 @@ use crate::constants; use anyhow::Result; use nix::unistd::gettid; use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command}; +use std::os::fd::FromRawFd; +use std::os::unix::net::UnixListener; +use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr}; +use rand::distributions::{Alphanumeric, DistString}; + +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"; @@ -16,7 +24,7 @@ pub fn set_socket_create_context(context: &str) -> Result<()> { } pub fn get_native_bridge() -> String { - std::env::var("NATIVE_BRIDGE").unwrap_or("0".to_string()) + std::env::var("NATIVE_BRIDGE").unwrap_or_default() } pub fn restore_native_bridge() -> Result<()> { @@ -29,6 +37,7 @@ pub trait UnixStreamExt { fn read_u8(&mut self) -> Result; fn read_u32(&mut self) -> Result; fn read_usize(&mut self) -> Result; + fn write_u8(&mut self, value: u8) -> Result<()>; fn write_usize(&mut self, value: usize) -> Result<()>; } @@ -51,8 +60,23 @@ impl UnixStreamExt for UnixStream { Ok(usize::from_ne_bytes(buf)) } + fn write_u8(&mut self, value: u8) -> 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(()) } } + +// TODO: Replace with SockAddrExt::from_abstract_name when it's stable +pub fn abstract_namespace_socket(name: &str) -> Result { + let addr = UnixAddr::new_abstract(name.as_bytes())?; + let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?; + nix::sys::socket::bind(socket, &addr)?; + nix::sys::socket::listen(socket, 2)?; + let listener = unsafe { UnixListener::from_raw_fd(socket) }; + Ok(listener) +} diff --git a/zygiskd/src/zygisk.rs b/zygiskd/src/zygisk.rs index f535eab..e617490 100644 --- a/zygiskd/src/zygisk.rs +++ b/zygiskd/src/zygisk.rs @@ -13,14 +13,13 @@ use std::sync::Arc; use std::thread; use std::ffi::c_void; use std::fs; -use std::os::fd::FromRawFd; +use std::os::fd::IntoRawFd; use std::os::unix::{ net::{UnixListener, UnixStream}, prelude::AsRawFd, }; use std::path::PathBuf; use std::process::Command; -use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr}; type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32); @@ -32,8 +31,7 @@ struct Module { struct Context { native_bridge: String, - modules: Arc>, - listener: UnixListener, + modules: Vec, } pub fn start(is64: bool) -> Result<()> { @@ -44,17 +42,25 @@ pub fn start(is64: bool) -> Result<()> { log::info!("Load modules"); let modules = load_modules(arch)?; + let context = Context { + native_bridge: utils::get_native_bridge(), + modules, + }; + let context = Arc::new(context); + log::info!("Create socket"); let listener = create_daemon_socket(is64)?; - let context = Context { - native_bridge: utils::get_native_bridge(), - modules: Arc::new(modules), - listener, - }; - - log::info!("Start to listen zygote connections"); - handle_daemon_actions(context)?; + log::info!("Handle zygote connections"); + for stream in listener.incoming() { + let stream = stream?; + let context = Arc::clone(&context); + thread::spawn(move || { + if let Err(e) = handle_daemon_action(stream, &context) { + log::warn!("Error handling daemon action: {e}"); + } + }); + } Ok(()) } @@ -166,68 +172,52 @@ fn preload_module(memfd: &Memfd) -> Result> { fn create_daemon_socket(is64: bool) -> Result { utils::set_socket_create_context("u:r:zygote:s0")?; - let name = if is64 { "zygiskd64" } else { "zygiskd32" }; - // TODO: Replace with SockAddrExt::from_abstract_name when it's stable - let addr = UnixAddr::new_abstract(name.as_bytes())?; - let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?; - nix::sys::socket::bind(socket, &addr)?; - nix::sys::socket::listen(socket, 2)?; - log::debug!("Listening on {}", addr); - log::debug!("Socket fd: {}", socket); - let listener = unsafe { UnixListener::from_raw_fd(socket) }; + let suffix = if is64 { "zygiskd64" } else { "zygiskd32" }; + let name = String::from(suffix) + constants::SOCKET_PLACEHOLDER; + let listener = utils::abstract_namespace_socket(&name)?; Ok(listener) } -fn handle_daemon_actions(context: Context) -> Result<()> { - loop { - let (mut stream, _) = context.listener.accept()?; - let action = stream.read_u8()?; - match DaemonSocketAction::try_from(action) { - // First connection from zygote - Ok(DaemonSocketAction::ReadNativeBridge) => { - restore_native_bridge()?; - stream.write_usize(context.native_bridge.len())?; - stream.write_all(context.native_bridge.as_bytes())?; +fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> { + let action = stream.read_u8()?; + match DaemonSocketAction::try_from(action) { + Ok(DaemonSocketAction::PingHeartbeat) => { + // Do nothing + } + Ok(DaemonSocketAction::ReadNativeBridge) => { + restore_native_bridge()?; + stream.write_usize(context.native_bridge.len())?; + stream.write_all(context.native_bridge.as_bytes())?; + } + Ok(DaemonSocketAction::ReadInjector) => { + let so_path = PathBuf::from(constants::PATH_INJECTOR); + let memfd = create_memfd("injector", &so_path)?; + stream.send_fd(memfd.into_raw_fd())?; + } + Ok(DaemonSocketAction::ReadModules) => { + stream.write_usize(context.modules.len())?; + for module in context.modules.iter() { + stream.write_usize(module.name.len())?; + stream.write_all(module.name.as_bytes())?; + stream.send_fd(module.memfd.as_raw_fd())?; } - Ok(DaemonSocketAction::ReadModules) => { - stream.write_usize(context.modules.len())?; - for module in context.modules.iter() { - stream.write_usize(module.name.len())?; - stream.write_all(module.name.as_bytes())?; - stream.send_fd(module.memfd.as_raw_fd())?; + } + Ok(DaemonSocketAction::RequestCompanionSocket) => { + let index = stream.read_usize()?; + let module = &context.modules[index]; + log::debug!("New companion request from module {}", module.name); + + match module.companion_entry { + Some(entry) => { + stream.write_u8(1)?; + unsafe { entry(stream.as_raw_fd()); } + } + None => { + stream.write_u8(0)?; } } - Ok(DaemonSocketAction::RequestCompanionSocket) => { - let (server, client) = UnixStream::pair()?; - stream.send_fd(client.as_raw_fd())?; - let modules_ref = Arc::clone(&context.modules); - thread::spawn(move || { - if let Err(e) = create_companion(server, modules_ref.as_ref()) { - log::warn!("Companion thread exited: {e}"); - } - }); - } - Err(_) => bail!("Invalid action code: {action}") - } - } -} - -fn create_companion(mut server: UnixStream, modules: &Vec) -> Result<()> { - loop { - let index = match server.read_usize() { - Ok(index) => index, - Err(_) => return Ok(()), // EOF - }; - let module = &modules[index]; - log::debug!("New companion request from module {}", module.name); - - match module.companion_entry { - Some(entry) => { - let (sock_app, sock_companion) = UnixStream::pair()?; - server.send_fd(sock_app.as_raw_fd())?; - unsafe { entry(sock_companion.as_raw_fd()); } - } - None => (), } + Err(_) => bail!("Invalid action code: {action}") } + Ok(()) }