From d0da6efd790d4c2096877381e912ffafc1042283 Mon Sep 17 00:00:00 2001 From: ThePedroo Date: Sun, 16 Jun 2024 21:13:44 -0300 Subject: [PATCH] update: some `loader/` code to C This commit changes some code from "loader" folder to use C keywords and not C++ only keywords. --- .gitignore | 1 + loader/src/ptracer/main.cpp | 92 ++- loader/src/ptracer/main.h | 26 + loader/src/ptracer/main.hpp | 21 - loader/src/ptracer/monitor.cpp | 1000 ++++++++++++++++++-------------- loader/src/ptracer/ptracer.cpp | 15 +- loader/src/ptracer/utils.cpp | 68 ++- loader/src/ptracer/utils.hpp | 4 +- 8 files changed, 693 insertions(+), 534 deletions(-) create mode 100644 loader/src/ptracer/main.h delete mode 100644 loader/src/ptracer/main.hpp diff --git a/.gitignore b/.gitignore index 563675f..b085359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .gradle .idea .cxx +.vscode build local.properties Cargo.lock diff --git a/loader/src/ptracer/main.cpp b/loader/src/ptracer/main.cpp index 9e04581..2b10c54 100644 --- a/loader/src/ptracer/main.cpp +++ b/loader/src/ptracer/main.cpp @@ -1,55 +1,53 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "main.hpp" +#include "main.h" #include "utils.hpp" #include "daemon.h" -#include - -using namespace std::string_view_literals; int main(int argc, char **argv) { - zygiskd::Init(getenv("TMP_PATH")); - if (argc >= 2 && argv[1] == "monitor"sv) { - init_monitor(); - return 0; - } else if (argc >= 3 && argv[1] == "trace"sv) { - if (argc >= 4 && argv[3] == "--restart"sv) { - zygiskd::ZygoteRestart(); - } - auto pid = strtol(argv[2], 0, 0); - if (!trace_zygote(pid)) { - kill(pid, SIGKILL); - return 1; - } - return 0; - } else if (argc >= 2 && argv[1] == "ctl"sv) { - if (argc == 3) { - if (argv[2] == "start"sv) { - send_control_command(START); - return 0; - } else if (argv[2] == "stop"sv) { - send_control_command(STOP); - return 0; - } else if (argv[2] == "exit"sv) { - send_control_command(EXIT); - return 0; - } - } - printf("ReZygisk Tracer %s\n", ZKSU_VERSION); - printf("Usage: %s ctl start|stop|exit\n", argv[0]); + zygiskd::Init(getenv("TMP_PATH")); + if (argc >= 2 && strcmp(argv[1], "monitor") == 0) { + init_monitor(); + + return 0; + } else if (argc >= 3 && strcmp(argv[1], "trace") == 0) { + if (argc >= 4 && strcmp(argv[3], "--restart") == 0) zygiskd::ZygoteRestart(); + + long pid = strtol(argv[2], 0, 0); + if (!trace_zygote(pid)) { + kill(pid, SIGKILL); + return 1; - } else if (argc >= 2 && argv[1] == "version"sv) { - printf("ReZygisk Tracer %s\n", ZKSU_VERSION); + } + + return 0; + } else if (argc >= 2 && strcmp(argv[1], "ctl") == 0) { + if (argc == 3) { + if (strcmp(argv[2], "start") == 0) { + send_control_command(START); + return 0; - } else { - printf("ReZygisk Tracer %s\n", ZKSU_VERSION); - printf("usage: %s monitor | trace | ctl | version\n", argv[0]); - return 1; + } else if (strcmp(argv[2], "stop") == 0) { + send_control_command(STOP); + + return 0; + } else if (strcmp(argv[2], "exit") == 0) { + send_control_command(EXIT); + + return 0; + } } + + printf("ReZygisk Tracer %s\n", ZKSU_VERSION); + printf("Usage: %s ctl start|stop|exit\n", argv[0]); + + return 1; + } else if (argc >= 2 && strcmp(argv[1], "version") == 0) { + printf("ReZygisk Tracer %s\n", ZKSU_VERSION); + + return 0; + } else { + printf("ReZygisk Tracer %s\n", ZKSU_VERSION); + printf("usage: %s monitor | trace | ctl | version\n", argv[0]); + + return 1; + } } diff --git a/loader/src/ptracer/main.h b/loader/src/ptracer/main.h new file mode 100644 index 0000000..3306cf6 --- /dev/null +++ b/loader/src/ptracer/main.h @@ -0,0 +1,26 @@ +#ifndef MAIN_HPP +#define MAIN_HPP + +#include + +void init_monitor(); +bool trace_zygote(int pid); + +enum Command { + START = 1, + STOP = 2, + EXIT = 3, + + /* sent from daemon */ + ZYGOTE64_INJECTED = 4, + ZYGOTE32_INJECTED = 5, + DAEMON64_SET_INFO = 6, + DAEMON32_SET_INFO = 7, + DAEMON64_SET_ERROR_INFO = 8, + DAEMON32_SET_ERROR_INFO = 9, + SYSTEM_SERVER_STARTED = 10 +}; + +void send_control_command(enum Command cmd); + +#endif /* MAIN_HPP */ \ No newline at end of file diff --git a/loader/src/ptracer/main.hpp b/loader/src/ptracer/main.hpp deleted file mode 100644 index 72fa101..0000000 --- a/loader/src/ptracer/main.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - - -void init_monitor(); -bool trace_zygote(int pid); - -enum Command { - START = 1, - STOP = 2, - EXIT = 3, - // sent from daemon - ZYGOTE64_INJECTED = 4, - ZYGOTE32_INJECTED = 5, - DAEMON64_SET_INFO = 6, - DAEMON32_SET_INFO = 7, - DAEMON64_SET_ERROR_INFO = 8, - DAEMON32_SET_ERROR_INFO = 9, - SYSTEM_SERVER_STARTED = 10 -}; - -void send_control_command(Command cmd); diff --git a/loader/src/ptracer/monitor.cpp b/loader/src/ptracer/monitor.cpp index 6960e66..8e91b23 100644 --- a/loader/src/ptracer/monitor.cpp +++ b/loader/src/ptracer/monitor.cpp @@ -1,11 +1,9 @@ +#include + #include #include -#include #include #include -#include -#include -#include #include #include #include @@ -13,29 +11,25 @@ #include #include #include -#include #include -#include "main.hpp" +#include "main.h" #include "utils.hpp" #include "files.hpp" #include "misc.hpp" -using namespace std::string_view_literals; - - #define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8))) static void updateStatus(); enum TracingState { - TRACING = 1, - STOPPING, - STOPPED, - EXITING + TRACING = 1, + STOPPING, + STOPPED, + EXITING }; -std::string monitor_stop_reason; +char monitor_stop_reason[32]; constexpr char SOCKET_NAME[] = "init_monitor"; @@ -95,7 +89,7 @@ public: } [[maybe_unused]] bool UnregisterHandler(EventHandler &handler) { - if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), nullptr) == -1) { + if (epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, handler.GetFd(), NULL) == -1) { PLOGE("failed to del event handler"); return false; } @@ -108,7 +102,7 @@ public: }; static TracingState tracing_state = TRACING; -static std::string prop_path; +static char prop_path[PATH_MAX]; struct Status { @@ -116,160 +110,211 @@ struct Status { bool zygote_injected = false; bool daemon_running = false; pid_t daemon_pid = -1; - std::string daemon_info; - std::string daemon_error_info; + char *daemon_info; + char *daemon_error_info; }; static Status status64; static Status status32; struct SocketHandler : public EventHandler { - int sock_fd_; + 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}, - }; - sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME); - socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path); - if (bind(sock_fd_, (struct sockaddr *) &addr, socklen) == -1) { - PLOGE("bind socket"); - return false; - } - return true; + bool Init() { + sock_fd_ = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (sock_fd_ == -1) { + PLOGE("socket create"); + + return false; } - int GetFd() override { - return sock_fd_; + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = { 0 } + }; + + size_t sun_path_len = sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME); + + socklen_t socklen = sizeof(sa_family_t) + sun_path_len; + if (bind(sock_fd_, (struct sockaddr *)&addr, socklen) == -1) { + PLOGE("bind socket"); + + return false; } - void HandleEvent(EventLoop &loop, uint32_t) override { - struct [[gnu::packed]] MsgHead { - Command cmd; - int length; - char data[0]; - }; - for (;;) { - std::vector buf; - buf.resize(sizeof(MsgHead), 0); - MsgHead &msg = *reinterpret_cast(buf.data()); - ssize_t real_size; - auto nread = recv(sock_fd_, &msg, sizeof(msg), MSG_PEEK); - if (nread == -1) { - if (errno == EAGAIN) { - break; - } - PLOGE("read socket"); + return true; + } + + int GetFd() override { + return sock_fd_; + } + + void HandleEvent(EventLoop &loop, uint32_t) override { + struct [[gnu::packed]] MsgHead { + enum Command cmd; + int length; + char data[0]; + }; + + while (1) { + std::vector buf; + buf.resize(sizeof(MsgHead), 0); + + MsgHead &msg = *reinterpret_cast(buf.data()); + + ssize_t real_size; + ssize_t nread = recv(sock_fd_, &msg, sizeof(msg), MSG_PEEK); + if (nread == -1) { + if (errno == EAGAIN) break; + + PLOGE("read socket"); + } + + if (static_cast(nread) < sizeof(Command)) { + LOGE("read %zu < %zu", nread, sizeof(Command)); + continue; + } + + if (msg.cmd >= Command::DAEMON64_SET_INFO && msg.cmd != Command::SYSTEM_SERVER_STARTED) { + if (nread != sizeof(msg)) { + LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(MsgHead)); + + continue; + } + + real_size = sizeof(MsgHead) + msg.length; + } else { + if (nread != sizeof(Command)) { + LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(Command)); + + continue; + } + + real_size = sizeof(Command); + } + + buf.resize(real_size); + nread = recv(sock_fd_, &msg, real_size, 0); + + if (nread == -1) { + if (errno == EAGAIN) break; + + PLOGE("recv"); + continue; + } + + if (nread != real_size) { + LOGE("real size %zu != %zu", real_size, nread); + + continue; + } + + switch (msg.cmd) { + case START: { + 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; } - if (static_cast(nread) < sizeof(Command)) { - LOGE("read %zu < %zu", nread, sizeof(Command)); - continue; + + updateStatus(); + + break; + } + case STOP: { + if (tracing_state == TRACING) { + LOGI("stop tracing requested"); + + tracing_state = STOPPING; + memcpy(monitor_stop_reason, "user requested", sizeof("user requested")); + + ptrace(PTRACE_INTERRUPT, 1, 0, 0); + updateStatus(); } - if (msg.cmd >= Command::DAEMON64_SET_INFO && msg.cmd != Command::SYSTEM_SERVER_STARTED) { - if (nread != sizeof(msg)) { - LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(MsgHead)); - continue; - } - real_size = sizeof(MsgHead) + msg.length; - } else { - if (nread != sizeof(Command)) { - LOGE("cmd %d size %zu != %zu", msg.cmd, nread, sizeof(Command)); - continue; - } - real_size = sizeof(Command); - } - buf.resize(real_size); - nread = recv(sock_fd_, &msg, real_size, 0); - if (nread == -1) { - if (errno == EAGAIN) { - break; - } - PLOGE("recv"); - continue; - } - if (nread != real_size) { - LOGE("real size %zu != %zu", real_size, nread); - continue; - } - switch (msg.cmd) { - case START: - 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; - } - updateStatus(); - break; - case STOP: - if (tracing_state == TRACING) { - LOGI("stop tracing requested"); - tracing_state = STOPPING; - monitor_stop_reason = "user requested"; - ptrace(PTRACE_INTERRUPT, 1, 0, 0); - updateStatus(); - } - break; - case EXIT: - LOGI("prepare for exit ..."); - tracing_state = EXITING; - monitor_stop_reason = "user requested"; - updateStatus(); - loop.Stop(); - break; - case ZYGOTE64_INJECTED: - status64.zygote_injected = true; - updateStatus(); - break; - case ZYGOTE32_INJECTED: - status32.zygote_injected = true; - updateStatus(); - break; - case DAEMON64_SET_INFO: - LOGD("received daemon64 info %s", msg.data); - status64.daemon_info = std::string(msg.data); - updateStatus(); - break; - case DAEMON32_SET_INFO: - LOGD("received daemon32 info %s", msg.data); - status32.daemon_info = std::string(msg.data); - updateStatus(); - break; - case DAEMON64_SET_ERROR_INFO: - LOGD("received daemon64 error info %s", msg.data); - status64.daemon_running = false; - status64.daemon_error_info = std::string(msg.data); - updateStatus(); - break; - case DAEMON32_SET_ERROR_INFO: - LOGD("received daemon32 error info %s", msg.data); - status32.daemon_running = false; - status32.daemon_error_info = std::string(msg.data); - updateStatus(); - break; - case SYSTEM_SERVER_STARTED: - LOGD("system server started, mounting prop"); - if (mount(prop_path.c_str(), "/data/adb/modules/zygisksu/module.prop", nullptr, MS_BIND, nullptr) == -1) { - PLOGE("failed to mount prop"); - } - break; + + break; + } + case EXIT: { + LOGI("prepare for exit ..."); + + tracing_state = EXITING; + memcpy(monitor_stop_reason, "user requested", sizeof("user requested")); + + updateStatus(); + loop.Stop(); + + break; + } + case ZYGOTE64_INJECTED: { + status64.zygote_injected = true; + + updateStatus(); + + break; + } + case ZYGOTE32_INJECTED: { + status32.zygote_injected = true; + + updateStatus(); + + break; + } + case DAEMON64_SET_INFO: { + LOGD("received daemon64 info %s", msg.data); + + status64.daemon_info = msg.data; + updateStatus(); + + break; + } + case DAEMON32_SET_INFO: { + LOGD("received daemon32 info %s", msg.data); + + status32.daemon_info = msg.data; + updateStatus(); + + break; + } + case DAEMON64_SET_ERROR_INFO: { + LOGD("received daemon64 error info %s", msg.data); + + status64.daemon_running = false; + status64.daemon_error_info = msg.data; + updateStatus(); + + break; + } + case DAEMON32_SET_ERROR_INFO: { + LOGD("received daemon32 error info %s", msg.data); + + status32.daemon_running = false; + status32.daemon_error_info = msg.data; + updateStatus(); + + break; + } + case SYSTEM_SERVER_STARTED: { + LOGD("system server started, mounting prop"); + + if (mount(prop_path, "/data/adb/modules/zygisksu/module.prop", NULL, MS_BIND, NULL) == -1) { + PLOGE("failed to mount prop"); } + + break; + } } } + } - ~SocketHandler() { - if (sock_fd_ >= 0) close(sock_fd_); - } + ~SocketHandler() { + if (sock_fd_ >= 0) close(sock_fd_); + } }; -constexpr auto MAX_RETRY_COUNT = 5; +constexpr int MAX_RETRY_COUNT = 5; #define CREATE_ZYGOTE_START_COUNTER(abi) \ struct timespec last_zygote##abi{.tv_sec = 0, .tv_nsec = 0}; \ @@ -290,319 +335,414 @@ CREATE_ZYGOTE_START_COUNTER(64) CREATE_ZYGOTE_START_COUNTER(32) static bool ensure_daemon_created(bool is_64bit) { - auto &status = is_64bit ? status64 : status32; - if (is_64bit) { - LOGD("new zygote started, unmounting prop ..."); - umount2("/data/adb/modules/zygisksu/module.prop", MNT_DETACH); - } - status.zygote_injected = false; - if (status.daemon_pid == -1) { - auto pid = fork(); - if (pid < 0) { - PLOGE("create daemon (64=%s)", is_64bit ? "true" : "false"); - return false; - } else if (pid == 0) { - std::string daemon_name = "./bin/zygiskd"; - daemon_name += is_64bit ? "64" : "32"; - execl(daemon_name.c_str(), daemon_name.c_str(), nullptr); - PLOGE("exec daemon %s failed", daemon_name.c_str()); - exit(1); - } else { - status.supported = true; - status.daemon_pid = pid; - status.daemon_running = true; - return true; - } + Status status = is_64bit ? status64 : status32; + if (is_64bit) { + LOGD("new zygote started, unmounting prop ..."); + + umount2("/data/adb/modules/zygisksu/module.prop", MNT_DETACH); + } + + status.zygote_injected = false; + + if (status.daemon_pid == -1) { + pid_t pid = fork(); + if (pid < 0) { + PLOGE("create daemon%s", is_64bit ? "64" : "32"); + + return false; + } else if (pid == 0) { + char daemon_name[PATH_MAX] = "./bin/zygiskd"; + strcat(daemon_name, is_64bit ? "64" : "32"); + + execl(daemon_name, daemon_name, NULL); + + PLOGE("exec daemon %s failed", daemon_name); + + exit(1); } else { - return status.daemon_running; + status.supported = true; + status.daemon_pid = pid; + status.daemon_running = true; + + return true; } + } else { + return status.daemon_running; + } } +#define CHECK_DAEMON_EXIT(abi) \ + if (status##abi.supported && pid == status64.daemon_pid) { \ + char status_str[64]; \ + parse_status(status, status_str, sizeof(status_str)); \ + \ + LOGW("daemon" #abi "pid %d exited: %s", pid, status_str); \ + status##abi.daemon_running = false; \ + \ + if (status##abi.daemon_error_info[0] == '\0') \ + memcpy(status##abi.daemon_error_info, status_str, strlen(status_str)); \ + \ + updateStatus(); \ + continue; \ + } + +#define PRE_INJECT(abi, is_64) \ + if (strcmp(program, "/system/bin/app_process" # abi) == 0) { \ + tracer = "./bin/zygisk-ptrace" # abi; \ + \ + if (should_stop_inject ## abi()) { \ + LOGW("zygote" # abi " restart too much times, stop injecting"); \ + \ + tracing_state = STOPPING; \ + memcpy(monitor_stop_reason, "zygote crashed", sizeof("zygote crashed")); \ + ptrace(PTRACE_INTERRUPT, 1, 0, 0); \ + \ + break; \ + } \ + if (!ensure_daemon_created(is_64)) { \ + LOGW("daemon" #abi " not running, stop injecting"); \ + \ + tracing_state = STOPPING; \ + memcpy(monitor_stop_reason, "daemon not running", sizeof("daemon not running")); \ + ptrace(PTRACE_INTERRUPT, 1, 0, 0); \ + \ + break; \ + } \ + } + struct SigChldHandler : public EventHandler { -private: + private: int signal_fd_; struct signalfd_siginfo fdsi; int status; std::set process; -public: + + 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; + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -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_; + return signal_fd_; } void HandleEvent(EventLoop &, uint32_t) 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; - PLOGE("waitpid"); - } - if (pid == 1) { - if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) { - long child_pid; - ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid); - LOGV("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; - } - if (WIFSTOPPED(status)) { - if (WPTEVENT(status) == 0) { - if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) { - LOGW("inject signal sent to init: %s %d", - sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); - ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)); - continue; - } else { - LOGW("suppress stopping signal sent to init: %s %d", - sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); - } - } - ptrace(PTRACE_CONT, pid, 0, 0); - } - continue; - } -#define CHECK_DAEMON_EXIT(abi) \ - if (status##abi.supported && pid == status64.daemon_pid) { \ - auto status_str = parse_status(status); \ - LOGW("daemon" #abi "pid %d exited: %s", pid, status_str.c_str()); \ - status##abi.daemon_running = false; \ - if (status##abi.daemon_error_info.empty()) { \ - status##abi.daemon_error_info = status_str; \ - } \ - updateStatus(); \ - continue; \ - } - CHECK_DAEMON_EXIT(64) - CHECK_DAEMON_EXIT(32) - auto state = process.find(pid); - if (state == process.end()) { - LOGV("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); - LOGV("%d program %s", pid, program.c_str()); - const char* tracer = nullptr; - do { - if (tracing_state != TRACING) { - LOGW("stop injecting %d because not tracing", pid); - break; - } -#define PRE_INJECT(abi, is_64) \ - if (program == "/system/bin/app_process"#abi) { \ - tracer = "./bin/zygisk-ptrace"#abi; \ - if (should_stop_inject##abi()) { \ - LOGW("zygote" #abi " restart too much times, stop injecting"); \ - tracing_state = STOPPING; \ - monitor_stop_reason = "zygote crashed"; \ - ptrace(PTRACE_INTERRUPT, 1, 0, 0); \ - break; \ - } \ - if (!ensure_daemon_created(is_64)) { \ - LOGW("daemon" #abi " not running, stop injecting"); \ - tracing_state = STOPPING; \ - monitor_stop_reason = "daemon not running"; \ - ptrace(PTRACE_INTERRUPT, 1, 0, 0); \ - break; \ - } \ - } - PRE_INJECT(64, true) - PRE_INJECT(32, false) - if (tracer != nullptr) { - LOGD("stopping %d", pid); - kill(pid, SIGSTOP); - ptrace(PTRACE_CONT, pid, 0, 0); - waitpid(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(), "--restart", nullptr); - PLOGE("failed to exec, kill"); - kill(pid, SIGKILL); - exit(1); - } else if (p == -1) { - PLOGE("failed to fork, kill"); - kill(pid, SIGKILL); - } - } - } - } while (false); - updateStatus(); - } else { - LOGW("process %d received unknown status %s", pid, - parse_status(status).c_str()); - } - process.erase(state); - if (WIFSTOPPED(status)) { - LOGV("detach process %d", pid); - ptrace(PTRACE_DETACH, pid, 0, 0); - } - } - } + while (1) { + 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; + PLOGE("waitpid"); + } + + if (pid == 1) { + if (STOPPED_WITH(SIGTRAP, PTRACE_EVENT_FORK)) { + long child_pid; + + ptrace(PTRACE_GETEVENTMSG, pid, 0, &child_pid); + + LOGV("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; + } + + if (WIFSTOPPED(status)) { + if (WPTEVENT(status) == 0) { + if (WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTTOU) { + LOGW("inject signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); + + ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)); + + continue; + } else { + LOGW("suppress stopping signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(status)), WSTOPSIG(status)); + } + } + + ptrace(PTRACE_CONT, pid, 0, 0); + } + + continue; + } + + CHECK_DAEMON_EXIT(64) + CHECK_DAEMON_EXIT(32) + + auto state = process.find(pid); + + if (state == process.end()) { + LOGV("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)) { + char program[PATH_MAX]; + if (get_program(pid, program, sizeof(program)) == -1) { + LOGW("failed to get program %d", pid); + + continue; + } + + LOGV("%d program %s", pid, program); + const char* tracer = NULL; + + do { + if (tracing_state != TRACING) { + LOGW("stop injecting %d because not tracing", pid); + + break; + } + + PRE_INJECT(64, true) + PRE_INJECT(32, false) + + if (tracer != NULL) { + LOGD("stopping %d", pid); + + kill(pid, SIGSTOP); + ptrace(PTRACE_CONT, pid, 0, 0); + waitpid(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) { + char pid_str[32]; + sprintf(pid_str, "%d", pid); + + execl(tracer, basename(tracer), "trace", pid_str, "--restart", NULL); + + PLOGE("failed to exec, kill"); + + kill(pid, SIGKILL); + exit(1); + } else if (p == -1) { + PLOGE("failed to fork, kill"); + + kill(pid, SIGKILL); + } + } + } + } while (false); + + updateStatus(); + } else { + char status_str[64]; + parse_status(status, status_str, sizeof(status_str)); + + LOGW("process %d received unknown status %s", pid, status_str); + } + + process.erase(state); + if (WIFSTOPPED(status)) { + LOGV("detach process %d", pid); + + ptrace(PTRACE_DETACH, pid, 0, 0); + } + } + } + } } ~SigChldHandler() { - if (signal_fd_ >= 0) close(signal_fd_); + if (signal_fd_ >= 0) close(signal_fd_); } }; -static std::string pre_section; -static std::string post_section; +static char pre_section[1024]; +static char post_section[1024]; + +#define WRITE_STATUS_ABI(suffix) \ + if (status ## suffix.supported) { \ + strcat(status_text, " zygote" #suffix ":"); \ + \ + if (tracing_state != TRACING) strcat(status_text, "❓ unknown,"); \ + else if (status##suffix.zygote_injected) strcat(status_text, "😋 injected,"); \ + else strcat(status_text, "❌ not injected,"); \ + \ + strcat(status_text, " daemon" #suffix ":"); \ + if (status ## suffix .daemon_running) { \ + strcat(status_text, "😋 running"); \ + \ + if (status ## suffix.daemon_info[0] != '\0') { \ + strcat(status_text, "("); \ + strcat(status_text, status ## suffix.daemon_info); \ + strcat(status_text, ")"); \ + } \ + } else { \ + strcat(status_text, "❌ crashed"); \ + \ + if (status ## suffix.daemon_error_info[0] != '\0') { \ + strcat(status_text, "("); \ + strcat(status_text, status ## suffix.daemon_error_info); \ + strcat(status_text, ")"); \ + } \ + } \ + } static void updateStatus() { - auto prop = xopen_file(prop_path.c_str(), "w"); - std::string status_text = "monitor:"; - switch (tracing_state) { - case TRACING: - status_text += "😋tracing"; - break; - case STOPPING: - [[fallthrough]]; - case STOPPED: - status_text += "❌stopped"; - break; - case EXITING: - status_text += "❌exited"; - break; + FILE *prop = fopen(prop_path, "w"); + char status_text[64] = "monitor: "; + + switch (tracing_state) { + case TRACING: { + strcat(status_text, "😋 tracing"); + + break; } - if (tracing_state != TRACING && !monitor_stop_reason.empty()) { - status_text += "("; - status_text += monitor_stop_reason; - status_text += ")"; + case STOPPING: { + [[fallthrough]]; } - status_text += ","; -#define WRITE_STATUS_ABI(suffix) \ - if (status##suffix.supported) { \ - status_text += " zygote" #suffix ":"; \ - if (tracing_state != TRACING) status_text += "❓unknown,"; \ - else if (status##suffix.zygote_injected) status_text += "😋injected,"; \ - else status_text += "❌not injected,"; \ - status_text += " daemon" #suffix ":"; \ - if (status##suffix.daemon_running) { \ - status_text += "😋running"; \ - if (!status##suffix.daemon_info.empty()) { \ - status_text += "("; \ - status_text += status##suffix.daemon_info; \ - status_text += ")"; \ - } \ - } else { \ - status_text += "❌crashed"; \ - if (!status##suffix.daemon_error_info.empty()) { \ - status_text += "("; \ - status_text += status##suffix.daemon_error_info; \ - status_text += ")"; \ - } \ - } \ + case STOPPED: { + strcat(status_text, "❌ stopped"); + + break; } - WRITE_STATUS_ABI(64) - WRITE_STATUS_ABI(32) - fprintf(prop.get(), "%s[%s] %s", pre_section.c_str(), status_text.c_str(), post_section.c_str()); + case EXITING: { + strcat(status_text, "❌ exited"); + + break; + } + } + + if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') { + size_t status_text_len = strlen(status_text); + snprintf(status_text + status_text_len, sizeof(status_text) - status_text_len, "(%s)", monitor_stop_reason); + } + + strcat(status_text, ", "); + + WRITE_STATUS_ABI(64) + WRITE_STATUS_ABI(32) + + fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section); } static bool prepare_environment() { - prop_path = zygiskd::GetTmpPath() + "/module.prop"; - close(open(prop_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644)); - auto orig_prop = xopen_file("./module.prop", "r"); - if (orig_prop == nullptr) { - PLOGE("failed to open orig prop"); - return false; - } - bool post = false; - file_readline(false, orig_prop.get(), [&](std::string_view line) -> bool { - if (line.starts_with("description=")) { - post = true; - pre_section += "description="; - post_section += line.substr(sizeof("description")); - } else { - if (post) { - post_section += line; - } else { - pre_section += line; - } - } - return true; - }); - updateStatus(); - return true; + strcat(prop_path, zygiskd::GetTmpPath().c_str()); + strcat(prop_path, "/module.prop"); + + close(open(prop_path, O_WRONLY | O_CREAT | O_TRUNC, 0644)); + + FILE *orig_prop = fopen("./module.prop", "r"); + if (orig_prop == NULL) { + PLOGE("failed to open orig prop"); + + return false; + } + + char line[1024]; + ssize_t len = fread(line, 1, sizeof(line), orig_prop); + /* TODO: Check if this part translation is correct. + Old approach may be a callback approach that gets called + every new line + */ + if ((unsigned long)len > (sizeof("description") - 1) && strncmp(line, "description=", sizeof("description") - 1) == 0) { + strcat(post_section, "description="); + strcat(post_section, line + sizeof("description")); + } else { + strcat(pre_section, line); + } + + updateStatus(); + + return true; } void init_monitor() { - LOGI("ReZygisk %s", ZKSU_VERSION); - LOGI("init monitor started"); - if (!prepare_environment()) { - exit(1); - } - SocketHandler socketHandler{}; - socketHandler.Init(); - SigChldHandler ptraceHandler{}; - ptraceHandler.Init(); - EventLoop looper; - looper.Init(); - looper.RegisterHandler(socketHandler, EPOLLIN | EPOLLET); - looper.RegisterHandler(ptraceHandler, EPOLLIN | EPOLLET); - looper.Loop(); - LOGI("exit"); + LOGI("ReZygisk %s", ZKSU_VERSION); + LOGI("init monitor started"); + + if (!prepare_environment()) exit(1); + + SocketHandler socketHandler{}; + socketHandler.Init(); + SigChldHandler 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}, - }; - sprintf(addr.sun_path, "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME); - socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path); - 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 %zu != %zu\n", nsend, sizeof(cmd)); - exit(1); - } - printf("command sent\n"); +void send_control_command(enum 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 } + }; + + size_t sun_path_len = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", zygiskd::GetTmpPath().c_str(), SOCKET_NAME); + socklen_t socklen = sizeof(sa_family_t) + sun_path_len; + + ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (sockaddr *)&addr, socklen); + if (nsend == -1) err(EXIT_FAILURE, "send"); + else if (nsend != sizeof(cmd)) { + printf("[ReZygisk]: Failed to send data. Tried to send %zu bytes but only %zu were sent.\n", sizeof(cmd), nsend); + + exit(1); + } + + printf("[ReZygisk]: command sent\n"); } diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.cpp index 7ee54b9..6ab61b0 100644 --- a/loader/src/ptracer/ptracer.cpp +++ b/loader/src/ptracer/ptracer.cpp @@ -161,7 +161,10 @@ bool inject_on_main(int pid, const char *lib_path) { return true; } else { - LOGE("stopped by other reason: %s", parse_status(status).c_str()); + char status_str[64]; + parse_status(status, status_str, sizeof(status_str)); + + LOGE("stopped by other reason: %s", status_str); } return false; } @@ -205,12 +208,18 @@ bool trace_zygote(int pid) { ptrace(PTRACE_DETACH, pid, 0, SIGCONT); } } else { - LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", parse_status(status).c_str()); + char status_str[64]; + parse_status(status, status_str, sizeof(status_str)); + + LOGE("unknown state %s, not SIGTRAP + EVENT_STOP", status_str); ptrace(PTRACE_DETACH, pid, 0, 0); return false; } } else { - LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", parse_status(status).c_str()); + char status_str[64]; + parse_status(status, status_str, sizeof(status_str)); + + LOGE("unknown state %s, not SIGSTOP + EVENT_STOP", status_str); ptrace(PTRACE_DETACH, pid, 0, 0); return false; } diff --git a/loader/src/ptracer/utils.cpp b/loader/src/ptracer/utils.cpp index 0706df3..072fb8d 100644 --- a/loader/src/ptracer/utils.cpp +++ b/loader/src/ptracer/utils.cpp @@ -359,7 +359,10 @@ 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 %s at addr %p", parse_status(status).c_str(), (void*) regs.REG_IP); + char status_str[64]; + parse_status(status, status_str, sizeof(status_str)); + + LOGE("stopped by other reason %s at addr %p", status_str, (void*) regs.REG_IP); } return 0; } @@ -394,42 +397,45 @@ void wait_for_trace(int pid, int* status, int flags) { } } if (!WIFSTOPPED(*status)) { - LOGE("process %d not stopped for trace: %s, exit", pid, parse_status(*status).c_str()); + char status_str[64]; + parse_status(*status, status_str, sizeof(status_str)); + + LOGE("process %d not stopped for trace: %s, exit", pid, status_str); exit(1); } return; } } -std::string parse_status(int status) { - std::ostringstream os; - 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 "; - auto stop_sig = WSTOPSIG(status); - os << "signal=" << sigabbrev_np(stop_sig) << "(" << stop_sig << "),"; - os << "event=" << parse_ptrace_event(status); - } else { - os << "unknown"; - } - return os.str(); +void parse_status(int status, char *buf, size_t len) { + snprintf(buf, len, "0x%x ", status); + if (WIFEXITED(status)) { + snprintf(buf + strlen(buf), len - strlen(buf), "exited with %d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + snprintf(buf + strlen(buf), len - strlen(buf), "signaled with %s(%d)", sigabbrev_np(WTERMSIG(status)), WTERMSIG(status)); + } else if (WIFSTOPPED(status)) { + snprintf(buf + strlen(buf), len - strlen(buf), "stopped by "); + auto stop_sig = WSTOPSIG(status); + snprintf(buf + strlen(buf), len - strlen(buf), "signal=%s(%d),", sigabbrev_np(stop_sig), stop_sig); + snprintf(buf + strlen(buf), len - strlen(buf), "event=%s", parse_ptrace_event(status)); + } else { + snprintf(buf + strlen(buf), len - strlen(buf), "unknown"); + } } -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; +int get_program(int pid, char *buf, size_t size) { + char path[64]; + snprintf(path, sizeof(path), "/proc/%d/exe", pid); + + ssize_t sz = readlink(path, buf, size); + + if (sz == -1) { + PLOGE("readlink /proc/%d/exe", pid); + + return -1; + } + + buf[sz] = 0; + + return 0; } diff --git a/loader/src/ptracer/utils.hpp b/loader/src/ptracer/utils.hpp index 8ece432..79652f4 100644 --- a/loader/src/ptracer/utils.hpp +++ b/loader/src/ptracer/utils.hpp @@ -89,7 +89,7 @@ int fork_dont_care(); void wait_for_trace(int pid, int* status, int flags); -std::string parse_status(int status); +void parse_status(int status, char *buf, size_t len); #define WPTEVENT(x) (x >> 16) @@ -116,7 +116,7 @@ inline const char* sigabbrev_np(int sig) { return "(unknown)"; } -std::string get_program(int pid); +int get_program(int pid, char *buf, size_t size); void *find_module_return_addr(std::vector &info, std::string_view suffix); // pid = 0, fd != nullptr -> set to fd