Refactor Zygisk loading

Co-authored-by: topjohnwu <topjohnwu@gmail.com>
This commit is contained in:
LoveSy 2022-02-06 16:27:31 +08:00 committed by GitHub
parent d2c2456fbe
commit ff7ac582f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 94 deletions

View File

@ -1,7 +1,10 @@
#pragma once #pragma once
#include <sys/syscall.h> #include <sys/syscall.h>
#include <linux/fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <cerrno>
#include <cstdio>
static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const timespec* timeout) { static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const timespec* timeout) {
union { union {
@ -11,3 +14,13 @@ static inline int sigtimedwait(const sigset_t* set, siginfo_t* info, const times
s.set = *set; s.set = *set;
return syscall(__NR_rt_sigtimedwait, &s.set64, info, timeout, sizeof(sigset64_t)); 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;
}

View File

@ -2,6 +2,7 @@
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <android/log.h> #include <android/log.h>
#include <android/dlext.h>
#include <utils.hpp> #include <utils.hpp>
#include <daemon.hpp> #include <daemon.hpp>
@ -27,15 +28,6 @@ static void zygisk_logging() {
log_cb.ex = nop_ex; 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 // Make sure /proc/self/environ is sanitized
// Filter env and reset MM_ENV_END // Filter env and reset MM_ENV_END
static void sanitize_environ() { static void sanitize_environ() {
@ -43,7 +35,7 @@ static void sanitize_environ() {
for (int i = 0; environ[i]; ++i) { for (int i = 0; environ[i]; ++i) {
// Copy all env onto the original stack // Copy all env onto the original stack
int len = strlen(environ[i]); size_t len = strlen(environ[i]);
memmove(cur, environ[i], len + 1); memmove(cur, environ[i], len + 1);
environ[i] = cur; environ[i] = cur;
cur += len + 1; cur += len + 1;
@ -52,7 +44,7 @@ static void sanitize_environ() {
prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0); prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0);
} }
__attribute__((destructor)) [[gnu::destructor]] [[maybe_unused]]
static void zygisk_cleanup_wait() { static void zygisk_cleanup_wait() {
if (self_handle) { if (self_handle) {
// Wait 10us to make sure none of our code is executing // 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() {
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);
zygisk_logging(); zygisk_logging();
ZLOGD("inject 2nd stage\n"); ZLOGD("inject 2nd stage\n");
hook_functions();
// First stage will be unloaded before the first fork char path[PATH_MAX];
first_stage_path = path; 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() { static void first_stage_entry() {
android_logging(); android_logging();
ZLOGD("inject 1st stage\n"); ZLOGD("inject 1st stage\n");
char path[PATH_MAX];
char buf[256];
char *ld = getenv("LD_PRELOAD"); char *ld = getenv("LD_PRELOAD");
char tmp[128];
strlcpy(tmp, getenv("MAGISKTMP"), sizeof(tmp));
char *path;
if (char *c = strrchr(ld, ':')) { if (char *c = strrchr(ld, ':')) {
*c = '\0'; *c = '\0';
strlcpy(path, c + 1, sizeof(path));
setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD
path = strdup(c + 1);
} else { } else {
unsetenv("LD_PRELOAD"); 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 int fd = xopen(path, O_RDONLY | O_CLOEXEC);
*num = '2'; // 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); setenv(INJECT_ENV_2, "1", 1);
void *handle = dlopen(path, RTLD_LAZY); // Force dlopen ourselves to make ourselves dlclose-able.
remap_all(path); // After this call, all global variables will be reset.
android_dlopen_ext(path, RTLD_LAZY, &info);
// 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);
} }
__attribute__((constructor)) [[gnu::constructor]] [[maybe_unused]]
static void zygisk_init() { static void zygisk_init() {
if (getenv(INJECT_ENV_2)) { if (getenv(INJECT_ENV_1)) {
// Return function pointer to first stage unsetenv(INJECT_ENV_1);
char buf[128];
snprintf(buf, sizeof(buf), "%p", &second_stage_entry);
setenv(SECOND_STAGE_PTR, buf, 1);
} else if (getenv(INJECT_ENV_1)) {
first_stage_entry(); 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; 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 && return (flags & UNMOUNT_MASK) != UNMOUNT_MASK &&
(flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP; (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); write_int(client, 0);
send_fd(client, is_64_bit ? app_process_64 : app_process_32); 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); write_string(client, MAGISKTMP);
} }

View File

@ -1,8 +1,10 @@
#include <dlfcn.h> #include <android/dlext.h>
#include <sys/mount.h> #include <sys/mount.h>
#include <xhook.h> #include <dlfcn.h>
#include <bitset> #include <bitset>
#include <xhook.h>
#include <utils.hpp> #include <utils.hpp>
#include <flags.h> #include <flags.h>
#include <daemon.hpp> #include <daemon.hpp>
@ -147,7 +149,6 @@ DCL_HOOK_FUNC(int, jniRegisterNativeMethods,
// Skip actual fork and return cached result if applicable // Skip actual fork and return cached result if applicable
// Also unload first stage zygisk if necessary // Also unload first stage zygisk if necessary
DCL_HOOK_FUNC(int, fork) { DCL_HOOK_FUNC(int, fork) {
unload_first_stage();
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); 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<int> &fds) { void HookContext::run_modules_pre(const vector<int> &fds) {
char buf[256];
// Since we directly use the pointer to elements in the vector, in order to prevent dangling // 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 // pointers, the vector has to be pre-allocated to ensure reallocation does not occur
modules.reserve(fds.size()); modules.reserve(fds.size());
for (int i = 0; i < fds.size(); ++i) { for (int i = 0; i < fds.size(); ++i) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fds[i]); android_dlextinfo info {
if (void *h = dlopen(buf, RTLD_LAZY)) { .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")) { if (void *e = dlsym(h, "zygisk_module_entry")) {
modules.emplace_back(i, h, e); modules.emplace_back(i, h, e);
} }

View File

@ -1,4 +1,5 @@
#include <sys/mount.h> #include <sys/mount.h>
#include <android/dlext.h>
#include <dlfcn.h> #include <dlfcn.h>
#include <magisk.hpp> #include <magisk.hpp>
@ -13,7 +14,7 @@ using namespace std;
// Entrypoint for app_process overlay // Entrypoint for app_process overlay
int app_process_main(int argc, char *argv[]) { int app_process_main(int argc, char *argv[]) {
android_logging(); android_logging();
char buf[256]; char buf[PATH_MAX];
bool zygote = false; bool zygote = false;
if (auto fp = open_file("/proc/self/attr/current", "r")) { if (auto fp = open_file("/proc/self/attr/current", "r")) {
@ -51,9 +52,8 @@ int app_process_main(int argc, char *argv[]) {
return 1; return 1;
close(fds[0]); close(fds[0]);
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
execve(buf, argv, environ); fexecve(app_proc_fd, argv, environ);
return 1; return 1;
} }
@ -70,26 +70,22 @@ int app_process_main(int argc, char *argv[]) {
break; break;
string tmp = read_string(socket); string tmp = read_string(socket);
#if defined(__LP64__) xreadlink("/proc/self/exe", buf, sizeof(buf));
string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process64.1.so";
#else
string lib = tmp + "/" ZYGISKBIN "/zygisk.app_process32.1.so";
#endif
if (char *ld = getenv("LD_PRELOAD")) { if (char *ld = getenv("LD_PRELOAD")) {
char env[256]; string env = ld;
sprintf(env, "%s:%s", ld, lib.data()); env += ':';
setenv("LD_PRELOAD", env, 1); env += buf;
setenv("LD_PRELOAD", env.data(), 1);
} else { } else {
setenv("LD_PRELOAD", lib.data(), 1); setenv("LD_PRELOAD", buf, 1);
} }
setenv(INJECT_ENV_1, "1", 1); setenv(INJECT_ENV_1, "1", 1);
setenv("MAGISKTMP", tmp.data(), 1); setenv(MAGISKTMP_ENV, tmp.data(), 1);
close(socket); close(socket);
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", app_proc_fd);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
execve(buf, argv, environ); fexecve(app_proc_fd, argv, environ);
} while (false); } while (false);
close(socket); close(socket);

View File

@ -12,9 +12,9 @@ struct map_info {
uintptr_t end; uintptr_t end;
uintptr_t off; uintptr_t off;
int perms; 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 } // namespace
@ -31,8 +31,8 @@ static void parse_maps(int pid, Func fn) {
char perm[5]; char perm[5];
int path_off; int path_off;
if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n%*s", if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %lu %n%*s",
&info.start, &info.end, perm, &info.off, &path_off) != 4) &info.start, &info.end, perm, &info.off, &info.inode, &path_off) != 5)
return true; return true;
// Parse permissions // Parse permissions
@ -43,22 +43,38 @@ static void parse_maps(int pid, Func fn) {
if (perm[2] != '-') if (perm[2] != '-')
info.perms |= PROT_EXEC; info.perms |= PROT_EXEC;
info.path = line + path_off; return fn(info, line + path_off);
return fn(info);
}); });
} }
static vector<map_info> find_maps(const char *name) { static vector<map_info> find_maps(const char *name) {
vector<map_info> maps; vector<map_info> maps;
parse_maps(getpid(), [=, &maps](map_info &info) -> bool { parse_maps(getpid(), [=, &maps](const map_info &info, const char *path) -> bool {
if (strcmp(info.path, name) == 0) if (strcmp(path, name) == 0)
maps.emplace_back(info); maps.emplace_back(info);
return true; return true;
}); });
return maps; return maps;
} }
std::pair<void *, size_t> find_map_range(const char *name, unsigned long inode) {
vector<map_info> 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<void *>(start), end - start);
}
void unmap_all(const char *name) { void unmap_all(const char *name) {
vector<map_info> maps = find_maps(name); vector<map_info> maps = find_maps(name);
for (map_info &info : maps) { 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 get_function_off(int pid, uintptr_t addr, char *lib) {
uintptr_t off = 0; 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 (addr >= info.start && addr < info.end) {
if (lib) if (lib)
strcpy(lib, info.path); strcpy(lib, path);
off = addr - info.start + info.off; off = addr - info.start + info.off;
return false; 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 get_function_addr(int pid, const char *lib, uintptr_t off) {
uintptr_t addr = 0; uintptr_t addr = 0;
parse_maps(pid, [=, &addr](map_info &info) -> bool { parse_maps(pid, [=, &addr](const map_info &info, const char *path) -> bool {
if (strcmp(info.path, lib) == 0 && (info.perms & PROT_EXEC)) { if (strcmp(path, lib) == 0 && (info.perms & PROT_EXEC)) {
addr = info.start - info.off + off; addr = info.start - info.off + off;
return false; return false;
} }

View File

@ -4,8 +4,10 @@
#include <jni.h> #include <jni.h>
#include <vector> #include <vector>
#define INJECT_ENV_1 "MAGISK_INJ_1" #define INJECT_ENV_1 "MAGISK_INJ_1"
#define INJECT_ENV_2 "MAGISK_INJ_2" #define INJECT_ENV_2 "MAGISK_INJ_2"
#define MAGISKFD_ENV "MAGISKFD"
#define MAGISKTMP_ENV "MAGISKTMP"
enum : int { enum : int {
ZYGISK_SETUP, ZYGISK_SETUP,
@ -25,6 +27,9 @@ enum : int {
#define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__) #define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__)
#endif #endif
// Find the memory address + size of the pages matching name + inode
std::pair<void*, size_t> find_map_range(const char *name, unsigned long inode);
// Unmap all pages matching the name // Unmap all pages matching the name
void unmap_all(const char *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; extern void *self_handle;
void unload_first_stage();
void hook_functions(); void hook_functions();
int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds); int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds);
int remote_request_unmount(); int remote_request_unmount();