From 4060c2107c9edf6880f0cf719a1e7076be94e918 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Wed, 6 Jan 2021 22:21:17 -0800 Subject: [PATCH] Add preliminary zygote code injection support Prototyping the injection setup and a clean "self unloading" mechanism. --- native/jni/Android.mk | 4 +- native/jni/core/applets.cpp | 19 ++-- native/jni/core/daemon.cpp | 13 +-- native/jni/include/magisk.hpp | 1 + native/jni/inject/entry.cpp | 124 +++++++++++++++++++++++++ native/jni/inject/inject.hpp | 17 ++++ native/jni/inject/ptrace.cpp | 81 ++-------------- native/jni/inject/ptrace.hpp | 9 +- native/jni/inject/utils.cpp | 101 ++++++++++++++++++++ native/jni/magiskhide/proc_monitor.cpp | 1 - native/jni/utils/misc.cpp | 10 +- native/jni/utils/misc.hpp | 2 +- native/jni/utils/xwrap.hpp | 1 + 13 files changed, 281 insertions(+), 102 deletions(-) create mode 100644 native/jni/inject/entry.cpp create mode 100644 native/jni/inject/inject.hpp create mode 100644 native/jni/inject/utils.cpp diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 82dfde97e..4382bc00c 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -30,7 +30,9 @@ LOCAL_SRC_FILES := \ su/su.cpp \ su/connect.cpp \ su/pts.cpp \ - su/su_daemon.cpp + su/su_daemon.cpp \ + inject/entry.cpp \ + inject/utils.cpp LOCAL_LDLIBS := -llog include $(BUILD_EXECUTABLE) diff --git a/native/jni/core/applets.cpp b/native/jni/core/applets.cpp index 808a55b9e..a6a815830 100644 --- a/native/jni/core/applets.cpp +++ b/native/jni/core/applets.cpp @@ -8,21 +8,26 @@ #include #include -using namespace std::literals; +using namespace std; using main_fun = int (*)(int, char *[]); static main_fun applet_main[] = { su_client_main, resetprop_main, magiskhide_main, nullptr }; -[[noreturn]] static void call_applet(int argc, char **argv) { +static int call_applet(int argc, char *argv[]) { // Applets + string_view base = basename(argv[0]); for (int i = 0; applet_names[i]; ++i) { - if (strcmp(basename(argv[0]), applet_names[i]) == 0) { - exit((*applet_main[i])(argc, argv)); + if (base == applet_names[i]) { + return (*applet_main[i])(argc, argv); } } - fprintf(stderr, "%s: applet not found\n", basename(argv[0])); - exit(1); + if (str_starts(base, "app_process")) { + return app_process_main(argc, argv); + } + + fprintf(stderr, "%s: applet not found\n", base.data()); + return 1; } int main(int argc, char *argv[]) { @@ -41,6 +46,6 @@ int main(int argc, char *argv[]) { } } - call_applet(argc, argv); + return call_applet(argc, argv); } diff --git a/native/jni/core/daemon.cpp b/native/jni/core/daemon.cpp index c802e80e9..e89efcd26 100644 --- a/native/jni/core/daemon.cpp +++ b/native/jni/core/daemon.cpp @@ -189,21 +189,22 @@ static int magisk_log(int prio, const char *fmt, va_list ap) { return vfprintf(local_log_file.get(), buf, args); } -static void android_logging() { +#define mlog(prio) [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_##prio, fmt, ap); } +static void magisk_logging() { auto in_mem_file = make_stream_fp(log_buf, log_buf_len); log_file.reset(in_mem_file.release(), [](FILE *) { free(log_buf); log_buf = nullptr; }); - log_cb.d = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_DEBUG, fmt, ap); }; - log_cb.i = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_INFO, fmt, ap); }; - log_cb.w = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_WARN, fmt, ap); }; - log_cb.e = [](auto fmt, auto ap){ return magisk_log(ANDROID_LOG_ERROR, fmt, ap); }; + log_cb.d = mlog(DEBUG); + log_cb.i = mlog(INFO); + log_cb.w = mlog(WARN); + log_cb.e = mlog(ERROR); log_cb.ex = nop_ex; } static void daemon_entry(int ppid) { - android_logging(); + magisk_logging(); int fd = xopen("/dev/null", O_WRONLY); xdup2(fd, STDOUT_FILENO); diff --git a/native/jni/include/magisk.hpp b/native/jni/include/magisk.hpp index f3f785338..7ad234dd4 100644 --- a/native/jni/include/magisk.hpp +++ b/native/jni/include/magisk.hpp @@ -36,3 +36,4 @@ int magiskhide_main(int argc, char *argv[]); int magiskpolicy_main(int argc, char *argv[]); int su_client_main(int argc, char *argv[]); int resetprop_main(int argc, char *argv[]); +int app_process_main(int argc, char *argv[]); diff --git a/native/jni/inject/entry.cpp b/native/jni/inject/entry.cpp new file mode 100644 index 000000000..34e468334 --- /dev/null +++ b/native/jni/inject/entry.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "inject.hpp" + +using namespace std; + +static void *self_handle = nullptr; +static atomic active_threads = -1; + +#define alog(prio) [](auto fmt, auto ap){ \ +return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); } +static void inject_logging() { + log_cb.d = alog(DEBUG); + log_cb.i = alog(INFO); + log_cb.w = alog(WARN); + log_cb.e = alog(ERROR); + log_cb.ex = nop_ex; +} + +__attribute__((destructor)) +static void inject_cleanup() { + if (active_threads < 0) + return; + + // Setup 1ms + timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L }; + + // Check flag in busy loop + while (active_threads) + nanosleep(&ts, nullptr); + + // Wait another 1ms to make sure all threads left our code + nanosleep(&ts, nullptr); +} + +static inline void self_unload() { + new_daemon_thread(reinterpret_cast(&dlclose), self_handle); + active_threads--; +} + +static void *unload_first_stage(void *) { + // Setup 1ms + timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L }; + + while (getenv(INJECT_ENV_2)) + nanosleep(&ts, nullptr); + + // Wait another 1ms to make sure all threads left our code + nanosleep(&ts, nullptr); + + unmap_all(INJECT_LIB_1); + active_threads--; + return nullptr; +} + +__attribute__((constructor)) +static void inject_init() { + inject_logging(); + if (char *env = getenv(INJECT_ENV_1)) { + LOGD("zygote: inject 1st stage\n"); + + if (env[0] == '1') + unsetenv("LD_PRELOAD"); + else + setenv("LD_PRELOAD", env, 1); // Restore original LD_PRELOAD + unsetenv(INJECT_ENV_1); + + // Setup second stage + setenv(INJECT_ENV_2, "1", 1); + cp_afc(INJECT_LIB_1, INJECT_LIB_2); + dlopen(INJECT_LIB_2, RTLD_LAZY); + } else if (getenv(INJECT_ENV_2)) { + LOGD("zygote: inject 2nd stage\n"); + + active_threads = 1; + + // Get our own handle + self_handle = dlopen(INJECT_LIB_2, RTLD_LAZY); + dlclose(self_handle); + + // Cleanup 1st stage maps + active_threads++; + new_daemon_thread(&unload_first_stage); + unsetenv(INJECT_ENV_2); + + // TODO: actually inject stuffs, for now we demonstrate clean self unloading + self_unload(); + } +} + +int app_process_main(int argc, char *argv[]) { + inject_logging(); + char buf[4096]; + if (realpath("/proc/self/exe", buf) == nullptr) + return 1; + + int in = xopen(buf, O_RDONLY); + int out = xopen(INJECT_LIB_1, O_CREAT | O_WRONLY | O_TRUNC, 0777); + sendfile(out, in, nullptr, INT_MAX); + close(in); + close(out); + + if (char *ld = getenv("LD_PRELOAD")) { + char env[128]; + sprintf(env, "%s:" INJECT_LIB_1, ld); + setenv("LD_PRELOAD", env, 1); + setenv(INJECT_ENV_1, ld, 1); // Backup original LD_PRELOAD + } else { + setenv("LD_PRELOAD", INJECT_LIB_1, 1); + setenv(INJECT_ENV_1, "1", 1); + } + + // Execute real app_process + xumount2(buf, MNT_DETACH); + execve(buf, argv, environ); + exit(1); +} diff --git a/native/jni/inject/inject.hpp b/native/jni/inject/inject.hpp new file mode 100644 index 000000000..96c843432 --- /dev/null +++ b/native/jni/inject/inject.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +#define INJECT_LIB_1 "/dev/tmp/magisk.1.so" +#define INJECT_LIB_2 "/dev/tmp/magisk.2.so" +#define INJECT_ENV_1 "MAGISK_INJ_1" +#define INJECT_ENV_2 "MAGISK_INJ_2" + +// Unmap all pages matching the name +void unmap_all(const char *name); + +// Get library name and base address that contains the function +uintptr_t get_function_lib(uintptr_t addr, char *lib); + +// Get library base address with name +uintptr_t get_remote_lib(int pid, const char *lib); diff --git a/native/jni/inject/ptrace.cpp b/native/jni/inject/ptrace.cpp index 4e57548c4..2d0b84bc7 100644 --- a/native/jni/inject/ptrace.cpp +++ b/native/jni/inject/ptrace.cpp @@ -24,6 +24,7 @@ #include +#include "inject.hpp" #include "ptrace.hpp" using namespace std; @@ -43,80 +44,6 @@ using namespace std; #define ARM_r0 regs[0] #endif -struct map_info { - uintptr_t start; - uintptr_t end; - int perms; - char *path; - - map_info() : start(0), end(0), perms(0), path(nullptr) {} - - enum { - EXEC = (1 << 0), - WRITE = (1 << 1), - READ = (1 << 2), - }; -}; - -// Func signature: bool(map_info&) -template -static void parse_maps(int pid, Func fn) { - char file[32]; - - // format: start-end perms offset dev inode path - sprintf(file, "/proc/%d/maps", pid); - file_readline(true, file, [=](string_view l) -> bool { - char *pos = (char *) l.data(); - map_info info; - - // Parse address hex strings - info.start = strtoul(pos, &pos, 16); - info.end = strtoul(++pos, &pos, 16); - - // Parse permissions - if (*(++pos) != '-') - info.perms |= map_info::READ; - if (*(++pos) != '-') - info.perms |= map_info::WRITE; - if (*(++pos) != '-') - info.perms |= map_info::EXEC; - pos += 3; - - // Skip everything except path - int path_off; - sscanf(pos, "%*s %*s %*s %n%*s", &path_off); - pos += path_off; - info.path = pos; - - return fn(info); - }); -} - -uintptr_t get_function_lib(uintptr_t addr, char *lib) { - uintptr_t base = 0; - parse_maps(getpid(), [=, &base](map_info &info) -> bool { - if (addr >= info.start && addr < info.end) { - strcpy(lib, info.path); - base = info.start; - return false; - } - return true; - }); - return base; -} - -uintptr_t get_remote_lib(int pid, const char *lib) { - uintptr_t base = 0; - parse_maps(pid, [=, &base](map_info &info) -> bool { - if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) { - base = info.start; - return false; - } - return true; - }); - return base; -} - bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) { for (size_t i = 0; i < len; i += sizeof(long)) { long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast(addr + i)); @@ -165,7 +92,7 @@ static void _remote_setregs(int pid, pt_regs *regs) { #endif } -static uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) { +uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) { pt_regs regs, regs_bak; // Get registers and save a backup @@ -300,7 +227,11 @@ static uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_lis uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) { char lib_name[4096]; auto local = get_function_lib(addr, lib_name); + if (local == 0) + return 0; auto remote = get_remote_lib(pid, lib_name); + if (remote == 0) + return 0; addr = addr - local + remote; va_list va; va_start(va, nargs); diff --git a/native/jni/inject/ptrace.hpp b/native/jni/inject/ptrace.hpp index fb6f5cdaf..b4951a625 100644 --- a/native/jni/inject/ptrace.hpp +++ b/native/jni/inject/ptrace.hpp @@ -2,12 +2,6 @@ #include -// Get library name and base address that contains the function -uintptr_t get_function_lib(uintptr_t addr, char *lib); - -// Get library base address with name -uintptr_t get_remote_lib(int pid, const char *lib); - // Write bytes to the remote process at addr bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len); #define remote_write(...) _remote_write(pid, __VA_ARGS__) @@ -19,6 +13,9 @@ bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len); // Call a remote function // Arguments are expected to be only integer-like or pointer types // as other more complex C ABIs are not implemented. +uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va); + +// Find remote offset and invoke function uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...); // C++ wrapper for auto argument counting and casting function pointers diff --git a/native/jni/inject/utils.cpp b/native/jni/inject/utils.cpp new file mode 100644 index 000000000..c6522c7a1 --- /dev/null +++ b/native/jni/inject/utils.cpp @@ -0,0 +1,101 @@ +#include + +#include "inject.hpp" + +using namespace std; + +namespace { + +struct map_info { + uintptr_t start; + uintptr_t end; + int perms; + char *path; + + map_info() : start(0), end(0), perms(0), path(nullptr) {} + + enum { + EXEC = (1 << 0), + WRITE = (1 << 1), + READ = (1 << 2), + }; +}; + +} // namespace + +template +static void parse_maps(int pid, Func fn) { + char file[32]; + + // format: start-end perms offset dev inode path + sprintf(file, "/proc/%d/maps", pid); + file_readline(true, file, [=](string_view l) -> bool { + char *pos = (char *) l.data(); + map_info info; + + // Parse address hex strings + info.start = strtoul(pos, &pos, 16); + info.end = strtoul(++pos, &pos, 16); + + // Parse permissions + if (*(++pos) != '-') + info.perms |= map_info::READ; + if (*(++pos) != '-') + info.perms |= map_info::WRITE; + if (*(++pos) != '-') + info.perms |= map_info::EXEC; + pos += 3; + + // Skip everything except path + int path_off; + sscanf(pos, "%*s %*s %*s %n%*s", &path_off); + pos += path_off; + info.path = pos; + + return fn(info); + }); +} + +void unmap_all(const char *name) { + vector unmaps; + parse_maps(getpid(), [=, &unmaps](map_info &info) -> bool { + if (strcmp(info.path, name) == 0) + unmaps.emplace_back(info); + return true; + }); + for (map_info &info : unmaps) { + void *addr = reinterpret_cast(info.start); + size_t size = info.end - info.start; + munmap(addr, size); + if (info.perms & map_info::READ) { + // Make sure readable pages are still readable + xmmap(addr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); + } + } +} + +uintptr_t get_function_lib(uintptr_t addr, char *lib) { + uintptr_t base = 0; + parse_maps(getpid(), [=, &base](map_info &info) -> bool { + if (addr >= info.start && addr < info.end) { + if (lib) + strcpy(lib, info.path); + base = info.start; + return false; + } + return true; + }); + return base; +} + +uintptr_t get_remote_lib(int pid, const char *lib) { + uintptr_t base = 0; + parse_maps(pid, [=, &base](map_info &info) -> bool { + if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) { + base = info.start; + return false; + } + return true; + }); + return base; +} diff --git a/native/jni/magiskhide/proc_monitor.cpp b/native/jni/magiskhide/proc_monitor.cpp index d99b4df15..e4ec37cec 100644 --- a/native/jni/magiskhide/proc_monitor.cpp +++ b/native/jni/magiskhide/proc_monitor.cpp @@ -339,7 +339,6 @@ static void new_zygote(int pid) { xptrace(PTRACE_CONT, pid); } -#define WEVENT(s) (((s) & 0xffff0000) >> 16) #define DETACH_AND_CONT { detach_pid(pid); continue; } void proc_monitor() { diff --git a/native/jni/utils/misc.cpp b/native/jni/utils/misc.cpp index 88884f6c5..87aa54693 100644 --- a/native/jni/utils/misc.cpp +++ b/native/jni/utils/misc.cpp @@ -113,12 +113,12 @@ int exec_command_sync(exec_t &exec) { return WEXITSTATUS(status); } -int new_daemon_thread(thread_entry entry, void *arg, const pthread_attr_t *attr) { +int new_daemon_thread(thread_entry entry, void *arg) { pthread_t thread; - int ret = xpthread_create(&thread, attr, entry, arg); - if (ret == 0) - pthread_detach(thread); - return ret; + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + return xpthread_create(&thread, &attr, entry, arg); } static void *proxy_routine(void *fp) { diff --git a/native/jni/utils/misc.hpp b/native/jni/utils/misc.hpp index 65e06cec7..441f87f91 100644 --- a/native/jni/utils/misc.hpp +++ b/native/jni/utils/misc.hpp @@ -59,7 +59,7 @@ static inline int parse_int(const std::string &s) { return parse_int(s.data()); static inline int parse_int(std::string_view s) { return parse_int(s.data()); } using thread_entry = void *(*)(void *); -int new_daemon_thread(thread_entry entry, void *arg = nullptr, const pthread_attr_t *attr = nullptr); +int new_daemon_thread(thread_entry entry, void *arg = nullptr); int new_daemon_thread(std::function &&entry); static inline bool str_contains(std::string_view s, std::string_view ss) { diff --git a/native/jni/utils/xwrap.hpp b/native/jni/utils/xwrap.hpp index 7cd888c83..af6ec4c19 100644 --- a/native/jni/utils/xwrap.hpp +++ b/native/jni/utils/xwrap.hpp @@ -64,3 +64,4 @@ long xptrace(int request, pid_t pid, void *addr = nullptr, void *data = nullptr) static inline long xptrace(int request, pid_t pid, void *addr, uintptr_t data) { return xptrace(request, pid, addr, reinterpret_cast(data)); } +#define WEVENT(s) (((s) & 0xffff0000) >> 16)