add: base for CMake building (non-working)

This commit will have a rebase soon.
This commit is contained in:
ThePedroo
2024-07-25 00:29:10 -03:00
parent 5d0adacf4a
commit 42a5ab989f
32 changed files with 1696 additions and 1399 deletions

View File

@@ -1,3 +0,0 @@
[build]
target-dir = "build/intermediates/rust"
target = "aarch64-linux-android"

View File

@@ -1,37 +0,0 @@
[package]
name = "zygiskd"
authors = ["Nullptr"]
version = "1.0.0"
edition = "2021"
rust-version = "1.69"
[dependencies]
csv = "1.3.0"
serde = { version = "1.0.130", features = ["derive"] }
android_logger = "0.13"
anyhow = { version = "1.0", features = ["backtrace"] }
bitflags = { version = "2.3" }
const_format = "0.2"
futures = "0.3"
konst = "0.3"
lazy_static = "1.4"
libc = "0.2"
log = "0.4"
memfd = "0.6"
num_enum = "0.5"
passfd = "0.1"
proc-maps = "0.3"
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] }
tokio = { version = "1.28", features = ["full"] }
[profile.dev]
strip = false
panic = "abort"
[profile.release]
strip = false
debug = true
panic = "abort"
opt-level = "z"
lto = true

View File

@@ -1,70 +0,0 @@
plugins {
alias(libs.plugins.agp.lib)
alias(libs.plugins.rust.android)
}
val minAPatchVersion: Int by rootProject.extra
val minKsuVersion: Int by rootProject.extra
val maxKsuVersion: Int by rootProject.extra
val minMagiskVersion: Int by rootProject.extra
val verCode: Int by rootProject.extra
val verName: String by rootProject.extra
val commitHash: String by rootProject.extra
android.buildFeatures {
androidResources = false
buildConfig = false
}
cargo {
module = "."
pythonCommand = "python3"
libname = "zygiskd"
targetIncludes = arrayOf("zygiskd")
targets = listOf("arm64", "arm", "x86", "x86_64")
targetDirectory = "build/intermediates/rust"
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
profile = if (isDebug) "debug" else "release"
exec = { spec, _ ->
spec.environment("ANDROID_NDK_HOME", android.ndkDirectory.path)
spec.environment("MIN_APATCH_VERSION", minAPatchVersion)
spec.environment("MIN_KSU_VERSION", minKsuVersion)
spec.environment("MAX_KSU_VERSION", maxKsuVersion)
spec.environment("MIN_MAGISK_VERSION", minMagiskVersion)
spec.environment("ZKSU_VERSION", "$verName-$verCode-$commitHash-$profile")
}
}
afterEvaluate {
task<Task>("buildAndStrip") {
dependsOn(":zygiskd:cargoBuild")
val isDebug = gradle.startParameter.taskNames.any { it.toLowerCase().contains("debug") }
doLast {
val dir = File(buildDir, "rustJniLibs/android")
val prebuilt = File(android.ndkDirectory, "toolchains/llvm/prebuilt").listFiles()!!.first()
val binDir = File(prebuilt, "bin")
val symbolDir = File(buildDir, "symbols/${if (isDebug) "debug" else "release"}")
symbolDir.mkdirs()
val suffix = if (prebuilt.name.contains("windows")) ".exe" else ""
val strip = File(binDir, "llvm-strip$suffix")
val objcopy = File(binDir, "llvm-objcopy$suffix")
dir.listFiles()!!.forEach {
if (!it.isDirectory) return@forEach
val symbolPath = File(symbolDir, "${it.name}/zygiskd.debug")
symbolPath.parentFile.mkdirs()
exec {
workingDir = it
commandLine(objcopy, "--only-keep-debug", "zygiskd", symbolPath)
}
exec {
workingDir = it
commandLine(strip, "--strip-all", "zygiskd")
}
exec {
workingDir = it
commandLine(objcopy, "--add-gnu-debuglink", symbolPath, "zygiskd")
}
}
}
}
}

85
zygiskd/build.gradlew.kts Normal file
View File

@@ -0,0 +1,85 @@
import java.nio.file.Paths
import org.gradle.internal.os.OperatingSystem
plugins {
alias(libs.plugins.agp.lib)
}
val verCode: Int by rootProject.extra
val verName: String by rootProject.extra
val commitHash: String by rootProject.extra
fun Project.findInPath(executable: String, property: String): String? {
val pathEnv = System.getenv("PATH")
return pathEnv.split(File.pathSeparator).map { folder ->
Paths.get("${folder}${File.separator}${executable}${if (OperatingSystem.current().isWindows) ".exe" else ""}")
.toFile()
}.firstOrNull { path ->
path.exists()
}?.absolutePath ?: properties.getOrDefault(property, null) as? String?
}
val ccachePath by lazy {
project.findInPath("ccache", "ccache.path")?.also {
println("loader: Use ccache: $it")
}
}
val defaultCFlags = arrayOf(
"-Wall", "-Wextra",
"-fno-rtti", "-fno-exceptions",
"-fno-stack-protector", "-fomit-frame-pointer",
"-Wno-builtin-macro-redefined", "-D__FILE__=__FILE_NAME__",
"-O0", "-g"
)
val releaseFlags = arrayOf(
"-Oz", "-flto",
"-Wno-unused", "-Wno-unused-parameter",
"-fvisibility=hidden", "-fvisibility-inlines-hidden",
"-fno-unwind-tables", "-fno-asynchronous-unwind-tables",
"-Wl,--exclude-libs,ALL", "-Wl,--gc-sections", "-Wl,--strip-all"
)
android {
buildFeatures {
androidResources = false
buildConfig = false
prefab = true
}
externalNativeBuild.cmake {
path("src/CMakeLists.txt")
}
defaultConfig {
externalNativeBuild.cmake {
arguments += "-DANDROID_STL=none"
arguments += "-DLSPLT_STANDALONE=ON"
cFlags("-std=c18", *defaultCFlags)
cppFlags("-std=c++20", *defaultCFlags)
ccachePath?.let {
arguments += "-DNDK_CCACHE=$it"
}
}
}
buildTypes {
debug {
externalNativeBuild.cmake {
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-debug"
}
}
release {
externalNativeBuild.cmake {
cFlags += releaseFlags
cppFlags += releaseFlags
arguments += "-DZKSU_VERSION=$verName-$verCode-$commitHash-release"
}
}
}
}
dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
}

2
zygiskd/src/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
zygiskd64
zygiskd32

View File

@@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.22.1)
project("zygiskd")
find_package(cxx REQUIRED CONFIG)
add_definitions(-DZKSU_VERSION=\"${ZKSU_VERSION}\")
aux_source_directory(common COMMON_SRC_LIST)
add_library(common STATIC ${COMMON_SRC_LIST})
target_include_directories(common PRIVATE include)
target_link_libraries(log)
aux_source_directory(ptracer PTRACER_SRC_LIST)
add_executable(libzygisk_ptrace.so ${PTRACER_SRC_LIST})
target_include_directories(libzygisk_ptrace.so PRIVATE include)
target_link_libraries(libzygisk_ptrace.so cxx::cxx log common)

24
zygiskd/src/LICENSE Normal file
View File

@@ -0,0 +1,24 @@
BSD 2-Clause License
Copyright (c) 2024, The PerformanC Organization
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

33
zygiskd/src/Makefile Normal file
View File

@@ -0,0 +1,33 @@
CC := ~/Android/Sdk/ndk/27.0.11902837/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android34-clang
# FILES = root_impl/common.c \
# root_impl/kernelsu.c \
# companion.c \
# dl.c \
# main.c \
# utils.c \
# zygiskd.c
FILES = root_impl/*.c \
*.c
CFLAGS = -D_GNU_SOURCE -std=c99 -Wpedantic -Wall -Wextra -Werror -Wformat -Wuninitialized -Wshadow -Wno-zero-length-array -Wno-fixed-enum-extension -Iroot_impl -llog
all: CFLAGS += -DDEBUG=0 -O3 -flto=thin -Wl,--strip-all
all:
$(CC) $(CFLAGS) $(FILES) -o zygiskd64
debug: CFLAGS += -DDEBUG=1 -g -O0
debug:
$(CC) $(CFLAGS) $(FILES) -o zygiskd64
32bit: CFLAGS += -m32 -DDEBUG=0 -O3 -flto=thin -Wl,--strip-all
32bit:
$(CC) $(CFLAGS) $(FILES) -o zygiskd32
32bit-debug: CFLAGS += -m32 -DDEBUG=1 -g -O0
32bit-debug:
$(CC) $(CFLAGS) $(FILES) -o zygiskd32
clean:
rm -f zygiskd

125
zygiskd/src/companion.c Normal file
View File

@@ -0,0 +1,125 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <errno.h>
#include <unistd.h>
#include <linux/limits.h>
#include <pthread.h>
#include <android/log.h>
#include "companion.h"
#include "dl.h"
#include "utils.h"
typedef void (*ZygiskCompanionEntryFn)(int);
ZygiskCompanionEntryFn load_module(int fd) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
void *handle = android_dlopen(path, RTLD_NOW);
void *entry = dlsym(handle, "zygisk_companion_entry");
if (entry == NULL) return NULL;
return (ZygiskCompanionEntryFn)entry;
}
void *ExecuteNew(void *arg) {
int fd = *((int *)arg);
struct stat st0;
if (fstat(fd, &st0) == -1) {
LOGE("Failed to stat client fd\n");
free(arg);
exit(0);
}
entry(fd);
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
struct stat st1;
if (fstat(fd, &st1) == -1) {
LOGE("Failed to stat client fd\n");
free(arg);
exit(0);
}
if (st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino) {
close(fd);
}
free(arg);
return NULL;
}
void entry(int fd) {
LOGI("companion entry fd: |%d|\n", fd);
char name[256 + 1];
/* INFO: Getting stuck here */
ssize_t ret = read_string(fd, name, sizeof(name) - 1);
if (ret == -1) return;
name[ret] = '\0';
LOGI("Companion process requested for `%s`\n", name);
int library_fd;
recv_fd(fd, &library_fd);
LOGI("Library fd: %d\n", library_fd);
ZygiskCompanionEntryFn entry = load_module(library_fd);
LOGI("Library loaded\n");
close(library_fd);
LOGI("Library closed\n");
if (entry == NULL) {
LOGI("No companion entry for: %s\n", name);
write(fd, (void *)0, 1);
return;
}
LOGI("Companion process created for: %s\n", name);
uint8_t response = 1;
write(fd, &response, sizeof(response));
while (1) {
int client_fd;
recv_fd(fd, &client_fd);
LOGI("New companion request from module \"%s\" with fd \"%d\"\n", name, client_fd);
write(fd, &response, sizeof(response));
int *client_fd_ptr = malloc(sizeof(int));
*client_fd_ptr = client_fd;
LOGI("Creating new thread for companion request\n");
pthread_t thread;
pthread_create(&thread, NULL, ExecuteNew, (void *)client_fd_ptr);
pthread_detach(thread);
}
}

