From 9a28dd4f6e737960314945e1f4bed1fd9c9cf102 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Tue, 12 Jan 2021 03:28:00 -0800 Subject: [PATCH] Implement MagiskHide through code injection --- native/jni/core/daemon.cpp | 28 ++++++-- native/jni/include/daemon.hpp | 6 +- native/jni/inject/hook.cpp | 98 +++++++++++++++++---------- native/jni/magiskhide/hide_policy.cpp | 2 - native/jni/magiskhide/hide_utils.cpp | 35 +++++++++- native/jni/magiskhide/magiskhide.cpp | 34 +++++++++- native/jni/magiskhide/magiskhide.hpp | 5 +- native/jni/magiskpolicy/rules.cpp | 5 +- 8 files changed, 166 insertions(+), 47 deletions(-) diff --git a/native/jni/core/daemon.cpp b/native/jni/core/daemon.cpp index e70f9e291..ef777eb41 100644 --- a/native/jni/core/daemon.cpp +++ b/native/jni/core/daemon.cpp @@ -36,10 +36,20 @@ static bool verify_client(pid_t pid) { return !(stat(path, &st) || st.st_dev != self_st.st_dev || st.st_ino != self_st.st_ino); } +static bool check_zygote(pid_t pid) { + char buf[32]; + sprintf(buf, "/proc/%d/attr/current", pid); + auto fp = open_file(buf, "r"); + if (!fp) + return false; + fscanf(fp.get(), "%s", buf); + return buf == "u:r:zygote:s0"sv; +} + static void request_handler(int client, int req_code, ucred cred) { switch (req_code) { case MAGISKHIDE: - magiskhide_handler(client); + magiskhide_handler(client, &cred); break; case SUPERUSER: su_daemon_handler(client, &cred); @@ -74,7 +84,12 @@ static void handle_request(int client) { // Verify client credentials ucred cred; get_client_cred(client, &cred); - if (cred.uid != 0 && !verify_client(cred.pid)) + + bool is_root = cred.uid == 0; + bool is_zygote = check_zygote(cred.pid); + bool is_client = verify_client(cred.pid); + + if (!is_root && !is_zygote && !is_client) goto shortcut; req_code = read_int(client); @@ -83,13 +98,12 @@ static void handle_request(int client) { // Check client permissions switch (req_code) { - case MAGISKHIDE: case POST_FS_DATA: case LATE_START: case BOOT_COMPLETE: case SQLITE_CMD: case GET_PATH: - if (cred.uid != 0) { + if (!is_root) { write_int(client, ROOT_REQUIRED); goto shortcut; } @@ -100,6 +114,12 @@ static void handle_request(int client) { goto shortcut; } break; + case MAGISKHIDE: // accept hide request from zygote + if (!is_root && !is_zygote) { + write_int(client, ROOT_REQUIRED); + goto shortcut; + } + break; } // Simple requests diff --git a/native/jni/include/daemon.hpp b/native/jni/include/daemon.hpp index c6a42a765..35baac204 100644 --- a/native/jni/include/daemon.hpp +++ b/native/jni/include/daemon.hpp @@ -44,9 +44,13 @@ int connect_daemon(bool create = false); void post_fs_data(int client); void late_start(int client); void boot_complete(int client); -void magiskhide_handler(int client); +void magiskhide_handler(int client, ucred *cred); void su_daemon_handler(int client, ucred *credential); // MagiskHide void auto_start_magiskhide(bool late_props); int stop_magiskhide(); + +// For injected process to access daemon +int remote_check_hide(int uid, const char *process); +void remote_request_hide(); diff --git a/native/jni/inject/hook.cpp b/native/jni/inject/hook.cpp index 02565aa78..6974629da 100644 --- a/native/jni/inject/hook.cpp +++ b/native/jni/inject/hook.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include "inject.hpp" @@ -17,18 +18,11 @@ using namespace std; extern const JNINativeMethod name##_methods[]; \ extern const int name##_methods_num; -// For some reason static vectors won't work, use pointers instead -static vector> *xhook_list; -static vector *jni_list; - -static JavaVM *g_jvm; -static int prev_fork_pid = -1; - namespace { struct HookContext { int pid; - bool unload; + bool do_hide; }; // JNI method declarations @@ -38,6 +32,14 @@ DCL_JNI_FUNC(nativeForkSystemServer) } +// For some reason static vectors won't work, use pointers instead +static vector> *xhook_list; +static vector *jni_list; + +static JavaVM *g_jvm; +static int prev_fork_pid = -1; +static HookContext *current_ctx; + #define HOOK_JNI(method) \ if (newMethods[i].name == #method##sv) { \ auto orig = new JNINativeMethod(); \ @@ -90,6 +92,17 @@ DCL_HOOK_FUNC(int, fork) { return pid; } +DCL_HOOK_FUNC(int, selinux_android_setcontext, + uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) { + if (current_ctx && current_ctx->do_hide) { + // Ask magiskd to hide ourselves before switching context + // because magiskd socket is not accessible on Android 8.0+ + remote_request_hide(); + LOGD("hook: process successfully hidden\n"); + } + return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname); +} + static int sigmask(int how, int signum) { sigset_t set; sigemptyset(&set); @@ -106,10 +119,43 @@ static int pre_specialize_fork() { // ----------------------------------------------------------------- +static void nativeSpecializeAppProcess_pre(HookContext *ctx, + 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, + jboolean &is_top_app, jobjectArray &pkg_data_info_list, + jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs, + jboolean &mount_storage_dirs) { + + current_ctx = ctx; + + const char *process = env->GetStringUTFChars(nice_name, nullptr); + LOGD("hook: %s %s\n", __FUNCTION__, process); + + if (mount_external != 0 /* TODO: Handle MOUNT_EXTERNAL_NONE cases */ + && remote_check_hide(uid, process)) { + ctx->do_hide = true; + LOGI("hook: [%s] should be hidden\n", process); + } + + env->ReleaseStringUTFChars(nice_name, process); +} + +static void nativeSpecializeAppProcess_post(HookContext *ctx, JNIEnv *env, jclass clazz) { + LOGD("hook: %s\n", __FUNCTION__); + + if (ctx->do_hide) + self_unload(); + + current_ctx = nullptr; +} + +// ----------------------------------------------------------------- + static void nativeForkAndSpecialize_pre(HookContext *ctx, JNIEnv *env, jclass clazz, jint &uid, jint &gid, jintArray &gids, jint &runtime_flags, jobjectArray &rlimits, jint &mount_external, jstring &se_info, jstring &nice_name, - jintArray &fds_to_close, jintArray &fds_to_ignore, /* These 2 arguments are unique to fork */ + jintArray fds_to_close, jintArray fds_to_ignore, /* These 2 arguments are unique to fork */ jboolean &is_child_zygote, jstring &instruction_set, jstring &app_data_dir, jboolean &is_top_app, jobjectArray &pkg_data_info_list, jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs, @@ -120,40 +166,19 @@ static void nativeForkAndSpecialize_pre(HookContext *ctx, if (ctx->pid != 0) return; - // TODO: check if we need to do hiding - // Demonstrate self unload in child process - ctx->unload = true; - - LOGD("hook: %s\n", __FUNCTION__); + nativeSpecializeAppProcess_pre( + ctx, env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, + nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app, + pkg_data_info_list, whitelisted_data_info_list, mount_data_dirs, mount_storage_dirs); } static void nativeForkAndSpecialize_post(HookContext *ctx, JNIEnv *env, jclass clazz) { // Unblock SIGCHLD in case the original method didn't sigmask(SIG_UNBLOCK, SIGCHLD); - if (ctx->pid != 0) return; - LOGD("hook: %s\n", __FUNCTION__); - - if (ctx->unload) - self_unload(); -} - -// ----------------------------------------------------------------- - -static void nativeSpecializeAppProcess_pre(HookContext *ctx, - 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, - jboolean &is_top_app, jobjectArray &pkg_data_info_list, - jobjectArray &whitelisted_data_info_list, jboolean &mount_data_dirs, - jboolean &mount_storage_dirs) { - LOGD("hook: %s\n", __FUNCTION__); -} - -static void nativeSpecializeAppProcess_post(HookContext *ctx, JNIEnv *env, jclass clazz) { - LOGD("hook: %s\n", __FUNCTION__); + nativeSpecializeAppProcess_post(ctx, env, clazz); } // ----------------------------------------------------------------- @@ -167,6 +192,7 @@ static void nativeForkSystemServer_pre(HookContext *ctx, if (ctx->pid != 0) return; + current_ctx = ctx; LOGD("hook: %s\n", __FUNCTION__); } @@ -178,6 +204,7 @@ static void nativeForkSystemServer_post(HookContext *ctx, JNIEnv *env, jclass cl return; LOGD("hook: %s\n", __FUNCTION__); + current_ctx = nullptr; } // ----------------------------------------------------------------- @@ -216,6 +243,7 @@ void hook_functions() { XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods); XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork); + XHOOK_REGISTER(".*\\libandroid_runtime.so$", selinux_android_setcontext); hook_refresh(); } diff --git a/native/jni/magiskhide/hide_policy.cpp b/native/jni/magiskhide/hide_policy.cpp index eb56a4b28..977bdb96e 100644 --- a/native/jni/magiskhide/hide_policy.cpp +++ b/native/jni/magiskhide/hide_policy.cpp @@ -77,7 +77,6 @@ static void lazy_unmount(const char* mountpoint) { LOGD("hide: Unmounted (%s)\n", mountpoint); } -#if ENABLE_PTRACE_MONITOR void hide_daemon(int pid) { if (fork_dont_care() == 0) { hide_unmount(pid); @@ -86,7 +85,6 @@ void hide_daemon(int pid) { _exit(0); } } -#endif #define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && \ strncmp(mentry->mnt_dir, "/" #dir, sizeof("/" #dir) - 1) == 0) diff --git a/native/jni/magiskhide/hide_utils.cpp b/native/jni/magiskhide/hide_utils.cpp index fc7c79b65..be5784834 100644 --- a/native/jni/magiskhide/hide_utils.cpp +++ b/native/jni/magiskhide/hide_utils.cpp @@ -274,7 +274,7 @@ int launch_magiskhide(bool late_props) { if (hide_state) return HIDE_IS_ENABLED; - if (access("/proc/1/ns/mnt", F_OK) != 0) + if (access("/proc/self/ns/mnt", F_OK) != 0) return HIDE_NO_NS; if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr) @@ -344,3 +344,36 @@ void test_proc_monitor() { proc_monitor(); } #endif + +int check_uid_map(int client) { + mutex_guard lock(hide_state_lock); + + if (!hide_state) + return 0; + + int uid = read_int(client); + string process = read_string(client); + + if (uid % 100000 > 90000) { + // Isolated process + auto it = uid_proc_map.find(-1); + if (it == uid_proc_map.end()) + return 0; + + for (auto &s : it->second) { + if (str_starts(process, s)) + return 1; + } + } else { + auto it = uid_proc_map.find(uid); + if (it == uid_proc_map.end()) + return 0; + + for (auto &s : it->second) { + if (process == s) + return 1; + } + } + + return 0; +} diff --git a/native/jni/magiskhide/magiskhide.cpp b/native/jni/magiskhide/magiskhide.cpp index 42544db0f..7b7406786 100644 --- a/native/jni/magiskhide/magiskhide.cpp +++ b/native/jni/magiskhide/magiskhide.cpp @@ -27,7 +27,7 @@ using namespace std; exit(1); } -void magiskhide_handler(int client) { +void magiskhide_handler(int client, ucred *cred) { int req = read_int(client); int res = DAEMON_ERROR; @@ -62,6 +62,15 @@ void magiskhide_handler(int client) { case HIDE_STATUS: res = hide_enabled() ? HIDE_IS_ENABLED : HIDE_NOT_ENABLED; break; + case REMOTE_CHECK_HIDE: + res = check_uid_map(client); + break; + case REMOTE_DO_HIDE: + kill(cred->pid, SIGSTOP); + write_int(client, 0); + hide_daemon(cred->pid); + close(client); + return; } write_int(client, res); @@ -158,3 +167,26 @@ int magiskhide_main(int argc, char *argv[]) { return_code: return req == HIDE_STATUS ? (code == HIDE_IS_ENABLED ? 0 : 1) : code != DAEMON_SUCCESS; } + +int remote_check_hide(int uid, const char *process) { + int fd = connect_daemon(); + write_int(fd, MAGISKHIDE); + write_int(fd, REMOTE_CHECK_HIDE); + write_int(fd, uid); + write_string(fd, process); + int res = read_int(fd); + close(fd); + return res; +} + +void remote_request_hide() { + int fd = connect_daemon(); + write_int(fd, MAGISKHIDE); + write_int(fd, REMOTE_DO_HIDE); + + // Should receive SIGSTOP before reading anything + // During process stop, magiskd will cleanup our mount ns + read_int(fd); + + close(fd); +} diff --git a/native/jni/magiskhide/magiskhide.hpp b/native/jni/magiskhide/magiskhide.hpp index 7cb154277..061a35710 100644 --- a/native/jni/magiskhide/magiskhide.hpp +++ b/native/jni/magiskhide/magiskhide.hpp @@ -15,7 +15,7 @@ #define ISOLATED_MAGIC "isolated" // Global toggle for ptrace monitor -#define ENABLE_PTRACE_MONITOR 1 +#define ENABLE_PTRACE_MONITOR 0 // CLI entries int launch_magiskhide(bool late_props); @@ -23,6 +23,7 @@ int stop_magiskhide(); int add_list(int client); int rm_list(int client); void ls_list(int client); +int check_uid_map(int client); #if ENABLE_PTRACE_MONITOR // Process monitoring @@ -52,6 +53,8 @@ enum { RM_HIDELIST, LS_HIDELIST, HIDE_STATUS, + REMOTE_CHECK_HIDE, + REMOTE_DO_HIDE }; enum { diff --git a/native/jni/magiskpolicy/rules.cpp b/native/jni/magiskpolicy/rules.cpp index 6732d1e04..af90f5c95 100644 --- a/native/jni/magiskpolicy/rules.cpp +++ b/native/jni/magiskpolicy/rules.cpp @@ -106,13 +106,14 @@ void sepolicy::magisk_rules() { // Don't allow pesky processes to monitor audit deny logs when poking magisk daemon socket dontaudit(ALL, SEPOL_PROC_DOMAIN, "unix_stream_socket", ALL); - // Only allow client processes to connect to magisk daemon socket + // Only allow client processes and zygote to connect to magisk daemon socket allow(SEPOL_CLIENT_DOMAIN, SEPOL_PROC_DOMAIN, "unix_stream_socket", ALL); + allow("zygote", SEPOL_PROC_DOMAIN, "unix_stream_socket", ALL); } else { // Fallback to poking holes in sandbox as Android 4.3 to 7.1 set PR_SET_NO_NEW_PRIVS // Allow these processes to access MagiskSU - const char *clients[] { "init", "shell", "appdomain" }; + const char *clients[] { "init", "shell", "appdomain", "zygote" }; for (auto type : clients) { if (!exists(type)) continue;