Replace native bridge with fuse + ptrace

This commit is contained in:
Nullptr
2023-10-20 13:49:03 +08:00
parent 77cb323506
commit 5f2dd50703
18 changed files with 452 additions and 301 deletions

View File

@@ -13,16 +13,7 @@ LOCAL_LDLIBS := -llog
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygisk_loader
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/loader))
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
LOCAL_STATIC_LIBRARIES := cxx common
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := zygisk_injector
LOCAL_MODULE := zygisk
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector))
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)

View File

@@ -8,32 +8,14 @@
namespace zygiskd {
bool sMagicRead = false;
static std::string sSocketName;
void ReadMagic() {
sMagicRead = true;
char magic[PATH_MAX]{0};
auto fp = fopen(kZygiskMagic, "r");
if (fp == nullptr) {
PLOGE("Open magic file");
return;
}
fgets(magic, PATH_MAX, fp);
fclose(fp);
sSocketName.append(LP_SELECT("zygiskd32", "zygiskd64")).append(magic);
LOGD("Socket name: %s", sSocketName.data());
}
int Connect(uint8_t retry) {
if (!sMagicRead) ReadMagic();
int fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
struct sockaddr_un addr{
.sun_family = AF_UNIX,
.sun_path={0},
};
strcpy(addr.sun_path + 1, sSocketName.data());
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
strcpy(addr.sun_path, kCPSocketPath);
socklen_t socklen = sizeof(addr);
while (retry--) {
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
@@ -66,16 +48,6 @@ namespace zygiskd {
return fd;
}
std::string ReadNativeBridge() {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("ReadNativeBridge");
return "";
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge);
return socket_utils::read_string(fd);
}
uint32_t GetProcessFlags(uid_t uid) {
UniqueFd fd = Connect(1);
if (fd == -1) {

View File

@@ -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<Module> ReadModules();
uint32_t GetProcessFlags(uid_t uid);

View File

@@ -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();
}

View File

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

View File

@@ -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' </dev/urandom | head -c 18)
echo -n "$MAGIC" > "$MODPATH/system/zygisk_magic"
ui_print "- Setting permissions"
chmod 0744 "$MODPATH/daemon.sh"
set_perm_recursive "$MODPATH/bin" 0 2000 0755 0755
set_perm_recursive "$MODPATH/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

View File

@@ -6,8 +6,6 @@ if [ "$ZYGISK_ENABLED" ]; then
fi
cd "$MODDIR"
getprop ro.dalvik.vm.native.bridge > /dev/.native_bridge
resetprop ro.dalvik.vm.native.bridge libzygisk_loader.so
if [ "$(which magisk)" ]; then
for file in ../*; do
@@ -21,3 +19,5 @@ if [ "$(which magisk)" ]; then
fi
done
fi
sh -c "bin/zygisk-fuse &"

View File

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

View File

@@ -8,8 +8,6 @@ if [ "$ZYGISK_ENABLED" ]; then
fi
cd "$MODDIR"
export NATIVE_BRIDGE=$(cat /dev/.native_bridge)
rm /dev/.native_bridge
if [ "$(which magisk)" ]; then
for file in ../*; do
@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

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

@@ -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<FileAttr> = LateInit::new();
static PCL_CONTENT: LateInit<Vec<u8>> = LateInit::new();
const ENTRIES: &[(u64, FileType, &str)] = &[
(INO_DIR, FileType::Directory, "."),
(INO_DIR, FileType::Directory, ".."),
(INO_PCL, FileType::RegularFile, "preloaded-classes"),
];
const TTL: Duration = Duration::from_secs(1);
impl Filesystem for DelegateFilesystem {
fn lookup(&mut self, _req: &Request<'_>, parent: u64, name: &OsStr, reply: ReplyEntry) {
if parent != INO_DIR {
reply.error(ENOENT);
return;
}
match name.as_str().unwrap() {
"preloaded-classes" => reply.entry(&TTL, &ATTR_PCL, 0),
_ => reply.error(ENOENT),
}
}
fn getattr(&mut self, _req: &Request<'_>, ino: u64, reply: ReplyAttr) {
match ino {
INO_DIR => reply.attr(&TTL, &ATTR_DIR),
INO_PCL => reply.attr(&TTL, &ATTR_PCL),
_ => reply.error(ENOENT),
}
}
fn open(&mut self, req: &Request<'_>, ino: u64, _flags: i32, reply: ReplyOpen) {
if ino == INO_PCL {
let pid = req.pid();
let process = format!("/proc/{}/cmdline", pid);
let process = fs::read_to_string(process).unwrap();
let process = &process[..process.find('\0').unwrap()];
info!("Process {} is reading preloaded-classes", process);
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<u64>, reply: ReplyData) {
let offset = offset as usize;
let size = size as usize;
if ino == INO_PCL {
let len = PCL_CONTENT.len();
if offset >= len {
reply.data(&[]);
} else {
let end = min(offset + size, len);
reply.data(&PCL_CONTENT[offset..end]);
}
} else {
reply.error(ENOENT);
}
}
fn readdir(&mut self, _req: &Request<'_>, ino: u64, _fh: u64, offset: i64, mut reply: ReplyDirectory) {
if ino != INO_DIR {
reply.error(ENOENT);
return;
}
for (i, entry) in ENTRIES.iter().enumerate().skip(offset as usize) {
if reply.add(entry.0, (i + 1) as i64, entry.1, entry.2) {
break;
}
}
reply.ok();
}
}
fn find_module_for_pid(pid: Pid, library: &str) -> Result<MapRange> {
let maps = get_process_maps(pid)?;
for map in maps.into_iter() {
if let Some(p) = map.filename() {
if p.as_str()?.contains(library) {
return Ok(map);
}
}
}
bail!("Cannot find module {library} for pid {pid}");
}
fn find_remote_procedure(
pid: Pid,
library: &str,
local_addr: usize,
) -> Result<usize> {
let local_module = find_module_for_pid(getpid().as_raw_nonzero().get(), library)?;
debug!(
"Identifed local range {library} ({:?}) at {:x}",
local_module.filename(),
local_module.start()
);
let remote_module = find_module_for_pid(pid, library)?;
debug!(
"Identifed remote range {library} ({:?}) at {:x}",
remote_module.filename(),
remote_module.start()
);
Ok(local_addr - local_module.start() + remote_module.start())
}
fn ptrace_zygote(pid: u32, is64: bool) -> Result<()> {
static LAST_32: Mutex<u32> = Mutex::new(0);
static LAST_64: Mutex<u32> = 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::<dl::AndroidDlextinfo>(),
)
};
frame.write_memory(buf_addr + 0x200, info)?;
let (regs, mut frame) = frame.invoke_remote(
dlopen_remote,
libc_base,
&[buf_addr, libc::RTLD_NOW as usize, buf_addr + 0x200],
)?;
let handle = regs.return_value();
debug!("Load zygisk into remote process: {:x}", handle);
let entry = CString::new("entry")?;
frame.write_memory(buf_addr, entry.as_bytes_with_nul())?;
let (regs, frame) = frame.invoke_remote(
dlsym_remote,
libc_base,
&[handle, buf_addr],
)?;
let entry = regs.return_value();
debug!("Call zygisk entry: {:x}", entry);
let (_, frame) = frame.invoke_remote(
entry,
libc_base,
&[handle],
)?;
// Cleanup
let _ = frame.invoke_remote(
munmap_remote,
libc_base,
&[buf_addr],
)?;
debug!("Cleaned up");
Ok(())
};
thread::spawn(move || {
if let Err(e) = worker() {
error!("Crashed: {:?}", e);
}
});
receiver.recv()?;
Ok(())
}
pub fn main() -> Result<()> {
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");
}

View File

@@ -1,19 +0,0 @@
use std::fs;
use anyhow::Result;
use crate::constants;
use crate::utils::LateInit;
pub static MAGIC: LateInit<String> = LateInit::new();
pub static PATH_TMP_DIR: LateInit<String> = LateInit::new();
pub static PATH_TMP_PROP: LateInit<String> = LateInit::new();
pub fn setup() -> Result<()> {
let name = fs::read_to_string(constants::ZYGISK_MAGIC)?;
let path_tmp_dir = format!("/dev/{}", name);
let path_tmp_prop = format!("{}/module.prop", path_tmp_dir);
MAGIC.init(name);
PATH_TMP_DIR.init(path_tmp_dir);
PATH_TMP_PROP.init(path_tmp_prop);
Ok(())
}

View File

@@ -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());
}
}

View File

@@ -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<T> std::ops::Deref for LateInit<T> {
}
}
pub fn random_string() -> String {
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
}
pub fn set_socket_create_context(context: &str) -> Result<()> {
let path = "/proc/thread-self/attr/sockcreate";
match fs::write(path, context) {
@@ -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<String> {
}
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<UnixListener> {
let addr = SocketAddrUnix::new_abstract_name(name.as_bytes())?;
pub fn unix_listener_from_path(path: &str) -> Result<UnixListener> {
let _ = fs::remove_file(path);
let addr = SocketAddrUnix::new(path)?;
let socket = socket(AddressFamily::UNIX, SocketType::STREAM, None)?;
bind_unix(&socket, &addr)?;
listen(&socket, 2)?;
chcon(path, "u:object_r:magisk_file:s0")?;
Ok(UnixListener::from(socket))
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32;
fn __system_property_set(name: *const c_char, value: *const c_char) -> u32;
}

View File

@@ -1,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<UnixListener> = LateInit::new();
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
pub async fn entry() -> Result<()> {
log::info!("Start zygisksu watchdog");
pub async fn main() -> Result<()> {
let result = run().await;
set_prop_hint(constants::STATUS_CRASHED)?;
result
}
async fn run() -> Result<()> {
info!("Start 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::<Pin<Box<dyn Future<Output=Result<()>>>>>::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")?;
}
}

View File

@@ -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<Module>,
}
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<OwnedFd> {
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let prefix = lp_select!("zygiskd32", "zygiskd64");
let name = format!("{}{}", prefix, magic::MAGIC.as_str());
let listener = utils::abstract_namespace_socket(&name)?;
log::debug!("Daemon socket: {name}");
log::debug!("Daemon socket: {}", constants::PATH_CP_SOCKET);
let listener = utils::unix_listener_from_path(constants::PATH_CP_SOCKET)?;
Ok(listener)
}
@@ -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();