6
zygiskd/src/companion.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef COMPANION_H
#define COMPANION_H
void entry(int fd);
#endif /* COMPANION_H */

View File

@@ -1,73 +0,0 @@
use crate::dl;
use crate::utils::{check_unix_socket, UnixStreamExt};
use anyhow::Result;
use passfd::FdPassingExt;
use rustix::fs::fstat;
use std::ffi::c_void;
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use std::os::unix::net::UnixStream;
use std::thread;
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
pub fn entry(fd: i32) {
log::info!("companion entry fd={}", fd);
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
let name = stream.read_string().expect("read name");
let library = stream.recv_fd().expect("receive library fd");
let entry = load_module(library).expect("load module");
unsafe { libc::close(library) };
let entry = match entry {
Some(entry) => {
log::debug!("Companion process created for `{name}`");
stream.write_u8(1).expect("reply 1");
entry
}
None => {
log::debug!("No companion entry for `{name}`");
stream.write_u8(0).expect("reply 0");
std::process::exit(0);
}
};
loop {
if !check_unix_socket(&stream, true) {
log::info!("Something bad happened in zygiskd, terminate companion");
std::process::exit(0);
}
let fd = stream.recv_fd().expect("recv fd");
log::trace!("New companion request from module `{name}` fd=`{fd}`");
let mut stream = unsafe { UnixStream::from_raw_fd(fd) };
stream.write_u8(1).expect("reply success");
thread::spawn(move || {
let st0 = fstat(&stream).expect("failed to stat stream");
unsafe {
entry(stream.as_raw_fd());
}
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if let Ok(st1) = fstat(&stream) {
if st0.st_dev != st1.st_dev || st0.st_ino != st1.st_ino {
std::mem::forget(stream);
}
}
});
}
}
fn load_module(fd: RawFd) -> Result<Option<ZygiskCompanionEntryFn>> {
unsafe {
let path = format!("/proc/self/fd/{fd}");
let handle = dl::dlopen(&path, libc::RTLD_NOW)?;
let symbol = std::ffi::CString::new("zygisk_companion_entry")?;
let entry = libc::dlsym(handle, symbol.as_ptr());
if entry.is_null() {
return Ok(None);
}
let fnptr = std::mem::transmute::<*mut c_void, ZygiskCompanionEntryFn>(entry);
Ok(Some(fnptr))
}
}

70
zygiskd/src/constants.h Normal file
View File

@@ -0,0 +1,70 @@
#ifndef CONSTANTS_H
#define CONSTANTS_H
#include <android/log.h>
#define bool _Bool
#define true 1
#define false 0
// #define MIN_APATCH_VERSION (atoi(getenv("MIN_APATCH_VERSION")))
// #define MIN_KSU_VERSION (atoi(getenv("MIN_KSU_VERSION")))
// #define MAX_KSU_VERSION (atoi(getenv("MAX_KSU_VERSION")))
// #define MIN_MAGISK_VERSION (atoi(getenv("MIN_MAGISK_VERSION")))
// #define ZKSU_VERSION (getenv("ZKSU_VERSION"))
#define MIN_APATCH_VERSION 0
// val minKsudVersion by extra(11425)
// val maxKsuVersion by extra(20000)
#define MIN_KSU_VERSION 11425
#define MAX_KSU_VERSION 20000
#define MIN_MAGISK_VERSION 0
#define ZKSU_VERSION "1.0.0"
#if DEBUG == false
#define MAX_LOG_LEVEL ANDROID_LOG_VERBOSE
#else
#define MAX_LOG_LEVEL ANDROID_LOG_INFO
#endif
#if (defined(__LP64__) || defined(_LP64))
#define lp_select(a, b) b
#else
#define lp_select(a, b) a
#endif
#define ZYGOTE_INJECTED lp_select(5, 4)
#define DAEMON_SET_INFO lp_select(7, 6)
#define DAEMON_SET_ERROR_INFO lp_select(9, 8)
#define SYSTEM_SERVER_STARTED 10
enum DaemonSocketAction {
PingHeartbeat,
RequestLogcatFd,
GetProcessFlags,
GetInfo,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted
};
enum ProcessFlags: uint32_t {
PROCESS_GRANTED_ROOT = (1u << 0),
PROCESS_ON_DENYLIST = (1u << 1),
PROCESS_IS_MANAGER = (1u << 28),
PROCESS_ROOT_IS_APATCH = (1u << 27),
PROCESS_ROOT_IS_KSU = (1u << 29),
PROCESS_ROOT_IS_MAGISK = (1u << 30),
PROCESS_IS_SYS_UI = (1u << 31),
PROCESS_IS_SYSUI = (1u << 31)
};
enum RootImplState {
Supported,
TooOld,
Abnormal
};
#endif /* CONSTANTS_H */

View File

@@ -1,51 +0,0 @@
use crate::lp_select;
use bitflags::bitflags;
use konst::primitive::parse_i32;
use konst::unwrap_ctx;
use log::LevelFilter;
use num_enum::TryFromPrimitive;
pub const MIN_APATCH_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_APATCH_VERSION")));
pub const MIN_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_KSU_VERSION")));
pub const MAX_KSU_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MAX_KSU_VERSION")));
pub const MIN_MAGISK_VERSION: i32 = unwrap_ctx!(parse_i32(env!("MIN_MAGISK_VERSION")));
pub const ZKSU_VERSION: &str = env!("ZKSU_VERSION");
#[cfg(debug_assertions)]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
#[cfg(not(debug_assertions))]
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
pub const PATH_MODULES_DIR: &str = "..";
pub const ZYGOTE_INJECTED: i32 = lp_select!(5, 4);
pub const DAEMON_SET_INFO: i32 = lp_select!(7, 6);
pub const DAEMON_SET_ERROR_INFO: i32 = lp_select!(9, 8);
pub const SYSTEM_SERVER_STARTED: i32 = 10;
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
#[repr(u8)]
pub enum DaemonSocketAction {
PingHeartbeat,
RequestLogcatFd,
GetProcessFlags,
GetInfo,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
SystemServerStarted,
}
// Zygisk process flags
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ProcessFlags: u32 {
const PROCESS_GRANTED_ROOT = 1 << 0;
const PROCESS_ON_DENYLIST = 1 << 1;
const PROCESS_IS_MANAGER = 1 << 28;
const PROCESS_ROOT_IS_APATCH = 1 << 27;
const PROCESS_ROOT_IS_KSU = 1 << 29;
const PROCESS_ROOT_IS_MAGISK = 1 << 30;
const PROCESS_IS_SYSUI = 1 << 31;
}
}

58
zygiskd/src/dl.c Normal file
View File

@@ -0,0 +1,58 @@
#include <stdlib.h>
#include <sys/types.h>
#include <libgen.h>
#include <dlfcn.h>
#define ANDROID_NAMESPACE_TYPE_SHARED 0x2
#define ANDROID_DLEXT_USE_NAMESPACE 0x200
struct AndroidNamespace {
u_int8_t _unused[0];
};
struct AndroidDlextinfo {
u_int64_t flags;
void *reserved_addr;
size_t reserved_size;
int relro_fd;
int library_fd;
off64_t library_fd_offset;
struct AndroidNamespace *library_namespace;
};
void *android_dlopen_ext(const char *filename, int flags, const struct AndroidDlextinfo *extinfo);
void *android_dlopen(char *path, u_int32_t flags) {
char *dir = dirname(path);
struct AndroidDlextinfo info = {
.flags = 0,
.reserved_addr = NULL,
.reserved_size = 0,
.relro_fd = 0,
.library_fd = 0,
.library_fd_offset = 0,
.library_namespace = NULL,
};
void *android_create_namespace_fn = dlsym(RTLD_DEFAULT, "__loader_android_create_namespace");
if (android_create_namespace_fn != NULL) {
void *ns = ((void *(*)(const char *, const char *, const char *, u_int32_t, void *, void *, void *))android_create_namespace_fn)(
path,
dir,
NULL,
ANDROID_NAMESPACE_TYPE_SHARED,
NULL,
NULL,
(void *)&android_dlopen
);
if (ns != NULL) {
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
info.library_namespace = ns;
}
}
return android_dlopen_ext(path, flags, &info);
}

6
zygiskd/src/dl.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef DL_H
#define DL_H
void *android_dlopen(char *path, u_int32_t flags);
#endif /* DL_H */

View File

