This commit is contained in:
5ec1cff
2023-11-03 11:41:28 +08:00
parent f78c217552
commit f5e0a418c9
18 changed files with 314 additions and 55 deletions

View File

@@ -93,7 +93,7 @@ namespace zygiskd {
}
int GetModuleDir(size_t index) {
int fd = Connect(1);
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("GetModuleDir");
return -1;
@@ -102,4 +102,13 @@ namespace zygiskd {
socket_utils::write_usize(fd, index);
return socket_utils::recv_fd(fd);
}
void ZygoteRestart() {
UniqueFd fd = Connect(1);
if (fd == -1) {
PLOGE("Could not notify ZygoteRestart");
return;
}
socket_utils::write_u8(fd, (uint8_t) SocketAction::ZygoteRestart);
}
}

View File

@@ -58,6 +58,7 @@ namespace zygiskd {
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestart,
};
bool PingHeartbeat();
@@ -71,4 +72,6 @@ namespace zygiskd {
int ConnectCompanion(size_t index);
int GetModuleDir(size_t index);
void ZygoteRestart();
}

View File

@@ -2,21 +2,61 @@
#include <cstdlib>
#include <string_view>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/system_properties.h>
#include "main.hpp"
#include "utils.hpp"
#include "daemon.h"
using namespace std::string_view_literals;
int main(int argc, char **argv) {
if (argc >= 2 && argv[1] == "prop_monitor"sv) {
if (access("/system/lib" LP_SELECT("", "64"), R_OK) == 0) prop_monitor_main();
} else if (argc >= 3 && argv[1] == "trace-zygote"sv) {
auto pid = strtol(argv[2], nullptr, 0);
trace_zygote_main(pid);
} else {
if (argc >= 2) LOGE("unknown command %s", argv[1]);
else LOGE("no command specified");
if (access("/system/lib" LP_SELECT("", "64"), R_OK) != 0) return 1;
auto lock_fd = open("/dev/zygisk/lock" LP_SELECT("32", "64"), O_CLOEXEC | O_CREAT, O_RDONLY);
if (lock_fd == -1) {
PLOGE("create lock file");
return 1;
}
return 0;
struct flock lock{
.l_type = F_RDLCK,
.l_whence = SEEK_SET,
.l_start = 0,
.l_len = 0
};
if (fcntl(lock_fd, F_SETLK, &lock) == -1) {
PLOGE("set file lock");
close(lock_fd);
return 1;
}
LOGI("zygote monitor started");
struct timespec last_launch_time { .tv_sec = 0, .tv_nsec = 0 }, ts;
int launch_count = 0;
bool first = true;
while (true) {
auto pid = wait_for_zygote();
if (pid == -1) break;
LOGI("inject zygote %d", pid);
if (first) first = false;
else {
LOGI("notify zygisk companion restart");
zygiskd::ZygoteRestart();
}
clock_gettime(CLOCK_MONOTONIC, &ts);
auto delta = ts.tv_sec - last_launch_time.tv_sec;
if (delta > 30) launch_count++;
else launch_count = 0;
if (launch_count >= 5) {
LOGE("zygote crash too much times, stop");
break;
}
memcpy(&last_launch_time, &ts, sizeof(struct timespec));
if (!trace_zygote(pid)) {
break;
}
}
__system_property_set("ctl.sigstop_off", "zygote");
__system_property_set("ctl.sigstop_off", "zygote_secondary");
return 1;
}

View File

@@ -1,4 +1,5 @@
#pragma once
void prop_monitor_main();
void trace_zygote_main(int pid);
int wait_for_zygote();
bool trace_zygote(int pid);
int find_zygote();

View File

@@ -1,37 +1,86 @@
#include <sys/system_properties.h>
#include <unistd.h>
#include <sys/stat.h>
#include "main.hpp"
#include "utils.hpp"
#include "files.hpp"
#include "misc.hpp"
void prop_monitor_main() {
LOGI("prop monitor started");
// if service is not running, pid = ""
auto name = "init.svc_debug_pid." LP_SELECT("zygote_secondary", "zygote"); // argv[1];
LOGI("start monitoring %s", name);
using namespace std::string_view_literals;
int find_zygote() {
LOGD("find zygote");
auto sockets = ScanUnixSockets();
auto dir = xopen_dir("/proc");
for (dirent *entry; (entry = readdir(dir.get()));) {
auto pid = parse_int(entry->d_name);
char comm[18];
char state;
if (pid == -1 || pid == 1) continue;
auto stat_file = xopen_file((std::string("/proc/") + std::to_string(pid) + "/stat").c_str(), "r");
if (stat_file == nullptr) continue;
if (fscanf(stat_file.get(), "%*d %17s %c", comm, &state) != 2
|| comm != "(init)"sv
|| state != 'T') {
continue;
}
LOGD("%d is stopped init", pid);
auto fd_dir = xopen_dir((std::string("/proc/") + std::to_string(pid) + "/fd").c_str());
if (fd_dir == nullptr) continue;
for (dirent *fd_entry; (fd_entry = readdir(fd_dir.get()));) {
if (fd_entry->d_name == "."sv || fd_entry->d_name == ".."sv) continue;
struct stat st{};
if (stat((std::string("/proc/") + std::to_string(pid) + "/fd/" + fd_entry->d_name).c_str(), &st) == -1) {
PLOGE("stat /proc/%d/fd/%s", pid, fd_entry->d_name);
continue;
}
if ((st.st_mode & S_IFSOCK) == 0) continue;
auto it = sockets.find(st.st_ino);
if (it != sockets.end() && it->second == LP_SELECT("/dev/socket/zygote_secondary", "/dev/socket/zygote")) {
LOGD("%d is zygote", pid);
return pid;
}
}
}
return -1;
}
int wait_for_zygote() {
auto name = "init.svc." LP_SELECT("zygote_secondary", "zygote");
auto prop = __system_property_find(name);
if (prop == nullptr) {
__system_property_set(name, "");
__system_property_set(name, "stopped");
prop = __system_property_find(name);
if (prop == nullptr) {
LOGE("failed to create prop");
exit(1);
}
}
std::string last_state = "running";
char val[PROP_VALUE_MAX];
uint32_t new_serial = 0;
while (true) {
__system_property_wait(prop, new_serial, &new_serial, nullptr);
__system_property_get(name, val);
LOGD("%s(%u): %s\n", name, new_serial, val);
auto pid = strtol(val, nullptr, 0);
if (pid != 0) {
LOGD("start ptrace %ld", pid);
if (fork_dont_care() == 0) {
execl("/proc/self/exe", "zygisk-ptracer", "trace-zygote", val, nullptr);
PLOGE("failed to exec");
if (val != last_state && val == "running"sv) {
LOGI("zygote is running, find zygote");
int pid = -1;
for (int i = 0; i < 5; i++) {
pid = find_zygote();
if (pid != -1) break;
else {
LOGW("could not find zygote, wait 1s");
sleep(1);
}
}
if (pid == -1) {
LOGE("failed to find zygote");
exit(1);
}
return pid;
}
last_state = val;
}
}

View File

@@ -152,49 +152,65 @@ bool inject_on_main(int pid, const char *lib_path) {
#define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event))
void trace_zygote_main(int pid) {
bool trace_zygote(int pid) {
#define WAIT_OR_DIE if (wait_pid(pid, &status, __WALL) != pid) return false;
#define CONT_OR_DIE \
if (ptrace(PTRACE_CONT, pid, 0, 0) == -1) { \
PLOGE("cont"); \
return false; \
}
int status;
LOGI("tracing %d (tracer %d)", pid, getpid());
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC) == -1) {
PLOGE("seize");
return;
return false;
}
wait_pid(pid, &status, __WALL);
struct finally {
int pid;
~finally() {
ptrace(PTRACE_DETACH, pid, 0, 0);
}
} _{pid};
WAIT_OR_DIE
if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) {
// if SIGSTOP is delivered before we seized it
LOGD("process is already stopped");
kill(pid, SIGCONT);
ptrace(PTRACE_CONT, pid, 0, 0);
wait_pid(pid, &status, __WALL);
if (kill(pid, SIGCONT)) {
PLOGE("kill");
return false;
}
CONT_OR_DIE
WAIT_OR_DIE
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP)) {
ptrace(PTRACE_CONT, pid, 0, 0);
wait_pid(pid, &status, __WALL);
CONT_OR_DIE
WAIT_OR_DIE
if (STOPPED_WITH(SIGCONT, 0)) {
LOGD("received SIGCONT");
ptrace(PTRACE_CONT, pid, 0, 0);
}
} else {
LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", parse_status(status).c_str());
return false;
}
} else if (STOPPED_WITH(SIGSTOP, 0)) {
// if SIGSTOP is delivered after we seized it
LOGD("process received SIGSTOP, suppress");
ptrace(PTRACE_CONT, pid, 0, 0);
CONT_OR_DIE
} else {
LOGE("unknown state %s, neither EVENT_STOP nor SIGSTOP", parse_status(status).c_str());
exit(1);
return false;
}
wait_pid(pid, &status, __WALL);
WAIT_OR_DIE
// enter the app_process
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
LOGD("app_process exec-ed");
LOGI("app_process exec-ed");
if (!inject_on_main(pid, "/dev/zygisk/lib" LP_SELECT("", "64") "/libzygisk.so")) {
LOGE("failed to inject");
exit(1);
return false;
}
} else {
LOGE("unknown status %d", status);
exit(1);
LOGE("unknown status %s", parse_status(status).c_str());
return false;
}
ptrace(PTRACE_DETACH, pid, 0, 0);
return true;
}

