From 9806b38d8e0014c39370d8b1222d38b5825a9db2 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 19 Aug 2022 04:49:19 -0700 Subject: [PATCH] Introduce zygisk loader Use a separate library for 1st stage --- build.py | 51 +++++++++++++-------- native/src/Android.mk | 8 +++- native/src/base/Android.mk | 2 +- native/src/base/include/embed.hpp | 13 ++++++ native/src/core/module.cpp | 6 --- native/src/exported_sym.txt | 3 ++ native/src/init/init.cpp | 16 +------ native/src/zygisk/entry.cpp | 75 ++++++++++--------------------- native/src/zygisk/loader.c | 27 +++++++++++ native/src/zygisk/main.cpp | 6 ++- native/src/zygisk/zygisk.hpp | 3 -- 11 files changed, 113 insertions(+), 97 deletions(-) create mode 100644 native/src/base/include/embed.hpp create mode 100644 native/src/exported_sym.txt create mode 100644 native/src/zygisk/loader.c diff --git a/build.py b/build.py index 33158d6fa..a1150b754 100755 --- a/build.py +++ b/build.py @@ -203,16 +203,6 @@ def clean_elf(): execv(args) -def binary_dump(src, var_name): - out_str = f'constexpr unsigned char {var_name}[] = {{' - for i, c in enumerate(xz(src.read())): - if i % 16 == 0: - out_str += '\n' - out_str += f'0x{c:02X},' - out_str += '\n};\n' - return out_str - - def run_ndk_build(flags): os.chdir('native') flags = 'NDK_PROJECT_PATH=. NDK_APPLICATION_MK=src/Application.mk ' + flags @@ -221,7 +211,7 @@ def run_ndk_build(flags): error('Build binary failed!') os.chdir('..') for arch in archs: - for tgt in support_targets + ['libpreload.so']: + for tgt in support_targets + ['libinit-ld.so', 'libzygisk-ld.so']: source = op.join('native', 'libs', arch, tgt) target = op.join('native', 'out', arch, tgt) mv(source, target) @@ -306,6 +296,16 @@ def write_if_diff(file_name, text): f.write(text) +def binary_dump(src, var_name, compressor=xz): + out_str = f'constexpr unsigned char {var_name}[] = {{' + for i, c in enumerate(compressor(src.read())): + if i % 16 == 0: + out_str += '\n' + out_str += f'0x{c:02X},' + out_str += '\n};\n' + return out_str + + def dump_bin_header(args): stub = op.join(config['outdir'], f'stub-{"release" if args.release else "debug"}.apk') if not op.exists(stub): @@ -315,10 +315,13 @@ def dump_bin_header(args): text = binary_dump(src, 'manager_xz') write_if_diff(op.join(native_gen_path, 'binaries.h'), text) for arch in archs: - preload = op.join('native', 'out', arch, 'libpreload.so') + preload = op.join('native', 'out', arch, 'libinit-ld.so') with open(preload, 'rb') as src: - text = binary_dump(src, 'preload_xz') - write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text) + text = binary_dump(src, 'init_ld_xz') + preload = op.join('native', 'out', arch, 'libzygisk-ld.so') + with open(preload, 'rb') as src: + text += binary_dump(src, 'zygisk_ld', compressor=lambda x: x) + write_if_diff(op.join(native_gen_path, f'{arch}_binaries.h'), text) def dump_flag_header(): @@ -363,8 +366,8 @@ def build_binary(args): flag = '' - if 'magisk' in args.target: - flag += ' B_MAGISK=1' + if 'magisk' in args.target or 'magiskinit' in args.target: + flag += ' B_PRELOAD=1' if 'magiskpolicy' in args.target: flag += ' B_POLICY=1' @@ -383,13 +386,23 @@ def build_binary(args): if flag: run_ndk_build(flag) - clean_elf() - # magiskinit and busybox has to be built separately + # magiskinit and magisk embeds preload.so + + flag = '' + + if 'magisk' in args.target: + flag += ' B_MAGISK=1' if 'magiskinit' in args.target: + flag += ' B_INIT=1' + + if flag: dump_bin_header(args) - run_ndk_build('B_INIT=1') + run_ndk_build(flag) + clean_elf() + + # BusyBox is built with different libc if 'busybox' in args.target: run_ndk_build('B_BB=1') diff --git a/native/src/Android.mk b/native/src/Android.mk index 5cabd20d4..eb8092306 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -47,6 +47,7 @@ LOCAL_SRC_FILES := \ zygisk/deny/revert.cpp LOCAL_LDLIBS := -llog +LOCAL_LDFLAGS := -Wl,--dynamic-list=src/exported_sym.txt include $(BUILD_EXECUTABLE) @@ -55,10 +56,15 @@ endif ifdef B_PRELOAD include $(CLEAR_VARS) -LOCAL_MODULE := preload +LOCAL_MODULE := init-ld LOCAL_SRC_FILES := init/preload.c include $(BUILD_SHARED_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := zygisk-ld +LOCAL_SRC_FILES := zygisk/loader.c +include $(BUILD_SHARED_LIBRARY) + endif ifdef B_INIT diff --git a/native/src/base/Android.mk b/native/src/base/Android.mk index 673ec289f..df3b7ca37 100644 --- a/native/src/base/Android.mk +++ b/native/src/base/Android.mk @@ -28,5 +28,5 @@ LOCAL_MODULE := libcompat LOCAL_SRC_FILES := compat/compat.cpp # Fix static variables' ctor/dtor when using LTO # See: https://github.com/android/ndk/issues/1461 -LOCAL_EXPORT_LDLIBS := -static -T src/lto_fix.lds +LOCAL_EXPORT_LDFLAGS := -static -T src/lto_fix.lds include $(BUILD_STATIC_LIBRARY) diff --git a/native/src/base/include/embed.hpp b/native/src/base/include/embed.hpp new file mode 100644 index 000000000..41250240e --- /dev/null +++ b/native/src/base/include/embed.hpp @@ -0,0 +1,13 @@ +#include + +#if defined(__arm__) +#include +#elif defined(__aarch64__) +#include +#elif defined(__i386__) +#include +#elif defined(__x86_64__) +#include +#else +#error Unsupported ABI +#endif diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index a24376960..7a269336e 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -567,19 +567,13 @@ int app_process_64 = -1; if (access("/system/bin/app_process" #bit, F_OK) == 0) { \ app_process_##bit = xopen("/system/bin/app_process" #bit, O_RDONLY | O_CLOEXEC); \ string zbin = zygisk_bin + "/app_process" #bit; \ - string dbin = zygisk_bin + "/magisk" #bit; \ string mbin = MAGISKTMP + "/magisk" #bit; \ int src = xopen(mbin.data(), O_RDONLY | O_CLOEXEC); \ int out = xopen(zbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \ xsendfile(out, src, nullptr, INT_MAX); \ close(out); \ - out = xopen(dbin.data(), O_CREAT | O_WRONLY | O_CLOEXEC, 0); \ - lseek(src, 0, SEEK_SET); \ - xsendfile(out, src, nullptr, INT_MAX); \ - close(out); \ close(src); \ clone_attr("/system/bin/app_process" #bit, zbin.data()); \ - clone_attr("/system/bin/app_process" #bit, dbin.data()); \ bind_mount(zbin.data(), "/system/bin/app_process" #bit); \ } diff --git a/native/src/exported_sym.txt b/native/src/exported_sym.txt new file mode 100644 index 000000000..9e50222b2 --- /dev/null +++ b/native/src/exported_sym.txt @@ -0,0 +1,3 @@ +{ + zygisk_inject_entry; +}; diff --git a/native/src/init/init.cpp b/native/src/init/init.cpp index 977e9c1da..33f59bcec 100644 --- a/native/src/init/init.cpp +++ b/native/src/init/init.cpp @@ -6,19 +6,7 @@ #include #include -#include - -#if defined(__arm__) -#include -#elif defined(__aarch64__) -#include -#elif defined(__i386__) -#include -#elif defined(__x86_64__) -#include -#else -#error Unsupported ABI -#endif +#include #include "init.hpp" @@ -78,7 +66,7 @@ int dump_manager(const char *path, mode_t mode) { } int dump_preload(const char *path, mode_t mode) { - return dump_bin(preload_xz, sizeof(preload_xz), path, mode); + return dump_bin(init_ld_xz, sizeof(init_ld_xz), path, mode); } class RecoveryInit : public BaseInit { diff --git a/native/src/zygisk/entry.cpp b/native/src/zygisk/entry.cpp index cb2304d2b..9d2667f69 100644 --- a/native/src/zygisk/entry.cpp +++ b/native/src/zygisk/entry.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include "zygisk.hpp" #include "module.hpp" @@ -51,30 +52,9 @@ static void *unload_first_stage(void *) { return nullptr; } -#if defined(__LP64__) -// Use symlink to workaround linker bug on old broken Android -// https://issuetracker.google.com/issues/36914295 -#define SECOND_STAGE_PATH "/system/bin/app_process" -#else -#define SECOND_STAGE_PATH "/system/bin/app_process32" -#endif - -static void second_stage_entry() { +extern "C" void zygisk_inject_entry(void *handle) { zygisk_logging(); - ZLOGD("inject 2nd stage\n"); - - MAGISKTMP = getenv(MAGISKTMP_ENV); - self_handle = dlopen(SECOND_STAGE_PATH, RTLD_NOLOAD); - dlclose(self_handle); - - unsetenv(MAGISKTMP_ENV); - sanitize_environ(); - hook_functions(); - new_daemon_thread(&unload_first_stage, nullptr); -} - -static void first_stage_entry() { - ZLOGD("inject 1st stage\n"); + ZLOGD("load success\n"); char *ld = getenv("LD_PRELOAD"); if (char *c = strrchr(ld, ':')) { @@ -84,31 +64,13 @@ static void first_stage_entry() { unsetenv("LD_PRELOAD"); } - // Load second stage - android_dlextinfo info { - .flags = ANDROID_DLEXT_FORCE_LOAD - }; - setenv(INJECT_ENV_2, "1", 1); - if (android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info) == nullptr) { - // Android 5.x doesn't support ANDROID_DLEXT_FORCE_LOAD - ZLOGI("ANDROID_DLEXT_FORCE_LOAD is not supported, fallback to dlopen\n"); - if (dlopen(SECOND_STAGE_PATH, RTLD_LAZY) == nullptr) { - ZLOGE("Cannot load the second stage\n"); - unsetenv(INJECT_ENV_2); - } - } -} + MAGISKTMP = getenv(MAGISKTMP_ENV); + self_handle = handle; -[[gnu::constructor]] [[maybe_unused]] -static void zygisk_init() { - android_logging(); - 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(); - } + unsetenv(MAGISKTMP_ENV); + sanitize_environ(); + hook_functions(); + new_daemon_thread(&unload_first_stage, nullptr); } // The following code runs in zygote/app process @@ -239,7 +201,7 @@ static int zygote_start_counts[] = { 0, 0 }; static void setup_files(int client, const sock_cred *cred) { LOGD("zygisk: setup files for pid=[%d]\n", cred->pid); - char buf[256]; + char buf[4096]; if (!get_exe(cred->pid, buf, sizeof(buf))) { write_int(client, 1); return; @@ -278,22 +240,31 @@ static void setup_files(int client, const sock_cred *cred) { } } - // Hijack some binary in /system/bin to host 1st stage + // Ack + write_int(client, 0); + + // Hijack some binary in /system/bin to host loader const char *hbin; string mbin; int app_fd; if (is_64_bit) { hbin = HIJACK_BIN64; - mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk64"; + mbin = MAGISKTMP + "/" ZYGISKBIN "/loader64.so"; app_fd = app_process_64; } else { hbin = HIJACK_BIN32; - mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk32"; + mbin = MAGISKTMP + "/" ZYGISKBIN "/loader32.so"; app_fd = app_process_32; } + + // Receive and bind mount loader + int ld_fd = xopen(mbin.data(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, 0755); + string ld_data = read_string(client); + xwrite(ld_fd, ld_data.data(), ld_data.size()); + close(ld_fd); + setfilecon(mbin.data(), "u:object_r:" SEPOL_FILE_TYPE ":s0"); xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr); - write_int(client, 0); send_fd(client, app_fd); write_string(client, MAGISKTMP); } diff --git a/native/src/zygisk/loader.c b/native/src/zygisk/loader.c new file mode 100644 index 000000000..fc06d6850 --- /dev/null +++ b/native/src/zygisk/loader.c @@ -0,0 +1,27 @@ +#include +#include + +#if defined(__LP64__) +// Use symlink to workaround linker bug on old broken Android +// https://issuetracker.google.com/issues/36914295 +#define SECOND_STAGE_PATH "/system/bin/app_process" +#else +#define SECOND_STAGE_PATH "/system/bin/app_process32" +#endif + +__attribute__((constructor)) +static void zygisk_loader() { + android_dlextinfo info = { + .flags = ANDROID_DLEXT_FORCE_LOAD + }; + // Android 5.x doesn't support ANDROID_DLEXT_FORCE_LOAD + void *handle = + android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info) ?: + dlopen(SECOND_STAGE_PATH, RTLD_LAZY); + if (handle) { + void(*entry)(void*) = dlsym(handle, "zygisk_inject_entry"); + if (entry) { + entry(handle); + } + } +} diff --git a/native/src/zygisk/main.cpp b/native/src/zygisk/main.cpp index 0c309caa2..58d058039 100644 --- a/native/src/zygisk/main.cpp +++ b/native/src/zygisk/main.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "zygisk.hpp" @@ -73,6 +74,10 @@ int app_process_main(int argc, char *argv[]) { if (read_int(socket) != 0) break; + // Send over zygisk loader + write_int(socket, sizeof(zygisk_ld)); + xwrite(socket, zygisk_ld, sizeof(zygisk_ld)); + int app_proc_fd = recv_fd(socket); if (app_proc_fd < 0) break; @@ -86,7 +91,6 @@ int app_process_main(int argc, char *argv[]) { } else { setenv("LD_PRELOAD", HIJACK_BIN, 1); } - setenv(INJECT_ENV_1, "1", 1); setenv(MAGISKTMP_ENV, tmp.data(), 1); close(socket); diff --git a/native/src/zygisk/zygisk.hpp b/native/src/zygisk/zygisk.hpp index 56d7e9476..9f45b056d 100644 --- a/native/src/zygisk/zygisk.hpp +++ b/native/src/zygisk/zygisk.hpp @@ -5,8 +5,6 @@ #include #include -#define INJECT_ENV_1 "MAGISK_INJ_1" -#define INJECT_ENV_2 "MAGISK_INJ_2" #define MAGISKTMP_ENV "MAGISKTMP" #define HIJACK_BIN64 "/system/bin/appwidget" @@ -55,7 +53,6 @@ extern void *self_handle; void hook_functions(); int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector &fds); -int remote_request_unmount(); inline int zygisk_request(int req) { int fd = connect_daemon(MainRequest::ZYGISK);