@@ -1,85 +0,0 @@
use anyhow::{bail, Result};
use std::ffi::{c_char, c_void};
pub const ANDROID_NAMESPACE_TYPE_SHARED: u64 = 0x2;
pub const ANDROID_DLEXT_USE_NAMESPACE: u64 = 0x200;
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct AndroidNamespace {
_unused: [u8; 0],
}
#[repr(C)]
pub struct AndroidDlextinfo {
pub flags: u64,
pub reserved_addr: *mut c_void,
pub reserved_size: libc::size_t,
pub relro_fd: libc::c_int,
pub library_fd: libc::c_int,
pub library_fd_offset: libc::off64_t,
pub library_namespace: *mut AndroidNamespace,
}
extern "C" {
pub fn android_dlopen_ext(
filename: *const c_char,
flags: libc::c_int,
extinfo: *const AndroidDlextinfo,
) -> *mut c_void;
}
type AndroidCreateNamespaceFn = unsafe extern "C" fn(
*const c_char, // name
*const c_char, // ld_library_path
*const c_char, // default_library_path
u64, // type
*const c_char, // permitted_when_isolated_path
*mut AndroidNamespace, // parent
*const c_void, // caller_addr
) -> *mut AndroidNamespace;
pub unsafe fn dlopen(path: &str, flags: i32) -> Result<*mut c_void> {
let filename = std::ffi::CString::new(path)?;
let filename = filename.as_ptr() as *mut _;
let dir = libc::dirname(filename);
let mut info = AndroidDlextinfo {
flags: 0,
reserved_addr: std::ptr::null_mut(),
reserved_size: 0,
relro_fd: 0,
library_fd: 0,
library_fd_offset: 0,
library_namespace: std::ptr::null_mut(),
};
let android_create_namespace_fn = libc::dlsym(
libc::RTLD_DEFAULT,
std::ffi::CString::new("__loader_android_create_namespace")?.as_ptr(),
);
let android_create_namespace_fn: AndroidCreateNamespaceFn =
std::mem::transmute(android_create_namespace_fn);
let ns = android_create_namespace_fn(
filename,
dir,
std::ptr::null(),
ANDROID_NAMESPACE_TYPE_SHARED,
std::ptr::null(),
std::ptr::null_mut(),
&dlopen as *const _ as *const c_void,
);
if ns != std::ptr::null_mut() {
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
info.library_namespace = ns;
log::debug!("Open {} with namespace {:p}", path, ns);
} else {
log::debug!("Cannot create namespace for {}", path);
};
let result = android_dlopen_ext(filename, flags, &info);
if result.is_null() {
let e = std::ffi::CStr::from_ptr(libc::dlerror()).to_string_lossy();
bail!(e);
}
Ok(result)
}

86
zygiskd/src/main.c Normal file
View File

@@ -0,0 +1,86 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <android/log.h>
#include "root_impl/common.h"
#include "companion.h"
#include "zygiskd.h"
#include "utils.h"
int __android_log_print(int prio, const char *tag, const char *fmt, ...);
int main(int argc, char *argv[]) {
errno = 0;
/* Initialize android logger */
LOGI("Initializing zygiskd\n");
LOGI("Argc: %d\n", argc);
for (int i = 0; i < argc; i++) {
LOGI("argv[%d] = %s\n", i, argv[i]);
}
if (argc > 1) {
if (strcmp(argv[1], "companion") == 0) {
if (argc < 3) {
LOGI("Usage: zygiskd companion <fd>\n");
return 1;
}
int fd = atoi(argv[2]);
entry(fd);
return 0;
}
else if (strcmp(argv[1], "version") == 0) {
LOGI("ReZygisk Daemon %s\n", ZKSU_VERSION);
return 0;
}
else if (strcmp(argv[1], "root") == 0) {
root_impls_setup();
enum RootImpl impl = get_impl();
switch (impl) {
case None: {
LOGI("No root implementation found.\n");
return 1;
}
case Multiple: {
LOGI("Multiple root implementations found.\n");
return 1;
}
case KernelSU: {
LOGI("KernelSU root implementation found.\n");
return 0;
}
}
return 0;
}
else {
LOGI("Usage: zygiskd [companion|version|root]\n");
return 0;
}
}
switch_mount_namespace((pid_t)1);
root_impls_setup();
zygiskd_start();
return 0;
}

View File

@@ -1,45 +0,0 @@
mod companion;
mod constants;
mod dl;
mod root_impl;
mod utils;
mod zygiskd;
use crate::constants::ZKSU_VERSION;
fn init_android_logger(tag: &str) {
android_logger::init_once(
android_logger::Config::default()
.with_max_level(constants::MAX_LOG_LEVEL)
.with_tag(tag),
);
}
fn start() {
let args: Vec<String> = std::env::args().collect();
if args.len() == 3 && args[1] == "companion" {
let fd: i32 = args[2].parse().unwrap();
companion::entry(fd);
return;
} else if args.len() == 2 && args[1] == "version" {
println!("ReZygisk daemon {}", ZKSU_VERSION);
return;
} else if args.len() == 2 && args[1] == "root" {
root_impl::setup();
println!("root impl: {:?}", root_impl::get_impl());
return;
}
utils::switch_mount_namespace(1).expect("switch mnt ns");
root_impl::setup();
log::info!("current root impl: {:?}", root_impl::get_impl());
zygiskd::main().expect("zygiskd main");
}
fn main() {
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
init_android_logger(nice_name);
start();
}

View File

@@ -1,141 +0,0 @@
use std::process::{Command, Stdio};
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use serde::Deserialize;
use crate::constants::MIN_APATCH_VERSION;
pub enum Version {
Supported,
TooOld,
Abnormal,
}
fn parse_version(output: &str) -> i32 {
let mut version: Option<i32> = None;
for line in output.lines() {
if let Some(num) = line.trim().split_whitespace().last() {
if let Ok(v) = num.parse::<i32>() {
version = Some(v);
break;
}
}
}
version.unwrap_or_default()
}
fn read_su_path() -> Result<String, io::Error> {
let file = File::open("/data/adb/ap/su_path")?;
let mut reader = BufReader::new(file);
let mut su_path = String::new();
reader.read_line(&mut su_path)?;
Ok(su_path.trim().to_string())
}
pub fn get_apatch() -> Option<Version> {
let default_su_path = String::from("/system/bin/su");
let su_path = read_su_path().ok()?;
let output = Command::new(&su_path)
.arg("-v")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
let stdout = String::from_utf8(output.stdout).ok()?;
if !stdout.contains("APatch") {
return None;
}
let output1 = Command::new("/data/adb/apd")
.arg("-V")
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output()
.ok()?;
let stdout1 = String::from_utf8(output1.stdout).ok()?;
if su_path == default_su_path {
let version = parse_version(&stdout1);
const MAX_OLD_VERSION: i32 = MIN_APATCH_VERSION - 1;
match version {
0 => Some(Version::Abnormal),
v if v >= MIN_APATCH_VERSION && v <= 999999 => Some(Version::Supported),
v if v >= 1 && v <= MAX_OLD_VERSION => Some(Version::TooOld),
_ => None,
}
} else {
return None;
}
}
#[derive(Deserialize)]
#[allow(dead_code)]
struct PackageConfig {
pkg: String,
exclude: i32,
allow: i32,
uid: i32,
to_uid: i32,
sctx: String,
}
fn read_package_config() -> Result<Vec<PackageConfig>, std::io::Error> {
let file = File::open("/data/adb/ap/package_config")?;
let mut reader = csv::Reader::from_reader(file);
let mut package_configs = Vec::new();
for record in reader.deserialize() {
match record {
Ok(config) => package_configs.push(config),
Err(error) => {
log::warn!("Error deserializing record: {}", error);
}
}
}
Ok(package_configs)
}
pub fn uid_granted_root(uid: i32) -> bool {
match read_package_config() {
Ok(package_configs) => {
package_configs
.iter()
.find(|config| config.uid == uid)
.map(|config| config.allow == 1)
.unwrap_or(false)
}
Err(err) => {
log::warn!("Error reading package config: {}", err);
return false;
}
}
}
pub fn uid_should_umount(uid: i32) -> bool {
match read_package_config() {
Ok(package_configs) => {
package_configs
.iter()
.find(|config| config.uid == uid)
.map(|config| {
match config.exclude {
1 => true,
_ => false,
}
})
.unwrap_or(false)
}
Err(err) => {
log::warn!("Error reading package configs: {}", err);
false
}
}
}
// TODO: signature
pub fn uid_is_manager(uid: i32) -> bool {
if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.bmax.apatch") {
return s.st_uid == uid as u32;
}
false
}

View File

@@ -0,0 +1,54 @@
#include <sys/types.h>
#include "kernelsu.h"
#include "common.h"
static enum RootImpl ROOT_IMPL = None;
void root_impls_setup(void) {
enum RootImplState ksu_version = ksu_get_kernel_su();
enum RootImpl impl = None;
if (ksu_version == Supported) impl = KernelSU;
ROOT_IMPL = impl;
}
enum RootImpl get_impl(void) {
return ROOT_IMPL;
}
bool uid_granted_root(uid_t uid) {
switch (get_impl()) {
case KernelSU: {
return ksu_uid_granted_root(uid);
}
default: {
return false;
}
}
}
bool uid_should_umount(uid_t uid) {
switch (get_impl()) {
case KernelSU: {
return ksu_uid_should_umount(uid);
}
default: {
return false;
}
}
}
bool uid_is_manager(uid_t uid) {
switch (get_impl()) {
case KernelSU: {
return ksu_uid_is_manager(uid);
}
default: {
return false;
}
}
}

View File

@@ -0,0 +1,22 @@
#ifndef COMMON_H
#define COMMON_H
#include "../constants.h"
enum RootImpl {
None,
Multiple, /* INFO: I know. */
KernelSU
};
void root_impls_setup(void);
enum RootImpl get_impl(void);
bool uid_granted_root(uid_t uid);
bool uid_should_umount(uid_t uid);
bool uid_is_manager(uid_t uid);
#endif /* COMMON_H */

View File

@@ -0,0 +1,68 @@
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <errno.h>
#include "../constants.h"
#include "../utils.h"
#include "kernelsu.h"
#define KERNEL_SU_OPTION 0xdeadbeef
#define CMD_GET_VERSION 2
#define CMD_UID_GRANTED_ROOT 12
#define CMD_UID_SHOULD_UMOUNT 13
enum RootImplState ksu_get_kernel_su(void) {
int version = 0;
prctl(KERNEL_SU_OPTION, CMD_GET_VERSION, &version, 0, 0);
errno = 0;
if (version == 0) return Abnormal;
if (version >= MIN_KSU_VERSION && version <= MAX_KSU_VERSION) return Supported;
if (version >= 1 && version <= MIN_KSU_VERSION - 1) return TooOld;
return Abnormal;
}
bool ksu_uid_granted_root(uid_t uid) {
uint32_t result = 0;
bool granted = false;
prctl(KERNEL_SU_OPTION, CMD_UID_GRANTED_ROOT, uid, &granted, &result);
LOGI("ksu_uid_granted_root: %d", granted);
if (result != KERNEL_SU_OPTION) return false;
return granted;
}
bool ksu_uid_should_umount(uid_t uid) {
uint32_t result = 0;
bool umount = false;
prctl(KERNEL_SU_OPTION, CMD_UID_SHOULD_UMOUNT, uid, &umount, &result);
LOGI("ksu_uid_should_umount: %d", umount);
if (result != KERNEL_SU_OPTION) return false;
return umount;
}
bool ksu_uid_is_manager(uid_t uid) {
struct stat s;
if (stat("/data/user_de/0/me.weishu.kernelsu", &s) == 0) {
LOGI("ksu_uid_is_manager: %d", uid);
return s.st_uid == uid;
}
LOGI("ksu_uid_is_manager: false");
return false;
}

