You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
init monitor
This commit is contained in:
@@ -14,8 +14,8 @@ target_include_directories(zygisk PRIVATE include)
|
||||
target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap)
|
||||
|
||||
aux_source_directory(ptracer PTRACER_SRC_LIST)
|
||||
add_executable(libptracer.so ${PTRACER_SRC_LIST})
|
||||
target_include_directories(libptracer.so PRIVATE include)
|
||||
target_link_libraries(libptracer.so cxx::cxx log common)
|
||||
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)
|
||||
|
||||
add_subdirectory(external)
|
||||
|
||||
@@ -747,8 +747,6 @@ void hook_functions() {
|
||||
std::remove_if(plt_hook_list->begin(), plt_hook_list->end(),
|
||||
[](auto &t) { return *std::get<3>(t) == nullptr;}),
|
||||
plt_hook_list->end());
|
||||
|
||||
initialize_jni_hook();
|
||||
}
|
||||
|
||||
static void hook_unloader() {
|
||||
|
||||
@@ -13,50 +13,17 @@
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
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");
|
||||
if (argc >= 2 && argv[1] == "monitor"sv) {
|
||||
init_monitor();
|
||||
return 0;
|
||||
} else if (argc >= 3 && argv[1] == "trace"sv) {
|
||||
auto pid = strtol(argv[2], 0, 0);
|
||||
return !trace_zygote(pid);
|
||||
} else if (argc >= 3 && argv[1] == "ctl"sv) {
|
||||
// TODO
|
||||
return 1;
|
||||
} else {
|
||||
LOGE("usage: %s monitor | trace <pid> | ctl <command>", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
int wait_for_zygote();
|
||||
void init_monitor();
|
||||
bool trace_zygote(int pid);
|
||||
int find_zygote();
|
||||
|
||||
@@ -1,6 +1,17 @@
|
||||
#include <sys/system_properties.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/signalfd.h>
|
||||
#include <err.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include "main.hpp"
|
||||
#include "utils.hpp"
|
||||
@@ -9,78 +20,330 @@
|
||||
|
||||
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;
|
||||
|
||||
#define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8)))
|
||||
|
||||
enum Command {
|
||||
START = 1,
|
||||
STOP,
|
||||
EXIT
|
||||
};
|
||||
|
||||
enum TracingState {
|
||||
TRACING = 1,
|
||||
STOPPING,
|
||||
STOPPED
|
||||
};
|
||||
|
||||
constexpr char SOCKET_NAME[] = "init_monitor";
|
||||
|
||||
struct EventLoop;
|
||||
|
||||
struct EventHandler {
|
||||
virtual int GetFd() = 0;
|
||||
virtual void HandleEvent(EventLoop& loop, uint32_t event) = 0;
|
||||
};
|
||||
|
||||
struct EventLoop {
|
||||
private:
|
||||
int epoll_fd_;
|
||||
bool running = false;
|
||||
public:
|
||||
bool Init() {
|
||||
epoll_fd_ = epoll_create(1);
|
||||
if (epoll_fd_ == -1) {
|
||||
PLOGE("failed to create");
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stop() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
void Loop() {
|
||||
running = true;
|
||||
constexpr auto MAX_EVENTS = 2;
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
while (running) {
|
||||
int nfds = epoll_wait(epoll_fd_, events, MAX_EVENTS, -1);
|
||||
if (nfds == -1) {
|
||||
PLOGE("epoll_wait");
|
||||
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;
|
||||
for (int i = 0; i < nfds; i++) {
|
||||
reinterpret_cast<EventHandler *>(events[i].data.ptr)->HandleEvent(*this,
|
||||
events[i].events);
|
||||
if (!running) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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, "stopped");
|
||||
prop = __system_property_find(name);
|
||||
if (prop == nullptr) {
|
||||
LOGE("failed to create prop");
|
||||
exit(1);
|
||||
bool RegisterHandler(EventHandler &handler, uint32_t events) {
|
||||
struct epoll_event ev{};
|
||||
ev.events = events;
|
||||
ev.data.ptr = &handler;
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, handler.GetFd(), &ev) == -1) {
|
||||
PLOGE("failed to add event handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnregisterHandler(EventHandler &handler) {
|
||||
if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), nullptr) == -1) {
|
||||
PLOGE("failed to del event handler");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
~EventLoop() {
|
||||
if (epoll_fd_ >= 0) close(epoll_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
static TracingState tracing_state = TRACING;
|
||||
static bool exit_requested = false;
|
||||
|
||||
struct SocketHandler : public EventHandler {
|
||||
int sock_fd_;
|
||||
|
||||
bool Init() {
|
||||
sock_fd_ = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
|
||||
if (sock_fd_ == -1) {
|
||||
PLOGE("socket create");
|
||||
return false;
|
||||
}
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path={0},
|
||||
};
|
||||
strcpy(addr.sun_path + 1, SOCKET_NAME);
|
||||
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
|
||||
if (bind(sock_fd_, (struct sockaddr *) &addr, socklen) == -1) {
|
||||
PLOGE("bind socket");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetFd() override {
|
||||
return sock_fd_;
|
||||
}
|
||||
|
||||
void HandleEvent(EventLoop &loop, uint32_t event) override {
|
||||
Command cmd;
|
||||
for (;;) {
|
||||
auto nread = read(sock_fd_, &cmd, sizeof(cmd));
|
||||
if (nread == -1) {
|
||||
if (errno == EAGAIN) {
|
||||
break;
|
||||
}
|
||||
PLOGE("read socket");
|
||||
}
|
||||
if (nread != sizeof(cmd)) {
|
||||
LOGE("read %zu != %zu", nread, sizeof(cmd));
|
||||
continue;
|
||||
}
|
||||
switch (cmd) {
|
||||
case START:
|
||||
if (exit_requested) break;
|
||||
if (tracing_state == STOPPING) {
|
||||
tracing_state = TRACING;
|
||||
} else if (tracing_state == STOPPED) {
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
LOGI("start tracing init");
|
||||
tracing_state = TRACING;
|
||||
}
|
||||
break;
|
||||
case STOP:
|
||||
if (exit_requested) break;
|
||||
if (tracing_state == TRACING) {
|
||||
LOGI("stop tracing requested");
|
||||
tracing_state = STOPPING;
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0);
|
||||
}
|
||||
break;
|
||||
case EXIT:
|
||||
LOGI("prepare for exit ...");
|
||||
exit_requested = true;
|
||||
if (tracing_state == TRACING) {
|
||||
LOGI("stop tracing requested");
|
||||
tracing_state = STOPPING;
|
||||
ptrace(PTRACE_INTERRUPT, 1, 0, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
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);
|
||||
|
||||
~SocketHandler() {
|
||||
if (sock_fd_ >= 0) close(sock_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
struct PtraceHandler : public EventHandler {
|
||||
private:
|
||||
int signal_fd_;
|
||||
struct signalfd_siginfo fdsi;
|
||||
int status;
|
||||
std::set<pid_t> process;
|
||||
public:
|
||||
bool Init() {
|
||||
sigset_t mask;
|
||||
sigemptyset(&mask);
|
||||
sigaddset(&mask, SIGCHLD);
|
||||
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
|
||||
PLOGE("set sigprocmask");
|
||||
return false;
|
||||
}
|
||||
signal_fd_ = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
|
||||
if (signal_fd_ == -1) {
|
||||
PLOGE("create signalfd");
|
||||
return false;
|
||||
}
|
||||
ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK);
|
||||
return true;
|
||||
}
|
||||
|
||||
int GetFd() override {
|
||||
return signal_fd_;
|
||||
}
|
||||
|
||||
void HandleEvent(EventLoop &loop, uint32_t event) override {
|
||||
for (;;) {
|
||||
ssize_t s = read(signal_fd_, &fdsi, sizeof(fdsi));
|
||||
if (s == -1) {
|
||||
if (errno == EAGAIN) break;
|
||||
PLOGE("read signalfd");
|
||||
continue;
|
||||
}
|
||||
if (s != sizeof(fdsi)) {
|
||||
LOGW("read %zu != %zu", s, sizeof(fdsi));
|
||||
continue;
|
||||
}
|
||||
if (fdsi.ssi_signo != SIGCHLD) {
|
||||
LOGW("no sigchld received");
|
||||
continue;
|
||||
}
|
||||
int pid;
|
||||
while ((pid = waitpid(-1, &status, __WALL | WNOHANG)) != 0) {
|
||||
if (pid == -1) {
|
||||
if (tracing_state == STOPPED && errno == ECHILD) break;
|
||||
err(EXIT_FAILURE, "waitpid");
|
||||
}
|
||||
if (pid == 1) {
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) {
|
||||
long child_pid;
|
||||
ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid);
|
||||
LOGI("forked %ld", child_pid);
|
||||
} else if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_STOP) &&
|
||||
tracing_state == STOPPING) {
|
||||
if (ptrace(PTRACE_DETACH, 1, 0, 0) == -1)
|
||||
PLOGE("failed to detach init");
|
||||
tracing_state = STOPPED;
|
||||
LOGI("stop tracing init");
|
||||
continue;
|
||||
}
|
||||
// suppress
|
||||
// TODO: inject signal
|
||||
if (WIFSTOPPED(status)) {
|
||||
if (WPTEVENT(status) == 0) {
|
||||
LOGW("suppressed signal sent to init: %s %d",
|
||||
sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status));
|
||||
}
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
auto state = process.find(pid);
|
||||
if (state == process.end()) {
|
||||
LOGI("new process %d attached", pid);
|
||||
process.emplace(pid);
|
||||
ptrace(PTRACE_SETOPTIONS, pid, 0, PTRACE_O_TRACEEXEC);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
continue;
|
||||
} else {
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
|
||||
auto program = get_program(pid);
|
||||
LOGD("%d program %s", pid, program.c_str());
|
||||
const char* tracer = nullptr;
|
||||
if (program == "/system/bin/app_process64") {
|
||||
tracer = "./bin/zygisk-ptrace64";
|
||||
} else if (program == "/system/bin/app_process32") {
|
||||
tracer = "./bin/zygisk-ptrace32";
|
||||
}
|
||||
if (tracer != nullptr) {
|
||||
LOGD("stopping %d", pid);
|
||||
kill(pid, SIGSTOP);
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
wait_pid(pid, &status, __WALL);
|
||||
if (STOPPED_WITH(SIGSTOP, 0)) {
|
||||
LOGD("detaching %d", pid);
|
||||
ptrace(PTRACE_DETACH, pid, 0, SIGSTOP);
|
||||
status = 0;
|
||||
auto p = fork_dont_care();
|
||||
if (p == 0) {
|
||||
execl(tracer, basename(tracer), "trace", std::to_string(pid).c_str(), nullptr);
|
||||
PLOGE("failed to exec, kill");
|
||||
kill(pid, SIGKILL);
|
||||
exit(1);
|
||||
} else if (p == -1) {
|
||||
PLOGE("failed to fork, kill");
|
||||
kill(pid, SIGKILL);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGD("process %d received unknown status %s", pid,
|
||||
parse_status(status).c_str());
|
||||
}
|
||||
process.erase(state);
|
||||
if (WIFSTOPPED(status)) {
|
||||
LOGI("detach process %d", pid);
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (pid == -1) {
|
||||
LOGE("failed to find zygote");
|
||||
exit(1);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
last_state = val;
|
||||
LOGD("sigchld handle done");
|
||||
}
|
||||
|
||||
~PtraceHandler() {
|
||||
if (signal_fd_ >= 0) close(signal_fd_);
|
||||
}
|
||||
};
|
||||
|
||||
void init_monitor() {
|
||||
LOGI("init monitor started");
|
||||
SocketHandler socketHandler{};
|
||||
socketHandler.Init();
|
||||
PtraceHandler ptraceHandler{};
|
||||
ptraceHandler.Init();
|
||||
EventLoop looper;
|
||||
looper.Init();
|
||||
looper.RegisterHandler(socketHandler, EPOLLIN | EPOLLET);
|
||||
looper.RegisterHandler(ptraceHandler, EPOLLIN | EPOLLET);
|
||||
looper.Loop();
|
||||
LOGI("exit");
|
||||
}
|
||||
|
||||
void send_control_command(Command cmd) {
|
||||
int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
|
||||
if (sockfd == -1) err(EXIT_FAILURE, "socket");
|
||||
struct sockaddr_un addr{
|
||||
.sun_family = AF_UNIX,
|
||||
.sun_path={0},
|
||||
};
|
||||
strcpy(addr.sun_path + 1, SOCKET_NAME);
|
||||
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
|
||||
auto nsend = sendto(sockfd, (void *) &cmd, sizeof(cmd), 0, (sockaddr *) &addr, socklen);
|
||||
if (nsend == -1) {
|
||||
err(EXIT_FAILURE, "send");
|
||||
} else if (nsend != sizeof(cmd)) {
|
||||
printf("send %ld != %ld\n", nsend, sizeof(cmd));
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ bool inject_on_main(int pid, const char *lib_path) {
|
||||
LOGD("other reason %d", status);
|
||||
}*/
|
||||
} else {
|
||||
LOGE("stopped by other reason: %d", status);
|
||||
LOGE("stopped by other reason: %s", parse_status(status).c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -153,6 +153,7 @@ bool inject_on_main(int pid, const char *lib_path) {
|
||||
#define STOPPED_WITH(sig, event) (WIFSTOPPED(status) && WSTOPSIG(status) == (sig) && (status >> 16) == (event))
|
||||
|
||||
bool trace_zygote(int pid) {
|
||||
LOGI("start tracing %d", 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) { \
|
||||
@@ -161,20 +162,17 @@ bool trace_zygote(int pid) {
|
||||
}
|
||||
int status;
|
||||
LOGI("tracing %d (tracer %d)", pid, getpid());
|
||||
if (ptrace(PTRACE_SEIZE, pid, 0, PTRACE_O_TRACEEXEC) == -1) {
|
||||
if (ptrace(PTRACE_SEIZE, pid, 0, 0) == -1) {
|
||||
PLOGE("seize");
|
||||
return false;
|
||||
}
|
||||
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");
|
||||
if (!inject_on_main(pid, "/dev/zygisk/lib" LP_SELECT("", "64") "/libzygisk.so")) {
|
||||
LOGE("failed to inject");
|
||||
return false;
|
||||
}
|
||||
LOGD("inject done, continue process");
|
||||
if (kill(pid, SIGCONT)) {
|
||||
PLOGE("kill");
|
||||
return false;
|
||||
@@ -186,30 +184,16 @@ bool trace_zygote(int pid) {
|
||||
WAIT_OR_DIE
|
||||
if (STOPPED_WITH(SIGCONT, 0)) {
|
||||
LOGD("received SIGCONT");
|
||||
ptrace(PTRACE_CONT, pid, 0, 0);
|
||||
ptrace(PTRACE_DETACH, pid, 0, SIGCONT);
|
||||
}
|
||||
} 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");
|
||||
CONT_OR_DIE
|
||||
} else {
|
||||
LOGE("unknown state %s, neither EVENT_STOP nor SIGSTOP", parse_status(status).c_str());
|
||||
return false;
|
||||
}
|
||||
WAIT_OR_DIE
|
||||
// enter the app_process
|
||||
if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_EXEC)) {
|
||||
LOGI("app_process exec-ed");
|
||||
if (!inject_on_main(pid, "/dev/zygisk/lib" LP_SELECT("", "64") "/libzygisk.so")) {
|
||||
LOGE("failed to inject");
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
LOGE("unknown status %s", parse_status(status).c_str());
|
||||
LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", parse_status(status).c_str());
|
||||
ptrace(PTRACE_DETACH, pid, 0, 0);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <sstream>
|
||||
#include <ios>
|
||||
#include <cstring>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "utils.hpp"
|
||||
#include "logging.h"
|
||||
@@ -62,37 +63,6 @@ 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{
|
||||
@@ -342,7 +312,7 @@ uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_add
|
||||
}
|
||||
return regs.REG_RET;
|
||||
} else {
|
||||
LOGE("stopped by other reason %d", status);
|
||||
LOGE("stopped by other reason %s", parse_status(status).c_str());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -375,19 +345,33 @@ int wait_pid(int pid, int* status, int flags) {
|
||||
|
||||
std::string parse_status(int status) {
|
||||
std::ostringstream os;
|
||||
os << "status " << std::hex << status;
|
||||
os << std::dec << " ";
|
||||
os << "0x" << std::hex << status << std::dec << " ";
|
||||
if (WIFEXITED(status)) {
|
||||
os << "exited with " << WEXITSTATUS(status);
|
||||
} else if (WIFSIGNALED(status)) {
|
||||
os << "signaled with " << sigabbrev_np(WTERMSIG(status)) << "(" << WTERMSIG(status) << ")";
|
||||
} else if (WIFSTOPPED(status)) {
|
||||
os << "stopped by";
|
||||
os << "stopped by ";
|
||||
auto stop_sig = WSTOPSIG(status);
|
||||
os << "signal " << sigabbrev_np(stop_sig) << "(" << stop_sig << "),";
|
||||
os << "signal=" << sigabbrev_np(stop_sig) << "(" << stop_sig << "),";
|
||||
os << "event=" << parse_ptrace_event(status);
|
||||
} else {
|
||||
os << "unknown";
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
std::string get_program(int pid) {
|
||||
std::string path = "/proc/";
|
||||
path += std::to_string(pid);
|
||||
path += "/exe";
|
||||
constexpr const auto SIZE = 256;
|
||||
char buf[SIZE + 1];
|
||||
auto sz = readlink(path.c_str(), buf, SIZE);
|
||||
if (sz == -1) {
|
||||
PLOGE("readlink /proc/%d/exe", pid);
|
||||
return "";
|
||||
}
|
||||
buf[sz] = 0;
|
||||
return buf;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include "daemon.h"
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LOG_TAG "zygisk-ptracer64"
|
||||
#define LOG_TAG "zygisk-ptrace64"
|
||||
#else
|
||||
#define LOG_TAG "zygisk-ptracer32"
|
||||
#define LOG_TAG "zygisk-ptrace32"
|
||||
#endif
|
||||
#include "logging.h"
|
||||
|
||||
@@ -91,6 +91,8 @@ int wait_pid(int pid, int* status, int flags);
|
||||
|
||||
std::string parse_status(int status);
|
||||
|
||||
#define WPTEVENT(x) (x >> 16)
|
||||
|
||||
#define CASE_CONST_RETURN(x) case x: return #x;
|
||||
|
||||
inline const char* parse_ptrace_event(int status) {
|
||||
@@ -114,5 +116,5 @@ inline const char* sigabbrev_np(int sig) {
|
||||
return "(unknown)";
|
||||
}
|
||||
|
||||
std::map<ino_t, std::string> ScanUnixSockets();
|
||||
std::string get_program(int pid);
|
||||
|
||||
|
||||
@@ -114,18 +114,17 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||
extract "$ZIPFILE" 'lib/x86/libzygisk.so' "$MODPATH/lib" true
|
||||
ln -sf "zygiskd32" "$MODPATH/bin/zygisk-cp32"
|
||||
extract "$ZIPFILE" 'lib/x86/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer32"
|
||||
extract "$ZIPFILE" 'lib/x86/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||
fi
|
||||
|
||||
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.so' "$MODPATH/lib64" true
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64"
|
||||
extract "$ZIPFILE" 'lib/x86_64/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer64"
|
||||
extract "$ZIPFILE" 'lib/x86_64/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||
else
|
||||
if [ "$HAS32BIT" = true ]; then
|
||||
ui_print "- Extracting arm libraries"
|
||||
@@ -133,18 +132,17 @@ else
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk.so' "$MODPATH/lib" true
|
||||
ln -sf "zygiskd32" "$MODPATH/bin/zygisk-cp32"
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer32"
|
||||
extract "$ZIPFILE" 'lib/armeabi-v7a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace32"
|
||||
fi
|
||||
|
||||
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.so' "$MODPATH/lib64" true
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-wd"
|
||||
ln -sf "zygiskd64" "$MODPATH/bin/zygisk-cp64"
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libptracer.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libptracer.so" "$MODPATH/bin/zygisk-ptracer64"
|
||||
extract "$ZIPFILE" 'lib/arm64-v8a/libzygisk_ptrace.so' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/libzygisk_ptrace.so" "$MODPATH/bin/zygisk-ptrace64"
|
||||
fi
|
||||
|
||||
ui_print "- Setting permissions"
|
||||
|
||||
@@ -32,15 +32,12 @@ if [ -f $MODDIR/lib64/libzygisk.so ];then
|
||||
create_sys_perm /dev/zygisk/lib64
|
||||
cp $MODDIR/lib64/libzygisk.so /dev/zygisk/lib64/libzygisk.so
|
||||
chcon u:object_r:system_lib_file:s0 /dev/zygisk/lib64/libzygisk.so
|
||||
setprop ctl.sigstop_on zygote
|
||||
unshare -m sh -c "./bin/zygisk-ptracer64 prop_monitor &"
|
||||
fi
|
||||
|
||||
if [ -f $MODDIR/lib/libzygisk.so ];then
|
||||
create_sys_perm /dev/zygisk/lib
|
||||
cp $MODDIR/lib/libzygisk.so /dev/zygisk/lib/libzygisk.so
|
||||
chcon u:object_r:system_lib_file:s0 /dev/zygisk/lib/libzygisk.so
|
||||
setprop ctl.sigstop_on zygote_secondary
|
||||
unshare -m sh -c "./bin/zygisk-ptracer32 prop_monitor &"
|
||||
fi
|
||||
|
||||
unshare -m sh -c "./bin/zygisk-ptrace64 monitor &"
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
deny vold fusectlfs file write
|
||||
|
||||
allow * tmpfs * *
|
||||
allow zygote appdomain_tmpfs dir *
|
||||
allow zygote appdomain_tmpfs file *
|
||||
|
||||
@@ -6,6 +6,7 @@ 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"
|
||||
@@ -15,13 +16,6 @@ touch /dev/zygisk/wd
|
||||
|
||||
cd "$MODDIR"
|
||||
|
||||
# temporary fix AVD 11 magisk
|
||||
# if [ -f /dev/zygisk_service ];then
|
||||
# log -p i -t "zygisk-sh" "service called twice";
|
||||
# exit;
|
||||
# fi
|
||||
# touch /dev/zygisk_service
|
||||
|
||||
if [ "$(which magisk)" ]; then
|
||||
for file in ../*; do
|
||||
if [ -d "$file" ] && [ -d "$file/zygisk" ] && ! [ -f "$file/disable" ]; then
|
||||
@@ -36,4 +30,5 @@ if [ "$(which magisk)" ]; then
|
||||
fi
|
||||
|
||||
[ "$DEBUG" = true ] && export RUST_BACKTRACE=1
|
||||
unshare -m sh -c "bin/zygisk-wd &"
|
||||
unshare -m sh -c "bin/zygisk-cp64 &"
|
||||
unshare -m sh -c "bin/zygisk-cp32 &"
|
||||
|
||||
@@ -2,7 +2,6 @@ mod constants;
|
||||
mod dl;
|
||||
mod root_impl;
|
||||
mod utils;
|
||||
mod watchdog;
|
||||
mod zygiskd;
|
||||
|
||||
use std::future::Future;
|
||||
@@ -25,9 +24,8 @@ fn start(name: &str) -> Result<()> {
|
||||
utils::switch_mount_namespace(1)?;
|
||||
root_impl::setup();
|
||||
match name.trim_start_matches("zygisk-") {
|
||||
"wd" => async_start(watchdog::main())?,
|
||||
lp_select!("cp32", "cp64") => zygiskd::main()?,
|
||||
_ => println!("Available commands: wd, fuse, cp, ptrace"),
|
||||
_ => println!("Available command: cp[32|64]"),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
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::fd::AsRawFd;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use futures::stream::FuturesUnordered;
|
||||
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();
|
||||
|
||||
pub async fn main() -> Result<()> {
|
||||
let result = run().await;
|
||||
if result.is_err() {
|
||||
set_prop_hint(constants::STATUS_CRASHED)?;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
async fn run() -> Result<()> {
|
||||
info!("Start zygisk watchdog");
|
||||
check_permission()?;
|
||||
mount_prop().await?;
|
||||
if check_and_set_hint()? == false {
|
||||
log::warn!("Requirements not met, exiting");
|
||||
return Ok(());
|
||||
}
|
||||
spawn_daemon().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_permission() -> Result<()> {
|
||||
info!("Check permission");
|
||||
let uid = getuid();
|
||||
if !uid.is_root() {
|
||||
bail!("UID is not 0");
|
||||
}
|
||||
|
||||
let gid = getgid();
|
||||
if !gid.is_root() {
|
||||
bail!("GID is not 0");
|
||||
}
|
||||
|
||||
let context = fs::read_to_string("/proc/self/attr/current")?;
|
||||
let context = context.trim_end_matches('\0');
|
||||
if context != "u:r:su:s0" && context != "u:r:magisk:s0" {
|
||||
bail!("SELinux context incorrect: {context}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn mount_prop() -> Result<()> {
|
||||
let module_prop_file = fs::File::open(constants::PATH_MODULE_PROP)?;
|
||||
let mut section = 0;
|
||||
let mut sections: [String; 2] = [String::new(), String::new()];
|
||||
let lines = BufReader::new(module_prop_file).lines();
|
||||
for line in lines {
|
||||
let line = line?;
|
||||
if line.starts_with("description=") {
|
||||
sections[0].push_str("description=");
|
||||
sections[1].push_str(line.trim_start_matches("description="));
|
||||
sections[1].push('\n');
|
||||
section = 1;
|
||||
} else {
|
||||
sections[section].push_str(&line);
|
||||
sections[section].push('\n');
|
||||
}
|
||||
}
|
||||
PROP_SECTIONS.init(sections);
|
||||
|
||||
info!("Mount {} -> {}", constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP);
|
||||
fs::File::create(constants::PATH_PROP_OVERLAY)?;
|
||||
mount_bind(constants::PATH_PROP_OVERLAY, constants::PATH_MODULE_PROP)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_prop_hint(hint: &str) -> Result<()> {
|
||||
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())?;
|
||||
file.write_all(b"] ")?;
|
||||
file.write_all(PROP_SECTIONS[1].as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_and_set_hint() -> Result<bool> {
|
||||
let root_impl = root_impl::get_impl();
|
||||
match root_impl {
|
||||
root_impl::RootImpl::None => set_prop_hint(constants::STATUS_ROOT_IMPL_NONE)?,
|
||||
root_impl::RootImpl::TooOld => set_prop_hint(constants::STATUS_ROOT_IMPL_TOO_OLD)?,
|
||||
root_impl::RootImpl::Abnormal => set_prop_hint(constants::STATUS_ROOT_IMPL_ABNORMAL)?,
|
||||
root_impl::RootImpl::Multiple => set_prop_hint(constants::STATUS_ROOT_IMPL_MULTIPLE)?,
|
||||
_ => {
|
||||
set_prop_hint(constants::STATUS_LOADED)?;
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
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?;
|
||||
// FIXME: we must not get id here
|
||||
log::error!("Daemon process {} died: {}", id, result);
|
||||
Ok(())
|
||||
}
|
||||
if let Ok(it) = daemon32 {
|
||||
child_ids.push(it.id().unwrap());
|
||||
futures.push(Box::pin(spawn_daemon(it)));
|
||||
}
|
||||
if let Ok(it) = daemon64 {
|
||||
child_ids.push(it.id().unwrap());
|
||||
futures.push(Box::pin(spawn_daemon(it)));
|
||||
}
|
||||
|
||||
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 {
|
||||
debug!("Killing child process {}", child);
|
||||
let _ = kill_process(Pid::from_raw(child as i32).unwrap(), Signal::Kill);
|
||||
}
|
||||
|
||||
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 ...");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user