diff --git a/build.py b/build.py index e34ec66676eb..55af084e05af 100755 --- a/build.py +++ b/build.py @@ -246,7 +246,7 @@ def run_ndk_build(flags): error("Build binary failed!") os.chdir("..") for arch in archs: - for tgt in support_targets + ["libinit-ld.so", "libzygisk-ld.so"]: + for tgt in support_targets + ["libinit-ld.so"]: source = op.join("native", "libs", arch, tgt) target = op.join("native", "out", arch, tgt) mv(source, target) @@ -342,9 +342,6 @@ def dump_bin_header(args): preload = op.join("native", "out", arch, "libinit-ld.so") with open(preload, "rb") as src: 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) @@ -395,8 +392,9 @@ def build_binary(args): flag = "" clean = False - if "magisk" in args.target or "magiskinit" in args.target: - flag += " B_PRELOAD=1" + if "magisk" in args.target: + flag += " B_MAGISK=1" + clean = True if "magiskpolicy" in args.target: flag += " B_POLICY=1" @@ -417,14 +415,10 @@ def build_binary(args): if flag: run_ndk_build(flag) - # magiskinit and magisk embeds preload.so + # magiskinit embeds preload.so flag = "" - if "magisk" in args.target: - flag += " B_MAGISK=1" - clean = True - if "magiskinit" in args.target: flag += " B_INIT=1" diff --git a/native/src/Android.mk b/native/src/Android.mk index 1bb0c2d995b1..acbc51d3db99 100644 --- a/native/src/Android.mk +++ b/native/src/Android.mk @@ -35,9 +35,8 @@ LOCAL_SRC_FILES := \ core/su/su_daemon.cpp \ zygisk/entry.cpp \ zygisk/main.cpp \ - zygisk/utils.cpp \ zygisk/hook.cpp \ - zygisk/memory.cpp \ + zygisk/native_bridge.cpp \ zygisk/deny/cli.cpp \ zygisk/deny/utils.cpp \ zygisk/deny/revert.cpp @@ -57,12 +56,6 @@ LOCAL_SRC_FILES := init/preload.c LOCAL_STRIP_MODE := --strip-all include $(BUILD_SHARED_LIBRARY) -include $(CLEAR_VARS) -LOCAL_MODULE := zygisk-ld -LOCAL_SRC_FILES := zygisk/loader.c -LOCAL_STRIP_MODE := --strip-all -include $(BUILD_SHARED_LIBRARY) - endif ifdef B_INIT diff --git a/native/src/base/include/base.hpp b/native/src/base/include/base.hpp index 7ca70dc5f530..01d350a5821e 100644 --- a/native/src/base/include/base.hpp +++ b/native/src/base/include/base.hpp @@ -4,7 +4,6 @@ #include "../files.hpp" #include "../misc.hpp" #include "../logging.hpp" -#include "../missing.hpp" #include "../base-rs.hpp" using rust::xpipe2; diff --git a/native/src/base/missing.hpp b/native/src/base/missing.hpp deleted file mode 100644 index c33f3138c593..000000000000 --- a/native/src/base/missing.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -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]; - ssprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); - execve(buf, argv, envp); - } - return -1; -} diff --git a/native/src/base/xwrap.hpp b/native/src/base/xwrap.hpp index ce7a6dda8778..917e300dc550 100644 --- a/native/src/base/xwrap.hpp +++ b/native/src/base/xwrap.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/native/src/core/applets.cpp b/native/src/core/applets.cpp index a81712be0931..ee1578812294 100644 --- a/native/src/core/applets.cpp +++ b/native/src/core/applets.cpp @@ -31,11 +31,6 @@ int main(int argc, char *argv[]) { string_view argv0 = basename(argv[0]); - // app_process is actually not an applet - if (argv0.starts_with("app_process")) { - return app_process_main(argc, argv); - } - umask(0); if (argv[0][0] == '\0') { diff --git a/native/src/core/bootstages.cpp b/native/src/core/bootstages.cpp index 166722f32849..fe6f4b5f7d6d 100644 --- a/native/src/core/bootstages.cpp +++ b/native/src/core/bootstages.cpp @@ -418,6 +418,8 @@ static void boot_complete() { // Ensure manager exists check_pkg_refresh(); get_manager(0, nullptr, true); + + reset_zygisk(true); } void boot_stage_handler(int client, int code) { diff --git a/native/src/core/daemon.cpp b/native/src/core/daemon.cpp index 90cb7da71530..27a4c85b9857 100644 --- a/native/src/core/daemon.cpp +++ b/native/src/core/daemon.cpp @@ -141,10 +141,11 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { su_daemon_handler(client, &cred); break; case MainRequest::ZYGOTE_RESTART: - close(client); LOGI("** zygote restarted\n"); pkg_xml_ino = 0; prune_su_access(); + reset_zygisk(false); + close(client); break; case MainRequest::SQLITE_CMD: exec_sql(client); @@ -158,7 +159,6 @@ static void handle_request_async(int client, int code, const sock_cred &cred) { break; } case MainRequest::ZYGISK: - case MainRequest::ZYGISK_PASSTHROUGH: zygisk_handler(client, &cred); break; default: diff --git a/native/src/core/module.cpp b/native/src/core/module.cpp index b70044df1228..48539f740739 100644 --- a/native/src/core/module.cpp +++ b/native/src/core/module.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -15,6 +16,8 @@ using namespace std; #define VLOGD(tag, from, to) LOGD("%-8s: %s <- %s\n", tag, to, from) +static string native_bridge = "0"; + static int bind_mount(const char *reason, const char *from, const char *to) { int ret = xmount(from, to, nullptr, MS_BIND | MS_REC, nullptr); if (ret == 0) @@ -22,9 +25,6 @@ static int bind_mount(const char *reason, const char *from, const char *to) { return ret; } -string node_entry::module_mnt; -string node_entry::mirror_dir; - /************************* * Node Tree Construction *************************/ @@ -211,6 +211,21 @@ class magisk_node : public node_entry { } }; +class zygisk_node : public node_entry { +public: + explicit zygisk_node(const char *name, bool is64bit) : node_entry(name, DT_REG, this), + is64bit(is64bit) {} + + void mount() override { + const string src = get_magisk_tmp() + "/magisk"s + (is64bit ? "64" : "32"); + create_and_mount("zygisk", src); + xmount(nullptr, node_path().data(), nullptr, MS_REMOUNT | MS_BIND | MS_RDONLY, nullptr); + } + +private: + bool is64bit; +}; + static void inject_magisk_bins(root_node *system) { auto bin = system->get_child("bin"); if (!bin) { @@ -228,24 +243,28 @@ static void inject_magisk_bins(root_node *system) { delete bin->extract("supolicy"); } -vector *module_list; -int app_process_32 = -1; -int app_process_64 = -1; - -#define mount_zygisk(bit) \ -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 mbin = get_magisk_tmp() + "/magisk"s #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); \ - close(src); \ - clone_attr("/system/bin/app_process" #bit, zbin.data()); \ - bind_mount("zygisk", zbin.data(), "/system/bin/app_process" #bit); \ +static void inject_zygisk_libs(root_node *system) { + if (access("/system/bin/linker", F_OK) == 0) { + auto lib = system->get_child("lib"); + if (!lib) { + lib = new inter_node("lib"); + system->insert(lib); + } + lib->insert(new zygisk_node(native_bridge.data(), false)); + } + + if (access("/system/bin/linker64", F_OK) == 0) { + auto lib64 = system->get_child("lib64"); + if (!lib64) { + lib64 = new inter_node("lib64"); + system->insert(lib64); + } + lib64->insert(new zygisk_node(native_bridge.data(), true)); + } } +vector *module_list; + void load_modules() { node_entry::mirror_dir = get_magisk_tmp() + "/"s MIRRDIR; node_entry::module_mnt = get_magisk_tmp() + "/"s MODULEMNT "/"; @@ -289,6 +308,22 @@ void load_modules() { inject_magisk_bins(system); } + if (zygisk_enabled) { + string native_bridge_orig = get_prop(NBPROP); + if (native_bridge_orig.empty()) { + native_bridge_orig = "0"; + } + native_bridge = native_bridge_orig != "0" ? ZYGISKLDR + native_bridge_orig : ZYGISKLDR; + set_prop(NBPROP, native_bridge.data(), true); + // Weather Huawei's Maple compiler is enabled. + // If so, system server will be created by a special Zygote which ignores the native bridge + // and make system server out of our control. Avoid it by disabling. + if (get_prop("ro.maple.enable") == "1") { + set_prop("ro.maple.enable", "0", true); + } + inject_zygisk_libs(system); + } + if (!system->is_empty()) { // Handle special read-only partitions for (const char *part : { "/vendor", "/product", "/system_ext" }) { @@ -304,14 +339,6 @@ void load_modules() { root->mount(); } - // Mount on top of modules to enable zygisk - if (zygisk_enabled) { - string zygisk_bin = get_magisk_tmp() + "/"s ZYGISKBIN; - mkdir(zygisk_bin.data(), 0); - mount_zygisk(32) - mount_zygisk(64) - } - ssprintf(buf, sizeof(buf), "%s/" WORKERDIR, get_magisk_tmp()); xmount(nullptr, buf, nullptr, MS_REMOUNT | MS_RDONLY, nullptr); } @@ -491,3 +518,23 @@ void exec_module_scripts(const char *stage) { [](const module_info &info) -> string_view { return info.name; }); exec_module_scripts(stage, module_names); } + +void reset_zygisk(bool restore) { + if (!zygisk_enabled) return; + static atomic_uint zygote_start_count{1}; + if (restore) { + zygote_start_count = 1; + } else if (zygote_start_count.fetch_add(1) > 3) { + LOGW("zygote crashes too many times, rolling-back\n"); + restore = true; + } + if (restore) { + string native_bridge_orig = "0"; + if (native_bridge.length() > strlen(ZYGISKLDR)) { + native_bridge_orig = native_bridge.substr(strlen(ZYGISKLDR)); + } + set_prop(NBPROP, native_bridge_orig.data(), true); + } else { + set_prop(NBPROP, native_bridge.data(), true); + } +} diff --git a/native/src/core/node.hpp b/native/src/core/node.hpp index aba9a44c2712..a75d290ed20e 100644 --- a/native/src/core/node.hpp +++ b/native/src/core/node.hpp @@ -49,8 +49,8 @@ class node_entry { virtual void mount() = 0; - static string module_mnt; - static string mirror_dir; + inline static string module_mnt; + inline static string mirror_dir; protected: template diff --git a/native/src/core/su/connect.cpp b/native/src/core/su/connect.cpp index 94efcc26a46a..2db72abb0d6f 100644 --- a/native/src/core/su/connect.cpp +++ b/native/src/core/su/connect.cpp @@ -10,11 +10,11 @@ using namespace std; #define CALL_PROVIDER \ -exe, "/system/bin", "com.android.commands.content.Content", \ +"/system/bin/app_process", "/system/bin", "com.android.commands.content.Content", \ "call", "--uri", target, "--user", user, "--method", action #define START_ACTIVITY \ -exe, "/system/bin", "com.android.commands.am.Am", \ +"/system/bin/app_process", "/system/bin", "com.android.commands.am.Am", \ "start", "-p", target, "--user", user, "-a", "android.intent.action.VIEW", \ "-f", "0x58800020", "--es", "action", action @@ -130,21 +130,10 @@ static bool check_no_error(int fd) { static void exec_cmd(const char *action, vector &data, const shared_ptr &info, bool provider = true) { - char exe[128]; char target[128]; char user[4]; ssprintf(user, sizeof(user), "%d", to_user_id(info->eval_uid)); - if (zygisk_enabled) { -#if defined(__LP64__) - ssprintf(exe, sizeof(exe), "/proc/self/fd/%d", app_process_64); -#else - ssprintf(exe, sizeof(exe), "/proc/self/fd/%d", app_process_32); -#endif - } else { - strscpy(exe, "/system/bin/app_process", sizeof(exe)); - } - // First try content provider call method if (provider) { ssprintf(target, sizeof(target), "content://%s.provider", info->mgr_pkg.data()); diff --git a/native/src/exported_sym.txt b/native/src/exported_sym.txt index 80dc508b4f17..802ae57bc7c6 100644 --- a/native/src/exported_sym.txt +++ b/native/src/exported_sym.txt @@ -1,4 +1,4 @@ { zygisk_inject_entry; - unload_first_stage; + NativeBridgeItf; }; diff --git a/native/src/include/daemon.hpp b/native/src/include/daemon.hpp index 9f8a4983c187..e9d063db7d6a 100644 --- a/native/src/include/daemon.hpp +++ b/native/src/include/daemon.hpp @@ -35,7 +35,6 @@ enum : int { SQLITE_CMD, REMOVE_MODULES, ZYGISK, - ZYGISK_PASSTHROUGH, _STAGE_BARRIER_, @@ -67,9 +66,8 @@ struct module_info { }; extern bool zygisk_enabled; -extern int app_process_32; -extern int app_process_64; extern std::vector *module_list; +void reset_zygisk(bool restore); extern "C" const char *get_magisk_tmp(); int connect_daemon(int req, bool create = false); diff --git a/native/src/include/magisk.hpp b/native/src/include/magisk.hpp index fd4230c2ebc2..1142a29cc924 100644 --- a/native/src/include/magisk.hpp +++ b/native/src/include/magisk.hpp @@ -3,6 +3,8 @@ #include #define JAVA_PACKAGE_NAME "com.topjohnwu.magisk" +#define ZYGISKLDR "libzygisk.so" +#define NBPROP "ro.dalvik.vm.native.bridge" #define SECURE_DIR "/data/adb" #define MODULEROOT SECURE_DIR "/modules" #define MODULEUPGRADE SECURE_DIR "/modules_update" @@ -49,5 +51,4 @@ extern int SDK_INT; int magisk_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[]); int zygisk_main(int argc, char *argv[]); diff --git a/native/src/init/rootdir.cpp b/native/src/init/rootdir.cpp index 79b22b419f23..fe27cbb16fcd 100644 --- a/native/src/init/rootdir.cpp +++ b/native/src/init/rootdir.cpp @@ -11,50 +11,86 @@ using namespace std; -static vector rc_list; +namespace { +vector rc_list; +string magic_mount_list; -static void patch_init_rc(const char *src, const char *dest, const char *tmp_dir) { - FILE *rc = xfopen(dest, "we"); - if (!rc) { - PLOGE("%s: open %s failed", __PRETTY_FUNCTION__, src); - return; - } - file_readline(src, [=](string_view line) -> bool { - // Do not start vaultkeeper - if (str_contains(line, "start vaultkeeper")) { - LOGD("Remove vaultkeeper\n"); - return true; - } - // Do not run flash_recovery - if (str_starts(line, "service flash_recovery")) { - LOGD("Remove flash_recovery\n"); - fprintf(rc, "service flash_recovery /system/bin/xxxxx\n"); - return true; +void magic_mount(const string &sdir, const string &ddir = "") { + auto dir = xopen_dir(sdir.data()); + if (!dir) return; + for (dirent *entry; (entry = xreaddir(dir.get()));) { + string src = sdir + "/" + entry->d_name; + string dest = ddir + "/" + entry->d_name; + if (access(dest.data(), F_OK) == 0) { + if (entry->d_type == DT_DIR) { + // Recursive + magic_mount(src, dest); + } else { + LOGD("Mount [%s] -> [%s]\n", src.data(), dest.data()); + xmount(src.data(), dest.data(), nullptr, MS_BIND, nullptr); + magic_mount_list += dest; + magic_mount_list += '\n'; + } } - // Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data - if (str_starts(line, "on property:persist.sys.zygote.early=")) { - LOGD("Invalidate persist.sys.zygote.early\n"); - fprintf(rc, "on property:persist.sys.zygote.early.xxxxx=true\n"); + } +} + +void patch_init_rc(const char *path, const char *tmp_dir, bool writable) { + auto src_dir = xopen_dir(path); + if (!src_dir) return; + int src_fd = dirfd(src_dir.get()); + // first of all, patch init.rc + auto dest_dir = writable ? [&] { + return xopen_dir(path); + }() : [&] { + char buf[PATH_MAX] = {}; + ssprintf(buf, sizeof(buf), "%s%s", ROOTOVL, path); + xmkdirs(buf, 0755); + return xopen_dir(buf); + }(); + int dest_fd = dirfd(dest_dir.get()); + { + auto src = xopen_file(xopenat(src_fd, "init.rc", O_RDONLY | O_CLOEXEC, 0), "re"); + if (!src) return; + if (writable) unlinkat(src_fd, "init.rc", 0); + auto dest = xopen_file(xopenat(dest_fd, "init.rc", O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we"); + LOGD("Patching init.rc in %s\n", path); + file_readline(false, src.get(), [&dest](string_view line) -> bool { + // Do not start vaultkeeper + if (str_contains(line, "start vaultkeeper")) { + LOGD("Remove vaultkeeper\n"); + return true; + } + // Do not run flash_recovery + if (line.starts_with("service flash_recovery")) { + LOGD("Remove flash_recovery\n"); + fprintf(dest.get(), "service flash_recovery /system/bin/true\n"); + return true; + } + // Samsung's persist.sys.zygote.early will cause Zygote to start before post-fs-data + if (line.starts_with("on property:persist.sys.zygote.early=")) { + LOGD("Invalidate persist.sys.zygote.early\n"); + fprintf(dest.get(), "on property:persist.sys.zygote.early.xxxxx=true\n"); + return true; + } + // Else just write the line + fprintf(dest.get(), "%s", line.data()); return true; - } - // Else just write the line - fprintf(rc, "%s", line.data()); - return true; - }); + }); - fprintf(rc, "\n"); + fprintf(dest.get(), "\n"); - // Inject custom rc scripts - for (auto &script : rc_list) { - // Replace template arguments of rc scripts with dynamic paths - replace_all(script, "${MAGISKTMP}", tmp_dir); - fprintf(rc, "\n%s\n", script.data()); - } - rc_list.clear(); + // Inject custom rc scripts + for (auto &script : rc_list) { + // Replace template arguments of rc scripts with dynamic paths + replace_all(script, "${MAGISKTMP}", tmp_dir); + fprintf(dest.get(), "\n%s\n", script.data()); + } + rc_list.clear(); - // Inject Magisk rc scripts - LOGD("Inject magisk rc\n"); - fprintf(rc, R"EOF( + // Inject Magisk rc scripts + LOGD("Inject magisk rc\n"); + fprintf(dest.get(), R"EOF( on post-fs-data start logd exec %2$s 0 0 -- %1$s/magisk --post-fs-data @@ -68,18 +104,38 @@ on nonencrypted on property:sys.boot_completed=1 exec %2$s 0 0 -- %1$s/magisk --boot-complete -on property:init.svc.zygote=restarting - exec %2$s 0 0 -- %1$s/magisk --zygote-restart - on property:init.svc.zygote=stopped exec %2$s 0 0 -- %1$s/magisk --zygote-restart )EOF", tmp_dir, MAGISK_PROC_CON); - fclose(rc); - clone_attr(src, dest); + fclone_attr(fileno(src.get()), fileno(dest.get())); + } + + // now, we can start patching init.zygisk*.rc + for (dirent *entry; (entry = readdir(src_dir.get()));) { + auto name = std::string_view(entry->d_name); + if (!name.starts_with("init.zygote") || !name.ends_with(".rc")) continue; + auto src = xopen_file(xopenat(src_fd, name.data(), O_RDONLY | O_CLOEXEC, 0), "re"); + if (!src) continue; + if (writable) unlinkat(src_fd, name.data(), 0); + auto dest = xopen_file(xopenat(dest_fd, name.data(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0), "we"); + if (!dest) continue; + LOGD("Patching %s in %s\n", name.data(), path); + file_readline(false, src.get(), [&dest, &tmp_dir](string_view line) -> bool { + if (line.starts_with("service zygote ")) { + LOGD("Inject zygote restart\n"); + fprintf(dest.get(), "%s", line.data()); + fprintf(dest.get(), " onrestart exec %2$s 0 0 -- %1$s/magisk --zygote-restart\n", tmp_dir, MAGISK_PROC_CON); + return true; + } + fprintf(dest.get(), "%s", line.data()); + return true; + }); + fclone_attr(fileno(src.get()), fileno(dest.get())); + } } -static void load_overlay_rc(const char *overlay) { +void load_overlay_rc(const char *overlay) { auto dir = open_dir(overlay); if (!dir) return; @@ -107,7 +163,7 @@ static void load_overlay_rc(const char *overlay) { } } -static void recreate_sbin(const char *mirror, bool use_bind_mount) { +void recreate_sbin(const char *mirror, bool use_bind_mount) { auto dp = xopen_dir(mirror); int src = dirfd(dp.get()); char buf[4096]; @@ -136,29 +192,7 @@ static void recreate_sbin(const char *mirror, bool use_bind_mount) { } } -static string magic_mount_list; - -static void magic_mount(const string &sdir, const string &ddir = "") { - auto dir = xopen_dir(sdir.data()); - if (!dir) return; - for (dirent *entry; (entry = xreaddir(dir.get()));) { - string src = sdir + "/" + entry->d_name; - string dest = ddir + "/" + entry->d_name; - if (access(dest.data(), F_OK) == 0) { - if (entry->d_type == DT_DIR) { - // Recursive - magic_mount(src, dest); - } else { - LOGD("Mount [%s] -> [%s]\n", src.data(), dest.data()); - xmount(src.data(), dest.data(), nullptr, MS_BIND, nullptr); - magic_mount_list += dest; - magic_mount_list += '\n'; - } - } - } -} - -static void extract_files(bool sbin) { +void extract_files(bool sbin) { const char *m32 = sbin ? "/sbin/magisk32.xz" : "magisk32.xz"; const char *m64 = sbin ? "/sbin/magisk64.xz" : "magisk64.xz"; const char *stub_xz = sbin ? "/sbin/stub.xz" : "stub.xz"; @@ -191,6 +225,7 @@ static void extract_files(bool sbin) { close(fd); } } +} // namespace void MagiskInit::parse_config_file() { parse_prop_file("/data/.backup/.magisk", [&](auto key, auto value) -> bool { @@ -202,8 +237,8 @@ void MagiskInit::parse_config_file() { }); } -#define ROOTMIR MIRRDIR "/system_root" -#define NEW_INITRC "/system/etc/init/hw/init.rc" +#define ROOTMIR MIRRDIR "/system_root" +#define NEW_INITRC_DIR "/system/etc/init/hw" void MagiskInit::patch_ro_root() { mount_list.emplace_back("/data"); @@ -259,12 +294,11 @@ void MagiskInit::patch_ro_root() { } // Patch init.rc - if (access(NEW_INITRC, F_OK) == 0) { + if (access(NEW_INITRC_DIR, F_OK) == 0) { // Android 11's new init.rc - xmkdirs(dirname(ROOTOVL NEW_INITRC), 0755); - patch_init_rc(NEW_INITRC, ROOTOVL NEW_INITRC, tmp_dir.data()); + patch_init_rc(NEW_INITRC_DIR, tmp_dir.data(), false); } else { - patch_init_rc("/init.rc", ROOTOVL "/init.rc", tmp_dir.data()); + patch_init_rc("/", tmp_dir.data(), false); } // Extract magisk @@ -312,8 +346,7 @@ void MagiskInit::patch_rw_root() { rm_rf("/.backup"); // Patch init.rc - patch_init_rc("/init.rc", "/init.p.rc", "/sbin"); - rename("/init.p.rc", "/init.rc"); + patch_init_rc("/", "/sbin", true); bool treble; { diff --git a/native/src/zygisk/entry.cpp b/native/src/zygisk/entry.cpp index 257211673403..b143c4ff3dc8 100644 --- a/native/src/zygisk/entry.cpp +++ b/native/src/zygisk/entry.cpp @@ -6,9 +6,7 @@ #include #include -#include #include -#include #include "zygisk.hpp" #include "module.hpp" @@ -18,43 +16,11 @@ using namespace std; void *self_handle = nullptr; -// Make sure /proc/self/environ is sanitized -// Filter env and reset MM_ENV_END -static void sanitize_environ() { - char *cur = environ[0]; - - for (int i = 0; environ[i]; ++i) { - // Copy all env onto the original stack - size_t len = strlen(environ[i]); - memmove(cur, environ[i], len + 1); - environ[i] = cur; - cur += len + 1; - } - - prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0); -} - -extern "C" void unload_first_stage() { - ZLOGD("unloading first stage\n"); - unmap_all(HIJACK_BIN); - xumount2(HIJACK_BIN, MNT_DETACH); -} - -extern "C" void zygisk_inject_entry(void *handle) { - zygisk_logging(); - ZLOGD("load success\n"); - - char *ld = getenv("LD_PRELOAD"); - if (char *c = strrchr(ld, ':')) { - *c = '\0'; - setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD - } else { - unsetenv("LD_PRELOAD"); - } - +extern "C" [[maybe_unused]] void zygisk_inject_entry(void *handle) { self_handle = handle; - sanitize_environ(); + zygisk_logging(); hook_functions(); + ZLOGD("load success\n"); } // The following code runs in zygote/app process @@ -146,88 +112,6 @@ static void connect_companion(int client, bool is_64_bit) { send_fd(zygiskd_socket, client); } -static timespec last_zygote_start; -static int zygote_start_counts[] = { 0, 0 }; -#define zygote_start_count zygote_start_counts[is_64_bit] -#define zygote_started (zygote_start_counts[0] + zygote_start_counts[1]) -#define zygote_start_reset(val) { zygote_start_counts[0] = val; zygote_start_counts[1] = val; } - -static void setup_files(int client, const sock_cred *cred) { - LOGD("zygisk: setup files for pid=[%d]\n", cred->pid); - - char buf[4096]; - if (!get_exe(cred->pid, buf, sizeof(buf))) { - write_int(client, 1); - return; - } - - // Hijack some binary in /system/bin to host loader - const char *hbin; - string mbin; - int app_fd; - bool is_64_bit = str_ends(buf, "64"); - if (is_64_bit) { - hbin = HIJACK_BIN64; - mbin = get_magisk_tmp() + "/"s ZYGISKBIN "/loader64.so"; - app_fd = app_process_64; - } else { - hbin = HIJACK_BIN32; - mbin = get_magisk_tmp() + "/"s ZYGISKBIN "/loader32.so"; - app_fd = app_process_32; - } - - if (!zygote_started) { - // First zygote launch, record time - clock_gettime(CLOCK_MONOTONIC, &last_zygote_start); - } - - if (zygote_start_count) { - // This zygote ABI had started before, kill existing zygiskd - close(zygiskd_sockets[0]); - close(zygiskd_sockets[1]); - zygiskd_sockets[0] = -1; - zygiskd_sockets[1] = -1; - xumount2(hbin, MNT_DETACH); - } - ++zygote_start_count; - - if (zygote_start_count >= 5) { - // Bootloop prevention - timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - if (ts.tv_sec - last_zygote_start.tv_sec > 60) { - // This is very likely manual soft reboot - memcpy(&last_zygote_start, &ts, sizeof(ts)); - zygote_start_reset(1); - } else { - // If any zygote relaunched more than 5 times within a minute, - // don't do any setups further to prevent bootloop. - zygote_start_reset(999); - write_int(client, 1); - return; - } - } - - // Ack - write_int(client, 0); - - // 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(), MAGISK_FILE_CON); - xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr); - - send_fd(client, app_fd); -} - -static void magiskd_passthrough(int client) { - bool is_64_bit = read_int(client); - write_int(client, 0); - send_fd(client, is_64_bit ? app_process_64 : app_process_32); -} - extern bool uid_granted_root(int uid); static void get_process_info(int client, const sock_cred *cred) { int uid = read_int(client); @@ -301,12 +185,6 @@ void zygisk_handler(int client, const sock_cred *cred) { int code = read_int(client); char buf[256]; switch (code) { - case ZygiskRequest::SETUP: - setup_files(client, cred); - break; - case ZygiskRequest::PASSTHROUGH: - magiskd_passthrough(client); - break; case ZygiskRequest::GET_INFO: get_process_info(client, cred); break; diff --git a/native/src/zygisk/gen_jni_hooks.py b/native/src/zygisk/gen_jni_hooks.py index 7a759b4d5bcb..b4b792da1a73 100755 --- a/native/src/zygisk/gen_jni_hooks.py +++ b/native/src/zygisk/gen_jni_hooks.py @@ -213,16 +213,15 @@ def gen_jni_def(clz, methods): decl += ind(1) + f'return {m.ret.value};' decl += ind(0) + '}' - decl += ind(0) + f'const JNINativeMethod {m.base_name()}_methods[] = {{' + decl += ind(0) + f'std::array {m.base_name()}_methods {{' for m in methods: - decl += ind(1) + '{' + decl += ind(1) + 'JNINativeMethod {' decl += ind(2) + f'"{m.base_name()}",' decl += ind(2) + f'"{m.jni()}",' decl += ind(2) + f'(void *) &{m.name}' decl += ind(1) + '},' decl += ind(0) + '};' decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl - decl += ind(0) + f'constexpr int {m.base_name()}_methods_num = std::size({m.base_name()}_methods);' decl += ind(0) hook_map[clz].append(m.base_name()) @@ -231,39 +230,22 @@ def gen_jni_def(clz, methods): def gen_jni_hook(): decl = '' - decl += ind(0) + 'static JNINativeMethod *hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {' - decl += ind(1) + 'JNINativeMethod *newMethods = nullptr;' - decl += ind(1) + 'int clz_id = -1;' - decl += ind(1) + 'int hook_cnt = 0;' - decl += ind(1) + 'do {' - - for index, (clz, methods) in enumerate(hook_map.items()): - decl += ind(2) + f'if (className == "{clz}"sv) {{' - decl += ind(3) + f'clz_id = {index};' - decl += ind(3) + f'hook_cnt = {len(methods)};' - decl += ind(3) + 'break;' - decl += ind(2) + '}' - - decl += ind(1) + '} while (false);' - - decl += ind(1) + 'if (hook_cnt) {' - decl += ind(2) + 'newMethods = new JNINativeMethod[numMethods];' - decl += ind(2) + 'memcpy(newMethods, methods, sizeof(JNINativeMethod) * numMethods);' - decl += ind(1) + '}' - - decl += ind(1) + 'auto &class_map = (*jni_method_map)[className];' - decl += ind(1) + 'for (int i = 0; i < numMethods; ++i) {' - - for index, methods in enumerate(hook_map.values()): - decl += ind(2) + f'if (hook_cnt && clz_id == {index}) {{' + decl += ind(0) + 'static void do_hook_zygote(JNIEnv *env) {' + decl += ind(1) + 'vector hooks;' + decl += ind(1) + 'const char *clz;' + for clz, methods in hook_map.items(): + decl += ind(1) + f'clz = "{clz}";' for m in methods: - decl += ind(3) + f'HOOK_JNI({m})' - decl += ind(2) + '}' + decl += ind(1) + f'hookJniNativeMethods(env, clz, {m}_methods.data(), {m}_methods.size());' + decl += ind(1) + f'for (auto &method : {m}_methods) {{' + decl += ind(2) + f'if (method.fnPtr) {{' + decl += ind(3) + f'{m}_orig = method.fnPtr;' + decl += ind(3) + f'hooks.emplace_back(method);' + decl += ind(3) + f'break;' + decl += ind(2) + f'}}' + decl += ind(1) + f'}}' + decl += ind(1) + f'jni_hook_list->emplace(clz, std::move(hooks));' - decl += ind(2) + 'class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;' - decl += ind(1) + '}' - - decl += ind(1) + 'return newMethods;' decl += ind(0) + '}' return decl diff --git a/native/src/zygisk/hook.cpp b/native/src/zygisk/hook.cpp index 4568888a9ce7..2612795235a7 100644 --- a/native/src/zygisk/hook.cpp +++ b/native/src/zygisk/hook.cpp @@ -2,24 +2,21 @@ #include #include #include +#include #include #include #include #include -#include -#include +#include #include "zygisk.hpp" -#include "memory.hpp" #include "module.hpp" +#include "native_bridge.h" #include "deny/deny.hpp" using namespace std; -using jni_hook::hash_map; -using jni_hook::tree_map; -using xstring = jni_hook::string; // Extreme verbose logging #define ZLOGV(...) ZLOGD(__VA_ARGS__) @@ -27,8 +24,8 @@ using xstring = jni_hook::string; static void hook_unloader(); static void unhook_functions(); -static void hook_jni_env(); -static void restore_jni_env(JNIEnv *env); +static void hook_zygote(); +static void reload_native_bridge(const string &nb); namespace { @@ -48,14 +45,12 @@ enum { // Global variables vector> *plt_hook_list; map, StringCmp> *jni_hook_list; -hash_map>> *jni_method_map; +const android::NativeBridgeRuntimeCallbacks* runtime_callbacks; bool should_unmap_zygisk = false; // Current context HookContext *g_ctx; bitset *g_allowed_fds = nullptr; -const JNINativeInterface *old_functions = nullptr; -JNINativeInterface *new_functions = nullptr; #define DCL_PRE_POST(name) \ void name##_pre(); \ @@ -95,14 +90,7 @@ struct HookContext { HookContext(JNIEnv *env, void *args) : env(env), args{args}, process(nullptr), pid(-1), info_flags(0), - hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { - static bool restored_env = false; - if (!restored_env) { - restore_jni_env(env); - restored_env = true; - } - g_ctx = this; - } + hook_info_lock(PTHREAD_MUTEX_INITIALIZER) { g_ctx = this; } ~HookContext(); @@ -136,12 +124,6 @@ struct HookContext { ret (*old_##func)(__VA_ARGS__); \ ret new_##func(__VA_ARGS__) -DCL_HOOK_FUNC(void, androidSetCreateThreadFunc, void *func) { - ZLOGD("androidSetCreateThreadFunc\n"); - hook_jni_env(); - old_androidSetCreateThreadFunc(func); -} - // Skip actual fork and return cached result if applicable DCL_HOOK_FUNC(int, fork) { return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); @@ -157,9 +139,6 @@ DCL_HOOK_FUNC(int, unshare, int flags) { (g_ctx->info_flags & PROCESS_IS_SYS_UI) == 0) { if (g_ctx->flags[DO_REVERT_UNMOUNT]) { revert_unmount(); - } else { - umount2("/system/bin/app_process64", MNT_DETACH); - umount2("/system/bin/app_process32", MNT_DETACH); } // Restore errno back to 0 errno = 0; @@ -209,43 +188,64 @@ DCL_HOOK_FUNC(int, pthread_attr_destroy, void *target) { return res; } +// it should be safe to assume all dlclose's in libnativebridge are for zygisk_loader +DCL_HOOK_FUNC(int, dlclose, void *handle) { + static bool kDone = false; + if (!kDone) { + ZLOGV("dlclose zygisk_loader\n"); + kDone = true; + reload_native_bridge(get_prop(NBPROP)); + } + [[clang::musttail]] return old_dlclose(handle); +} + +DCL_HOOK_FUNC(char *, strdup, const char *s) { + if (s == "com.android.internal.os.ZygoteInit"sv) { + ZLOGD("strdup %s\n", s); + hook_zygote(); + } + return old_strdup(s); +} + #undef DCL_HOOK_FUNC // ----------------------------------------------------------------- void hookJniNativeMethods(JNIEnv *env, const char *clz, JNINativeMethod *methods, int numMethods) { - auto class_map = jni_method_map->find(clz); - if (class_map == jni_method_map->end()) { - for (int i = 0; i < numMethods; ++i) { + jclass clazz = nullptr; + if (!runtime_callbacks || !env || !clz || !(clazz = env->FindClass(clz))) { + for (auto i = 0; i < numMethods; ++i) { methods[i].fnPtr = nullptr; } return; } - - vector hooks; - for (int i = 0; i < numMethods; ++i) { - auto method_map = class_map->second.find(methods[i].name); - if (method_map != class_map->second.end()) { - auto it = method_map->second.find(methods[i].signature); - if (it != method_map->second.end()) { - // Copy the JNINativeMethod - hooks.push_back(methods[i]); - // Save the original function pointer - methods[i].fnPtr = it->second; - // Do not allow double hook, remove method from map - method_map->second.erase(it); - continue; - } + auto total = runtime_callbacks->getNativeMethodCount(env, clazz); + auto old_methods = make_unique(total); + runtime_callbacks->getNativeMethods(env, clazz, old_methods.get(), total); + std::map new_method_map; + for (auto i = 0; i < numMethods; ++i) { + auto res = env->RegisterNatives(clazz, methods + i, 1); + // It's normal that the method is not found + if (res == JNI_ERR || env->ExceptionCheck()) { + auto exception = env->ExceptionOccurred(); + if (exception) env->DeleteLocalRef(exception); + env->ExceptionClear(); + methods[i].fnPtr = nullptr; + } else { + new_method_map[methods[i].fnPtr] = methods + i; + } + } + auto new_methods = make_unique(total); + runtime_callbacks->getNativeMethods(env, clazz, new_methods.get(), total); + for (auto i = 0; i < total; ++i) { + auto old_method = old_methods.get()[i]; + auto new_method = new_methods.get()[i]; + if (auto it = new_method_map.find(new_method.fnPtr); + it != new_method_map.end()) { + ZLOGD("Hooked %s.%s%s %p -> %p\n", clz, it->second->name, it->second->signature, old_method.fnPtr, it->second->fnPtr); + it->second->fnPtr = old_method.fnPtr; } - // No matching method found, set fnPtr to null - methods[i].fnPtr = nullptr; } - - if (hooks.empty()) - return; - - old_functions->RegisterNatives( - env, env->FindClass(clz), hooks.data(), static_cast(hooks.size())); } ZygiskModule::ZygiskModule(int id, void *handle, void *entry) @@ -633,12 +633,6 @@ HookContext::~HookContext() { delete jni_hook_list; jni_hook_list = nullptr; - // Do NOT directly call delete - operator delete(jni_method_map); - // Directly unmap the whole memory block - jni_hook::memory_block::release(); - jni_method_map = nullptr; - // Strip out all API function pointers for (auto &m : modules) { m.clearApi(); @@ -714,6 +708,102 @@ void HookContext::nativeForkAndSpecialize_post() { // ----------------------------------------------------------------- +inline void *unwind_get_region_start(_Unwind_Context *ctx) { + auto fp = _Unwind_GetRegionStart(ctx); +#if defined(__arm__) + // On arm32, we need to check if the pc is in thumb mode, + // if so, we need to set the lowest bit of fp to 1 + auto pc = _Unwind_GetGR(ctx, 15); // r15 is pc + if (pc & 1) { + // Thumb mode + fp |= 1; + } +#endif + return reinterpret_cast(fp); +} + +// As we use NativeBridgeRuntimeCallbacks to reload native bridge and to hook jni functions, +// we need to find it by the native bridge's unwind context. +// For abi's that use registers to pass arguments, i.e. arm32, arm64, x86_64, the registers are +// caller-saved, and they are not preserved in the unwind context. However, they will be saved +// into the callee-saved registers, so we will search the callee-saved registers for the second +// argument, which is the pointer to NativeBridgeRuntimeCallbacks. +// For x86, whose abi uses stack to pass arguments, we can directly get the pointer to +// NativeBridgeRuntimeCallbacks from the stack. +static const android::NativeBridgeRuntimeCallbacks* find_cbs(struct _Unwind_Context *ctx) { + // Find the writable memory region of libart.so, where the NativeBridgeRuntimeCallbacks is located. + auto [start, end] = []()-> tuple { + for (const auto &map : lsplt::MapInfo::Scan()) { + if (map.path.ends_with("/libart.so") && map.perms == (PROT_WRITE | PROT_READ)) { + ZLOGV("libart.so: start=%p, end=%p\n", reinterpret_cast(map.start), reinterpret_cast(map.end)); + return {map.start, map.end}; + } + } + return {0, 0}; + }(); +#if defined(__aarch64__) + // r19-r28 are callee-saved registers + for (int i = 19; i <= 28; ++i) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#elif defined(__arm__) + // r4-r10 are callee-saved registers + for (int i = 4; i <= 10; ++i) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#elif defined(__i386__) + // get ebp, which points to the bottom of the stack frame + auto ebp = static_cast(_Unwind_GetGR(ctx, 5)); + // 1 pointer size above ebp is the old ebp + // 2 pointer sizes above ebp is the return address + // 3 pointer sizes above ebp is the 2nd arg + auto val = *reinterpret_cast(ebp + 3 * sizeof(void *)); + ZLOGV("ebp + 3 * ptr_size = %p\n", reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); +#elif defined(__x86_64__) + // r12-r15 and rbx are callee-saved registers, but the compiler is likely to use them reversely + for (int i : {3, 15, 14, 13, 12}) { + auto val = static_cast(_Unwind_GetGR(ctx, i)); + ZLOGV("r%d = %p\n", i, reinterpret_cast(val)); + if (val >= start && val < end) + return reinterpret_cast(val); + } +#else +#error "Unsupported architecture" +#endif + return nullptr; +} + +static void reload_native_bridge(const string &nb) { + // Use unwind to find the address of LoadNativeBridge and NativeBridgeRuntimeCallbacks + bool (*load_native_bridge)(const char *nb_library_filename, + const android::NativeBridgeRuntimeCallbacks *runtime_cbs) = nullptr; + _Unwind_Backtrace(+[](struct _Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code { + void *fp = unwind_get_region_start(ctx); + Dl_info info{}; + dladdr(fp, &info); + ZLOGV("backtrace: %p %s\n", fp, info.dli_fname ? info.dli_fname : "???"); + if (info.dli_fname && std::string_view(info.dli_fname).ends_with("/libnativebridge.so")) { + *reinterpret_cast(arg) = fp; + runtime_callbacks = find_cbs(ctx); + ZLOGD("cbs: %p\n", runtime_callbacks); + return _URC_END_OF_STACK; + } + return _URC_NO_REASON; + }, &load_native_bridge); + auto len = strlen(ZYGISKLDR); + if (nb.size() > len) { + load_native_bridge(nb.data() + len, runtime_callbacks); + } +} + static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) { if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) { ZLOGE("Failed to register plt_hook \"%s\"\n", symbol); @@ -737,23 +827,27 @@ static void hook_commit() { void hook_functions() { default_new(plt_hook_list); default_new(jni_hook_list); - default_new(jni_method_map); ino_t android_runtime_inode = 0; dev_t android_runtime_dev = 0; + ino_t native_bridge_inode = 0; + dev_t native_bridge_dev = 0; for (auto &map : lsplt::MapInfo::Scan()) { - if (map.path.ends_with("libandroid_runtime.so")) { + if (map.path.ends_with("/libandroid_runtime.so")) { android_runtime_inode = map.inode; android_runtime_dev = map.dev; - break; + } else if (map.path.ends_with("/libnativebridge.so")) { + native_bridge_inode = map.inode; + native_bridge_dev = map.dev; } } + PLT_HOOK_REGISTER(native_bridge_dev, native_bridge_inode, dlclose); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, fork); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, unshare); - PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, androidSetCreateThreadFunc); PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, selinux_android_setcontext); + PLT_HOOK_REGISTER(android_runtime_dev, android_runtime_inode, strdup); PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close); hook_commit(); @@ -796,31 +890,10 @@ static void unhook_functions() { } } -// ----------------------------------------------------------------- - -static JNINativeMethod *hookAndSaveJNIMethods(const char *, const JNINativeMethod *, int); - -static string get_class_name(JNIEnv *env, jclass clazz) { - static auto class_getName = env->GetMethodID( - env->FindClass("java/lang/Class"), "getName", "()Ljava/lang/String;"); - auto nameRef = (jstring) env->CallObjectMethod(clazz, class_getName); - const char *name = env->GetStringUTFChars(nameRef, nullptr); - string className(name); - env->ReleaseStringUTFChars(nameRef, name); - std::replace(className.begin(), className.end(), '.', '/'); - return className; -} - -static jint env_RegisterNatives( - JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { - auto className = get_class_name(env, clazz); - ZLOGV("JNIEnv->RegisterNatives [%s]\n", className.data()); - auto newMethods = unique_ptr( - hookAndSaveJNIMethods(className.data(), methods, numMethods)); - return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); -} +// JNI method hook definitions, auto generated +#include "jni_hooks.hpp" -static void hook_jni_env() { +static void hook_zygote() { using method_sig = jint(*)(JavaVM **, jsize, jsize *); auto get_created_vms = reinterpret_cast( dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs")); @@ -841,7 +914,6 @@ static void hook_jni_env() { return; } } - JavaVM *vm = nullptr; jsize num = 0; jint res = get_created_vms(&vm, 1, &num); @@ -855,39 +927,5 @@ static void hook_jni_env() { ZLOGW("JNIEnv not found\n"); return; } - - // Replace the function table in JNIEnv to hook RegisterNatives - default_new(new_functions); - memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &env_RegisterNatives; - old_functions = env->functions; - env->functions = new_functions; -} - -static void restore_jni_env(JNIEnv *env) { - env->functions = old_functions; - delete new_functions; - new_functions = nullptr; + do_hook_zygote(env); } - -#define HOOK_JNI(method) \ -if (methods[i].name == #method##sv) { \ - int j = 0; \ - for (; j < method##_methods_num; ++j) { \ - if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \ - jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \ - method##_orig = methods[i].fnPtr; \ - newMethods[i] = method##_methods[j]; \ - ZLOGI("replaced %s#" #method "\n", className); \ - --hook_cnt; \ - break; \ - } \ - } \ - if (j == method##_methods_num) { \ - ZLOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \ - } \ - continue; \ -} - -// JNI method hook definitions, auto generated -#include "jni_hooks.hpp" diff --git a/native/src/zygisk/jni_hooks.hpp b/native/src/zygisk/jni_hooks.hpp index 9b62083986bd..ea18140b7b34 100644 --- a/native/src/zygisk/jni_hooks.hpp +++ b/native/src/zygisk/jni_hooks.hpp @@ -109,54 +109,53 @@ void *nativeForkAndSpecialize_orig = nullptr; ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -const JNINativeMethod nativeForkAndSpecialize_methods[] = { - { +std::array nativeForkAndSpecialize_methods { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_l }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_p }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I", (void *) &nativeForkAndSpecialize_q_alt }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I", (void *) &nativeForkAndSpecialize_r }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_m }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I", (void *) &nativeForkAndSpecialize_samsung_n }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_o }, - { + JNINativeMethod { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I", (void *) &nativeForkAndSpecialize_samsung_p }, }; -constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods); void *nativeSpecializeAppProcess_orig = nullptr; [[clang::no_stack_protector]] void nativeSpecializeAppProcess_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { @@ -205,29 +204,28 @@ void *nativeSpecializeAppProcess_orig = nullptr; ); ctx.nativeSpecializeAppProcess_post(); } -const JNINativeMethod nativeSpecializeAppProcess_methods[] = { - { +std::array nativeSpecializeAppProcess_methods { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_q }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V", (void *) &nativeSpecializeAppProcess_q_alt }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V", (void *) &nativeSpecializeAppProcess_r }, - { + JNINativeMethod { "nativeSpecializeAppProcess", "(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V", (void *) &nativeSpecializeAppProcess_samsung_q }, }; -constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods); void *nativeForkSystemServer_orig = nullptr; [[clang::no_stack_protector]] jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { @@ -250,45 +248,48 @@ void *nativeForkSystemServer_orig = nullptr; ctx.nativeForkSystemServer_post(); return ctx.pid; } -const JNINativeMethod nativeForkSystemServer_methods[] = { - { +std::array nativeForkSystemServer_methods { + JNINativeMethod { "nativeForkSystemServer", "(II[II[[IJJ)I", (void *) &nativeForkSystemServer_l }, - { + JNINativeMethod { "nativeForkSystemServer", "(II[IIII[[IJJ)I", (void *) &nativeForkSystemServer_samsung_q }, }; -constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods); } // namespace -static JNINativeMethod *hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { - JNINativeMethod *newMethods = nullptr; - int clz_id = -1; - int hook_cnt = 0; - do { - if (className == "com/android/internal/os/Zygote"sv) { - clz_id = 0; - hook_cnt = 3; +static void do_hook_zygote(JNIEnv *env) { + vector hooks; + const char *clz; + clz = "com/android/internal/os/Zygote"; + hookJniNativeMethods(env, clz, nativeForkAndSpecialize_methods.data(), nativeForkAndSpecialize_methods.size()); + for (auto &method : nativeForkAndSpecialize_methods) { + if (method.fnPtr) { + nativeForkAndSpecialize_orig = method.fnPtr; + hooks.emplace_back(method); break; } - } while (false); - if (hook_cnt) { - newMethods = new JNINativeMethod[numMethods]; - memcpy(newMethods, methods, sizeof(JNINativeMethod) * numMethods); } - auto &class_map = (*jni_method_map)[className]; - for (int i = 0; i < numMethods; ++i) { - if (hook_cnt && clz_id == 0) { - HOOK_JNI(nativeForkAndSpecialize) - HOOK_JNI(nativeSpecializeAppProcess) - HOOK_JNI(nativeForkSystemServer) + hookJniNativeMethods(env, clz, nativeSpecializeAppProcess_methods.data(), nativeSpecializeAppProcess_methods.size()); + for (auto &method : nativeSpecializeAppProcess_methods) { + if (method.fnPtr) { + nativeSpecializeAppProcess_orig = method.fnPtr; + hooks.emplace_back(method); + break; + } + } + hookJniNativeMethods(env, clz, nativeForkSystemServer_methods.data(), nativeForkSystemServer_methods.size()); + for (auto &method : nativeForkSystemServer_methods) { + if (method.fnPtr) { + nativeForkSystemServer_orig = method.fnPtr; + hooks.emplace_back(method); + break; } - class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; } - return newMethods; + jni_hook_list->emplace(clz, std::move(hooks)); } diff --git a/native/src/zygisk/loader.c b/native/src/zygisk/loader.c deleted file mode 100644 index 363d4c70d392..000000000000 --- a/native/src/zygisk/loader.c +++ /dev/null @@ -1,28 +0,0 @@ -#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(void) { - android_dlextinfo info = { - .flags = ANDROID_DLEXT_FORCE_LOAD - }; - void *handle = android_dlopen_ext(SECOND_STAGE_PATH, RTLD_LAZY, &info); - if (handle) { - void(*entry)(void*) = dlsym(handle, "zygisk_inject_entry"); - if (entry) { - entry(handle); - } - void (*unload)(void) = dlsym(handle, "unload_first_stage"); - if (unload) { - __attribute__((musttail)) return unload(); - } - } -} diff --git a/native/src/zygisk/main.cpp b/native/src/zygisk/main.cpp index 36e6084970c7..66ded08581bc 100644 --- a/native/src/zygisk/main.cpp +++ b/native/src/zygisk/main.cpp @@ -7,98 +7,11 @@ #include #include #include -#include #include "zygisk.hpp" using namespace std; -// Entrypoint for app_process overlay -int app_process_main(int argc, char *argv[]) { - android_logging(); - char buf[PATH_MAX]; - - bool zygote = false; - if (auto fp = open_file("/proc/self/attr/current", "r")) { - fscanf(fp.get(), "%s", buf); - zygote = (buf == "u:r:zygote:s0"sv); - } - - if (!zygote) { - // For the non zygote case, we need to get real app_process via passthrough - // We have to connect magiskd via exec-ing magisk due to SELinux restrictions - - // This is actually only relevant for calling app_process via ADB shell - // because zygisk shall already have the app_process overlays unmounted - // during app process specialization within its private mount namespace. - - int fds[2]; - socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds); - if (fork_dont_care() == 0) { - // This fd has to survive exec - fcntl(fds[1], F_SETFD, 0); - ssprintf(buf, sizeof(buf), "%d", fds[1]); -#if defined(__LP64__) - execlp("magisk", "", "zygisk", "passthrough", buf, "1", (char *) nullptr); -#else - execlp("magisk", "", "zygisk", "passthrough", buf, "0", (char *) nullptr); -#endif - exit(-1); - } - - close(fds[1]); - if (read_int(fds[0]) != 0) { - fprintf(stderr, "Failed to connect magiskd, try umount %s or reboot.\n", argv[0]); - return 1; - } - int app_proc_fd = recv_fd(fds[0]); - if (app_proc_fd < 0) - return 1; - close(fds[0]); - - fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - fexecve(app_proc_fd, argv, environ); - return 1; - } - - if (int socket = zygisk_request(ZygiskRequest::SETUP); socket >= 0) { - do { - 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; - - if (char *ld = getenv("LD_PRELOAD")) { - string env = ld; - env += ':'; - env += HIJACK_BIN; - setenv("LD_PRELOAD", env.data(), 1); - } else { - setenv("LD_PRELOAD", HIJACK_BIN, 1); - } - - close(socket); - - fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC); - fexecve(app_proc_fd, argv, environ); - } while (false); - - close(socket); - } - - // If encountering any errors, unmount and execute the original app_process - xreadlink("/proc/self/exe", buf, sizeof(buf)); - xumount2("/proc/self/exe", MNT_DETACH); - execve(buf, argv, environ); - return 1; -} - static void zygiskd(int socket) { if (getuid() != 0 || fcntl(socket, F_GETFD) < 0) exit(-1); @@ -177,30 +90,8 @@ static void zygiskd(int socket) { // This should only ever be called internally int zygisk_main(int argc, char *argv[]) { android_logging(); - if (argc == 3 && argv[1] == "companion"sv) { zygiskd(parse_int(argv[2])); - } else if (argc == 4 && argv[1] == "passthrough"sv) { - int client = parse_int(argv[2]); - int is_64_bit = parse_int(argv[3]); - if (fcntl(client, F_GETFD) < 0) - return 1; - if (int magiskd = connect_daemon(MainRequest::ZYGISK_PASSTHROUGH); magiskd >= 0) { - write_int(magiskd, ZygiskRequest::PASSTHROUGH); - write_int(magiskd, is_64_bit); - - if (read_int(magiskd) != 0) { - write_int(client, 1); - return 0; - } - - write_int(client, 0); - int real_app_fd = recv_fd(magiskd); - send_fd(client, real_app_fd); - } else { - write_int(client, 1); - return 0; - } } return 0; } diff --git a/native/src/zygisk/memory.cpp b/native/src/zygisk/memory.cpp deleted file mode 100644 index 0b4158fcfdca..000000000000 --- a/native/src/zygisk/memory.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include -#include "memory.hpp" - -namespace jni_hook { - -// We know our minimum alignment is WORD size (size of pointer) -static constexpr size_t ALIGN = sizeof(long); - -// 4MB is more than enough -static constexpr size_t CAPACITY = (1 << 22); - -// No need to be thread safe as the initial mmap always happens on the main thread -static uint8_t *_area = nullptr; - -static std::atomic _curr = nullptr; - -void *memory_block::allocate(size_t sz) { - if (!_area) { - // Memory will not actually be allocated because physical pages are mapped in on-demand - _area = static_cast(xmmap( - nullptr, CAPACITY, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); - _curr = _area; - } - return _curr.fetch_add(align_to(sz, ALIGN)); -} - -void memory_block::release() { - if (_area) - munmap(_area, CAPACITY); -} - -} // namespace jni_hook diff --git a/native/src/zygisk/memory.hpp b/native/src/zygisk/memory.hpp deleted file mode 100644 index 3c221514884f..000000000000 --- a/native/src/zygisk/memory.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -#include - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-builtins" -#include -#pragma clang diagnostic pop - -#include - -namespace jni_hook { - -struct memory_block { - static void *allocate(size_t sz); - static void deallocate(void *, size_t) { /* Monotonic increase */ } - static void release(); -}; - -template -using allocator = stateless_allocator; - -using string = std::basic_string, allocator>; - -// Use node_hash_map since it will use less memory because we are using a monotonic allocator -template -using hash_map = phmap::node_hash_map, - phmap::priv::hash_default_eq, - allocator> ->; - -template -using tree_map = std::map, - allocator> ->; - -} // namespace jni_hook - -// Provide heterogeneous lookup for jni_hook::string -namespace phmap::priv { -template <> struct HashEq : StringHashEqT {}; -} // namespace phmap::priv diff --git a/native/src/zygisk/native_bridge.cpp b/native/src/zygisk/native_bridge.cpp new file mode 100644 index 000000000000..d8734872ba7d --- /dev/null +++ b/native/src/zygisk/native_bridge.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include "magisk.hpp" +#include "native_bridge.h" + +std::string get_prop(const char *name, bool persist = false); + +static bool is_compatible_with(uint32_t) { + auto name = get_prop(NBPROP); + android_dlextinfo info = { + .flags = ANDROID_DLEXT_FORCE_LOAD + }; + void *handle = android_dlopen_ext(name.data(), RTLD_LAZY, &info); + if (handle) { + auto entry = reinterpret_cast(dlsym(handle, "zygisk_inject_entry")); + if (entry) { + entry(handle); + } + } + return false; +} + +extern "C" [[maybe_unused]] android::NativeBridgeCallbacks NativeBridgeItf{ + .version = 2, + .isCompatibleWith = &is_compatible_with, +}; diff --git a/native/src/zygisk/native_bridge.h b/native/src/zygisk/native_bridge.h new file mode 100644 index 000000000000..5904c0fe3fb2 --- /dev/null +++ b/native/src/zygisk/native_bridge.h @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ART_LIBNATIVEBRIDGE_INCLUDE_NATIVEBRIDGE_NATIVE_BRIDGE_H_ +#define ART_LIBNATIVEBRIDGE_INCLUDE_NATIVEBRIDGE_NATIVE_BRIDGE_H_ + +#include +#include +#include +#include + +#include "jni.h" + +#ifdef __cplusplus +namespace android { +extern "C" { +#endif // __cplusplus + +// Loads a shared library from the system linker namespace, suitable for +// platform libraries in /system/lib(64). If linker namespaces don't exist (i.e. +// on host), this simply calls dlopen(). +void* OpenSystemLibrary(const char* path, int flags); + +struct NativeBridgeRuntimeCallbacks; +struct NativeBridgeRuntimeValues; + +// Function pointer type for sigaction. This is mostly the signature of a signal handler, except +// for the return type. The runtime needs to know whether the signal was handled or should be given +// to the chain. +typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*); // NOLINT + +// Open the native bridge, if any. Should be called by Runtime::Init(). A null library filename +// signals that we do not want to load a native bridge. +bool LoadNativeBridge(const char* native_bridge_library_filename, + const struct NativeBridgeRuntimeCallbacks* runtime_callbacks); + +// Quick check whether a native bridge will be needed. This is based off of the instruction set +// of the process. +bool NeedsNativeBridge(const char* instruction_set); + +// Do the early initialization part of the native bridge, if necessary. This should be done under +// high privileges. +bool PreInitializeNativeBridge(const char* app_data_dir, const char* instruction_set); + +// Prepare to fork from zygote. May be required to clean-up the enviroment, e.g. +// close emulated file descriptors, after doPreload() in app-zygote. +void PreZygoteForkNativeBridge(); + +// Initialize the native bridge, if any. Should be called by Runtime::DidForkFromZygote. The JNIEnv* +// will be used to modify the app environment for the bridge. +bool InitializeNativeBridge(JNIEnv* env, const char* instruction_set); + +// Unload the native bridge, if any. Should be called by Runtime::DidForkFromZygote. +void UnloadNativeBridge(); + +// Check whether a native bridge is available (opened or initialized). Requires a prior call to +// LoadNativeBridge. +bool NativeBridgeAvailable(); + +// Check whether a native bridge is available (initialized). Requires a prior call to +// LoadNativeBridge & InitializeNativeBridge. +bool NativeBridgeInitialized(); + +// Load a shared library that is supported by the native bridge. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Use NativeBridgeLoadLibraryExt() instead in namespace scenario. +void* NativeBridgeLoadLibrary(const char* libpath, int flag); + +// Get a native bridge trampoline for specified native method. +void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len); + +// True if native library paths are valid and is for an ABI that is supported by native bridge. +// The *libpath* must point to a library. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Use NativeBridgeIsPathSupported() instead in namespace scenario. +bool NativeBridgeIsSupported(const char* libpath); + +// Returns the version number of the native bridge. This information is available after a +// successful LoadNativeBridge() and before closing it, that is, as long as NativeBridgeAvailable() +// returns true. Returns 0 otherwise. +uint32_t NativeBridgeGetVersion(); + +// Returns a signal handler that the bridge would like to be managed. Only valid for a native +// bridge supporting the version 2 interface. Will return null if the bridge does not support +// version 2, or if it doesn't have a signal handler it wants to be known. +NativeBridgeSignalHandlerFn NativeBridgeGetSignalHandler(int signal); + +// Returns whether we have seen a native bridge error. This could happen because the library +// was not found, rejected, could not be initialized and so on. +// +// This functionality is mainly for testing. +bool NativeBridgeError(); + +// Returns whether a given string is acceptable as a native bridge library filename. +// +// This functionality is exposed mainly for testing. +bool NativeBridgeNameAcceptable(const char* native_bridge_library_filename); + +// Decrements the reference count on the dynamic library handler. If the reference count drops +// to zero then the dynamic library is unloaded. Returns 0 on success and non-zero on error. +int NativeBridgeUnloadLibrary(void* handle); + +// Get last error message of native bridge when fail to load library or search symbol. +// This is reflection of dlerror() for native bridge. +const char* NativeBridgeGetError(); + +struct native_bridge_namespace_t; + +// True if native library paths are valid and is for an ABI that is supported by native bridge. +// Different from NativeBridgeIsSupported(), the *path* here must be a directory containing +// libraries of an ABI. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Use NativeBridgeIsSupported() instead in non-namespace scenario. +bool NativeBridgeIsPathSupported(const char* path); + +// Initializes anonymous namespace. +// NativeBridge's peer of android_init_anonymous_namespace() of dynamic linker. +// +// The anonymous namespace is used in the case when a NativeBridge implementation +// cannot identify the caller of dlopen/dlsym which happens for the code not loaded +// by dynamic linker; for example calls from the mono-compiled code. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Should not use in non-namespace scenario. +bool NativeBridgeInitAnonymousNamespace(const char* public_ns_sonames, + const char* anon_ns_library_path); + +// Create new namespace in which native libraries will be loaded. +// NativeBridge's peer of android_create_namespace() of dynamic linker. +// +// The libraries in the namespace are searched by folowing order: +// 1. ld_library_path (Think of this as namespace-local LD_LIBRARY_PATH) +// 2. In directories specified by DT_RUNPATH of the "needed by" binary. +// 3. deault_library_path (This of this as namespace-local default library path) +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Should not use in non-namespace scenario. +struct native_bridge_namespace_t* NativeBridgeCreateNamespace( + const char* name, const char* ld_library_path, const char* default_library_path, uint64_t type, + const char* permitted_when_isolated_path, struct native_bridge_namespace_t* parent_ns); + +// Creates a link which shares some libraries from one namespace to another. +// NativeBridge's peer of android_link_namespaces() of dynamic linker. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Should not use in non-namespace scenario. +bool NativeBridgeLinkNamespaces(struct native_bridge_namespace_t* from, + struct native_bridge_namespace_t* to, + const char* shared_libs_sonames); + +// Load a shared library with namespace key that is supported by the native bridge. +// NativeBridge's peer of android_dlopen_ext() of dynamic linker, only supports namespace +// extension. +// +// Starting with v3, NativeBridge has two scenarios: with/without namespace. +// Use NativeBridgeLoadLibrary() instead in non-namespace scenario. +void* NativeBridgeLoadLibraryExt(const char* libpath, int flag, + struct native_bridge_namespace_t* ns); + +// Returns exported namespace by the name. This is a reflection of +// android_get_exported_namespace function. Introduced in v5. +struct native_bridge_namespace_t* NativeBridgeGetExportedNamespace(const char* name); + +// Native bridge interfaces to runtime. +struct NativeBridgeCallbacks { + // Version number of the interface. + uint32_t version; + + // Initialize native bridge. Native bridge's internal implementation must ensure MT safety and + // that the native bridge is initialized only once. Thus it is OK to call this interface for an + // already initialized native bridge. + // + // Parameters: + // runtime_cbs [IN] the pointer to NativeBridgeRuntimeCallbacks. + // Returns: + // true if initialization was successful. + bool (*initialize)(const struct NativeBridgeRuntimeCallbacks* runtime_cbs, + const char* private_dir, const char* instruction_set); + + // Load a shared library that is supported by the native bridge. + // + // Parameters: + // libpath [IN] path to the shared library + // flag [IN] the stardard RTLD_XXX defined in bionic dlfcn.h + // Returns: + // The opaque handle of the shared library if sucessful, otherwise NULL + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Use loadLibraryExt instead in namespace scenario. + void* (*loadLibrary)(const char* libpath, int flag); + + // Get a native bridge trampoline for specified native method. The trampoline has same + // sigature as the native method. + // + // Parameters: + // handle [IN] the handle returned from loadLibrary + // shorty [IN] short descriptor of native method + // len [IN] length of shorty + // Returns: + // address of trampoline if successful, otherwise NULL + void* (*getTrampoline)(void* handle, const char* name, const char* shorty, uint32_t len); + + // Check whether native library is valid and is for an ABI that is supported by native bridge. + // + // Parameters: + // libpath [IN] path to the shared library + // Returns: + // TRUE if library is supported by native bridge, FALSE otherwise + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Use isPathSupported instead in namespace scenario. + bool (*isSupported)(const char* libpath); + + // Provide environment values required by the app running with native bridge according to the + // instruction set. + // + // Parameters: + // instruction_set [IN] the instruction set of the app + // Returns: + // NULL if not supported by native bridge. + // Otherwise, return all environment values to be set after fork. + const struct NativeBridgeRuntimeValues* (*getAppEnv)(const char* instruction_set); + + // Added callbacks in version 2. + + // Check whether the bridge is compatible with the given version. A bridge may decide not to be + // forwards- or backwards-compatible, and libnativebridge will then stop using it. + // + // Parameters: + // bridge_version [IN] the version of libnativebridge. + // Returns: + // true if the native bridge supports the given version of libnativebridge. + bool (*isCompatibleWith)(uint32_t bridge_version); + + // A callback to retrieve a native bridge's signal handler for the specified signal. The runtime + // will ensure that the signal handler is being called after the runtime's own handler, but before + // all chained handlers. The native bridge should not try to install the handler by itself, as + // that will potentially lead to cycles. + // + // Parameters: + // signal [IN] the signal for which the handler is asked for. Currently, only SIGSEGV is + // supported by the runtime. + // Returns: + // NULL if the native bridge doesn't use a handler or doesn't want it to be managed by the + // runtime. + // Otherwise, a pointer to the signal handler. + NativeBridgeSignalHandlerFn (*getSignalHandler)(int signal); + + // Added callbacks in version 3. + + // Decrements the reference count on the dynamic library handler. If the reference count drops + // to zero then the dynamic library is unloaded. + // + // Parameters: + // handle [IN] the handler of a dynamic library. + // + // Returns: + // 0 on success, and nonzero on error. + int (*unloadLibrary)(void* handle); + + // Dump the last failure message of native bridge when fail to load library or search symbol. + // + // Parameters: + // + // Returns: + // A string describing the most recent error that occurred when load library + // or lookup symbol via native bridge. + const char* (*getError)(); + + // Check whether library paths are supported by native bridge. + // + // Parameters: + // library_path [IN] search paths for native libraries (directories separated by ':') + // Returns: + // TRUE if libraries within search paths are supported by native bridge, FALSE otherwise + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Use isSupported instead in non-namespace scenario. + bool (*isPathSupported)(const char* library_path); + + // Initializes anonymous namespace at native bridge side. + // NativeBridge's peer of android_init_anonymous_namespace() of dynamic linker. + // + // The anonymous namespace is used in the case when a NativeBridge implementation + // cannot identify the caller of dlopen/dlsym which happens for the code not loaded + // by dynamic linker; for example calls from the mono-compiled code. + // + // Parameters: + // public_ns_sonames [IN] the name of "public" libraries. + // anon_ns_library_path [IN] the library search path of (anonymous) namespace. + // Returns: + // true if the pass is ok. + // Otherwise, false. + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Should not use in non-namespace scenario. + bool (*initAnonymousNamespace)(const char* public_ns_sonames, const char* anon_ns_library_path); + + // Create new namespace in which native libraries will be loaded. + // NativeBridge's peer of android_create_namespace() of dynamic linker. + // + // Parameters: + // name [IN] the name of the namespace. + // ld_library_path [IN] the first set of library search paths of the namespace. + // default_library_path [IN] the second set of library search path of the namespace. + // type [IN] the attribute of the namespace. + // permitted_when_isolated_path [IN] the permitted path for isolated namespace(if it is). + // parent_ns [IN] the pointer of the parent namespace to be inherited from. + // Returns: + // native_bridge_namespace_t* for created namespace or nullptr in the case of error. + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Should not use in non-namespace scenario. + struct native_bridge_namespace_t* (*createNamespace)(const char* name, + const char* ld_library_path, + const char* default_library_path, + uint64_t type, + const char* permitted_when_isolated_path, + struct native_bridge_namespace_t* parent_ns); + + // Creates a link which shares some libraries from one namespace to another. + // NativeBridge's peer of android_link_namespaces() of dynamic linker. + // + // Parameters: + // from [IN] the namespace where libraries are accessed. + // to [IN] the namespace where libraries are loaded. + // shared_libs_sonames [IN] the libraries to be shared. + // + // Returns: + // Whether successed or not. + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Should not use in non-namespace scenario. + bool (*linkNamespaces)(struct native_bridge_namespace_t* from, + struct native_bridge_namespace_t* to, const char* shared_libs_sonames); + + // Load a shared library within a namespace. + // NativeBridge's peer of android_dlopen_ext() of dynamic linker, only supports namespace + // extension. + // + // Parameters: + // libpath [IN] path to the shared library + // flag [IN] the stardard RTLD_XXX defined in bionic dlfcn.h + // ns [IN] the pointer of the namespace in which the library should be loaded. + // Returns: + // The opaque handle of the shared library if sucessful, otherwise NULL + // + // Starting with v3, NativeBridge has two scenarios: with/without namespace. + // Use loadLibrary instead in non-namespace scenario. + void* (*loadLibraryExt)(const char* libpath, int flag, struct native_bridge_namespace_t* ns); + + // Get native bridge version of vendor namespace. + // The vendor namespace is the namespace used to load vendor public libraries. + // With O release this namespace can be different from the default namespace. + // For the devices without enable vendor namespaces this function should return null + // + // Returns: + // vendor namespace or null if it was not set up for the device + // + // Starting with v5 (Android Q) this function is no longer used. + // Use getExportedNamespace() below. + struct native_bridge_namespace_t* (*getVendorNamespace)(); + + // Get native bridge version of exported namespace. Peer of + // android_get_exported_namespace(const char*) function. + // + // Returns: + // exported namespace or null if it was not set up for the device + struct native_bridge_namespace_t* (*getExportedNamespace)(const char* name); + + // If native bridge is used in app-zygote (in doPreload()) this callback is + // required to clean-up the environment before the fork (see b/146904103). + void (*preZygoteFork)(); +}; + +// Runtime interfaces to native bridge. +struct NativeBridgeRuntimeCallbacks { + // Get shorty of a Java method. The shorty is supposed to be persistent in memory. + // + // Parameters: + // env [IN] pointer to JNIenv. + // mid [IN] Java methodID. + // Returns: + // short descriptor for method. + const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid); + + // Get number of native methods for specified class. + // + // Parameters: + // env [IN] pointer to JNIenv. + // clazz [IN] Java class object. + // Returns: + // number of native methods. + uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz); + + // Get at most 'method_count' native methods for specified class 'clazz'. Results are outputed + // via 'methods' [OUT]. The signature pointer in JNINativeMethod is reused as the method shorty. + // + // Parameters: + // env [IN] pointer to JNIenv. + // clazz [IN] Java class object. + // methods [OUT] array of method with the name, shorty, and fnPtr. + // method_count [IN] max number of elements in methods. + // Returns: + // number of method it actually wrote to methods. + uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, + uint32_t method_count); +}; + +#ifdef __cplusplus +} // extern "C" +} // namespace android +#endif // __cplusplus + +#endif // ART_LIBNATIVEBRIDGE_INCLUDE_NATIVEBRIDGE_NATIVE_BRIDGE_H_ diff --git a/native/src/zygisk/ptrace.cpp b/native/src/zygisk/ptrace.cpp deleted file mode 100644 index ae5bb00edfe1..000000000000 --- a/native/src/zygisk/ptrace.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/* - * Original code: https://github.com/Chainfire/injectvm-binderjack/blob/master/app/src/main/jni/libinject/inject.cpp - * The code is heavily modified and sublicensed to GPLv3 for incorporating into Magisk. - * - * Copyright (c) 2015, Simone 'evilsocket' Margaritelli - * Copyright (c) 2015-2019, Jorrit 'Chainfire' Jongma - * Copyright (c) 2021, John 'topjohnwu' Wu - * - * See original LICENSE file from the original project for additional details: - * https://github.com/Chainfire/injectvm-binderjack/blob/master/LICENSE - */ - -/* - * NOTE: - * The code in this file was originally planned to be used for some features, - * but it turned out to be unsuitable for the task. However, this shall remain - * in our arsenal in case it may be used in the future. - */ - -#include -#include -#include -#include - -#include - -#include "zygisk.hpp" -#include "ptrace.hpp" - -using namespace std; - -#if defined(__arm__) -#define CPSR_T_MASK (1u << 5) -#define PARAMS_IN_REGS 4 -#elif defined(__aarch64__) -#define CPSR_T_MASK (1u << 5) -#define PARAMS_IN_REGS 8 -#define pt_regs user_pt_regs -#define uregs regs -#define ARM_pc pc -#define ARM_sp sp -#define ARM_cpsr pstate -#define ARM_lr regs[30] -#define ARM_r0 regs[0] -#endif - -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)); - if (data < 0) - return false; - memcpy(static_cast(buf) + i, &data, std::min(len - i, sizeof(data))); - } - return true; -} - -bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len) { - for (size_t i = 0; i < len; i += sizeof(long)) { - long data = 0; - memcpy(&data, static_cast(buf) + i, std::min(len - i, sizeof(data))); - if (xptrace(PTRACE_POKETEXT, pid, reinterpret_cast(addr + i), data) < 0) - return false; - } - return true; -} - -// Get remote registers -#define remote_getregs(regs) _remote_getregs(pid, regs) -static void _remote_getregs(int pid, pt_regs *regs) { -#if defined(__LP64__) - uintptr_t regset = NT_PRSTATUS; - iovec iov{}; - iov.iov_base = regs; - iov.iov_len = sizeof(*regs); - xptrace(PTRACE_GETREGSET, pid, reinterpret_cast(regset), &iov); -#else - xptrace(PTRACE_GETREGS, pid, nullptr, regs); -#endif -} - -// Set remote registers -#define remote_setregs(regs) _remote_setregs(pid, regs) -static void _remote_setregs(int pid, pt_regs *regs) { -#if defined(__LP64__) - uintptr_t regset = NT_PRSTATUS; - iovec iov{}; - iov.iov_base = regs; - iov.iov_len = sizeof(*regs); - xptrace(PTRACE_SETREGSET, pid, reinterpret_cast(regset), &iov); -#else - xptrace(PTRACE_SETREGS, pid, nullptr, regs); -#endif -} - -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 - remote_getregs(®s); - memcpy(®s_bak, ®s, sizeof(regs)); - - // ABI dependent: Setup stack and registers to perform the call - -#if defined(__arm__) || defined(__aarch64__) - // Fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters - for (int i = 0; (i < nargs) && (i < PARAMS_IN_REGS); ++i) { - regs.uregs[i] = va_arg(va, uintptr_t); - } - - // Push remaining parameters onto stack - if (nargs > PARAMS_IN_REGS) { - regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS); - uintptr_t stack = regs.ARM_sp; - for (int i = PARAMS_IN_REGS; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - } - - // Set return address - regs.ARM_lr = 0; - - // Set function address to call - regs.ARM_pc = func_addr; - - // Setup the current processor status register - if (regs.ARM_pc & 1u) { - // thumb - regs.ARM_pc &= (~1u); - regs.ARM_cpsr |= CPSR_T_MASK; - } else { - // arm - regs.ARM_cpsr &= ~CPSR_T_MASK; - } -#elif defined(__i386__) - // Push all params onto stack - regs.esp -= sizeof(uintptr_t) * nargs; - uintptr_t stack = regs.esp; - for (int i = 0; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - - // Push return address onto stack - uintptr_t ret_addr = 0; - regs.esp -= sizeof(uintptr_t); - remote_write(regs.esp, &ret_addr, sizeof(uintptr_t)); - - // Set function address to call - regs.eip = func_addr; -#elif defined(__x86_64__) - // Align, rsp - 8 must be a multiple of 16 at function entry point - uintptr_t space = sizeof(uintptr_t); - if (nargs > 6) - space += sizeof(uintptr_t) * (nargs - 6); - while (((regs.rsp - space - 8) & 0xF) != 0) - regs.rsp--; - - // Fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters - for (int i = 0; (i < nargs) && (i < 6); ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - switch (i) { - case 0: regs.rdi = arg; break; - case 1: regs.rsi = arg; break; - case 2: regs.rdx = arg; break; - case 3: regs.rcx = arg; break; - case 4: regs.r8 = arg; break; - case 5: regs.r9 = arg; break; - } - } - - // Push remaining parameters onto stack - if (nargs > 6) { - regs.rsp -= sizeof(uintptr_t) * (nargs - 6); - uintptr_t stack = regs.rsp; - for(int i = 6; i < nargs; ++i) { - uintptr_t arg = va_arg(va, uintptr_t); - remote_write(stack, &arg, sizeof(uintptr_t)); - stack += sizeof(uintptr_t); - } - } - - // Push return address onto stack - uintptr_t ret_addr = 0; - regs.rsp -= sizeof(uintptr_t); - remote_write(regs.rsp, &ret_addr, sizeof(uintptr_t)); - - // Set function address to call - regs.rip = func_addr; - - // may be needed - regs.rax = 0; - regs.orig_rax = 0; -#else -#error Unsupported ABI -#endif - - // Resume process to do the call - remote_setregs(®s); - xptrace(PTRACE_CONT, pid); - - // Catch SIGSEGV caused by the 0 return address - int status; - while (waitpid(pid, &status, __WALL | __WNOTHREAD) == pid) { - if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV)) - break; - xptrace(PTRACE_CONT, pid); - } - - // Get registers again for return value - remote_getregs(®s); - - // Restore registers - remote_setregs(®s_bak); - -#if defined(__arm__) || defined(__aarch64__) - return regs.ARM_r0; -#elif defined(__i386__) - return regs.eax; -#elif defined(__x86_64__) - return regs.rax; -#endif -} - -uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) { - char lib_name[4096]; - auto off = get_function_off(getpid(), addr, lib_name); - if (off == 0) - return 0; - auto remote = get_function_addr(pid, lib_name, off); - if (remote == 0) - return 0; - va_list va; - va_start(va, nargs); - auto result = remote_call_abi(pid, remote, nargs, va); - va_end(va); - return result; -} diff --git a/native/src/zygisk/ptrace.hpp b/native/src/zygisk/ptrace.hpp deleted file mode 100644 index b4951a62502f..000000000000 --- a/native/src/zygisk/ptrace.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include - -// 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__) - -// Read bytes from the remote process at addr -bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len); -#define remote_read(...) _remote_read(pid, __VA_ARGS__) - -// 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 -template -static uintptr_t _remote_call(int pid, FuncPtr sym, Args && ...args) { - auto addr = reinterpret_cast(sym); - return remote_call_vararg(pid, addr, sizeof...(args), std::forward(args)...); -} -#define remote_call(...) _remote_call(pid, __VA_ARGS__) diff --git a/native/src/zygisk/utils.cpp b/native/src/zygisk/utils.cpp deleted file mode 100644 index 97cc5e2a389f..000000000000 --- a/native/src/zygisk/utils.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include -#include - -#include "zygisk.hpp" -#include - -using namespace std; -static vector find_maps(const char *name) { - auto maps = lsplt::MapInfo::Scan(); - for (auto iter = maps.begin(); iter != maps.end();) { - if (iter->path != name) { - iter = maps.erase(iter); - } else { - ++iter; - } - } - return maps; -} - -void unmap_all(const char *name) { - auto maps = find_maps(name); - for (auto &info : maps) { - void *addr = reinterpret_cast(info.start); - size_t size = info.end - info.start; - if (info.perms & PROT_READ) { - // Make sure readable pages are still readable - void *dummy = xmmap(nullptr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - mremap(dummy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); - } else { - munmap(addr, size); - } - } -} - -void remap_all(const char *name) { - auto maps = find_maps(name); - for (auto &info : maps) { - void *addr = reinterpret_cast(info.start); - size_t size = info.end - info.start; - void *copy = xmmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if ((info.perms & PROT_READ) == 0) { - mprotect(addr, size, PROT_READ); - } - memcpy(copy, addr, size); - mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr); - mprotect(addr, size, info.perms); - } -} - -uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) { - for (auto &info : lsplt::MapInfo::Scan()) { - if (addr >= info.start && addr < info.end) { - if (lib) - strcpy(lib, info.path.data()); - return addr - info.start + info.offset; - } - } - return 0; -} - -uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) { - for (auto &info : lsplt::MapInfo::Scan()) { - if (info.path == lib && (info.perms & PROT_EXEC)) { - return info.start - info.offset + off; - } - } - return 0; -} diff --git a/native/src/zygisk/zygisk.hpp b/native/src/zygisk/zygisk.hpp index dbc65f892ee3..1895830a0a05 100644 --- a/native/src/zygisk/zygisk.hpp +++ b/native/src/zygisk/zygisk.hpp @@ -6,18 +6,11 @@ #include #include -#define MAGISKTMP_ENV "MAGISKTMP" - -#define HIJACK_BIN64 "/system/bin/appwidget" -#define HIJACK_BIN32 "/system/bin/bu" - namespace ZygiskRequest { enum : int { - SETUP, GET_INFO, CONNECT_COMPANION, GET_MODDIR, - PASSTHROUGH, END }; } @@ -27,27 +20,13 @@ enum : int { #define ZLOGE(...) LOGE("zygisk64: " __VA_ARGS__) #define ZLOGI(...) LOGI("zygisk64: " __VA_ARGS__) #define ZLOGW(...) LOGW("zygisk64: " __VA_ARGS__) -#define HIJACK_BIN HIJACK_BIN64 #else #define ZLOGD(...) LOGD("zygisk32: " __VA_ARGS__) #define ZLOGE(...) LOGE("zygisk32: " __VA_ARGS__) #define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__) #define ZLOGW(...) LOGW("zygisk32: " __VA_ARGS__) -#define HIJACK_BIN HIJACK_BIN32 #endif -// Unmap all pages matching the name -void unmap_all(const char *name); - -// Remap all matching pages with anonymous pages -void remap_all(const char *name); - -// Get library name + offset (from start of ELF), given function address -uintptr_t get_function_off(int pid, uintptr_t addr, char *lib); - -// Get function address, given library name + offset -uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off); - extern void *self_handle; void hook_functions();