View File

@@ -0,0 +1,14 @@
#ifndef KERNELSU_H
#define KERNELSU_H
#include "../constants.h"
enum RootImplState ksu_get_kernel_su(void);
bool ksu_uid_granted_root(uid_t uid);
bool ksu_uid_should_umount(uid_t uid);
bool ksu_uid_is_manager(uid_t uid);
#endif

View File

@@ -1,77 +0,0 @@
use crate::constants::{MAX_KSU_VERSION, MIN_KSU_VERSION};
const KERNEL_SU_OPTION: u32 = 0xdeadbeefu32;
const CMD_GET_VERSION: usize = 2;
const CMD_UID_GRANTED_ROOT: usize = 12;
const CMD_UID_SHOULD_UMOUNT: usize = 13;
pub enum Version {
Supported,
TooOld,
Abnormal,
}
pub fn get_kernel_su() -> Option<Version> {
let mut version = 0;
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_GET_VERSION,
&mut version as *mut i32,
0,
0,
)
};
const MAX_OLD_VERSION: i32 = MIN_KSU_VERSION - 1;
match version {
0 => None,
MIN_KSU_VERSION..=MAX_KSU_VERSION => Some(Version::Supported),
1..=MAX_OLD_VERSION => Some(Version::TooOld),
_ => Some(Version::Abnormal),
}
}
pub fn uid_granted_root(uid: i32) -> bool {
let mut result: u32 = 0;
let mut granted = false;
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_UID_GRANTED_ROOT,
uid,
&mut granted as *mut bool,
&mut result as *mut u32,
)
};
if result != KERNEL_SU_OPTION {
log::warn!("uid_granted_root failed");
}
granted
}
pub fn uid_should_umount(uid: i32) -> bool {
let mut result: u32 = 0;
let mut umount = false;
unsafe {
libc::prctl(
KERNEL_SU_OPTION as i32,
CMD_UID_SHOULD_UMOUNT,
uid,
&mut umount as *mut bool,
&mut result as *mut u32,
)
};
if result != KERNEL_SU_OPTION {
log::warn!("uid_granted_root failed");
}
umount
}
// TODO: signature
pub fn uid_is_manager(uid: i32) -> bool {
if let Ok(s) = rustix::fs::stat("/data/user_de/0/me.weishu.kernelsu") {
return s.st_uid == uid as u32;
}
false
}

View File

@@ -1,124 +0,0 @@
use std::fs;
use std::os::android::fs::MetadataExt;
use crate::constants::MIN_MAGISK_VERSION;
use std::process::{Command, Stdio};
use log::info;
use crate::utils::LateInit;
const MAGISK_OFFICIAL: &str = "com.topjohnwu.magisk";
const MAGISK_THIRD_PARTIES: &[(&str, &str)] = &[
("alpha", "io.github.vvb2060.magisk"),
("kitsune", "io.github.huskydg.magisk"),
];
pub enum Version {
Supported,
TooOld,
}
static VARIANT: LateInit<&str> = LateInit::new();
pub fn get_magisk() -> Option<Version> {
if !VARIANT.initiated() {
Command::new("magisk")
.arg("-v")
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|version| {
let third_party = MAGISK_THIRD_PARTIES.iter().find_map(|v| {
version.contains(v.0).then_some(v.1)
});
VARIANT.init(third_party.unwrap_or(MAGISK_OFFICIAL));
info!("Magisk variant: {}", *VARIANT);
});
}
Command::new("magisk")
.arg("-V")
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.and_then(|output| output.trim().parse::<i32>().ok())
.map(|version| {
if version >= MIN_MAGISK_VERSION {
Version::Supported
} else {
Version::TooOld
}
})
}
pub fn uid_granted_root(uid: i32) -> bool {
Command::new("magisk")
.arg("--sqlite")
.arg(format!(
"select 1 from policies where uid={uid} and policy=2 limit 1"
))
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| output.is_empty())
== Some(false)
}
pub fn uid_should_umount(uid: i32) -> bool {
let output = Command::new("pm")
.args(["list", "packages", "--uid", &uid.to_string()])
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok());
let line = match output {
Some(line) => line,
None => return false,
};
let pkg = line
.strip_prefix("package:")
.and_then(|line| line.split(' ').next());
let pkg = match pkg {
Some(pkg) => pkg,
None => return false,
};
Command::new("magisk")
.arg("--sqlite")
.arg(format!(
"select 1 from denylist where package_name=\"{pkg}\" limit 1"
))
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| output.is_empty())
== Some(false)
}
// TODO: signature
pub fn uid_is_manager(uid: i32) -> bool {
let output = Command::new("magisk")
.arg("--sqlite")
.arg(format!("select value from strings where key=\"requester\" limit 1"))
.stdout(Stdio::piped())
.spawn()
.ok()
.and_then(|child| child.wait_with_output().ok())
.and_then(|output| String::from_utf8(output.stdout).ok())
.map(|output| output.trim().to_string());
if let Some(output) = output {
if let Some(manager) = output.strip_prefix("value=") {
return fs::metadata(format!("/data/user_de/0/{}", manager))
.map(|s| s.st_uid() == uid as u32)
.unwrap_or(false);
}
}
fs::metadata(format!("/data/user_de/0/{}", *VARIANT))
.map(|s| s.st_uid() == uid as u32)
.unwrap_or(false)
}

View File

@@ -1,78 +0,0 @@
use std::ptr::addr_of;
mod kernelsu;
mod magisk;
mod apatch;
#[derive(Debug)]
pub enum RootImpl {
None,
TooOld,
Abnormal,
Multiple,
KernelSU,
Magisk,
APatch,
}
static mut ROOT_IMPL: RootImpl = RootImpl::None;
pub fn setup() {
let apatch_version = apatch::get_apatch();
let ksu_version = kernelsu::get_kernel_su();
let magisk_version = magisk::get_magisk();
let impl_ = match (apatch_version, ksu_version, magisk_version) {
(None, None, None) => RootImpl::None,
(Some(_), Some(_), Some(_)) => RootImpl::Multiple,
(Some(apatch_version),None, None) => match apatch_version {
apatch::Version::Supported => RootImpl::APatch,
apatch::Version::TooOld => RootImpl::TooOld,
apatch::Version::Abnormal => RootImpl::Abnormal,
},
(None,Some(ksu_version), None) => match ksu_version {
kernelsu::Version::Supported => RootImpl::KernelSU,
kernelsu::Version::TooOld => RootImpl::TooOld,
kernelsu::Version::Abnormal => RootImpl::Abnormal,
},
(None, None, Some(magisk_version)) => match magisk_version {
magisk::Version::Supported => RootImpl::Magisk,
magisk::Version::TooOld => RootImpl::TooOld,
},
_ => RootImpl::None,
};
unsafe {
ROOT_IMPL = impl_;
}
}
pub fn get_impl() -> &'static RootImpl {
unsafe { &*addr_of!(ROOT_IMPL) }
}
pub fn uid_granted_root(uid: i32) -> bool {
match get_impl() {
RootImpl::KernelSU => kernelsu::uid_granted_root(uid),
RootImpl::Magisk => magisk::uid_granted_root(uid),
RootImpl::APatch => apatch::uid_granted_root(uid),
_ => panic!("uid_granted_root: unknown root impl {:?}", get_impl()),
}
}
pub fn uid_should_umount(uid: i32) -> bool {
match get_impl() {
RootImpl::KernelSU => kernelsu::uid_should_umount(uid),
RootImpl::Magisk => magisk::uid_should_umount(uid),
RootImpl::APatch => apatch::uid_should_umount(uid),
_ => panic!("uid_should_umount: unknown root impl {:?}", get_impl()),
}
}
pub fn uid_is_manager(uid: i32) -> bool {
match get_impl() {
RootImpl::KernelSU => kernelsu::uid_is_manager(uid),
RootImpl::Magisk => magisk::uid_is_manager(uid),
RootImpl::APatch => apatch::uid_is_manager(uid),
_ => panic!("uid_is_manager: unknown root impl {:?}", get_impl()),
}
}

295
zygiskd/src/utils.c Normal file
View File

