From b94ea0c5f5ab3583bfca3c8a09d7e99fce830965 Mon Sep 17 00:00:00 2001 From: ThePedroo Date: Mon, 14 Apr 2025 19:32:49 -0300 Subject: [PATCH] improve: port ptracer to C This commit ports the C++ to C99 from zygisk-ptracer code, allowing a ~3x size reduce in its binary. --- loader/build.gradle.kts | 2 + loader/src/CMakeLists.txt | 5 +- loader/src/common/daemon.c | 31 +- loader/src/common/elf_util.c | 2 - loader/src/common/misc.c | 18 +- loader/src/include/daemon.h | 9 +- loader/src/include/native_bridge_callbacks.h | 33 - loader/src/injector/entry.cpp | 2 +- loader/src/injector/hook.cpp | 11 +- loader/src/injector/solist.c | 2 - loader/src/ptracer/{main.cpp => main.c} | 17 +- loader/src/ptracer/monitor.c | 892 ++++++++++++++++++ loader/src/ptracer/monitor.cpp | 838 ---------------- loader/src/ptracer/monitor.h | 10 +- loader/src/ptracer/{ptracer.cpp => ptracer.c} | 179 ++-- loader/src/ptracer/utils.c | 578 ++++++++++++ loader/src/ptracer/utils.cpp | 528 ----------- loader/src/ptracer/utils.h | 109 +++ loader/src/ptracer/utils.hpp | 125 --- zygiskd/src/utils.h | 2 +- zygiskd/src/zygiskd.c | 36 +- 21 files changed, 1739 insertions(+), 1690 deletions(-) delete mode 100644 loader/src/include/native_bridge_callbacks.h rename loader/src/ptracer/{main.cpp => main.c} (92%) create mode 100644 loader/src/ptracer/monitor.c delete mode 100644 loader/src/ptracer/monitor.cpp rename loader/src/ptracer/{ptracer.cpp => ptracer.c} (65%) create mode 100644 loader/src/ptracer/utils.c delete mode 100644 loader/src/ptracer/utils.cpp create mode 100644 loader/src/ptracer/utils.h delete mode 100644 loader/src/ptracer/utils.hpp diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index f9d9f8e..748350a 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -26,6 +26,8 @@ val ccachePath by lazy { } val defaultCFlags = arrayOf( + "-D_GNU_SOURCE", + "-Wall", "-Wextra", "-fno-rtti", "-fno-exceptions", "-fno-stack-protector", "-fomit-frame-pointer", diff --git a/loader/src/CMakeLists.txt b/loader/src/CMakeLists.txt index 84ba7b9..a0b60b5 100644 --- a/loader/src/CMakeLists.txt +++ b/loader/src/CMakeLists.txt @@ -10,7 +10,7 @@ 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(common cxx::cxx log) +target_link_libraries(common log) aux_source_directory(injector INJECTOR_SRC_LIST) add_library(zygisk SHARED ${INJECTOR_SRC_LIST}) @@ -20,6 +20,5 @@ target_link_libraries(zygisk cxx::cxx log common lsplt_static phmap) 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) - +target_link_libraries(libzygisk_ptrace.so log common) add_subdirectory(external) diff --git a/loader/src/common/daemon.c b/loader/src/common/daemon.c index 4a4b836..9910ef1 100644 --- a/loader/src/common/daemon.c +++ b/loader/src/common/daemon.c @@ -1,8 +1,3 @@ -// #include -// #include -// #include -// #include -// #include #include #include #include @@ -15,19 +10,6 @@ #include "daemon.h" -char daemon_path[PATH_MAX]; - -void rezygiskd_init(const char *path) { - snprintf(daemon_path, sizeof(daemon_path), "%s/%s", path, SOCKET_FILE_NAME); -} - -void rezygiskd_get_path(char *buf, size_t buf_size) { - size_t fileless_daemon_path = strlen(daemon_path) - strlen("/") - strlen(SOCKET_FILE_NAME); - - strncpy(buf, daemon_path, buf_size > fileless_daemon_path ? fileless_daemon_path : buf_size); - buf[fileless_daemon_path] = '\0'; -} - int rezygiskd_connect(uint8_t retry) { retry++; @@ -49,7 +31,7 @@ int rezygiskd_connect(uint8_t retry) { Sources: - https://pubs.opengroup.org/onlinepubs/009696699/basedefs/sys/un.h.html */ - strcpy(addr.sun_path, daemon_path); + strcpy(addr.sun_path, TMP_PATH "/" SOCKET_FILE_NAME); socklen_t socklen = sizeof(addr); while (--retry) { @@ -343,15 +325,18 @@ bool rezygiskd_update_mns(enum mount_namespace_state nms_state, char *buf, size_ write_uint8_t(fd, (uint8_t)nms_state); uint32_t target_pid = 0; - read_uint32_t(fd, &target_pid); - if (target_pid == 0) { + if (read_uint32_t(fd, &target_pid) < 0) { + PLOGE("Failed to read target pid"); + close(fd); return false; } - int target_fd = read_fd(fd); - if (target_fd == -1) { + uint32_t target_fd = 0; + if (read_uint32_t(fd, &target_fd) < 0) { + PLOGE("Failed to read target fd"); + close(fd); return false; diff --git a/loader/src/common/elf_util.c b/loader/src/common/elf_util.c index bfe89ba..bb4ee69 100644 --- a/loader/src/common/elf_util.c +++ b/loader/src/common/elf_util.c @@ -1,5 +1,3 @@ -/* INFO: This file is written in C99 */ - #include #include #include diff --git a/loader/src/common/misc.c b/loader/src/common/misc.c index 8d84de4..bf80144 100644 --- a/loader/src/common/misc.c +++ b/loader/src/common/misc.c @@ -1,14 +1,14 @@ int parse_int(const char *str) { - int val = 0; + int val = 0; - char *c = (char *)str; - while (*c) { - if (*c > '9' || *c < '0') - return -1; + char *c = (char *)str; + while (*c) { + if (*c > '9' || *c < '0') + return -1; - val = val * 10 + *c - '0'; - c++; - } + val = val * 10 + *c - '0'; + c++; + } - return val; + return val; } diff --git a/loader/src/include/daemon.h b/loader/src/include/daemon.h index 977eca5..c00dd7a 100644 --- a/loader/src/include/daemon.h +++ b/loader/src/include/daemon.h @@ -1,9 +1,12 @@ #ifndef DAEMON_H #define DAEMON_H + #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ +#include + #include #ifdef __LP64__ @@ -51,9 +54,11 @@ enum mount_namespace_state { Module }; -void rezygiskd_init(const char *path); +#define TMP_PATH "/data/adb/rezygisk" -void rezygiskd_get_path(char *buf, size_t buf_size); +static inline const char *rezygiskd_get_path() { + return TMP_PATH; +} int rezygiskd_connect(uint8_t retry); diff --git a/loader/src/include/native_bridge_callbacks.h b/loader/src/include/native_bridge_callbacks.h deleted file mode 100644 index 60ff4be..0000000 --- a/loader/src/include/native_bridge_callbacks.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include -#include - -template -struct NativeBridgeCallbacks; - -template<> -struct NativeBridgeCallbacks<__ANDROID_API_Q__> { - [[maybe_unused]] uint32_t version; - [[maybe_unused]] void *initialize; - [[maybe_unused]] void *loadLibrary; - [[maybe_unused]] void *getTrampoline; - [[maybe_unused]] void *isSupported; - [[maybe_unused]] void *getAppEnv; - [[maybe_unused]] void *isCompatibleWith; - [[maybe_unused]] void *getSignalHandler; - [[maybe_unused]] void *unloadLibrary; - [[maybe_unused]] void *getError; - [[maybe_unused]] void *isPathSupported; - [[maybe_unused]] void *initAnonymousNamespace; - [[maybe_unused]] void *createNamespace; - [[maybe_unused]] void *linkNamespaces; - [[maybe_unused]] void *loadLibraryExt; - [[maybe_unused]] void *getVendorNamespace; - [[maybe_unused]] void *getExportedNamespace; -}; - -template<> -struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> { - [[maybe_unused]] void *preZygoteFork; -}; diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp index dbbe379..543a374 100644 --- a/loader/src/injector/entry.cpp +++ b/loader/src/injector/entry.cpp @@ -10,9 +10,9 @@ size_t block_size = 0; extern "C" [[gnu::visibility("default")]] void entry(void* addr, size_t size, const char* path) { LOGD("Zygisk library injected, version %s", ZKSU_VERSION); + start_addr = addr; block_size = size; - rezygiskd_init(path); if (!rezygiskd_ping()) { LOGE("Zygisk daemon is not running"); diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp index a37b542..c50af31 100644 --- a/loader/src/injector/hook.cpp +++ b/loader/src/injector/hook.cpp @@ -181,8 +181,6 @@ DCL_HOOK_FUNC(int, unshare, int flags) { update_mnt_ns(Rooted, false); } else if (!(g_ctx->flags[DO_REVERT_UNMOUNT])) { update_mnt_ns(Module, false); - } else { - LOGI("Process [%s] is on denylist, skipping unmount", g_ctx->process); } old_unshare(CLONE_NEWNS); @@ -636,6 +634,7 @@ void ZygiskContext::run_modules_pre() { for (auto &m : modules) { m.onLoad(env); + if (flags[APP_SPECIALIZE]) m.preAppSpecialize(args.app); else if (flags[SERVER_FORK_AND_SPECIALIZE]) m.preServerSpecialize(args.server); } @@ -674,6 +673,12 @@ void ZygiskContext::app_specialize_pre() { if ((info_flags & (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) == (PROCESS_IS_MANAGER | PROCESS_ROOT_IS_MAGISK)) { LOGD("Manager process detected. Notifying that Zygisk has been enabled."); + /* INFO: This environment variable is related to Magisk Zygisk/Manager. It + it used by Magisk's Zygisk to communicate to Magisk Manager whether + Zygisk is working or not. + + To allow Zygisk modules to both work properly and for the manager to + identify Zygisk, being it not built-in, as working, we also set it. */ setenv("ZYGISK_ENABLED", "1", 1); } else { run_modules_pre(); @@ -818,7 +823,7 @@ void clean_trace(const char* path, size_t load, size_t unload, bool spoof_maps) if (load > 0 || unload > 0) solist_reset_counters(load, unload); - LOGI("Dropping solist record for %s", path); + LOGD("Dropping solist record for %s", path); bool path_found = solist_drop_so_path(path); if (!path_found || !spoof_maps) return; diff --git a/loader/src/injector/solist.c b/loader/src/injector/solist.c index b338938..1e460a8 100644 --- a/loader/src/injector/solist.c +++ b/loader/src/injector/solist.c @@ -1,5 +1,3 @@ -/* INFO: This file is written in C99 */ - #include #include #include diff --git a/loader/src/ptracer/main.cpp b/loader/src/ptracer/main.c similarity index 92% rename from loader/src/ptracer/main.cpp rename to loader/src/ptracer/main.c index 9122f9c..2b76131 100644 --- a/loader/src/ptracer/main.cpp +++ b/loader/src/ptracer/main.c @@ -1,19 +1,22 @@ #include +#include + +#ifdef __LP64__ + #define LOG_TAG "zygisk-ptrace64" +#else + #define LOG_TAG "zygisk-ptrace32" +#endif #include "monitor.h" -#include "utils.hpp" +#include "utils.h" #include "daemon.h" int main(int argc, char **argv) { - rezygiskd_init("/data/adb/rezygisk"); - printf("The ReZygisk Tracer %s\n\n", ZKSU_VERSION); if (argc >= 2 && strcmp(argv[1], "monitor") == 0) { init_monitor(); - printf("[ReZygisk]: Started monitoring...\n"); - return 0; } else if (argc >= 3 && strcmp(argv[1], "trace") == 0) { if (argc >= 4 && strcmp(argv[3], "--restart") == 0) rezygiskd_zygote_restart(); @@ -25,11 +28,9 @@ int main(int argc, char **argv) { return 1; } - printf("[ReZygisk]: Tracing %ld...\n", pid); - return 0; } else if (argc >= 2 && strcmp(argv[1], "ctl") == 0) { - enum Command command; + enum rezygiskd_command command; if (strcmp(argv[2], "start") == 0) command = START; else if (strcmp(argv[2], "stop") == 0) command = STOP; diff --git a/loader/src/ptracer/monitor.c b/loader/src/ptracer/monitor.c new file mode 100644 index 0000000..8188e84 --- /dev/null +++ b/loader/src/ptracer/monitor.c @@ -0,0 +1,892 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "daemon.h" +#include "utils.h" +#include "misc.h" + +#include "monitor.h" + +#define PROP_PATH TMP_PATH "/module.prop" +#define SOCKET_NAME "init_monitor" + +#define STOPPED_WITH(sig, event) WIFSTOPPED(sigchld_status) && (sigchld_status >> 8 == ((sig) | (event << 8))) + +static bool update_status(const char *message); + +char monitor_stop_reason[32]; + +enum ptracer_tracing_state { + TRACING, + STOPPING, + STOPPED, + EXITING +}; + +enum ptracer_tracing_state tracing_state = TRACING; + +struct rezygiskd_status { + bool supported; + bool zygote_injected; + bool daemon_running; + pid_t daemon_pid; + char *daemon_info; + char *daemon_error_info; +}; + +struct rezygiskd_status status64 = { + .daemon_pid = -1 +}; +struct rezygiskd_status status32 = { + .daemon_pid = -1 +}; + +int monitor_epoll_fd; +bool monitor_events_running = true; + +bool monitor_events_init() { + monitor_epoll_fd = epoll_create(1); + if (monitor_epoll_fd == -1) { + PLOGE("epoll_create"); + + return false; + } + + return true; +} + +struct monitor_event_cbs { + void (*callback)(); + void (*stop_callback)(); +}; + +bool monitor_events_register_event(struct monitor_event_cbs *event_cbs, int fd, uint32_t events) { + struct epoll_event ev = { + .data.ptr = event_cbs, + .events = events + }; + + if (epoll_ctl(monitor_epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1) { + PLOGE("epoll_ctl"); + + return false; + } + + return true; +} + +bool monitor_events_unregister_event(int fd) { + if (epoll_ctl(monitor_epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1) { + PLOGE("epoll_ctl"); + + return false; + } + + return true; +} + +void monitor_events_stop() { + monitor_events_running = false; +}; + +void monitor_events_loop() { + struct epoll_event events[2]; + while (monitor_events_running) { + int nfds = epoll_wait(monitor_epoll_fd, events, 2, -1); + if (nfds == -1) { + if (errno != EINTR) PLOGE("epoll_wait"); + + continue; + } + + for (int i = 0; i < nfds; i++) { + struct monitor_event_cbs *event_cbs = (struct monitor_event_cbs *)events[i].data.ptr; + event_cbs->callback(); + + if (!monitor_events_running) break; + } + } + + if (monitor_epoll_fd >= 0) close(monitor_epoll_fd); + monitor_epoll_fd = -1; + + for (int i = 0; i < (int)(sizeof(events) / sizeof(events[0])); i++) { + struct monitor_event_cbs *event_cbs = (struct monitor_event_cbs *)events[i].data.ptr; + event_cbs->stop_callback(); + } +} + +int monitor_sock_fd; + +bool rezygiskd_listener_init() { + monitor_sock_fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (monitor_sock_fd == -1) { + PLOGE("socket create"); + + return false; + } + + struct sockaddr_un addr = { + .sun_family = AF_UNIX, + .sun_path = { 0 } + }; + + size_t sun_path_len = sprintf(addr.sun_path, "%s/%s", rezygiskd_get_path(), SOCKET_NAME); + + socklen_t socklen = sizeof(sa_family_t) + sun_path_len; + if (bind(monitor_sock_fd, (struct sockaddr *)&addr, socklen) == -1) { + PLOGE("bind socket"); + + return false; + } + + return true; +} + +void rezygiskd_listener_callback() { + struct [[gnu::packed]] MsgHead { + enum rezygiskd_command cmd; + int length; + char data[0]; + }; + + while (1) { + struct MsgHead *msg = (struct MsgHead *)malloc(sizeof(struct MsgHead)); + + ssize_t real_size; + ssize_t nread = recv(monitor_sock_fd, msg, sizeof(struct MsgHead), MSG_PEEK); + if (nread == -1) { + if (errno == EAGAIN) break; + + PLOGE("read socket"); + } + + if ((size_t)nread < sizeof(enum rezygiskd_command)) { + LOGE("read %zu < %zu", nread, sizeof(enum rezygiskd_command)); + continue; + } + + if (msg->cmd >= DAEMON64_SET_INFO && msg->cmd != SYSTEM_SERVER_STARTED) { + if (nread != sizeof(msg)) { + LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(struct MsgHead)); + + continue; + } + + real_size = sizeof(struct MsgHead) + msg->length; + } else { + if (nread != sizeof(enum rezygiskd_command)) { + LOGE("cmd %d size %zu != %zu", msg->cmd, nread, sizeof(enum rezygiskd_command)); + + continue; + } + + real_size = sizeof(enum rezygiskd_command); + } + + msg = (struct MsgHead *)realloc(msg, real_size); + nread = recv(monitor_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; + } + + update_status(NULL); + + break; + } + case STOP: { + if (tracing_state == TRACING) { + LOGI("stop tracing requested"); + + tracing_state = STOPPING; + strcpy(monitor_stop_reason, "user requested"); + + ptrace(PTRACE_INTERRUPT, 1, 0, 0); + update_status(NULL); + } + + break; + } + case EXIT: { + LOGI("prepare for exit ..."); + + tracing_state = EXITING; + strcpy(monitor_stop_reason, "user requested"); + + update_status(NULL); + monitor_events_stop(); + + break; + } + case ZYGOTE64_INJECTED: { + status64.zygote_injected = true; + + update_status(NULL); + + break; + } + case ZYGOTE32_INJECTED: { + status32.zygote_injected = true; + + update_status(NULL); + + break; + } + case DAEMON64_SET_INFO: { + LOGD("received daemon64 info %s", msg->data); + + /* Will only happen if somehow the daemon restarts */ + if (status64.daemon_info) { + free(status64.daemon_info); + status64.daemon_info = NULL; + } + + status64.daemon_info = (char *)malloc(msg->length); + if (!status64.daemon_info) { + PLOGE("malloc daemon64 info"); + + break; + } + + strcpy(status64.daemon_info, msg->data); + + update_status(NULL); + + break; + } + case DAEMON32_SET_INFO: { + LOGD("received daemon32 info %s", msg->data); + + if (status32.daemon_info) { + free(status32.daemon_info); + status32.daemon_info = NULL; + } + + status32.daemon_info = (char *)malloc(msg->length); + if (!status32.daemon_info) { + PLOGE("malloc daemon32 info"); + + break; + } + + strcpy(status32.daemon_info, msg->data); + + update_status(NULL); + + break; + } + case DAEMON64_SET_ERROR_INFO: { + LOGD("received daemon64 error info %s", msg->data); + + status64.daemon_running = false; + + if (status64.daemon_error_info) { + free(status64.daemon_error_info); + status64.daemon_error_info = NULL; + } + + status64.daemon_error_info = (char *)malloc(msg->length); + if (!status64.daemon_error_info) { + PLOGE("malloc daemon64 error info"); + + break; + } + + strcpy(status64.daemon_error_info, msg->data); + + update_status(NULL); + + break; + } + case DAEMON32_SET_ERROR_INFO: { + LOGD("received daemon32 error info %s", msg->data); + + status32.daemon_running = false; + + if (status32.daemon_error_info) { + free(status32.daemon_error_info); + status32.daemon_error_info = NULL; + } + + status32.daemon_error_info = (char *)malloc(msg->length); + if (!status32.daemon_error_info) { + PLOGE("malloc daemon32 error info"); + + break; + } + + strcpy(status32.daemon_error_info, msg->data); + + update_status(NULL); + + 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; + } + } + + free(msg); + } +} + +void rezygiskd_listener_stop() { + if (monitor_sock_fd >= 0) close(monitor_sock_fd); + monitor_sock_fd = -1; +} + +#define MAX_RETRY_COUNT 5 + +#define CREATE_ZYGOTE_START_COUNTER(abi) \ + struct timespec last_zygote##abi = { \ + .tv_sec = 0, \ + .tv_nsec = 0 \ + }; \ + \ + int count_zygote ## abi = 0; \ + bool should_stop_inject ## abi() { \ + struct timespec now = {}; \ + clock_gettime(CLOCK_MONOTONIC, &now); \ + if (now.tv_sec - last_zygote ## abi.tv_sec < 30) \ + count_zygote ## abi++; \ + else \ + count_zygote ## abi = 0; \ + \ + last_zygote##abi = now; \ + \ + return count_zygote##abi >= MAX_RETRY_COUNT; \ + } + +CREATE_ZYGOTE_START_COUNTER(64) +CREATE_ZYGOTE_START_COUNTER(32) + +static bool ensure_daemon_created(bool is_64bit) { + struct rezygiskd_status *status = is_64bit ? &status64 : &status32; + if (is_64bit) { + LOGD("new zygote started."); + + 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 { + 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 == status##abi.daemon_pid) { \ + char status_str[64]; \ + parse_status(sigchld_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) { \ + status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \ + if (!status##abi.daemon_error_info) { \ + LOGE("malloc daemon" #abi " error info failed"); \ + \ + return; \ + } \ + \ + memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \ + } \ + \ + update_status(NULL); \ + 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; \ + } \ + } + +int sigchld_signal_fd; +struct signalfd_siginfo sigchld_fdsi; +int sigchld_status; + +pid_t *sigchld_process; +size_t sigchld_process_count = 0; + +bool sigchld_listener_init() { + sigchld_process = NULL; + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) { + PLOGE("set sigprocmask"); + + return false; + } + + sigchld_signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (sigchld_signal_fd == -1) { + PLOGE("create signalfd"); + + return false; + } + + ptrace(PTRACE_SEIZE, 1, 0, PTRACE_O_TRACEFORK); + + return true; +} + +void sigchld_listener_callback() { + while (1) { + ssize_t s = read(sigchld_signal_fd, &sigchld_fdsi, sizeof(sigchld_fdsi)); + if (s == -1) { + if (errno == EAGAIN) break; + + PLOGE("read signalfd"); + + continue; + } + + if (s != sizeof(sigchld_fdsi)) { + LOGW("read %zu != %zu", s, sizeof(sigchld_fdsi)); + + continue; + } + + if (sigchld_fdsi.ssi_signo != SIGCHLD) { + LOGW("no sigchld received"); + + continue; + } + + int pid; + while ((pid = waitpid(-1, &sigchld_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(sigchld_status)) { + if (WPTEVENT(sigchld_status) == 0) { + if (WSTOPSIG(sigchld_status) != SIGSTOP && WSTOPSIG(sigchld_status) != SIGTSTP && WSTOPSIG(sigchld_status) != SIGTTIN && WSTOPSIG(sigchld_status) != SIGTTOU) { + LOGW("inject signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(sigchld_status)), WSTOPSIG(sigchld_status)); + + ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(sigchld_status)); + + continue; + } else { + LOGW("suppress stopping signal sent to init: %s %d", sigabbrev_np(WSTOPSIG(sigchld_status)), WSTOPSIG(sigchld_status)); + } + } + + ptrace(PTRACE_CONT, pid, 0, 0); + } + + continue; + } + + CHECK_DAEMON_EXIT(64) + CHECK_DAEMON_EXIT(32) + + pid_t state = 0; + for (size_t i = 0; i < sigchld_process_count; i++) { + if (sigchld_process[i] != pid) continue; + + state = sigchld_process[i]; + + break; + } + + if (state == 0) { + LOGV("new process %d attached", pid); + + for (size_t i = 0; i < sigchld_process_count; i++) { + if (sigchld_process[i] != 0) continue; + + sigchld_process[i] = pid; + + goto ptrace_process; + } + + sigchld_process = (pid_t *)realloc(sigchld_process, sizeof(pid_t) * (sigchld_process_count + 1)); + if (sigchld_process == NULL) { + PLOGE("realloc sigchld_process"); + + continue; + } + + sigchld_process[sigchld_process_count] = pid; + sigchld_process_count++; + + ptrace_process: + + 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, &sigchld_status, __WALL); + + if (STOPPED_WITH(SIGSTOP, 0)) { + LOGD("detaching %d", pid); + + ptrace(PTRACE_DETACH, pid, 0, SIGSTOP); + sigchld_status = 0; + int 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); + + update_status(NULL); + } else { + char status_str[64]; + parse_status(sigchld_status, status_str, sizeof(status_str)); + + LOGW("process %d received unknown sigchld_status %s", pid, status_str); + } + + for (size_t i = 0; i < sigchld_process_count; i++) { + if (sigchld_process[i] != pid) continue; + + sigchld_process[i] = 0; + + break; + } + + if (WIFSTOPPED(sigchld_status)) { + LOGV("detach process %d", pid); + + ptrace(PTRACE_DETACH, pid, 0, 0); + } + } + } + } +} + +void sigchld_listener_stop() { + if (sigchld_signal_fd >= 0) close(sigchld_signal_fd); + sigchld_signal_fd = -1; + + if (sigchld_process != NULL) free(sigchld_process); + sigchld_process = NULL; + sigchld_process_count = 0; +} + +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 != NULL) { \ + strcat(status_text, "("); \ + strcat(status_text, status ## suffix.daemon_info); \ + strcat(status_text, ")"); \ + } \ + } else { \ + strcat(status_text, "❌ crashed "); \ + \ + if (status ## suffix.daemon_error_info != NULL) { \ + strcat(status_text, "("); \ + strcat(status_text, status ## suffix.daemon_error_info); \ + strcat(status_text, ")"); \ + } \ + } \ + } + +static bool update_status(const char *message) { + FILE *prop = fopen(PROP_PATH, "w"); + if (prop == NULL) { + PLOGE("failed to open prop"); + + return false; + } + + if (message) { + fprintf(prop, "%s[%s] %s", pre_section, message, post_section); + fclose(prop); + + return true; + } + + char status_text[1024] = "monitor: "; + + switch (tracing_state) { + case TRACING: { + strcat(status_text, "😋 tracing"); + + break; + } + case STOPPING: [[fallthrough]]; + case STOPPED: { + strcat(status_text, "❌ stopped"); + + break; + } + case EXITING: { + strcat(status_text, "❌ exited"); + + break; + } + } + + if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') { + strcat(status_text, " ("); + strcat(status_text, monitor_stop_reason); + strcat(status_text, ")"); + } + strcat(status_text, ","); + + WRITE_STATUS_ABI(64) + WRITE_STATUS_ABI(32) + + fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section); + + fclose(prop); + + return true; +} + +static bool prepare_environment() { + /* INFO: We need to create the file first, otherwise the mount will fail */ + close(open(PROP_PATH, O_WRONLY | O_CREAT | O_TRUNC, 0644)); + + FILE *orig_prop = fopen("/data/adb/modules/zygisksu/module.prop", "r"); + if (orig_prop == NULL) { + PLOGE("failed to open orig prop"); + + return false; + } + + bool after_description = false; + + char line[1024]; + while (fgets(line, sizeof(line), orig_prop) != NULL) { + if (strncmp(line, "description=", strlen("description=")) == 0) { + strcat(pre_section, "description="); + strcat(post_section, line + strlen("description=")); + after_description = true; + + continue; + } + + if (after_description) strcat(post_section, line); + else strcat(pre_section, line); + } + + fclose(orig_prop); + + /* INFO: This environment variable is related to Magisk Zygisk/Manager. It + it used by Magisk's Zygisk to communicate to Magisk Manager whether + Zygisk is working or not. + + Because of that behavior, we can knowledge built-in Zygisk is being + used and stop the continuation of initialization of ReZygisk.*/ + if (getenv("ZYGISK_ENABLED")) { + update_status("❌ Disable Magisk's built-in Zygisk"); + + return false; + } + + return update_status(NULL); +} + +void init_monitor() { + LOGI("ReZygisk %s", ZKSU_VERSION); + + if (!prepare_environment()) exit(1); + + monitor_events_init(); + + rezygiskd_listener_init(); + + struct monitor_event_cbs listener_cbs = { + .callback = rezygiskd_listener_callback, + .stop_callback = rezygiskd_listener_stop + }; + monitor_events_register_event(&listener_cbs, monitor_sock_fd, EPOLLIN | EPOLLET); + + sigchld_listener_init(); + + struct monitor_event_cbs sigchld_cbs = { + .callback = sigchld_listener_callback, + .stop_callback = sigchld_listener_stop + }; + monitor_events_register_event(&sigchld_cbs, sigchld_signal_fd, EPOLLIN | EPOLLET); + + monitor_events_loop(); + + if (status64.daemon_info) free(status64.daemon_info); + if (status64.daemon_error_info) free(status64.daemon_error_info); + if (status32.daemon_info) free(status32.daemon_info); + if (status32.daemon_error_info) free(status32.daemon_error_info); + + LOGI("exit"); +} + +int send_control_command(enum rezygiskd_command cmd) { + int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (sockfd == -1) return -1; + + 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", rezygiskd_get_path(), SOCKET_NAME); + + socklen_t socklen = sizeof(sa_family_t) + sun_path_len; + + ssize_t nsend = sendto(sockfd, (void *)&cmd, sizeof(cmd), 0, (struct sockaddr *)&addr, socklen); + + close(sockfd); + + return nsend != sizeof(cmd) ? -1 : 0; +} diff --git a/loader/src/ptracer/monitor.cpp b/loader/src/ptracer/monitor.cpp deleted file mode 100644 index 5217ea5..0000000 --- a/loader/src/ptracer/monitor.cpp +++ /dev/null @@ -1,838 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "monitor.h" -#include "utils.hpp" -#include "misc.h" - -#define STOPPED_WITH(sig, event) WIFSTOPPED(status) && (status >> 8 == ((sig) | (event << 8))) - -static void updateStatus(); - -char monitor_stop_reason[32]; - -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; - } - - 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) { - if (errno != EINTR) PLOGE("epoll_wait"); - - continue; - } - - for (int i = 0; i < nfds; i++) { - reinterpret_cast(events[i].data.ptr)->HandleEvent(*this, - events[i].events); - if (!running) break; - } - } - } - - 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(), NULL) == -1) { - PLOGE("failed to del event handler"); - - return false; - } - - return true; - } - - ~EventLoop() { - if (epoll_fd_ >= 0) close(epoll_fd_); - } -}; - -enum TracingState { - TRACING = 1, - STOPPING, - STOPPED, - EXITING -}; - -TracingState tracing_state = TRACING; -static char prop_path[PATH_MAX]; - -struct Status { - bool supported = false; - bool zygote_injected = false; - bool daemon_running = false; - pid_t daemon_pid = -1; - char *daemon_info = NULL; - char *daemon_error_info = NULL; -}; - -Status status64; -Status status32; - -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 } - }; - - char tmp_path[PATH_MAX]; - rezygiskd_get_path(tmp_path, sizeof(tmp_path)); - - size_t sun_path_len = sprintf(addr.sun_path, "%s/%s", tmp_path, 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; - } - - 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) { - struct MsgHead *msg = (struct MsgHead *)malloc(sizeof(struct MsgHead)); - - ssize_t real_size; - ssize_t nread = recv(sock_fd_, msg, sizeof(struct MsgHead), MSG_PEEK); - if (nread == -1) { - if (errno == EAGAIN) break; - - PLOGE("read socket"); - } - - if ((size_t)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); - } - - msg = (struct MsgHead *)realloc(msg, 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; - strcpy(monitor_stop_reason, "user requested"); - - ptrace(PTRACE_INTERRUPT, 1, 0, 0); - updateStatus(); - } - - break; - } - case EXIT: { - LOGI("prepare for exit ..."); - - tracing_state = EXITING; - strcpy(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); - - /* Will only happen if somehow the daemon restarts */ - if (status64.daemon_info) { - free(status64.daemon_info); - status64.daemon_info = NULL; - } - - status64.daemon_info = (char *)malloc(msg->length); - if (!status64.daemon_info) { - PLOGE("malloc daemon64 info"); - - break; - } - - strcpy(status64.daemon_info, msg->data); - - updateStatus(); - - break; - } - case DAEMON32_SET_INFO: { - LOGD("received daemon32 info %s", msg->data); - - if (status32.daemon_info) { - free(status32.daemon_info); - status32.daemon_info = NULL; - } - - status32.daemon_info = (char *)malloc(msg->length); - if (!status32.daemon_info) { - PLOGE("malloc daemon32 info"); - - break; - } - - strcpy(status32.daemon_info, msg->data); - - updateStatus(); - - break; - } - case DAEMON64_SET_ERROR_INFO: { - LOGD("received daemon64 error info %s", msg->data); - - status64.daemon_running = false; - - if (status64.daemon_error_info) { - free(status64.daemon_error_info); - status64.daemon_error_info = NULL; - } - - status64.daemon_error_info = (char *)malloc(msg->length); - if (!status64.daemon_error_info) { - PLOGE("malloc daemon64 error info"); - - break; - } - - strcpy(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; - - if (status32.daemon_error_info) { - free(status32.daemon_error_info); - status32.daemon_error_info = NULL; - } - - status32.daemon_error_info = (char *)malloc(msg->length); - if (!status32.daemon_error_info) { - PLOGE("malloc daemon32 error info"); - - break; - } - - strcpy(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; - } - } - - free(msg); - } - } - - ~SocketHandler() { - if (sock_fd_ >= 0) close(sock_fd_); - } -}; - -constexpr int MAX_RETRY_COUNT = 5; - -#define CREATE_ZYGOTE_START_COUNTER(abi) \ - struct timespec last_zygote##abi = { \ - .tv_sec = 0, \ - .tv_nsec = 0 \ - }; \ - \ - int count_zygote ## abi = 0; \ - bool should_stop_inject ## abi() { \ - struct timespec now = {}; \ - clock_gettime(CLOCK_MONOTONIC, &now); \ - if (now.tv_sec - last_zygote ## abi.tv_sec < 30) \ - count_zygote ## abi++; \ - else \ - count_zygote ## abi = 0; \ - \ - last_zygote##abi = now; \ - \ - return count_zygote##abi >= MAX_RETRY_COUNT; \ - } - -CREATE_ZYGOTE_START_COUNTER(64) -CREATE_ZYGOTE_START_COUNTER(32) - -static bool ensure_daemon_created(bool is_64bit) { - Status *status = is_64bit ? &status64 : &status32; - if (is_64bit) { - LOGD("new zygote started."); - - 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 { - 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) { \ - status##abi.daemon_error_info = (char *)malloc(strlen(status_str) + 1); \ - if (!status##abi.daemon_error_info) { \ - LOGE("malloc daemon" #abi " error info failed"); \ - \ - return; \ - } \ - \ - memcpy(status##abi.daemon_error_info, status_str, strlen(status_str) + 1); \ - } \ - \ - 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: - int signal_fd_; - struct signalfd_siginfo fdsi; - int status; - std::set process; - - public: - bool Init() { - 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_; - } - - void HandleEvent(EventLoop &, uint32_t) override { - 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_); - } -}; - -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 != NULL) { \ - strcat(status_text, "("); \ - strcat(status_text, status ## suffix.daemon_info); \ - strcat(status_text, ")"); \ - } \ - } else { \ - strcat(status_text, "❌ crashed "); \ - \ - if (status ## suffix.daemon_error_info != NULL) { \ - strcat(status_text, "("); \ - strcat(status_text, status ## suffix.daemon_error_info); \ - strcat(status_text, ")"); \ - } \ - } \ - } - -static void updateStatus() { - FILE *prop = fopen(prop_path, "w"); - char status_text[1024] = "monitor: "; - - switch (tracing_state) { - case TRACING: { - strcat(status_text, "😋 tracing"); - - break; - } - case STOPPING: [[fallthrough]]; - case STOPPED: { - strcat(status_text, "❌ stopped"); - - break; - } - case EXITING: { - strcat(status_text, "❌ exited"); - - break; - } - } - - if (tracing_state != TRACING && monitor_stop_reason[0] != '\0') { - strcat(status_text, " ("); - strcat(status_text, monitor_stop_reason); - strcat(status_text, ")"); - } - strcat(status_text, ","); - - WRITE_STATUS_ABI(64) - WRITE_STATUS_ABI(32) - - fprintf(prop, "%s[%s] %s", pre_section, status_text, post_section); - - fclose(prop); -} - -static bool prepare_environment() { - char tmp_path[PATH_MAX]; - rezygiskd_get_path(tmp_path, sizeof(tmp_path)); - - strcat(prop_path, tmp_path); - 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; - } - - bool after_description = false; - - char line[1024]; - while (fgets(line, sizeof(line), orig_prop) != NULL) { - if (strncmp(line, "description=", strlen("description=")) == 0) { - strcat(pre_section, "description="); - strcat(post_section, line + strlen("description=")); - after_description = true; - - continue; - } - - if (after_description) strcat(post_section, line); - else strcat(pre_section, line); - } - - fclose(orig_prop); - - /* TODO: See if ZYGISK_ENABLED flag is already set, - if so, set a status saying to disable built-in Zygisk. */ - updateStatus(); - - return true; -} - -void init_monitor() { - LOGI("ReZygisk %s", ZKSU_VERSION); - - 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(); - - if (status64.daemon_info) free(status64.daemon_info); - if (status64.daemon_error_info) free(status64.daemon_error_info); - if (status32.daemon_info) free(status32.daemon_info); - if (status32.daemon_error_info) free(status32.daemon_error_info); - - LOGI("exit"); -} - -int send_control_command(enum Command cmd) { - int sockfd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (sockfd == -1) return -1; - - struct sockaddr_un addr = { - .sun_family = AF_UNIX, - .sun_path = { 0 } - }; - - char tmp_path[PATH_MAX]; - rezygiskd_get_path(tmp_path, sizeof(tmp_path)); - - size_t sun_path_len = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", tmp_path, 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); - - /* TODO: Should we close even when it fails? */ - close(sockfd); - - return nsend != sizeof(cmd) ? -1 : 0; -} diff --git a/loader/src/ptracer/monitor.h b/loader/src/ptracer/monitor.h index c7db0f3..99ed3be 100644 --- a/loader/src/ptracer/monitor.h +++ b/loader/src/ptracer/monitor.h @@ -1,5 +1,5 @@ -#ifndef MAIN_HPP -#define MAIN_HPP +#ifndef MONITOR_H +#define MONITOR_H #include @@ -7,7 +7,7 @@ void init_monitor(); bool trace_zygote(int pid); -enum Command { +enum rezygiskd_command { START = 1, STOP = 2, EXIT = 3, @@ -22,6 +22,6 @@ enum Command { SYSTEM_SERVER_STARTED = 10 }; -int send_control_command(enum Command cmd); +int send_control_command(enum rezygiskd_command cmd); -#endif /* MAIN_HPP */ \ No newline at end of file +#endif /* MONITOR_H */ \ No newline at end of file diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.c similarity index 65% rename from loader/src/ptracer/ptracer.cpp rename to loader/src/ptracer/ptracer.c index c8a7571..7ae1254 100644 --- a/loader/src/ptracer/ptracer.cpp +++ b/loader/src/ptracer/ptracer.c @@ -1,20 +1,18 @@ +#include +#include +#include + #include -#include -#include #include #include #include -#include -#include -#include #include #include #include -#include -#include -#include -#include "utils.hpp" +#include + +#include "utils.h" bool inject_on_main(int pid, const char *lib_path) { LOGI("injecting %s to zygote %d", lib_path, pid); @@ -25,16 +23,26 @@ bool inject_on_main(int pid, const char *lib_path) { https://cs.android.com/android/platform/superproject/main/+/main:bionic/libc/private/KernelArgumentBlock.h;l=30;drc=6d1ee77ee32220e4202c3066f7e1f69572967ad8 */ - struct user_regs_struct regs {}, - backup {}; + struct user_regs_struct regs = { 0 }; - /* WARNING: C++ keyword */ - std::vector map = MapInfo::Scan(std::to_string(pid)); - if (!get_regs(pid, regs)) return false; + char pid_maps[PATH_MAX]; + snprintf(pid_maps, sizeof(pid_maps), "/proc/%d/maps", pid); + + struct maps *map = parse_maps(pid_maps); + if (map == NULL) { + LOGE("failed to parse remote maps"); + + return false; + } + + if (!get_regs(pid, ®s)) return false; uintptr_t arg = (uintptr_t)regs.REG_SP; - LOGV("kernel argument %" PRIxPTR " %s", arg, get_addr_mem_region(map, arg).c_str()); + char addr_mem_region[1024]; + get_addr_mem_region(map, arg, addr_mem_region, sizeof(addr_mem_region)); + + LOGV("kernel argument %" PRIxPTR " %s", arg, addr_mem_region); int argc; char **argv = (char **)((uintptr_t *)arg + 1); @@ -43,18 +51,16 @@ bool inject_on_main(int pid, const char *lib_path) { read_proc(pid, arg, &argc, sizeof(argc)); LOGV("argc %d", argc); - /* WARNING: C++ keyword */ - auto envp = argv + argc + 1; + char **envp = argv + argc + 1; LOGV("envp %p", (void *)envp); - /* WARNING: C++ keyword */ - auto p = envp; + char **p = envp; while (1) { uintptr_t *buf; read_proc(pid, (uintptr_t)p, &buf, sizeof(buf)); if (buf == NULL) break; - + /* TODO: Why ++p? */ p++; } @@ -63,7 +69,9 @@ bool inject_on_main(int pid, const char *lib_path) { p++; ElfW(auxv_t) *auxv = (ElfW(auxv_t) *)p; - LOGV("auxv %p %s", auxv, get_addr_mem_region(map, (uintptr_t) auxv).c_str()); + + get_addr_mem_region(map, (uintptr_t)auxv, addr_mem_region, sizeof(addr_mem_region)); + LOGV("auxv %p %s", auxv, addr_mem_region); ElfW(auxv_t) *v = auxv; uintptr_t entry_addr = 0; @@ -78,8 +86,9 @@ bool inject_on_main(int pid, const char *lib_path) { entry_addr = (uintptr_t)buf.a_un.a_val; addr_of_entry_addr = (uintptr_t)v + offsetof(ElfW(auxv_t), a_un); + get_addr_mem_region(map, entry_addr, addr_mem_region, sizeof(addr_mem_region)); LOGV("entry address %" PRIxPTR " %s (entry=%" PRIxPTR ", entry_addr=%" PRIxPTR ")", entry_addr, - get_addr_mem_region(map, entry_addr).c_str(), (uintptr_t)v, addr_of_entry_addr); + addr_mem_region, (uintptr_t)v, addr_of_entry_addr); break; } @@ -113,7 +122,7 @@ bool inject_on_main(int pid, const char *lib_path) { int status; wait_for_trace(pid, &status, __WALL); if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV) { - if (!get_regs(pid, regs)) return false; + if (!get_regs(pid, ®s)) return false; if (((int)regs.REG_IP & ~1) != ((int)break_addr & ~1)) { LOGE("stopped at unknown addr %p", (void *) regs.REG_IP); @@ -128,13 +137,25 @@ bool inject_on_main(int pid, const char *lib_path) { if (!write_proc(pid, (uintptr_t) addr_of_entry_addr, &entry_addr, sizeof(entry_addr))) return false; /* backup registers */ + struct user_regs_struct backup; memcpy(&backup, ®s, sizeof(regs)); - /* WARNING: C++ keyword */ - map = MapInfo::Scan(std::to_string(pid)); + free_maps(map); + + map = parse_maps(pid_maps); + if (!map) { + LOGE("failed to parse remote maps"); + + return false; + } + + struct maps *local_map = parse_maps("/proc/self/maps"); + if (!local_map) { + LOGE("failed to parse local maps"); + + return false; + } - /* WARNING: C++ keyword */ - std::vector local_map = MapInfo::Scan(); void *libc_return_addr = find_module_return_addr(map, "libc.so"); LOGD("libc return addr %p", libc_return_addr); @@ -142,17 +163,19 @@ bool inject_on_main(int pid, const char *lib_path) { void *dlopen_addr = find_func_addr(local_map, map, "libdl.so", "dlopen"); if (dlopen_addr == NULL) return false; - /* WARNING: C++ keyword */ - std::vector args; + long *args = (long *)malloc(3 * sizeof(long)); + if (args == NULL) { + LOGE("malloc args"); - /* WARNING: C++ keyword */ - uintptr_t str = push_string(pid, regs, lib_path); + return false; + } - args.clear(); - args.push_back((long) str); - args.push_back((long) RTLD_NOW); + uintptr_t str = push_string(pid, ®s, lib_path); - uintptr_t remote_handle = remote_call(pid, regs, (uintptr_t)dlopen_addr, (uintptr_t)libc_return_addr, args); + args[0] = (long) str; + args[1] = (long) RTLD_NOW; + + uintptr_t remote_handle = remote_call(pid, ®s, (uintptr_t)dlopen_addr, (uintptr_t)libc_return_addr, args, 2); LOGD("remote handle %p", (void *)remote_handle); if (remote_handle == 0) { LOGE("handle is null"); @@ -162,37 +185,47 @@ bool inject_on_main(int pid, const char *lib_path) { if (dlerror_addr == NULL) { LOGE("find dlerror"); + free(args); + return false; } - args.clear(); + uintptr_t dlerror_str_addr = remote_call(pid, ®s, (uintptr_t)dlerror_addr, (uintptr_t)libc_return_addr, args, 0); + LOGD("dlerror str %p", (void *)dlerror_str_addr); + if (dlerror_str_addr == 0) { + LOGE("dlerror str is null"); - uintptr_t dlerror_str_addr = remote_call(pid, regs, (uintptr_t)dlerror_addr, (uintptr_t)libc_return_addr, args); - LOGD("dlerror str %p", (void*) dlerror_str_addr); - if (dlerror_str_addr == 0) return false; + free(args); + + return false; + } void *strlen_addr = find_func_addr(local_map, map, "libc.so", "strlen"); if (strlen_addr == NULL) { LOGE("find strlen"); + free(args); + return false; } - args.clear(); - args.push_back(dlerror_str_addr); + args[0] = (long) dlerror_str_addr; - uintptr_t dlerror_len = remote_call(pid, regs, (uintptr_t)strlen_addr, (uintptr_t)libc_return_addr, args); + uintptr_t dlerror_len = remote_call(pid, ®s, (uintptr_t)strlen_addr, (uintptr_t)libc_return_addr, args, 1); if (dlerror_len <= 0) { LOGE("dlerror len <= 0"); + free(args); + return false; } - /* NOTICE: C++ -> C */ char *err = (char *)malloc((dlerror_len + 1) * sizeof(char)); if (err == NULL) { LOGE("malloc err"); + free(args); + return false; } @@ -201,6 +234,7 @@ bool inject_on_main(int pid, const char *lib_path) { LOGE("dlerror info %s", err); free(err); + free(args); return false; } @@ -209,12 +243,13 @@ bool inject_on_main(int pid, const char *lib_path) { void *dlsym_addr = find_func_addr(local_map, map, "libdl.so", "dlsym"); if (dlsym_addr == NULL) return false; - args.clear(); - str = push_string(pid, regs, "entry"); - args.push_back(remote_handle); - args.push_back((long) str); + free_maps(local_map); - uintptr_t injector_entry = remote_call(pid, regs, (uintptr_t)dlsym_addr, (uintptr_t)libc_return_addr, args); + str = push_string(pid, ®s, "entry"); + args[0] = remote_handle; + args[1] = (long) str; + + uintptr_t injector_entry = remote_call(pid, ®s, (uintptr_t)dlsym_addr, (uintptr_t)libc_return_addr, args, 2); LOGD("injector entry %p", (void *)injector_entry); if (injector_entry == 0) { LOGE("injector entry is null"); @@ -223,38 +258,41 @@ bool inject_on_main(int pid, const char *lib_path) { } /* record the address range of libzygisk.so */ - map = MapInfo::Scan(std::to_string(pid)); - void *start_addr = nullptr; + map = parse_maps(pid_maps); + + void *start_addr = NULL; size_t block_size = 0; - for (auto &info : map) { - if (strstr(info.path.c_str(), "libzygisk.so")) { - void *addr = (void *)info.start; - if (start_addr == nullptr) start_addr = addr; - size_t size = info.end - info.start; - block_size += size; - LOGD("found block %s: [%p-%p] with size %zu", info.path.c_str(), addr, (void *)info.end, size); - } + + for (size_t i = 0; i < map->size; i++) { + if (!strstr(map->maps[i].path, "libzygisk.so")) continue; + + if (start_addr == NULL) start_addr = (void *)map->maps[i].start; + + size_t size = map->maps[i].end - map->maps[i].start; + block_size += size; + + LOGD("found block %s: [%p-%p] with size %zu", map->maps[i].path, (void *)map->maps[i].start, + (void *)map->maps[i].end, size); } + free_maps(map); + /* call injector entry(start_addr, block_size, path) */ - args.clear(); - args.push_back((uintptr_t) start_addr); - args.push_back(block_size); + args[0] = (uintptr_t)start_addr; + args[1] = block_size; + str = push_string(pid, ®s, rezygiskd_get_path()); + args[2] = (uintptr_t)str; - char tmp_path[PATH_MAX]; - rezygiskd_get_path(tmp_path, sizeof(tmp_path)); + remote_call(pid, ®s, injector_entry, (uintptr_t)libc_return_addr, args, 3); - str = push_string(pid, regs, tmp_path); - args.push_back((long) str); - - remote_call(pid, regs, injector_entry, (uintptr_t)libc_return_addr, args); + free(args); /* reset pc to entry */ backup.REG_IP = (long) entry_addr; LOGD("invoke entry"); /* restore registers */ - if (!set_regs(pid, backup)) return false; + if (!set_regs(pid, &backup)) return false; return true; } else { @@ -290,11 +328,8 @@ bool trace_zygote(int pid) { WAIT_OR_DIE if (STOPPED_WITH(SIGSTOP, PTRACE_EVENT_STOP)) { - /* WARNING: C++ keyword */ char lib_path[PATH_MAX]; - rezygiskd_get_path(lib_path, sizeof(lib_path)); - - strcat(lib_path,"/lib" LP_SELECT("", "64") "/libzygisk.so"); + snprintf(lib_path, sizeof(lib_path), "%s/lib" LP_SELECT("", "64") "/libzygisk.so", rezygiskd_get_path()); if (!inject_on_main(pid, lib_path)) { LOGE("failed to inject"); diff --git a/loader/src/ptracer/utils.c b/loader/src/ptracer/utils.c new file mode 100644 index 0000000..5aa9a35 --- /dev/null +++ b/loader/src/ptracer/utils.c @@ -0,0 +1,578 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "logging.h" + +#include "utils.h" + +bool switch_mnt_ns(int pid, int *fd) { + int nsfd, old_nsfd = -1; + + char path[PATH_MAX]; + if (pid == 0) { + if (fd != NULL) { + nsfd = *fd; + *fd = -1; + } else return false; + + snprintf(path, sizeof(path), "/proc/self/fd/%d", nsfd); + } else { + if (fd != NULL) { + old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (old_nsfd == -1) { + PLOGE("get old nsfd"); + + return false; + } + + *fd = old_nsfd; + } + + snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); + + nsfd = open(path, O_RDONLY | O_CLOEXEC); + if (nsfd == -1) { + PLOGE("open nsfd %s", path); + + close(old_nsfd); + + return false; + } + } + + if (setns(nsfd, CLONE_NEWNS) == -1) { + PLOGE("set ns to %s", path); + + close(nsfd); + close(old_nsfd); + + return false; + } + + close(nsfd); + + return true; +} + +struct maps *parse_maps(const char *filename) { + FILE *fp = fopen(filename, "r"); + if (!fp) { + LOGE("Failed to open %s", filename); + + return NULL; + } + + struct maps *maps = (struct maps *)malloc(sizeof(struct maps)); + if (!maps) { + LOGE("Failed to allocate memory for maps"); + + fclose(fp); + + return NULL; + } + + char line[4096 * 2]; + size_t i = 0; + + while (fgets(line, sizeof(line), fp) != NULL) { + /* INFO: Remove line ending at the end */ + line[strlen(line) - 1] = '\0'; + + uintptr_t addr_start; + uintptr_t addr_end; + uintptr_t addr_offset; + ino_t inode; + unsigned int dev_major; + unsigned int dev_minor; + char permissions[5] = ""; + int path_offset; + + sscanf(line, + "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", + &addr_start, &addr_end, permissions, &addr_offset, &dev_major, &dev_minor, + &inode, &path_offset); + + while (isspace(line[path_offset])) { + path_offset++; + } + + maps->maps = (struct map *)realloc(maps->maps, (i + 1) * sizeof(struct map)); + if (!maps->maps) { + LOGE("Failed to allocate memory for maps->maps"); + + maps->size = i; + + fclose(fp); + free_maps(maps); + + return NULL; + } + + maps->maps[i].start = addr_start; + maps->maps[i].end = addr_end; + maps->maps[i].offset = addr_offset; + + maps->maps[i].perms = 0; + if (permissions[0] == 'r') maps->maps[i].perms |= PROT_READ; + if (permissions[1] == 'w') maps->maps[i].perms |= PROT_WRITE; + if (permissions[2] == 'x') maps->maps[i].perms |= PROT_EXEC; + + maps->maps[i].is_private = permissions[3] == 'p'; + maps->maps[i].dev = makedev(dev_major, dev_minor); + maps->maps[i].inode = inode; + maps->maps[i].path = strdup(line + path_offset); + if (!maps->maps[i].path) { + LOGE("Failed to allocate memory for maps->maps[%zu].path", i); + + maps->size = i; + + fclose(fp); + free_maps(maps); + + return NULL; + } + + i++; + } + + fclose(fp); + + maps->size = i; + + return maps; +} + +void free_maps(struct maps *maps) { + if (!maps) { + return; + } + + for (size_t i = 0; i < maps->size; i++) { + free((void *)maps->maps[i].path); + } + + free(maps->maps); + free(maps); +} + +ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len) { + LOGV("write to remote addr %" PRIxPTR " size %zu", remote_addr, len); + + struct iovec local = { + .iov_base = (void *)buf, + .iov_len = len + }; + + struct iovec remote = { + .iov_base = (void *)remote_addr, + .iov_len = len + }; + + ssize_t l = process_vm_writev(pid, &local, 1, &remote, 1, 0); + if (l == -1) PLOGE("process_vm_writev"); + else if ((size_t)l != len) LOGW("not fully written: %zu, excepted %zu", l, len); + + return l; +} + +ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) { + struct iovec local = { + .iov_base = (void *)buf, + .iov_len = len + }; + + struct iovec remote = { + .iov_base = (void *)remote_addr, + .iov_len = len + }; + + ssize_t l = process_vm_readv(pid, &local, 1, &remote, 1, 0); + if (l == -1) PLOGE("process_vm_readv"); + else if ((size_t)l != len) LOGW("not fully read: %zu, excepted %zu", l, len); + + return l; +} + +bool get_regs(int pid, struct user_regs_struct *regs) { + #if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_GETREGS, pid, 0, regs) == -1) { + PLOGE("getregs"); + + return false; + } + #elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = regs, + .iov_len = sizeof(struct user_regs_struct), + }; + + if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("getregs"); + + return false; + } + #endif + + return true; +} + +bool set_regs(int pid, struct user_regs_struct *regs) { + #if defined(__x86_64__) || defined(__i386__) + if (ptrace(PTRACE_SETREGS, pid, 0, regs) == -1) { + PLOGE("setregs"); + + return false; + } + #elif defined(__aarch64__) || defined(__arm__) + struct iovec iov = { + .iov_base = regs, + .iov_len = sizeof(struct user_regs_struct), + }; + + if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) { + PLOGE("setregs"); + + return false; + } + #endif + + return true; +} + +void get_addr_mem_region(struct maps *info, uintptr_t addr, char *buf, size_t buf_size) { + for (size_t i = 0; i < info->size; i++) { + /* TODO: Early "leave" */ + if (info->maps[i].start <= addr && info->maps[i].end > addr) { + snprintf(buf, buf_size, "%s %s%s%s", + info->maps[i].path, + info->maps[i].perms & PROT_READ ? "r" : "-", + info->maps[i].perms & PROT_WRITE ? "w" : "-", + info->maps[i].perms & PROT_EXEC ? "x" : "-"); + + return; + } + } + + snprintf(buf, buf_size, ""); +} + +/* INFO: strrchr but without modifying the string */ +const char *position_after(const char *str, const char needle) { + const char *positioned = str + strlen(str); + + int i = strlen(str); + while (i != 0) { + i--; + if (str[i] == needle) { + positioned = str + i + 1; + + break; + } + } + + return positioned; +} + +void *find_module_return_addr(struct maps *map, const char *suffix) { + for (size_t i = 0; i < map->size; i++) { + /* TODO: Make it NULL in 1 length path */ + if (map->maps[i].path == NULL) continue; + + const char *file_name = position_after(map->maps[i].path, '/'); + if (!file_name) continue; + + if (strlen(file_name) < strlen(suffix) || (map->maps[i].perms & PROT_EXEC) != 0 || strncmp(file_name, suffix, strlen(suffix)) != 0) continue; + + return (void *)map->maps[i].start; + } + + return NULL; +} + +void *find_module_base(struct maps *map, const char *suffix) { + for (size_t i = 0; i < map->size; i++) { + /* TODO: Make it NULL in 1 length path */ + if (map->maps[i].path == NULL) continue; + + const char *file_name = position_after(map->maps[i].path, '/'); + if (!file_name) continue; + + if (strlen(file_name) < strlen(suffix) || map->maps[i].offset != 0 || strncmp(file_name, suffix, strlen(suffix)) != 0) continue; + + return (void *)map->maps[i].start; + } + + return NULL; +} + +void *find_func_addr(struct maps *local_info, struct maps *remote_info, const char *module, const char *func) { + void *lib = dlopen(module, RTLD_NOW); + if (lib == NULL) { + LOGE("failed to open lib %s: %s", module, dlerror()); + + return NULL; + } + + uint8_t *sym = (uint8_t *)dlsym(lib, func); + if (sym == NULL) { + LOGE("failed to find sym %s in %s: %s", func, module, dlerror()); + + dlclose(lib); + + return NULL; + } + + LOGD("sym %s: %p", func, sym); + + dlclose(lib); + + uint8_t *local_base = (uint8_t *)find_module_base(local_info, module); + if (local_base == NULL) { + LOGE("failed to find local base for module %s", module); + + return NULL; + } + + uint8_t *remote_base = (uint8_t *)find_module_base(remote_info, module); + if (remote_base == NULL) { + LOGE("failed to find remote base for module %s", module); + + return NULL; + } + + LOGD("found local base %p remote base %p", local_base, remote_base); + + uint8_t *addr = (sym - local_base) + remote_base; + LOGD("addr %p", addr); + + return addr; +} + +void align_stack(struct user_regs_struct *regs, long preserve) { + /* INFO: ~0xf is a negative value, and REG_SP is unsigned, + so we must cast REG_SP to signed type before subtracting + then cast back to unsigned type. + */ + regs->REG_SP = (uintptr_t)((intptr_t)(regs->REG_SP - preserve) & ~0xf); +} + +uintptr_t push_string(int pid, struct user_regs_struct *regs, const char *str) { + size_t len = strlen(str) + 1; + + regs->REG_SP -= len; + + align_stack(regs, 0); + + uintptr_t addr = (uintptr_t)regs->REG_SP; + if (!write_proc(pid, addr, str, len)) LOGE("failed to write string %s", str); + + LOGD("pushed string %" PRIxPTR, addr); + + return addr; +} + +uintptr_t remote_call(int pid, struct user_regs_struct *regs, uintptr_t func_addr, uintptr_t return_addr, long *args, size_t args_size) { + align_stack(regs, 0); + + LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args_size); + + for (size_t i = 0; i < args_size; i++) { + LOGV("arg %p", (void *)args[i]); + } + + #if defined(__x86_64__) + if (args_size >= 1) regs->rdi = args[0]; + if (args_size >= 2) regs->rsi = args[1]; + if (args_size >= 3) regs->rdx = args[2]; + if (args_size >= 4) regs->rcx = args[3]; + if (args_size >= 5) regs->r8 = args[4]; + if (args_size >= 6) regs->r9 = args[5]; + if (args_size > 6) { + long remain = (args_size - 6L) * sizeof(long); + align_stack(regs, remain); + + if (!write_proc(pid, (uintptr_t) regs->REG_SP, args, remain)) LOGE("failed to push arguments"); + } + + regs->REG_SP -= sizeof(long); + + if (!write_proc(pid, (uintptr_t) regs->REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr"); + + regs->REG_IP = func_addr; + #elif defined(__i386__) + if (args_size > 0) { + long remain = (args_size) * sizeof(long); + align_stack(regs, remain); + + if (!write_proc(pid, (uintptr_t) regs->REG_SP, args, remain)) LOGE("failed to push arguments"); + } + + regs->REG_SP -= sizeof(long); + + if (!write_proc(pid, (uintptr_t) regs->REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr"); + + regs->REG_IP = func_addr; + #elif defined(__aarch64__) + for (size_t i = 0; i < args_size && i < 8; i++) { + regs->regs[i] = args[i]; + } + + if (args_size > 8) { + long remain = (args_size - 8) * sizeof(long); + align_stack(regs, remain); + + write_proc(pid, (uintptr_t)regs->REG_SP, args, remain); + } + + regs->regs[30] = return_addr; + regs->REG_IP = func_addr; + #elif defined(__arm__) + for (size_t i = 0; i < args_size && i < 4; i++) { + regs->uregs[i] = args[i]; + } + + if (args_size > 4) { + long remain = (args_size - 4) * sizeof(long); + align_stack(regs, remain); + + write_proc(pid, (uintptr_t)regs->REG_SP, args, remain); + } + + regs->uregs[14] = return_addr; + regs->REG_IP = func_addr; + + unsigned long CPSR_T_MASK = 1lu << 5; + + if ((regs->REG_IP & 1) != 0) { + regs->REG_IP = regs->REG_IP & ~1; + regs->uregs[16] = regs->uregs[16] | CPSR_T_MASK; + } else { + regs->uregs[16] = regs->uregs[16] & ~CPSR_T_MASK; + } + #endif + + if (!set_regs(pid, regs)) { + LOGE("failed to set regs"); + + return 0; + } + + ptrace(PTRACE_CONT, pid, 0, 0); + + int status; + wait_for_trace(pid, &status, __WALL); + if (!get_regs(pid, regs)) { + LOGE("failed to get regs after call"); + + return 0; + } + + if (WSTOPSIG(status) == SIGSEGV) { + if ((uintptr_t)regs->REG_IP != return_addr) { + LOGE("wrong return addr %p", (void *) regs->REG_IP); + + return 0; + } + + return regs->REG_RET; + } else { + 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; +} + +int fork_dont_care() { + pid_t pid = fork(); + + if (pid < 0) PLOGE("fork 1"); + else if (pid == 0) { + pid = fork(); + if (pid < 0) PLOGE("fork 2"); + else if (pid > 0) exit(0); + } else { + int status; + waitpid(pid, &status, __WALL); + } + + return pid; +} + +void wait_for_trace(int pid, int *status, int flags) { + while (1) { + pid_t result = waitpid(pid, status, flags); + if (result == -1) { + if (errno == EINTR) continue; + + PLOGE("wait %d failed", pid); + exit(1); + } + + if (!WIFSTOPPED(*status)) { + 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; + } +} + +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 "); + + int 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"); + } +} + +int get_program(int pid, char *buf, size_t size) { + char path[PATH_MAX]; + 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.cpp b/loader/src/ptracer/utils.cpp deleted file mode 100644 index 1acc32c..0000000 --- a/loader/src/ptracer/utils.cpp +++ /dev/null @@ -1,528 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "utils.hpp" -#include "logging.h" - -bool switch_mnt_ns(int pid, int *fd) { - int nsfd, old_nsfd = -1; - - /* WARNING: C++ keyword */ - char path[PATH_MAX]; - if (pid == 0) { - if (fd != NULL) { - nsfd = *fd; - *fd = -1; - } else return false; - - snprintf(path, sizeof(path), "/proc/self/fd/%d", nsfd); - } else { - if (fd != NULL) { - old_nsfd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); - if (old_nsfd == -1) { - PLOGE("get old nsfd"); - - return false; - } - - *fd = old_nsfd; - } - - snprintf(path, sizeof(path), "/proc/%d/ns/mnt", pid); - - nsfd = open(path, O_RDONLY | O_CLOEXEC); - if (nsfd == -1) { - PLOGE("open nsfd %s", path); - - close(old_nsfd); - - return false; - } - } - - if (setns(nsfd, CLONE_NEWNS) == -1) { - PLOGE("set ns to %s", path); - - close(nsfd); - close(old_nsfd); - - return false; - } - - close(nsfd); - - return true; -} - -/* WARNING: C++ keyword */ -std::vector MapInfo::Scan(const std::string &pid) { - constexpr static auto kPermLength = 5; - constexpr static auto kMapEntry = 7; - - /* WARNING: C++ keyword */ - std::vector info; - char file_name[NAME_MAX]; - snprintf(file_name, sizeof(file_name), "/proc/%s/maps", pid.c_str()); - - /* WARNING: C++ keyword */ - auto maps = std::unique_ptr{fopen(file_name, "r"), &fclose}; - if (maps) { - char *line = NULL; - size_t len = 0; - ssize_t read; - - /* WARNING: C++ keyword */ - while ((read = getline(&line, &len, maps.get())) > 0) { - line[read - 1] = '\0'; - - uintptr_t start = 0; - uintptr_t end = 0; - uintptr_t off = 0; - ino_t inode = 0; - unsigned int dev_major = 0; - unsigned int dev_minor = 0; - - /* WARNING: C++ keyword */ - std::array perm {'\0'}; - - int path_off; - if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start, - &end, perm.data(), &off, &dev_major, &dev_minor, &inode, - &path_off) != kMapEntry) continue; - - while (path_off < read && isspace(line[path_off])) path_off++; - - /* WARNING: C++ keyword */ - MapInfo &ref = info.emplace_back(MapInfo{ - start, - end, - 0, - perm[3] == 'p', - off, - static_cast(makedev(dev_major, dev_minor)), - inode, - line + path_off - }); - - if (perm[0] == 'r') ref.perms |= PROT_READ; - if (perm[1] == 'w') ref.perms |= PROT_WRITE; - if (perm[2] == 'x') ref.perms |= PROT_EXEC; - } - - free(line); - } - - return info; -} - -ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len) { - LOGV("write to remote addr %" PRIxPTR " size %zu", remote_addr, len); - - struct iovec local = { - .iov_base = (void *)buf, - .iov_len = len - }; - - struct iovec remote = { - .iov_base = (void *)remote_addr, - .iov_len = len - }; - - ssize_t l = process_vm_writev(pid, &local, 1, &remote, 1, 0); - if (l == -1) PLOGE("process_vm_writev"); - else if ((size_t)l != len) LOGW("not fully written: %zu, excepted %zu", l, len); - - return l; -} - -ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len) { - struct iovec local = { - .iov_base = (void *)buf, - .iov_len = len - }; - - struct iovec remote = { - .iov_base = (void *)remote_addr, - .iov_len = len - }; - - ssize_t l = process_vm_readv(pid, &local, 1, &remote, 1, 0); - if (l == -1) PLOGE("process_vm_readv"); - else if ((size_t)l != len) LOGW("not fully read: %zu, excepted %zu", l, len); - - return l; -} - -bool get_regs(int pid, struct user_regs_struct ®s) { - #if defined(__x86_64__) || defined(__i386__) - if (ptrace(PTRACE_GETREGS, pid, 0, ®s) == -1) { - PLOGE("getregs"); - - return false; - } - #elif defined(__aarch64__) || defined(__arm__) - struct iovec iov = { - .iov_base = ®s, - .iov_len = sizeof(struct user_regs_struct), - }; - - if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) { - PLOGE("getregs"); - - return false; - } - #endif - - return true; -} - -bool set_regs(int pid, struct user_regs_struct ®s) { - #if defined(__x86_64__) || defined(__i386__) - if (ptrace(PTRACE_SETREGS, pid, 0, ®s) == -1) { - PLOGE("setregs"); - - return false; - } - #elif defined(__aarch64__) || defined(__arm__) - struct iovec iov = { - .iov_base = ®s, - .iov_len = sizeof(struct user_regs_struct), - }; - - if (ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov) == -1) { - PLOGE("setregs"); - - return false; - } - #endif - - return true; -} - -/* WARNING: C++ keyword */ -std::string get_addr_mem_region(std::vector &info, uintptr_t addr) { - /* WARNING: C++ keyword */ - for (auto &map: info) { - if (map.start <= addr && map.end > addr) { - /* WARNING: C++ keyword */ - auto s = std::string(map.path); - - s += ' '; - s += map.perms & PROT_READ ? 'r' : '-'; - s += map.perms & PROT_WRITE ? 'w' : '-'; - s += map.perms & PROT_EXEC ? 'x' : '-'; - - return s; - } - } - - return ""; -} - -/* WARNING: C++ keyword */ -void *find_module_return_addr(std::vector &info, std::string_view suffix) { - /* WARNING: C++ keyword */ - for (auto &map: info) { - /* WARNING: C++ keyword */ - if ((map.perms & PROT_EXEC) == 0 && map.path.ends_with(suffix)) return (void *)map.start; - } - - return NULL; -} - -/* WARNING: C++ keyword */ -void *find_module_base(std::vector &info, std::string_view suffix) { - /* WARNING: C++ keyword */ - for (auto &map: info) { - /* WARNING: C++ keyword */ - if (map.offset == 0 && map.path.ends_with(suffix)) return (void *)map.start; - } - - return NULL; -} - -/* WARNING: C++ keyword */ -void *find_func_addr(std::vector &local_info, std::vector &remote_info, std::string_view module, std::string_view func) { - void *lib = dlopen(module.data(), RTLD_NOW); - if (lib == NULL) { - LOGE("failed to open lib %s: %s", module.data(), dlerror()); - - return NULL; - } - - uint8_t *sym = (uint8_t *)dlsym(lib, func.data()); - if (sym == NULL) { - LOGE("failed to find sym %s in %s: %s", func.data(), module.data(), dlerror()); - - dlclose(lib); - - return NULL; - } - - LOGD("sym %s: %p", func.data(), sym); - - dlclose(lib); - - uint8_t *local_base = (uint8_t *)find_module_base(local_info, module); - if (local_base == NULL) { - LOGE("failed to find local base for module %s", module.data()); - - return NULL; - } - - uint8_t *remote_base = (uint8_t *)find_module_base(remote_info, module); - if (remote_base == NULL) { - LOGE("failed to find remote base for module %s", module.data()); - - return NULL; - } - - LOGD("found local base %p remote base %p", local_base, remote_base); - - uint8_t *addr = (sym - local_base) + remote_base; - LOGD("addr %p", addr); - - return addr; -} - -/* WARNING: C++ keyword */ -void align_stack(struct user_regs_struct ®s, long preserve) { - /* INFO: ~0xf is a negative value, and REG_SP is unsigned, - so we must cast REG_SP to signed type before subtracting - then cast back to unsigned type. - */ - regs.REG_SP = (uintptr_t)((intptr_t)(regs.REG_SP - preserve) & ~0xf); -} - -/* WARNING: C++ keyword */ -uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str) { - size_t len = strlen(str) + 1; - - regs.REG_SP -= len; - - align_stack(regs); - - uintptr_t addr = (uintptr_t)regs.REG_SP; - if (!write_proc(pid, addr, str, len)) LOGE("failed to write string %s", str); - - LOGD("pushed string %" PRIxPTR, addr); - - return addr; -} - -/* WARNING: C++ keyword */ -uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, std::vector &args) { - align_stack(regs); - - /* WARNING: C++ keyword */ - LOGV("calling remote function %" PRIxPTR " args %zu", func_addr, args.size()); - - /* WARNING: C++ keyword */ - for (auto &a: args) { - LOGV("arg %p", (void *) a); - } - - #if defined(__x86_64__) - if (args.size() >= 1) regs.rdi = args[0]; - if (args.size() >= 2) regs.rsi = args[1]; - if (args.size() >= 3) regs.rdx = args[2]; - if (args.size() >= 4) regs.rcx = args[3]; - if (args.size() >= 5) regs.r8 = args[4]; - if (args.size() >= 6) regs.r9 = args[5]; - if (args.size() > 6) { - long remain = (args.size() - 6L) * sizeof(long); - align_stack(regs, remain); - - if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) LOGE("failed to push arguments"); - } - - regs.REG_SP -= sizeof(long); - - if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr"); - - regs.REG_IP = func_addr; - #elif defined(__i386__) - if (args.size() > 0) { - long remain = (args.size()) * sizeof(long); - align_stack(regs, remain); - - if (!write_proc(pid, (uintptr_t) regs.REG_SP, args.data(), remain)) LOGE("failed to push arguments"); - } - - regs.REG_SP -= sizeof(long); - - if (!write_proc(pid, (uintptr_t) regs.REG_SP, &return_addr, sizeof(return_addr))) LOGE("failed to write return addr"); - - regs.REG_IP = func_addr; - #elif defined(__aarch64__) - for (size_t i = 0; i < args.size() && i < 8; i++) { - regs.regs[i] = args[i]; - } - - if (args.size() > 8) { - long remain = (args.size() - 8) * sizeof(long); - align_stack(regs, remain); - - write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain); - } - - regs.regs[30] = return_addr; - regs.REG_IP = func_addr; - #elif defined(__arm__) - for (size_t i = 0; i < args.size() && i < 4; i++) { - regs.uregs[i] = args[i]; - } - - if (args.size() > 4) { - long remain = (args.size() - 4) * sizeof(long); - align_stack(regs, remain); - - write_proc(pid, (uintptr_t)regs.REG_SP, args.data(), remain); - } - - regs.uregs[14] = return_addr; - regs.REG_IP = func_addr; - - constexpr auto CPSR_T_MASK = 1lu << 5; - - if ((regs.REG_IP & 1) != 0) { - regs.REG_IP = regs.REG_IP & ~1; - regs.uregs[16] = regs.uregs[16] | CPSR_T_MASK; - } else { - regs.uregs[16] = regs.uregs[16] & ~CPSR_T_MASK; - } - #endif - - if (!set_regs(pid, regs)) { - LOGE("failed to set regs"); - - return 0; - } - - ptrace(PTRACE_CONT, pid, 0, 0); - - int status; - wait_for_trace(pid, &status, __WALL); - if (!get_regs(pid, regs)) { - LOGE("failed to get regs after call"); - - return 0; - } - - if (WSTOPSIG(status) == SIGSEGV) { - if ((uintptr_t)regs.REG_IP != return_addr) { - LOGE("wrong return addr %p", (void *) regs.REG_IP); - - return 0; - } - - return regs.REG_RET; - } else { - 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; -} - -int fork_dont_care() { - pid_t pid = fork(); - - if (pid < 0) PLOGE("fork 1"); - else if (pid == 0) { - pid = fork(); - if (pid < 0) PLOGE("fork 2"); - else if (pid > 0) exit(0); - } else { - int status; - waitpid(pid, &status, __WALL); - } - - return pid; -} - -void wait_for_trace(int pid, int *status, int flags) { - while (1) { - pid_t result = waitpid(pid, status, flags); - if (result == -1) { - if (errno == EINTR) continue; - - PLOGE("wait %d failed", pid); - exit(1); - } - - if (!WIFSTOPPED(*status)) { - 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; - } -} - -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 "); - - int 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"); - } -} - -int get_program(int pid, char *buf, size_t size) { - char path[PATH_MAX]; - 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.h b/loader/src/ptracer/utils.h new file mode 100644 index 0000000..02b37ac --- /dev/null +++ b/loader/src/ptracer/utils.h @@ -0,0 +1,109 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +#include "daemon.h" + +#include "logging.h" + +struct map { + uintptr_t start; + uintptr_t end; + uint8_t perms; + bool is_private; + uintptr_t offset; + dev_t dev; + ino_t inode; + const char *path; +}; + +struct maps { + struct map *maps; + size_t size; +}; + +struct maps *parse_maps(const char *filename); + +void free_maps(struct maps *maps); + +#if defined(__x86_64__) + #define REG_SP rsp + #define REG_IP rip + #define REG_RET rax +#elif defined(__i386__) + #define REG_SP esp + #define REG_IP eip + #define REG_RET eax +#elif defined(__aarch64__) + #define REG_SP sp + #define REG_IP pc + #define REG_RET regs[0] +#elif defined(__arm__) + #define REG_SP uregs[13] + #define REG_IP uregs[15] + #define REG_RET uregs[0] + #define user_regs_struct user_regs +#endif + +ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len); + +ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len); + +bool get_regs(int pid, struct user_regs_struct *regs); + +bool set_regs(int pid, struct user_regs_struct *regs); + +void get_addr_mem_region(struct maps *map, uintptr_t addr, char *buf, size_t buf_size); + +void *find_module_return_addr(struct maps *map, const char *suffix); + +void *find_func_addr(struct maps *local_info, struct maps *remote_info, const char *module, const char *func); + +void align_stack(struct user_regs_struct *regs, long preserve); + +uintptr_t push_string(int pid, struct user_regs_struct *regs, const char *str); + +uintptr_t remote_call(int pid, struct user_regs_struct *regs, uintptr_t func_addr, uintptr_t return_addr, long *args, size_t args_size); + +int fork_dont_care(); + +void wait_for_trace(int pid, int* status, int flags); + +void parse_status(int status, char *buf, size_t len); + +#define WPTEVENT(x) (x >> 16) + +#define CASE_CONST_RETURN(x) case x: return #x; + +static inline const char *parse_ptrace_event(int status) { + status = status >> 16; + + switch (status) { + CASE_CONST_RETURN(PTRACE_EVENT_FORK) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK) + CASE_CONST_RETURN(PTRACE_EVENT_CLONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXEC) + CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE) + CASE_CONST_RETURN(PTRACE_EVENT_EXIT) + CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP) + CASE_CONST_RETURN(PTRACE_EVENT_STOP) + default: + return "(no event)"; + } +} + +static inline const char *sigabbrev_np(int sig) { + if (sig > 0 && sig < NSIG) return sys_signame[sig]; + + return "(unknown)"; +} + +int get_program(int pid, char *buf, size_t size); + +/* INFO: pid = 0, fd != nullptr -> set to fd + pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd +*/ +bool switch_mnt_ns(int pid, int *fd); + +#endif /* UTILS_H */ \ No newline at end of file diff --git a/loader/src/ptracer/utils.hpp b/loader/src/ptracer/utils.hpp deleted file mode 100644 index 83858b3..0000000 --- a/loader/src/ptracer/utils.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#pragma once -#include -#include -#include - -#include "daemon.h" - -#ifdef __LP64__ -#define LOG_TAG "zygisk-ptrace64" -#else -#define LOG_TAG "zygisk-ptrace32" -#endif - -#include "logging.h" - -struct MapInfo { - /// \brief The start address of the memory region. - uintptr_t start; - /// \brief The end address of the memory region. - uintptr_t end; - /// \brief The permissions of the memory region. This is a bit mask of the following values: - /// - PROT_READ - /// - PROT_WRITE - /// - PROT_EXEC - uint8_t perms; - /// \brief Whether the memory region is private. - bool is_private; - /// \brief The offset of the memory region. - uintptr_t offset; - /// \brief The device number of the memory region. - /// Major can be obtained by #major() - /// Minor can be obtained by #minor() - dev_t dev; - /// \brief The inode number of the memory region. - ino_t inode; - /// \brief The path of the memory region. - std::string path; - - /// \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. - static std::vector Scan(const std::string& pid = "self"); -}; - -#if defined(__x86_64__) -#define REG_SP rsp -#define REG_IP rip -#define REG_RET rax -#elif defined(__i386__) -#define REG_SP esp -#define REG_IP eip -#define REG_RET eax -#elif defined(__aarch64__) -#define REG_SP sp -#define REG_IP pc -#define REG_RET regs[0] -#elif defined(__arm__) -#define REG_SP uregs[13] -#define REG_IP uregs[15] -#define REG_RET uregs[0] -#define user_regs_struct user_regs -#endif - -ssize_t write_proc(int pid, uintptr_t remote_addr, const void *buf, size_t len); - -ssize_t read_proc(int pid, uintptr_t remote_addr, void *buf, size_t len); - -bool get_regs(int pid, struct user_regs_struct ®s); - -bool set_regs(int pid, struct user_regs_struct ®s); - -std::string get_addr_mem_region(std::vector &info, uintptr_t addr); - -void *find_module_base(std::vector &info, std::string_view suffix); - -void *find_func_addr( - std::vector &local_info, - std::vector &remote_info, - std::string_view module, - std::string_view func); - -void align_stack(struct user_regs_struct ®s, long preserve = 0); - -uintptr_t push_string(int pid, struct user_regs_struct ®s, const char *str); - -uintptr_t remote_call(int pid, struct user_regs_struct ®s, uintptr_t func_addr, uintptr_t return_addr, - std::vector &args); - -int fork_dont_care(); - -void wait_for_trace(int pid, int* status, int flags); - -void parse_status(int status, char *buf, size_t len); - -#define WPTEVENT(x) (x >> 16) - -#define CASE_CONST_RETURN(x) case x: return #x; - -inline const char* parse_ptrace_event(int status) { - status = status >> 16; - switch (status) { - CASE_CONST_RETURN(PTRACE_EVENT_FORK) - CASE_CONST_RETURN(PTRACE_EVENT_VFORK) - CASE_CONST_RETURN(PTRACE_EVENT_CLONE) - CASE_CONST_RETURN(PTRACE_EVENT_EXEC) - CASE_CONST_RETURN(PTRACE_EVENT_VFORK_DONE) - CASE_CONST_RETURN(PTRACE_EVENT_EXIT) - CASE_CONST_RETURN(PTRACE_EVENT_SECCOMP) - CASE_CONST_RETURN(PTRACE_EVENT_STOP) - default: - return "(no event)"; - } -} - -inline const char* sigabbrev_np(int sig) { - if (sig > 0 && sig < NSIG) return sys_signame[sig]; - return "(unknown)"; -} - -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 -// pid != 0, fd != nullptr -> set to pid ns, give orig ns in fd -bool switch_mnt_ns(int pid, int *fd); diff --git a/zygiskd/src/utils.h b/zygiskd/src/utils.h index 1d8bebb..d3946c9 100644 --- a/zygiskd/src/utils.h +++ b/zygiskd/src/utils.h @@ -65,7 +65,7 @@ return -1; \ } -#define write_func_def(type) \ +#define write_func_def(type) \ ssize_t write_## type(int fd, type val) #define read_func_def(type) \ diff --git a/zygiskd/src/zygiskd.c b/zygiskd/src/zygiskd.c index 44fa9fc..eb9bc29 100644 --- a/zygiskd/src/zygiskd.c +++ b/zygiskd/src/zygiskd.c @@ -58,34 +58,6 @@ static enum Architecture get_arch(void) { exit(1); } -int create_library_fd(const char *restrict so_path) { - int so_fd = open(so_path, O_RDONLY); - if (so_fd == -1) { - LOGE("Failed opening so file: %s\n", strerror(errno)); - - return -1; - } - - off_t so_size = lseek(so_fd, 0, SEEK_END); - if (so_size == -1) { - LOGE("Failed getting so file size: %s\n", strerror(errno)); - - close(so_fd); - - return -1; - } - - if (lseek(so_fd, 0, SEEK_SET) == -1) { - LOGE("Failed seeking so file: %s\n", strerror(errno)); - - close(so_fd); - - return -1; - } - - return so_fd; -} - /* WARNING: Dynamic memory based */ static void load_modules(enum Architecture arch, struct Context *restrict context) { context->len = 0; @@ -138,7 +110,7 @@ static void load_modules(enum Architecture arch, struct Context *restrict contex errno = 0; } else continue; - int lib_fd = create_library_fd(so_path); + int lib_fd = open(so_path, O_RDONLY | O_CLOEXEC); if (lib_fd == -1) { LOGE("Failed loading module `%s`\n", name); @@ -556,12 +528,6 @@ void zygiskd_start(char *restrict argv[]) { break; } - - if (write_string(client_fd, context.modules[i].name) == -1) { - LOGE("Failed writing module name.\n"); - - break; - } } break;