View File

@@ -62,6 +62,37 @@ std::vector<MapInfo> MapInfo::Scan(const std::string& pid) {
return info;
}
// https://cs.android.com/android/platform/superproject/main/+/main:external/toybox/toys/net/netstat.c;l=200;drc=657f94698c7fc7d4f9838cbcf3b4b78e38939d5c
std::map<ino_t, std::string> ScanUnixSockets() {
constexpr static auto kSocketEntry = 1;
LOGD("scanning unix sockets");
std::map<ino_t, std::string> info;
auto sockets = std::unique_ptr<FILE, decltype(&fclose)>{fopen("/proc/net/unix", "r"), &fclose};
char *line = nullptr;
size_t len = 0;
// skip header
getline(&line, &len, sockets.get());
if (sockets) {
ssize_t read;
while ((read = getline(&line, &len, sockets.get())) > 0) {
line[read - 1] = '\0';
ino_t ino;
char *path = nullptr;
// Num RefCount Protocol Flags Type St Inode Path
if (sscanf(line, "%*p: %*lx %*lx %*lx %*lx %*lx %lu%m[^\n]", &ino, &path) < kSocketEntry) {
continue;
}
if (path != nullptr) {
LOGD("%ld -> %s", ino, path + 1);
info.emplace(ino, path + 1);
free(path);
}
}
free(line);
}
return info;
}
ssize_t write_proc(int pid, uintptr_t *remote_addr, const void *buf, size_t len) {
LOGD("write to remote addr %p size %zu", remote_addr, len);
struct iovec local{

View File

@@ -1,6 +1,7 @@
#pragma once
#include <string>
#include <sys/ptrace.h>
#include <map>
#include "daemon.h"
@@ -37,7 +38,7 @@ struct MapInfo {
/// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries.
/// This is useful to find out the inode of the library to hook.
/// \return A list of \ref MapInfo entries.
[[maybe_unused, gnu::visibility("default")]] static std::vector<MapInfo> Scan(const std::string& pid = "self");
static std::vector<MapInfo> Scan(const std::string& pid = "self");
};
#if defined(__x86_64__)
@@ -112,3 +113,6 @@ inline const char* sigabbrev_np(int sig) {
if (sig > 0 && sig < NSIG) return sys_signame[sig];
return "(unknown)";
}
std::map<ino_t, std::string> ScanUnixSockets();

View File

@@ -6,6 +6,12 @@ MODDIR=${0%/*}
if [ "$ZYGISK_ENABLED" ]; then
exit 0
fi
# temporary fix for AVD 30
if [ -f /dev/zygisk/wd ]; then
log -p i -t "zygisk-sh" "prevent from instance duplicated"
exit
fi
touch /dev/zygisk/wd
cd "$MODDIR"

View File

@@ -20,7 +20,7 @@ num_enum = "0.5"
passfd = "0.1"
proc-maps = "0.3"
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread"] }
rustix = { version = "0.38", features = [ "fs", "process", "mount", "net", "thread" ] }
tokio = { version = "1.28", features = ["full"] }
[profile.release]

View File

@@ -16,19 +16,20 @@ pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Trace;
pub const MAX_LOG_LEVEL: LevelFilter = LevelFilter::Info;
pub const PROP_CTL_RESTART: &str = "ctl.restart";
pub const PROP_CTL_SIGSTOP_OFF: &str = "ctl.sigstop_off";
pub const PATH_PCL: &str = "/system/etc/preloaded-classes";
pub const PATH_ZYGISK_LIB: &str = concatcp!(lp_select!("/system/lib", "/system/lib64"), "/libzygisk.so");
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");
pub const PATH_CP_SOCKET: &str = concatcp!(PATH_WORK_DIR, lp_select!("/cp32.sock", "/cp64.sock"));
pub const PATH_PT_LOCK32: &str = concatcp!(PATH_WORK_DIR, "/lock32");
pub const PATH_PT_LOCK64: &str = concatcp!(PATH_WORK_DIR, "/lock64");
pub const PATH_MODULES_DIR: &str = "..";
pub const PATH_MODULE_PROP: &str = "module.prop";
pub const PATH_CP_BIN32: &str = "bin/zygisk-cp32";
pub const PATH_CP_BIN64: &str = "bin/zygisk-cp64";
pub const PATH_PTRACE_BIN32: &str = "bin/zygisk-ptrace32";
pub const PATH_PTRACE_BIN64: &str = "bin/zygisk-ptrace64";
pub const PATH_PT_BIN32: &str = "bin/zygisk-ptracer32";
pub const PATH_PT_BIN64: &str = "bin/zygisk-ptracer64";
pub const STATUS_LOADED: &str = "😋 Zygisk Next is loaded";
@@ -47,6 +48,7 @@ pub enum DaemonSocketAction {
ReadModules,
RequestCompanionSocket,
GetModuleDir,
ZygoteRestarted,
}
// Zygisk process flags

View File

@@ -1,6 +1,6 @@
use anyhow::Result;
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream};
use std::ffi::{c_char, CStr, CString};
use std::ffi::{c_char, c_void, CStr, CString};
use std::os::fd::AsFd;
use std::os::unix::net::UnixListener;
use std::process::Command;
@@ -177,4 +177,6 @@ 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: *u32, timeout: *const libc::timespec) -> bool;
}

View File

@@ -3,13 +3,18 @@ use anyhow::{bail, Result};
use std::fs;
use std::future::Future;
use std::io::{BufRead, BufReader, Write};
use std::os::fd::AsRawFd;
use std::path::Path;
use std::pin::Pin;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use futures::{FutureExt, pin_mut, select, StreamExt};
use futures::future::Fuse;
use log::{debug, error, info};
use rustix::mount::mount_bind;
use rustix::process::{getgid, getuid, kill_process, Pid, Signal};
use tokio::process::{Child, Command};
use tokio::task;
use tokio::task::JoinHandle;
use crate::utils::LateInit;
static PROP_SECTIONS: LateInit<[String; 2]> = LateInit::new();
@@ -105,16 +110,72 @@ fn check_and_set_hint() -> Result<bool> {
Ok(false)
}
fn wait_for_ptrace(is_32bit: bool) -> Option<JoinHandle<Result<()>>> {
let lock_path = if is_32bit {
if !Path::new(constants::PATH_PT_BIN32).is_file() {
return None
}
constants::PATH_PT_LOCK32
} else {
if !Path::new(constants::PATH_PT_BIN64).is_file() {
return None
}
constants::PATH_PT_LOCK64
};
info!("wait for ptrace 32={}", is_32bit);
Some(task::spawn_blocking(move || -> Result<()> {
let file = match fs::OpenOptions::new().write(true).open(lock_path) {
Ok(f) => f,
Err(e) => {
bail!("failed to open lock: {}", e)
}
};
unsafe {
let lock = libc::flock {
l_type: libc::F_WRLCK as libc::c_short,
l_whence: libc::SEEK_SET as libc::c_short,
l_start: 0,
l_len: 0,
l_pid: 0,
};
loop {
if libc::fcntl(file.as_raw_fd(), libc::F_SETLKW, &lock) == 0 {
bail!("file lock obtained")
} else {
let errno = *libc::__errno();
match errno {
libc::EINTR => continue,
_ => {
bail!("failed to wait on lock: {}", errno)
}
}
}
}
}
}))
}
async fn spawn_daemon() -> Result<()> {
let mut lives = 5;
let lock32 = match wait_for_ptrace(true) {
Some(f) => f.fuse(),
None => Fuse::terminated()
};
let lock64 = match wait_for_ptrace(false) {
Some(f) => f.fuse(),
None => Fuse::terminated()
};
pin_mut!(lock32, lock64);
loop {
let mut futures = FuturesUnordered::<Pin<Box<dyn Future<Output=Result<()>>>>>::new();
let mut child_ids = vec![];
let daemon32 = Command::new(constants::PATH_CP_BIN32).arg("daemon").spawn();
let daemon64 = Command::new(constants::PATH_CP_BIN64).arg("daemon").spawn();
async fn spawn_daemon(mut daemon: Child) -> Result<()> {
let id = daemon.id().unwrap();
let result = daemon.wait().await?;
log::error!("Daemon process {} died: {}", daemon.id().unwrap(), result);
// FIXME: we must not get id here
log::error!("Daemon process {} died: {}", id, result);
Ok(())
}
if let Ok(it) = daemon32 {
@@ -126,8 +187,35 @@ async fn spawn_daemon() -> Result<()> {
futures.push(Box::pin(spawn_daemon(it)));
}
if let Err(e) = futures.next().await.unwrap() {
error!("{}", e);
let mut stop = false;
select! {
l32 = lock32 => {
if let Ok(Err(it)) = l32 {
error!("wait on lock 32: {}", it);
}
error!("wait on lock 32");
stop = true;
},
l64 = lock64 => {
if let Ok(Err(it)) = l64 {
error!("wait on lock 64: {}", it);
}
error!("wait on lock 64");
stop = true;
},
res = futures.select_next_some() => {
if let Err(it) = res {
error!("wait on daemon: {}", it);
}
lives -= 1;
if lives == 0 {
error!("Too many crashes, abort");
stop = true;
}
error!("wait on daemon");
},
complete => panic!("completed unexpectedly")
}
for child in child_ids {
@@ -135,12 +223,14 @@ async fn spawn_daemon() -> Result<()> {
let _ = kill_process(Pid::from_raw(child as i32).unwrap(), Signal::Kill);
}
lives -= 1;
if lives == 0 {
bail!("Too many crashes, abort");
if stop {
utils::set_property(constants::PROP_CTL_SIGSTOP_OFF, "zygote")?;
utils::set_property(constants::PROP_CTL_SIGSTOP_OFF, "zygote_secondary")?;
}
error!("Restarting zygote...");
utils::set_property(constants::PROP_CTL_RESTART, "zygote")?;
if stop {
bail!("Injecting failed or crashed too much times, Resetting ...");
}
}
}

View File

@@ -13,6 +13,8 @@ use std::os::unix::{
prelude::AsRawFd,
};
use std::path::PathBuf;
use std::process::exit;
use log::info;
use rustix::fs::fstat;
use rustix::process::{set_parent_process_death_signal, Signal};
@@ -223,6 +225,10 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
let dir = fs::File::open(dir)?;
stream.send_fd(dir.as_raw_fd())?;
}
DaemonSocketAction::ZygoteRestarted => {
info!("zygote restarted, exit");
exit(0);
}
}
Ok(())
}