@@ -0,0 +1,295 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <unistd.h>
#include <linux/limits.h>
#include <sched.h>
#include <pthread.h>
#include <android/log.h>
#include "utils.h"
void switch_mount_namespace(pid_t pid) {
char current_path[PATH_MAX];
if (getcwd(current_path, PATH_MAX) == NULL) {
/* TODO: Improve error messages */
LOGE("getcwd: %s\n", strerror(errno));
return;
}
/* INFO: We will NEVER achieve PATH_MAX value, but this is for ensurance. */
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/%d/ns/mnt", pid);
FILE *mnt_ns = fopen(path, "r");
if (mnt_ns == NULL) {
/* TODO: Improve error messages */
LOGE("fopen: %s\n", strerror(errno));
return;
}
if (setns(fileno(mnt_ns), 0) == -1) {
/* TODO: Improve error messages */
LOGE("setns: %s\n", strerror(errno));
return;
}
fclose(mnt_ns);
if (chdir(current_path) == -1) {
/* TODO: Improve error messages */
LOGE("chdir: %s\n", strerror(errno));
return;
}
}
int __system_property_get(const char *, char *);
void get_property(const char *name, char *output) {
__system_property_get(name, output);
}
void set_socket_create_context(const char *context) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/thread-self/attr/sockcreate");
FILE *sockcreate = fopen(path, "w");
if (sockcreate == NULL) {
LOGE("fopen: %s\n", strerror(errno));
return;
}
if (fwrite(context, 1, strlen(context), sockcreate) != strlen(context)) {
LOGE("fwrite: %s\n", strerror(errno));
return;
}
fclose(sockcreate);
}
static void get_current_attr(char *output) {
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/self/attr/current");
FILE *current = fopen(path, "r");
if (current == NULL) {
LOGE("fopen: %s\n", strerror(errno));
return;
}
if (fgets(output, PATH_MAX, current) == NULL) {
LOGE("fgets: %s\n", strerror(errno));
return;
}
fclose(current);
}
void unix_datagram_sendto(const char *path, void *buf, size_t len) {
char current_attr[PATH_MAX];
get_current_attr(current_attr);
set_socket_create_context(current_attr);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
int socket_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (socket_fd == -1) {
LOGE("socket: %s\n", strerror(errno));
return;
}
if (connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
LOGE("connect: %s\n", strerror(errno));
return;
}
if (sendto(socket_fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
LOGE("sendto: %s\n", strerror(errno));
return;
}
set_socket_create_context("u:r:zygote:s0");
close(socket_fd);
}
int chcon(const char *path, const char *context) {
char command[PATH_MAX];
snprintf(command, PATH_MAX, "chcon %s %s", context, path);
return system(command);
}
int unix_listener_from_path(char *path) {
if (remove(path) == -1 && errno != ENOENT) {
LOGE("remove: %s\n", strerror(errno));
return -1;
}
int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socket_fd == -1) {
LOGE("socket: %s\n", strerror(errno));
return -1;
}
struct sockaddr_un addr;
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);
if (bind(socket_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
LOGE("bind: %s\n", strerror(errno));
return -1;
}
if (listen(socket_fd, 2) == -1) {
LOGE("listen: %s\n", strerror(errno));
return -1;
}
if (chcon(path, "u:object_r:magisk_file:s0") == -1) {
LOGE("chcon: %s\n", strerror(errno));
return -1;
}
return socket_fd;
}
ssize_t send_fd(int sockfd, int fd) {
char control_buf[CMSG_SPACE(sizeof(int))];
memset(control_buf, 0, sizeof(control_buf));
int cnt = 1;
struct iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt)
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control_buf,
.msg_controllen = sizeof(control_buf)
};
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
ssize_t sent_bytes = sendmsg(sockfd, &msg, 0);
if (sent_bytes == -1) {
LOGE("Failed to send fd: %s\n", strerror(errno));
return -1;
}
return sent_bytes;
}
ssize_t recv_fd(int sockfd, int *fd) {
char control_buf[CMSG_SPACE(sizeof(int))];
memset(control_buf, 0, sizeof(control_buf));
int cnt = 1;
struct iovec iov = {
.iov_base = &cnt,
.iov_len = sizeof(cnt)
};
struct msghdr msg = {
.msg_iov = &iov,
.msg_iovlen = 1,
.msg_control = control_buf,
.msg_controllen = sizeof(control_buf)
};
ssize_t received_bytes = recvmsg(sockfd, &msg, 0);
if (received_bytes == -1) {
LOGE("Failed to read fd: %s\n", strerror(errno));
return -1;
}
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
memcpy(fd, CMSG_DATA(cmsg), sizeof(int));
return received_bytes;
}
ssize_t write_string(int fd, const char *str) {
size_t len = strlen(str);
ssize_t written_bytes = write(fd, &len, sizeof(len));
if (written_bytes != sizeof(len)) {
LOGE("Failed to write string length: %s\n", strerror(errno));
return -1;
}
written_bytes = write(fd, str, len);
if ((size_t)written_bytes != len) {
LOGE("Failed to write string: Not all bytes were written.\n");
return -1;
}
return written_bytes;
}
ssize_t read_string(int fd, char *str, size_t len) {
size_t str_len_buf[1];
ssize_t read_bytes = read(fd, &str_len_buf, sizeof(str_len_buf));
if (read_bytes != (ssize_t)sizeof(str_len_buf)) {
LOGE("Failed to read string length: %s\n", strerror(errno));
return -1;
}
size_t str_len = str_len_buf[0];
if (str_len > len) {
LOGE("Failed to read string: Buffer is too small (%zu > %zu).\n", str_len, len);
return -1;
}
read_bytes = read(fd, str, str_len);
if (read_bytes != (ssize_t)str_len) {
LOGE("Failed to read string: Not all bytes were read (%zd != %zu).\n", read_bytes, str_len);
return -1;
}
return read_bytes;
}

