From ff7ac582f013dbc27b27e4194424044ee79c3980 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sun, 6 Feb 2022 16:27:31 +0800 Subject: [PATCH] Refactor Zygisk loading Co-authored-by: topjohnwu --- native/jni/utils/missing.hpp | 13 ++++ native/jni/zygisk/entry.cpp | 115 ++++++++++++++++++----------------- native/jni/zygisk/hook.cpp | 15 +++-- native/jni/zygisk/main.cpp | 26 ++++---- native/jni/zygisk/utils.cpp | 42 +++++++++---- native/jni/zygisk/zygisk.hpp | 10 ++- 6 files changed, 127 insertions(+), 94 deletions(-) diff --git a/native/jni/utils/missing.hpp b/native/jni/utils/missing.hpp index dfa9d9aa4..5ca414a4f 100644 --- a/native/jni/utils/missing.hpp +++ b/native/jni/utils/missing.hpp @@ -1,7 +1,10 @@ #pragma once #include +#include #include +#include +#include static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const timespec* timeout) { union { @@ -11,3 +14,13 @@ static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const times s.set = *set; return syscall(__NR_rt_sigtimedwait, &s.set64, info, timeout, sizeof(sigset64_t)); } + +static inline int fexecve(int fd, char* const* argv, char* const* envp) { + syscall(__NR_execveat, fd, "", argv, envp, AT_EMPTY_PATH); + if (errno == ENOSYS) { + char buf[256]; + std::snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); + execve(buf, argv, envp); + } + return -1; +} diff --git a/native/jni/zygisk/entry.cpp b/native/jni/zygisk/entry.cpp index d103143a4..89c32926a 100644 --- a/native/jni/zygisk/entry.cpp +++ b/native/jni/zygisk/entry.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include @@ -27,15 +28,6 @@ static void zygisk_logging() { log_cb.ex = nop_ex; } -static char *first_stage_path = nullptr; -void unload_first_stage() { - if (first_stage_path) { - unmap_all(first_stage_path); - free(first_stage_path); - first_stage_path = nullptr; - } -} - // Make sure /proc/self/environ is sanitized // Filter env and reset MM_ENV_END static void sanitize_environ() { @@ -43,7 +35,7 @@ static void sanitize_environ() { for (int i = 0; environ[i]; ++i) { // Copy all env onto the original stack - int len = strlen(environ[i]); + size_t len = strlen(environ[i]); memmove(cur, environ[i], len + 1); environ[i] = cur; cur += len + 1; @@ -52,7 +44,7 @@ static void sanitize_environ() { prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0); } -__attribute__((destructor)) +[[gnu::destructor]] [[maybe_unused]] static void zygisk_cleanup_wait() { if (self_handle) { // Wait 10us to make sure none of our code is executing @@ -61,71 +53,84 @@ static void zygisk_cleanup_wait() { } } -#define SECOND_STAGE_PTR "ZYGISK_PTR" - -static void second_stage_entry(void *handle, const char *tmp, char *path) { - self_handle = handle; - MAGISKTMP = tmp; - unsetenv(INJECT_ENV_2); - unsetenv(SECOND_STAGE_PTR); - +static void second_stage_entry() { zygisk_logging(); ZLOGD("inject 2nd stage\n"); - hook_functions(); - // First stage will be unloaded before the first fork - first_stage_path = path; + char path[PATH_MAX]; + MAGISKTMP = getenv(MAGISKTMP_ENV); + int fd = parse_int(getenv(MAGISKFD_ENV)); + + snprintf(path, sizeof(path), "/proc/self/fd/%d", fd); + xreadlink(path, path, PATH_MAX); + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fd, + }; + self_handle = android_dlopen_ext(path, RTLD_LAZY, &info); + dlclose(self_handle); + close(fd); + unsetenv(MAGISKTMP_ENV); + unsetenv(MAGISKFD_ENV); + sanitize_environ(); + hook_functions(); } static void first_stage_entry() { android_logging(); ZLOGD("inject 1st stage\n"); + char path[PATH_MAX]; + char buf[256]; char *ld = getenv("LD_PRELOAD"); - char tmp[128]; - strlcpy(tmp, getenv("MAGISKTMP"), sizeof(tmp)); - char *path; if (char *c = strrchr(ld, ':')) { *c = '\0'; + strlcpy(path, c + 1, sizeof(path)); setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD - path = strdup(c + 1); } else { unsetenv("LD_PRELOAD"); - path = strdup(ld); + strlcpy(path, ld, sizeof(path)); } - unsetenv(INJECT_ENV_1); - unsetenv("MAGISKTMP"); - sanitize_environ(); - char *num = strrchr(path, '.') - 1; + // Force the linker to load the library on top of ourselves, so we do not + // need to unmap the 1st stage library that was loaded with LD_PRELOAD. - // Update path to 2nd stage lib - *num = '2'; + int fd = xopen(path, O_RDONLY | O_CLOEXEC); + // Use fd here instead of path to make sure inode is the same as 2nd stage + snprintf(buf, sizeof(buf), "%d", fd); + setenv(MAGISKFD_ENV, buf, 1); + struct stat s{}; + xfstat(fd, &s); + + android_dlextinfo info { + .flags = ANDROID_DLEXT_FORCE_LOAD | ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fd, + }; + auto [addr, size] = find_map_range(path, s.st_ino); + if (addr && size) { + info.flags |= ANDROID_DLEXT_RESERVED_ADDRESS; + info.reserved_addr = addr; + // The existing address is guaranteed to fit, as 1st stage and 2nd stage + // are exactly the same ELF (same inode). However, the linker could over + // estimate the required size and refuse to dlopen. Add 2 more page_sizes + // (one at the beginning and one at the end) as a safety measure. + info.reserved_size = size + 2 * 4096; + } - // Load second stage setenv(INJECT_ENV_2, "1", 1); - void *handle = dlopen(path, RTLD_LAZY); - remap_all(path); - - // Revert path to 1st stage lib - *num = '1'; - - // Run second stage entry - char *env = getenv(SECOND_STAGE_PTR); - decltype(&second_stage_entry) second_stage; - sscanf(env, "%p", &second_stage); - second_stage(handle, tmp, path); + // Force dlopen ourselves to make ourselves dlclose-able. + // After this call, all global variables will be reset. + android_dlopen_ext(path, RTLD_LAZY, &info); } -__attribute__((constructor)) +[[gnu::constructor]] [[maybe_unused]] static void zygisk_init() { - if (getenv(INJECT_ENV_2)) { - // Return function pointer to first stage - char buf[128]; - snprintf(buf, sizeof(buf), "%p", &second_stage_entry); - setenv(SECOND_STAGE_PTR, buf, 1); - } else if (getenv(INJECT_ENV_1)) { + if (getenv(INJECT_ENV_1)) { + unsetenv(INJECT_ENV_1); first_stage_entry(); + } else if (getenv(INJECT_ENV_2)) { + unsetenv(INJECT_ENV_2); + second_stage_entry(); } } @@ -167,7 +172,7 @@ static int zygisk_log(int prio, const char *fmt, va_list ap) { return ret; } -static inline bool should_load_modules(int flags) { +static inline bool should_load_modules(uint32_t flags) { return (flags & UNMOUNT_MASK) != UNMOUNT_MASK && (flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP; } @@ -304,10 +309,6 @@ static void setup_files(int client, const sock_cred *cred) { write_int(client, 0); send_fd(client, is_64_bit ? app_process_64 : app_process_32); - - string path = MAGISKTMP + "/" ZYGISKBIN "/zygisk." + basename(buf); - cp_afc(buf, (path + ".1.so").data()); - cp_afc(buf, (path + ".2.so").data()); write_string(client, MAGISKTMP); } diff --git a/native/jni/zygisk/hook.cpp b/native/jni/zygisk/hook.cpp index d4d092c50..7488c1cfc 100644 --- a/native/jni/zygisk/hook.cpp +++ b/native/jni/zygisk/hook.cpp @@ -1,8 +1,10 @@ -#include +#include #include -#include +#include #include +#include + #include #include #include @@ -147,7 +149,6 @@ DCL_HOOK_FUNC(int, jniRegisterNativeMethods, // Skip actual fork and return cached result if applicable // Also unload first stage zygisk if necessary DCL_HOOK_FUNC(int, fork) { - unload_first_stage(); return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); } @@ -360,15 +361,17 @@ uint32_t ZygiskModule::getFlags() { } void HookContext::run_modules_pre(const vector &fds) { - char buf[256]; // Since we directly use the pointer to elements in the vector, in order to prevent dangling // pointers, the vector has to be pre-allocated to ensure reallocation does not occur modules.reserve(fds.size()); for (int i = 0; i < fds.size(); ++i) { - snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fds[i]); - if (void *h = dlopen(buf, RTLD_LAZY)) { + android_dlextinfo info { + .flags = ANDROID_DLEXT_USE_LIBRARY_FD, + .library_fd = fds[i], + }; + if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) { if (void *e = dlsym(h, "zygisk_module_entry")) { modules.emplace_back(i, h, e); } diff --git a/native/jni/zygisk/main.cpp b/native/jni/zygisk/main.cpp index 9ab2fbda8..209b772b2 100644 --- a/native/jni/zygisk/main.cpp +++ b/native/jni/zygisk/main.cpp @@ -1,4 +1,5 @@ #include +#include #include #include @@ -13,7 +14,7 @@ using namespace std; // Entrypoint for app_process overlay int app_process_main(int argc, char *argv[]) { android_logging(); - char buf[256]; + char buf[PATH_MAX]; bool zygote = false; if (auto fp = open_file("/proc/self/attr/current", "r")) { @@ -51,9 +52,8 @@ int app_process_main(int argc, char *argv[]) { return 1; close(fds[0]); - snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd); fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - execve(buf, argv, environ); + fexecve(app_proc_fd, argv, environ); return 1; } @@ -70,26 +70,22 @@ int app_process_main(int argc, char *argv[]) { break; string tmp = read_string(socket); -#if defined(__LP64__) - string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process64.1.so"; -#else - string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process32.1.so"; -#endif + xreadlink("/proc/self/exe", buf, sizeof(buf)); if (char *ld = getenv("LD_PRELOAD")) { - char env[256]; - sprintf(env, "%s:%s", ld, lib.data()); - setenv("LD_PRELOAD", env, 1); + string env = ld; + env += ':'; + env += buf; + setenv("LD_PRELOAD", env.data(), 1); } else { - setenv("LD_PRELOAD", lib.data(), 1); + setenv("LD_PRELOAD", buf, 1); } setenv(INJECT_ENV_1, "1", 1); - setenv("MAGISKTMP", tmp.data(), 1); + setenv(MAGISKTMP_ENV, tmp.data(), 1); close(socket); - snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd); fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - execve(buf, argv, environ); + fexecve(app_proc_fd, argv, environ); } while (false); close(socket); diff --git a/native/jni/zygisk/utils.cpp b/native/jni/zygisk/utils.cpp index aebeba4e4..af9a52802 100644 --- a/native/jni/zygisk/utils.cpp +++ b/native/jni/zygisk/utils.cpp @@ -12,9 +12,9 @@ struct map_info { uintptr_t end; uintptr_t off; int perms; - const char *path; + unsigned long inode; - map_info() : start(0), end(0), off(0), perms(0), path(nullptr) {} + map_info() : start(0), end(0), off(0), perms(0), inode(0) {} }; } // namespace @@ -31,8 +31,8 @@ static void parse_maps(int pid, Func fn) { char perm[5]; int path_off; - if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n%*s", - &info.start, &info.end, perm, &info.off, &path_off) != 4) + if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %lu %n%*s", + &info.start, &info.end, perm, &info.off, &info.inode, &path_off) != 5) return true; // Parse permissions @@ -43,22 +43,38 @@ static void parse_maps(int pid, Func fn) { if (perm[2] != '-') info.perms |= PROT_EXEC; - info.path = line + path_off; - - return fn(info); + return fn(info, line + path_off); }); } static vector find_maps(const char *name) { vector maps; - parse_maps(getpid(), [=, &maps](map_info &info) -> bool { - if (strcmp(info.path, name) == 0) + parse_maps(getpid(), [=, &maps](const map_info &info, const char *path) -> bool { + if (strcmp(path, name) == 0) maps.emplace_back(info); return true; }); return maps; } +std::pair find_map_range(const char *name, unsigned long inode) { + vector maps = find_maps(name); + uintptr_t start = 0u; + uintptr_t end = 0u; + for (const auto &map : maps) { + if (map.inode == inode) { + if (start == 0) { + start = map.start; + end = map.end; + } else if (map.start == end) { + end = map.end; + } + } + } + LOGD("found map %s with start = %zx, end = %zx\n", name, start, end); + return make_pair(reinterpret_cast(start), end - start); +} + void unmap_all(const char *name) { vector maps = find_maps(name); for (map_info &info : maps) { @@ -91,10 +107,10 @@ void remap_all(const char *name) { uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) { uintptr_t off = 0; - parse_maps(pid, [=, &off](map_info &info) -> bool { + parse_maps(pid, [=, &off](const map_info &info, const char *path) -> bool { if (addr >= info.start && addr < info.end) { if (lib) - strcpy(lib, info.path); + strcpy(lib, path); off = addr - info.start + info.off; return false; } @@ -105,8 +121,8 @@ uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) { uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) { uintptr_t addr = 0; - parse_maps(pid, [=, &addr](map_info &info) -> bool { - if (strcmp(info.path, lib) == 0 && (info.perms & PROT_EXEC)) { + parse_maps(pid, [=, &addr](const map_info &info, const char *path) -> bool { + if (strcmp(path, lib) == 0 && (info.perms & PROT_EXEC)) { addr = info.start - info.off + off; return false; } diff --git a/native/jni/zygisk/zygisk.hpp b/native/jni/zygisk/zygisk.hpp index 39266d678..ddc1076c5 100644 --- a/native/jni/zygisk/zygisk.hpp +++ b/native/jni/zygisk/zygisk.hpp @@ -4,8 +4,10 @@ #include #include -#define INJECT_ENV_1 "MAGISK_INJ_1" -#define INJECT_ENV_2 "MAGISK_INJ_2" +#define INJECT_ENV_1 "MAGISK_INJ_1" +#define INJECT_ENV_2 "MAGISK_INJ_2" +#define MAGISKFD_ENV "MAGISKFD" +#define MAGISKTMP_ENV "MAGISKTMP" enum : int { ZYGISK_SETUP, @@ -25,6 +27,9 @@ enum : int { #define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__) #endif +// Find the memory address + size of the pages matching name + inode +std::pair find_map_range(const char *name, unsigned long inode); + // Unmap all pages matching the name void unmap_all(const char *name); @@ -39,7 +44,6 @@ uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off); extern void *self_handle; -void unload_first_stage(); void hook_functions(); int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector &fds); int remote_request_unmount();