36
zygiskd/src/utils.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef UTILS_H
#define UTILS_H
#include <sys/types.h>
#include "constants.h"
#define LOGI(...) \
__android_log_print(ANDROID_LOG_INFO, lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \
printf(__VA_ARGS__)
#define LOGE(...) \
__android_log_print(ANDROID_LOG_INFO , lp_select("zygiskd32", "zygiskd64"), __VA_ARGS__); \
printf(__VA_ARGS__)
void switch_mount_namespace(pid_t pid);
void get_property(const char *name, char *output);
void set_socket_create_context(const char *context);
void unix_datagram_sendto(const char *path, void *buf, size_t len);
int chcon(const char *path, const char *context);
int unix_listener_from_path(char *path);
ssize_t send_fd(int sockfd, int fd);
ssize_t recv_fd(int sockfd, int *fd);
ssize_t write_string(int fd, const char *str);
ssize_t read_string(int fd, char *str, size_t len);
#endif /* UTILS_H */

View File

@@ -1,264 +0,0 @@
use anyhow::Result;
use rustix::net::{
bind_unix, connect_unix, listen, sendto_unix, socket, AddressFamily, SendFlags, SocketAddrUnix,
SocketType,
};
use rustix::path::Arg;
use rustix::thread::gettid;
use std::ffi::{c_char, c_void, CStr, CString};
use std::os::fd::{AsFd, AsRawFd};
use std::os::unix::net::{UnixListener};
use std::process::Command;
use std::sync::OnceLock;
use std::{
fs,
io::{Read, Write},
os::unix::net::UnixStream,
};
#[cfg(target_pointer_width = "64")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => {
$lp64
};
}
#[cfg(target_pointer_width = "32")]
#[macro_export]
macro_rules! lp_select {
($lp32:expr, $lp64:expr) => {
$lp32
};
}
#[cfg(debug_assertions)]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => {
$debug
};
}
#[cfg(not(debug_assertions))]
#[macro_export]
macro_rules! debug_select {
($debug:expr, $release:expr) => {
$release
};
}
pub struct LateInit<T> {
cell: OnceLock<T>,
}
impl<T> LateInit<T> {
pub const fn new() -> Self {
LateInit {
cell: OnceLock::new(),
}
}
pub fn init(&self, value: T) {
assert!(self.cell.set(value).is_ok())
}
pub fn initiated(&self) -> bool {
self.cell.get().is_some()
}
}
impl<T> std::ops::Deref for LateInit<T> {
type Target = T;
fn deref(&self) -> &T {
self.cell.get().unwrap()
}
}
pub fn set_socket_create_context(context: &str) -> Result<()> {
let path = "/proc/thread-self/attr/sockcreate";
match fs::write(path, context) {
Ok(_) => Ok(()),
Err(_) => {
let path = format!(
"/proc/self/task/{}/attr/sockcreate",
gettid().as_raw_nonzero()
);
fs::write(path, context)?;
Ok(())
}
}
}
pub fn get_current_attr() -> Result<String> {
let s = fs::read("/proc/self/attr/current")?;
Ok(s.to_string_lossy().to_string())
}
pub fn chcon(path: &str, context: &str) -> Result<()> {
Command::new("chcon").arg(context).arg(path).status()?;
Ok(())
}
pub fn log_raw(level: i32, tag: &str, message: &str) -> Result<()> {
let tag = CString::new(tag)?;
let message = CString::new(message)?;
unsafe {
__android_log_print(level, tag.as_ptr(), message.as_ptr());
}
Ok(())
}
pub fn get_property(name: &str) -> Result<String> {
let name = CString::new(name)?;
let mut buf = vec![0u8; 92];
let prop = unsafe {
__system_property_get(name.as_ptr(), buf.as_mut_ptr() as *mut c_char);
CStr::from_bytes_until_nul(&buf)?
};
Ok(prop.to_string_lossy().to_string())
}
#[allow(dead_code)]
pub fn set_property(name: &str, value: &str) -> Result<()> {
let name = CString::new(name)?;
let value = CString::new(value)?;
unsafe {
__system_property_set(name.as_ptr(), value.as_ptr());
}
Ok(())
}
#[allow(dead_code)]
pub fn wait_property(name: &str, serial: u32) -> Result<u32> {
let name = CString::new(name)?;
let info = unsafe { __system_property_find(name.as_ptr()) };
let mut serial = serial;
unsafe {
__system_property_wait(info, serial, &mut serial, std::ptr::null());
}
Ok(serial)
}
#[allow(dead_code)]
pub fn get_property_serial(name: &str) -> Result<u32> {
let name = CString::new(name)?;
let info = unsafe { __system_property_find(name.as_ptr()) };
Ok(unsafe { __system_property_serial(info) })
}
pub fn switch_mount_namespace(pid: i32) -> Result<()> {
let cwd = std::env::current_dir()?;
let mnt = fs::File::open(format!("/proc/{}/ns/mnt", pid))?;
rustix::thread::move_into_link_name_space(mnt.as_fd(), None)?;
std::env::set_current_dir(cwd)?;
Ok(())
}
pub trait UnixStreamExt {
fn read_u8(&mut self) -> Result<u8>;
fn read_u32(&mut self) -> Result<u32>;
fn read_usize(&mut self) -> Result<usize>;
fn read_string(&mut self) -> Result<String>;
fn write_u8(&mut self, value: u8) -> Result<()>;
fn write_u32(&mut self, value: u32) -> Result<()>;
fn write_usize(&mut self, value: usize) -> Result<()>;
fn write_string(&mut self, value: &str) -> Result<()>;
}
impl UnixStreamExt for UnixStream {
fn read_u8(&mut self) -> Result<u8> {
let mut buf = [0u8; 1];
self.read_exact(&mut buf)?;
Ok(buf[0])
}
fn read_u32(&mut self) -> Result<u32> {
let mut buf = [0u8; 4];
self.read_exact(&mut buf)?;
Ok(u32::from_ne_bytes(buf))
}
fn read_usize(&mut self) -> Result<usize> {
let mut buf = [0u8; std::mem::size_of::<usize>()];
self.read_exact(&mut buf)?;
Ok(usize::from_ne_bytes(buf))
}
fn read_string(&mut self) -> Result<String> {
let len = self.read_usize()?;
let mut buf = vec![0u8; len];
self.read_exact(&mut buf)?;
Ok(String::from_utf8(buf)?)
}
fn write_u8(&mut self, value: u8) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_u32(&mut self, value: u32) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_usize(&mut self, value: usize) -> Result<()> {
self.write_all(&value.to_ne_bytes())?;
Ok(())
}
fn write_string(&mut self, value: &str) -> Result<()> {
self.write_usize(value.len())?;
self.write_all(value.as_bytes())?;
Ok(())
}
}
pub fn unix_listener_from_path(path: &str) -> Result<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))
}
pub fn unix_datagram_sendto(path: &str, buf: &[u8]) -> Result<()> {
// FIXME: shall we set create context every time?
set_socket_create_context(get_current_attr()?.as_str())?;
let addr = SocketAddrUnix::new(path.as_bytes())?;
let socket = socket(AddressFamily::UNIX, SocketType::DGRAM, None)?;
connect_unix(&socket, &addr)?;
sendto_unix(socket, buf, SendFlags::empty(), &addr)?;
set_socket_create_context("u:r:zygote:s0")?;
Ok(())
}
pub fn check_unix_socket(stream: &UnixStream, block: bool) -> bool {
unsafe {
let mut pfd = libc::pollfd {
fd: stream.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
let timeout = if block { -1 } else { 0 };
libc::poll(&mut pfd, 1, timeout);
if pfd.revents & !libc::POLLIN != 0 {
return false;
}
}
return true;
}
extern "C" {
fn __android_log_print(prio: i32, tag: *const c_char, fmt: *const c_char, ...) -> i32;
fn __system_property_get(name: *const c_char, value: *mut c_char) -> u32;
fn __system_property_set(name: *const c_char, value: *const c_char) -> u32;
fn __system_property_find(name: *const c_char) -> *const c_void;
fn __system_property_wait(
info: *const c_void,
old_serial: u32,
new_serial: *mut u32,
timeout: *const libc::timespec,
) -> bool;
fn __system_property_serial(info: *const c_void) -> u32;
}

690
zygiskd/src/zygiskd.c Normal file
View File

@@ -0,0 +1,690 @@
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <linux/limits.h>
#include <pthread.h>
#include "root_impl/common.h"
#include "constants.h"
#include "utils.h"
struct Module {
char *name;
int lib_fd;
int companion;
};
struct Context {
struct Module *modules;
int len;
};
enum Architecture {
ARM32,
ARM64,
X86,
X86_64,
};
#define PATH_MODULES_DIR "/data/adb/modules"
#define TMP_PATH "/data/adb/rezygisk"
#define CONTROLLER_SOCKET TMP_PATH "/init_monitor"
#define PATH_CP_NAME TMP_PATH "/" lp_select("cp32.sock", "cp64.sock")
#define ZYGISKD_FILE "zygiskd" lp_select("32", "64")
#define ZYGISKD_PATH "/data/adb/modules/zygisksu/bin/zygiskd" lp_select("32", "64")
#define ASSURE_SIZE_WRITE(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
return; \
}
#define ASSURE_SIZE_READ(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
return; \
}
#define ASSURE_SIZE_WRITE_BREAK(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
break; \
}
#define ASSURE_SIZE_READ_BREAK(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
break; \
}
#define ASSURE_SIZE_WRITE_WR(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to sent " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
return -1; \
}
#define ASSURE_SIZE_READ_WR(area_name, subarea_name, sent_size, expected_size) \
if (sent_size != (ssize_t)(expected_size)) { \
LOGE("Failed to read " subarea_name " in " area_name ": Expected %zu, got %zd\n", expected_size, sent_size); \
\
return -1; \
}
static enum Architecture get_arch(void) {
char system_arch[32];
get_property("ro.product.cpu.abi", system_arch);
if (strstr(system_arch, "arm") != NULL) return lp_select(ARM32, ARM64);
if (strstr(system_arch, "x86") != NULL) return lp_select(X86, X86_64);
LOGE("Unsupported system architecture: %s\n", system_arch);
exit(1);
}
int create_library_fd(const char *so_path) {
int memfd = memfd_create("jit-cache-zygisk", MFD_ALLOW_SEALING);
if (memfd == -1) {
perror("memfd_create");
return -1;
}
int so_fd = open(so_path, O_RDONLY);
if (so_fd == -1) {
perror("open");
close(memfd);
return -1;
}
struct stat st;
if (fstat(so_fd, &st) == -1) {
perror("fstat");
close(so_fd);
close(memfd);
return -1;
}
if (sendfile(memfd, so_fd, NULL, st.st_size) == -1) {
perror("sendfile");
close(so_fd);
close(memfd);
return -1;
}
close(so_fd);
if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) == -1) {
perror("fcntl");
close(memfd);
return -1;
}
return memfd;
}
/* WARNING: Dynamic memory based */
static void load_modules(enum Architecture arch, struct Context *context) {
context->len = 0;
context->modules = malloc(1);
DIR *dir = opendir(PATH_MODULES_DIR);
if (dir == NULL) {
LOGE("Failed opening modules directory: %s.", PATH_MODULES_DIR);
return;
}
char arch_str[32];
switch (arch) {
case ARM32: {
strcpy(arch_str, "armeabi-v7a");
break;
}
case ARM64: {
strcpy(arch_str, "arm64-v8a");
break;
}
case X86: {
strcpy(arch_str, "x86");
break;
}
case X86_64: {
strcpy(arch_str, "x86_64");
break;
}
}
LOGI("Loading modules for architecture: %s\n", arch_str);
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type != DT_DIR) continue; /* INFO: Only directories */
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0 || strcmp(entry->d_name, "zygisksu") == 0) continue;
char *name = entry->d_name;
char so_path[PATH_MAX];
snprintf(so_path, PATH_MAX, "/data/adb/modules/%s/zygisk/%s.so", name, arch_str);
struct stat st;
if (stat(so_path, &st) == -1) {
errno = 0;
continue;
}
char disabled[PATH_MAX];
snprintf(disabled, PATH_MAX, "/data/adb/modules/%s/disable", name);
if (stat(disabled, &st) != -1) {
errno = 0;
continue;
}
LOGI("Loading module `%s`...\n", name);
int lib_fd = create_library_fd(so_path);
if (lib_fd == -1) {
LOGE("Failed loading module `%s`\n", name);
continue;
}
LOGI("Loaded module lib fd: %d\n", lib_fd);
context->modules = realloc(context->modules, ((context->len + 1) * sizeof(struct Module)));
context->modules[context->len].name = strdup(name);
context->modules[context->len].lib_fd = lib_fd;
context->modules[context->len].companion = -1;
context->len++;
}
}
static void free_modules(struct Context *context) {
for (int i = 0; i < context->len; i++) {
free(context->modules[i].name);
if (context->modules[i].companion != -1) close(context->modules[i].companion);
}
}
static int create_daemon_socket(void) {
set_socket_create_context("u:r:zygote:s0");
return unix_listener_from_path(PATH_CP_NAME);
}
static int spawn_companion(char *name, int lib_fd) {
int sockets[2];
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) == -1) {
LOGE("Failed creating socket pair.\n");
return -1;
}
int daemon_fd = sockets[0];
int companion_fd = sockets[1];
LOGI("Companion fd: %d\n", companion_fd);
LOGI("Daemon fd: %d\n", daemon_fd);
pid_t pid = fork();
LOGI("Forked: %d\n", pid);
if (pid < 0) {
LOGE("Failed forking companion: %s\n", strerror(errno));
close(companion_fd);
close(daemon_fd);
exit(1);
} else if (pid > 0) {
close(companion_fd);
LOGI("Waiting for companion to start (%d)\n", pid);
int status = 0;
// waitpid(pid, &status, 0);
LOGI("Companion exited with status %d\n", status);
// if (WIFEXITED(status) && WEXITSTATUS(status) == 0) {
if (write_string(daemon_fd, name) == -1) return -1;
if (send_fd(daemon_fd, lib_fd) == -1) return -1;
LOGI("Sent module name and lib fd\n");
uint8_t response_buf[1];
ssize_t ret = read(daemon_fd, &response_buf, sizeof(response_buf));
ASSURE_SIZE_READ_WR("companion", "response", ret, sizeof(response_buf));
uint8_t response = response_buf[0];
LOGI("Companion response: %hhu\n", response);
if (response == 0) return -2;
else if (response == 1) return daemon_fd;
else return -2;
// } else {
// LOGE("Exited with status %d\n", status);
// close(daemon_fd);
// return -1;
// }
/* INFO: if pid == 0: */
} else {
LOGI("Companion started (%d)\n", pid);
/* INFO: There is no case where this will fail with a valid fd. */
fcntl(companion_fd, F_SETFD, 0);
}
char companion_fd_str[32];
snprintf(companion_fd_str, 32, "%d", companion_fd);
LOGI("Executing companion...\n");
char *argv[] = { ZYGISKD_FILE, "companion", companion_fd_str, NULL };
if (execv(ZYGISKD_PATH, argv) == -1) {
LOGE("Failed executing companion: %s\n", strerror(errno));
close(companion_fd);
exit(1);
}
exit(0);
}
/* TODO: Is packed attribute really necessary? */
struct __attribute__((__packed__)) MsgHead {
unsigned int cmd;
int length;
char data[0];
};
void zygiskd_start(void) {
LOGI("Welcome to ReZygisk %s!", ZKSU_VERSION);
enum Architecture arch = get_arch();
struct Context context;
load_modules(arch, &context);
struct MsgHead *msg = NULL;
size_t msg_sz = 0;
switch (get_impl()) {
case None: {
/* INFO: Stop, compiler. */
break;
}
case Multiple: {
/* INFO: Stop, compiler. */
break;
}
case KernelSU: {
if (context.len == 0) {
msg_sz = sizeof(struct MsgHead) + strlen("Root: KernelSU, Modules: None") + 1;
msg = malloc(msg_sz);
msg->cmd = DAEMON_SET_INFO;
msg->length = strlen("Root: KernelSU, Modules: None") + 1;
memcpy(msg->data, "Root: KernelSU, Modules: None", strlen("Root: KernelSU, Modules: None"));
} else {
char *module_list = malloc(1);
size_t module_list_len = 0;
for (int i = 0; i < context.len; i++) {
if (i != context.len - 1) {
module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + strlen(", ") + 1);
memcpy(module_list + module_list_len, context.modules[i].name, strlen(context.modules[i].name));
module_list_len += strlen(context.modules[i].name);
memcpy(module_list + module_list_len, ", ", strlen(", "));
module_list_len += strlen(", ");
} else {
module_list = realloc(module_list, module_list_len + strlen(context.modules[i].name) + 1);
memcpy(module_list + module_list_len, context.modules[i].name, strlen(context.modules[i].name));
module_list_len += strlen(context.modules[i].name);
}
}
msg_sz = sizeof(struct MsgHead) + strlen("Root: KernelSU, Modules: ") + module_list_len + 1;
msg = malloc(msg_sz);
msg->cmd = DAEMON_SET_INFO;
msg->length = strlen("Root: KernelSU, Modules: ") + module_list_len + 1;
memcpy(msg->data, "Root: KernelSU, Modules: ", strlen("Root: KernelSU, Modules: "));
memcpy(msg->data + strlen("Root: KernelSU, Modules: "), module_list, module_list_len);
free(module_list);
}
break;
}
default: {
msg_sz = sizeof(struct MsgHead) + strlen("Invalid root implementation") + 1;
msg = malloc(msg_sz);
msg->cmd = DAEMON_SET_ERROR_INFO;
msg->length = strlen("Invalid root implementation") + 1;
memcpy(msg->data, "Invalid root implementation", strlen("Invalid root implementation"));
break;
}
}
unix_datagram_sendto(CONTROLLER_SOCKET, (void *)msg, msg_sz);
free(msg);
int socket_fd = create_daemon_socket();
if (socket_fd == -1) {
LOGE("Failed creating daemon socket\n");
return;
}
while (1) {
int client_fd = accept(socket_fd, NULL, NULL);
if (client_fd == -1) {
LOGE("accept: %s\n", strerror(errno));
return;
}
LOGI("Accepted client: %d\n", client_fd);
unsigned char buf[1];
ssize_t len = read(client_fd, buf, sizeof(buf));
if (len == -1) {
LOGE("read: %s\n", strerror(errno));
return;
} else if (len == 0) {
LOGI("Client disconnected\n");
return;
}
LOGI("Action: %hhu\n", (uint8_t)buf[0]);
enum DaemonSocketAction action = (enum DaemonSocketAction)buf[0];
switch (action) {
case PingHeartbeat: {
enum DaemonSocketAction msgr = ZYGOTE_INJECTED;
unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction));
break;
}
case ZygoteRestart: {
LOGI("Zygote restart\n");
for (int i = 0; i < context.len; i++) {
if (context.modules[i].companion != -1) {
close(context.modules[i].companion);
context.modules[i].companion = -1;
}
}
break;
}
case SystemServerStarted: {
enum DaemonSocketAction msgr = SYSTEM_SERVER_STARTED;
unix_datagram_sendto(CONTROLLER_SOCKET, &msgr, sizeof(enum DaemonSocketAction));
break;
}
case RequestLogcatFd: {
char level_buf[1];
ssize_t ret = read(client_fd, &level_buf, sizeof(level_buf));
ASSURE_SIZE_READ_BREAK("RequestLogcatFd", "level", ret, sizeof(level_buf));
char level = level_buf[0];
char tag[128 + 1];
ret = read_string(client_fd, tag, sizeof(tag) - 1);
if (ret == -1) break;
tag[ret] = '\0';
char message[1024];
ret = read_string(client_fd, message, sizeof(message));
if (ret == -1) break;
__android_log_print(level, tag, "%.*s", (int)ret, message);
break;
}
case GetProcessFlags: {
LOGI("Getting process flags\n");
uid_t uid_buf[1];
ssize_t ret = read(client_fd, &uid_buf, sizeof(uid_buf));
ASSURE_SIZE_READ_BREAK("GetProcessFlags", "uid", ret, sizeof(uid_buf));
uid_t uid = uid_buf[0];
LOGI("Checking flags for uid: %d\n", uid);
uint32_t flags = 0;
if (uid_is_manager(uid)) {
flags |= PROCESS_IS_MANAGER;
} else {
if (uid_granted_root(uid)) {
flags |= PROCESS_GRANTED_ROOT;
}
if (uid_should_umount(uid)) {
flags |= PROCESS_ON_DENYLIST;
}
}
LOGI("Flags for uid %d: %d\n", uid, flags);
switch (get_impl()) {
case None: {
break;
}
case Multiple: {
break;
}
case KernelSU: {
flags |= PROCESS_ROOT_IS_KSU;
}
}
// LOGI("Flags for uid %d: %d\n", uid, flags);
LOGI("Sending flags\n");
ret = write(client_fd, &flags, sizeof(flags));
// ASSURE_SIZE_WRITE_BREAK("GetProcessFlags", "flags", ret, sizeof(flags));
LOGI("Sent flags\n");
break;
}
case GetInfo: {
uint32_t flags = 0;
LOGI("Getting info\n");
switch (get_impl()) {
case None: {
break;
}
case Multiple: {
break;
}
case KernelSU: {
flags |= PROCESS_ROOT_IS_KSU;
}
}
LOGI("Flags: %d\n", flags);
ssize_t ret = write(client_fd, &flags, sizeof(flags));
ASSURE_SIZE_WRITE_BREAK("GetInfo", "flags", ret, sizeof(flags));
pid_t pid = getpid();
LOGI("Getting pid: %d\n", pid);
ret = write(client_fd, &pid, sizeof(pid));
ASSURE_SIZE_WRITE_BREAK("GetInfo", "pid", ret, sizeof(pid));
LOGI("Sent pid\n");
break;
}
case ReadModules: {
LOGI("Reading modules to stream\n");
size_t clen = context.len;
ssize_t ret = write(client_fd, &clen, sizeof(clen));
ASSURE_SIZE_WRITE_BREAK("ReadModules", "len", ret, sizeof(clen));
for (int i = 0; i < (int)clen; i++) {
LOGI("Hey, we're talking about: %d\n", i);
LOGI("Writing module `%s` to stream\n", context.modules[i].name);
LOGI("Lib fd: %d\n", context.modules[i].lib_fd);
size_t name_len = strlen(context.modules[i].name);
LOGI("Name length: %zu\n", name_len);
ret = write(client_fd, &name_len, sizeof(name_len));
ASSURE_SIZE_WRITE_BREAK("ReadModules", "name length", ret, sizeof(name_len));
LOGI("Writing name: %s\n", context.modules[i].name);
ret = write(client_fd, context.modules[i].name, name_len);
ASSURE_SIZE_WRITE_BREAK("ReadModules", "name", ret, name_len);
LOGI("Writing lib fd: %d\n", context.modules[i].lib_fd);
if (send_fd(client_fd, context.modules[i].lib_fd) == -1) break;
}
LOGI("Finished reading modules to stream\n");
break;
}
case RequestCompanionSocket: {
LOGI("Requesting companion socket\n");
size_t index_buf[1];
ssize_t ret = read(client_fd, &index_buf, sizeof(index_buf));
ASSURE_SIZE_READ_BREAK("RequestCompanionSocket", "index", ret, sizeof(index_buf));
size_t index = index_buf[0];
struct Module *module = &context.modules[index];
int companion_fd = module->companion;
if (companion_fd != -1) {
LOGI("Companion for module `%s` already exists\n", module->name);
if (fcntl(companion_fd, F_GETFD) == -1) {
LOGE("Poll companion for module `%s` crashed\n", module->name);
close(companion_fd);
module->companion = -1;
}
}
if (companion_fd == -1) {
LOGI("Spawning companion for `%s`\n", module->name);
companion_fd = spawn_companion(module->name, module->lib_fd);
if (companion_fd != -1) {
LOGI("Spawned companion for `%s`\n", module->name);
module->companion = companion_fd;
if (send_fd(client_fd, companion_fd) == -1) break;
} else if (companion_fd == -2) {
LOGI("Could not spawn companion for `%s` as it has no entry\n", module->name);
/* TODO: Avoid duplicated code -- Merge this and the one below. */
uint8_t response = 0;
ret = write(client_fd, &response, sizeof(response));
ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(response));
} else {
LOGE("Failed to spawn companion for `%s`\n", module->name);
uint8_t response = 0;
ret = write(client_fd, &response, sizeof(response));
ASSURE_SIZE_WRITE_BREAK("RequestCompanionSocket", "response", ret, sizeof(response));
}
LOGI("Companion fd: %d\n", companion_fd);
}
break;
}
case GetModuleDir: {
LOGI("Getting module directory\n");
size_t index_buf[1];
ssize_t ret = read(client_fd, &index_buf, sizeof(index_buf));
ASSURE_SIZE_READ_BREAK("GetModuleDir", "index", ret, sizeof(index_buf));
size_t index = index_buf[0];
LOGI("Index: %zu\n", index);
char dir[PATH_MAX];
snprintf(dir, PATH_MAX, "%s/%s", PATH_MODULES_DIR, context.modules[index].name);
LOGI("Module directory: %s\n", dir);
int dir_fd = open(dir, O_RDONLY);
LOGI("Module directory fd: %d\n", dir_fd);
if (send_fd(client_fd, dir_fd) == -1) break;
LOGI("Sent module directory fd\n");
break;
}
close(client_fd);
}
continue;
}
close(socket_fd);
free_modules(&context);
}

6
zygiskd/src/zygiskd.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef ZYGISKD_H
#define ZYGISKD_H
void zygiskd_start(void);
#endif /* ZYGISKD_H */

View File

@@ -1,351 +0,0 @@
use crate::constants::{DaemonSocketAction, ProcessFlags};
use crate::utils::{check_unix_socket, LateInit, UnixStreamExt};
use crate::{constants, lp_select, root_impl, utils};
use anyhow::{bail, Result};
use log::{debug, error, info, trace, warn};
use passfd::FdPassingExt;
use rustix::fs::{fcntl_setfd, FdFlags};
use std::fs;
use std::io::Error;
use std::ops::Deref;
use std::os::fd::{AsFd, OwnedFd, RawFd};
use std::os::unix::process::CommandExt;
use std::os::unix::{
net::{UnixListener, UnixStream},
prelude::AsRawFd,
};
use std::path::PathBuf;
use std::process::{exit, Command};
use std::sync::{Arc, Mutex};
use std::thread;
struct Module {
name: String,
lib_fd: OwnedFd,
companion: Mutex<Option<Option<UnixStream>>>,
}
struct Context {
modules: Vec<Module>,
}
static TMP_PATH: LateInit<String> = LateInit::new();
static CONTROLLER_SOCKET: LateInit<String> = LateInit::new();
static PATH_CP_NAME: LateInit<String> = LateInit::new();
pub fn main() -> Result<()> {
info!("Welcome to ReZygisk ({}) !", constants::ZKSU_VERSION);
TMP_PATH.init(std::env::var("TMP_PATH")?);
CONTROLLER_SOCKET.init(format!("{}/init_monitor", TMP_PATH.deref()));
PATH_CP_NAME.init(format!(
"{}/{}",
TMP_PATH.deref(),
lp_select!("/cp32.sock", "/cp64.sock")
));
let arch = get_arch()?;
debug!("Daemon architecture: {arch}");
let modules = load_modules(arch)?;
{
let mut msg = Vec::<u8>::new();
let info = match root_impl::get_impl() {
root_impl::RootImpl::KernelSU | root_impl::RootImpl::Magisk | root_impl::RootImpl::APatch => {
msg.extend_from_slice(&constants::DAEMON_SET_INFO.to_le_bytes());
let module_names: Vec<_> = modules.iter().map(|m| m.name.as_str()).collect();
format!(
"Root: {:?},module({}): {}",
root_impl::get_impl(),
modules.len(),
module_names.join(",")
)
}
_ => {
msg.extend_from_slice(&constants::DAEMON_SET_ERROR_INFO.to_le_bytes());
format!("Invalid root implementation: {:?}", root_impl::get_impl())
}
};
msg.extend_from_slice(&(info.len() as u32 + 1).to_le_bytes());
msg.extend_from_slice(info.as_bytes());
msg.extend_from_slice(&[0u8]);
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, msg.as_slice())
.expect("failed to send info");
}
let context = Context { modules };
let context = Arc::new(context);
let listener = create_daemon_socket()?;
for stream in listener.incoming() {
let mut stream = stream?;
let context = Arc::clone(&context);
let action = stream.read_u8()?;
let action = DaemonSocketAction::try_from(action)?;
trace!("New daemon action {:?}", action);
match action {
DaemonSocketAction::PingHeartbeat => {
let value = constants::ZYGOTE_INJECTED;
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?;
}
DaemonSocketAction::ZygoteRestart => {
info!("Zygote restarted, clean up companions");
for module in &context.modules {
let mut companion = module.companion.lock().unwrap();
companion.take();
}
}
DaemonSocketAction::SystemServerStarted => {
let value = constants::SYSTEM_SERVER_STARTED;
utils::unix_datagram_sendto(&CONTROLLER_SOCKET, &value.to_le_bytes())?;
}
_ => {
thread::spawn(move || {
if let Err(e) = handle_daemon_action(action, stream, &context) {
warn!("Error handling daemon action: {}\n{}", e, e.backtrace());
}
});
}
}
}
Ok(())
}
fn get_arch() -> Result<&'static str> {
let system_arch = utils::get_property("ro.product.cpu.abi")?;
if system_arch.contains("arm") {
return Ok(lp_select!("armeabi-v7a", "arm64-v8a"));
}
if system_arch.contains("x86") {
return Ok(lp_select!("x86", "x86_64"));
}
bail!("Unsupported system architecture: {}", system_arch);
}
fn load_modules(arch: &str) -> Result<Vec<Module>> {
let mut modules = Vec::new();
let dir = match fs::read_dir(constants::PATH_MODULES_DIR) {
Ok(dir) => dir,
Err(e) => {
warn!("Failed reading modules directory: {}", e);
return Ok(modules);
}
};
for entry in dir.into_iter() {
let entry = entry?;
let name = entry.file_name().into_string().unwrap();
let so_path = entry.path().join(format!("zygisk/{arch}.so"));
let disabled = entry.path().join("disable");
if !so_path.exists() || disabled.exists() {
continue;
}
info!(" Loading module `{name}`...");
let lib_fd = match create_library_fd(&so_path) {
Ok(fd) => fd,
Err(e) => {
warn!(" Failed to create memfd for `{name}`: {e}");
continue;
}
};
let companion = Mutex::new(None);
let module = Module {
name,
lib_fd,
companion,
};
modules.push(module);
}
Ok(modules)
}
fn create_library_fd(so_path: &PathBuf) -> Result<OwnedFd> {
let opts = memfd::MemfdOptions::default().allow_sealing(true);
let memfd = opts.create("jit-cache-zygisk")?;
let file = fs::File::open(so_path)?;
let mut reader = std::io::BufReader::new(file);
let mut writer = memfd.as_file();
std::io::copy(&mut reader, &mut writer)?;
let mut seals = memfd::SealsHashSet::new();
seals.insert(memfd::FileSeal::SealShrink);
seals.insert(memfd::FileSeal::SealGrow);
seals.insert(memfd::FileSeal::SealWrite);
seals.insert(memfd::FileSeal::SealSeal);
memfd.add_seals(&seals)?;
Ok(OwnedFd::from(memfd.into_file()))
}
fn create_daemon_socket() -> Result<UnixListener> {
utils::set_socket_create_context("u:r:zygote:s0")?;
let listener = utils::unix_listener_from_path(&PATH_CP_NAME)?;
Ok(listener)
}
fn spawn_companion(name: &str, lib_fd: RawFd) -> Result<Option<UnixStream>> {
let (mut daemon, companion) = UnixStream::pair()?;
// FIXME: avoid getting self path from arg0
let process = std::env::args().next().unwrap();
let nice_name = process.split('/').last().unwrap();
unsafe {
let pid = libc::fork();
if pid < 0 {
bail!(Error::last_os_error());
} else if pid > 0 {
drop(companion);
let mut status: libc::c_int = 0;
libc::waitpid(pid, &mut status, 0);
if libc::WIFEXITED(status) && libc::WEXITSTATUS(status) == 0 {
daemon.write_string(name)?;
daemon.send_fd(lib_fd)?;
return match daemon.read_u8()? {
0 => Ok(None),
1 => Ok(Some(daemon)),
_ => bail!("Invalid companion response"),
};
} else {
bail!("exited with status {}", status);
}
} else {
// Remove FD_CLOEXEC flag
fcntl_setfd(companion.as_fd(), FdFlags::empty())?;
}
}
Command::new(&process)
.arg0(format!("{}-{}", nice_name, name))
.arg("companion")
.arg(format!("{}", companion.as_raw_fd()))
.spawn()?;
exit(0)
}
fn handle_daemon_action(
action: DaemonSocketAction,
mut stream: UnixStream,
context: &Context,
) -> Result<()> {
match action {
DaemonSocketAction::RequestLogcatFd => loop {
let level = match stream.read_u8() {
Ok(level) => level,
Err(_) => break,
};
let tag = stream.read_string()?;
let message = stream.read_string()?;
utils::log_raw(level as i32, &tag, &message)?;
},
DaemonSocketAction::GetProcessFlags => {
let uid = stream.read_u32()? as i32;
let mut flags = ProcessFlags::empty();
if root_impl::uid_is_manager(uid) {
flags |= ProcessFlags::PROCESS_IS_MANAGER;
} else {
if root_impl::uid_granted_root(uid) {
flags |= ProcessFlags::PROCESS_GRANTED_ROOT;
}
if root_impl::uid_should_umount(uid) {
flags |= ProcessFlags::PROCESS_ON_DENYLIST;
}
}
match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK,
root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH,
_ => panic!("wrong root impl: {:?}", root_impl::get_impl()),
}
trace!(
"Uid {} granted root: {}",
uid,
flags.contains(ProcessFlags::PROCESS_GRANTED_ROOT)
);
trace!(
"Uid {} on denylist: {}",
uid,
flags.contains(ProcessFlags::PROCESS_ON_DENYLIST)
);
stream.write_u32(flags.bits())?;
}
DaemonSocketAction::GetInfo => {
let mut flags = ProcessFlags::empty();
match root_impl::get_impl() {
root_impl::RootImpl::KernelSU => flags |= ProcessFlags::PROCESS_ROOT_IS_KSU,
root_impl::RootImpl::Magisk => flags |= ProcessFlags::PROCESS_ROOT_IS_MAGISK,
root_impl::RootImpl::APatch => flags |= ProcessFlags::PROCESS_ROOT_IS_APATCH,
_ => panic!("wrong root impl: {:?}", root_impl::get_impl()),
}
stream.write_u32(flags.bits())?;
stream.write_usize(context.modules.len())?;
for module in context.modules.iter() {
stream.write_string(&module.name)?;
}
}
DaemonSocketAction::ReadModules => {
stream.write_usize(context.modules.len())?;
for module in context.modules.iter() {
stream.write_string(&module.name)?;
stream.send_fd(module.lib_fd.as_raw_fd())?;
}
}
DaemonSocketAction::RequestCompanionSocket => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let mut companion = module.companion.lock().unwrap();
if let Some(Some(sock)) = companion.as_ref() {
if !check_unix_socket(sock, false) {
error!("Poll companion for module `{}` crashed", module.name);
companion.take();
}
}
if companion.is_none() {
match spawn_companion(&module.name, module.lib_fd.as_raw_fd()) {
Ok(c) => {
if c.is_some() {
trace!(" Spawned companion for `{}`", module.name);
} else {
trace!(
" No companion spawned for `{}` because it has not entry",
module.name
);
}
*companion = Some(c);
}
Err(e) => {
warn!(" Failed to spawn companion for `{}`: {}", module.name, e);
}
};
}
match companion.as_ref() {
Some(Some(sock)) => {
if let Err(e) = sock.send_fd(stream.as_raw_fd()) {
error!(
"Failed to send companion fd socket of module `{}`: {}",
module.name, e
);
stream.write_u8(0)?;
}
// Ok: Send by companion
}
_ => {
stream.write_u8(0)?;
}
}
}
DaemonSocketAction::GetModuleDir => {
let index = stream.read_usize()?;
let module = &context.modules[index];
let dir = format!("{}/{}", constants::PATH_MODULES_DIR, module.name);
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
_ => {}
}
Ok(())
}