From 5754782a4ea4bdd872b0e0024a0442b02bc71b72 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Fri, 20 Aug 2021 23:40:57 -0700 Subject: [PATCH] Generalize gen_jni_hooks.py --- build.gradle.kts | 2 +- native/jni/zygisk/gen_jni_hooks.py | 259 +++++++++++++++++++---------- native/jni/zygisk/hook.cpp | 198 ++++++++++------------ native/jni/zygisk/jni_hooks.hpp | 101 ++++++----- 4 files changed, 324 insertions(+), 236 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9620babf9..e56bebe56 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ buildscript { extra["vNav"] = vNav dependencies { - classpath("com.android.tools.build:gradle:7.0.0") + classpath("com.android.tools.build:gradle:7.0.1") classpath(kotlin("gradle-plugin", version = "1.5.21")) classpath("androidx.navigation:navigation-safe-args-gradle-plugin:${vNav}") diff --git a/native/jni/zygisk/gen_jni_hooks.py b/native/jni/zygisk/gen_jni_hooks.py index c68d80860..70f7595e0 100755 --- a/native/jni/zygisk/gen_jni_hooks.py +++ b/native/jni/zygisk/gen_jni_hooks.py @@ -3,44 +3,73 @@ primitives = ['jint', 'jboolean', 'jlong'] class JType: - def __init__(self, name, sig) -> None: - self.name = name - self.sig = sig + def __init__(self, cpp, jni): + self.cpp = cpp + self.jni = jni class JArray(JType): - def __init__(self, type) -> None: - if type.name in primitives: - name = type.name + 'Array' + def __init__(self, type): + if type.cpp in primitives: + name = type.cpp + 'Array' else: name = 'jobjectArray' - super().__init__(name, '[' + type.sig) + super().__init__(name, '[' + type.jni) class Argument: - def __init__(self, name, type, set_arg = False) -> None: + def __init__(self, name, type, set_arg = False): self.name = name self.type = type self.set_arg = set_arg def cpp(self): - return f'{self.type.name} {self.name}' + return f'{self.type.cpp} {self.name}' +# Args we don't care, give it an auto generated name +class Anon(Argument): + cnt = 0 + def __init__(self, type): + super().__init__(f'_{Anon.cnt}', type) + Anon.cnt += 1 + +class Return: + def __init__(self, value, type): + self.value = value + self.type = type class Method: - def __init__(self, name, args) -> None: + def __init__(self, name, ret, args): self.name = name + self.ret = ret self.args = args def cpp(self): - return ', '.join(map(lambda a: a.cpp(), self.args)) + return ', '.join(map(lambda x: x.cpp(), self.args)) def name_list(self): - return ', '.join(map(lambda a: a.name, self.args)) + return ', '.join(map(lambda x: x.name, self.args)) def jni(self): - return ''.join(map(lambda a: a.type.sig, self.args)) + args = ''.join(map(lambda x: x.type.jni, self.args)) + return f'({args}){self.ret.type.jni}' + def body(self): + return '' + +class JNIHook(Method): + def __init__(self, ver, ret, args): + name = f'{self.base_name()}_{ver}' + super().__init__(name, ret, args) + + def base_name(self): + return '' + + def orig_method(self): + return f'reinterpret_cast({self.base_name()}_orig)' + +def ind(i): + return '\n' + ' ' * i # Common types jint = JType('jint', 'I') @@ -48,6 +77,48 @@ jintArray = JArray(jint) jstring = JType('jstring', 'Ljava/lang/String;') jboolean = JType('jboolean', 'Z') jlong = JType('jlong', 'J') +void = JType('void', 'V') + +class ForkAndSpec(JNIHook): + def __init__(self, ver, args): + super().__init__(ver, Return('ctx.pid', jint), args) + + def base_name(self): + return 'nativeForkAndSpecialize' + + def init_args(self): + return 'SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);' + + def body(self): + decl = '' + decl += ind(1) + self.init_args() + for a in self.args: + if a.set_arg: + decl += ind(1) + f'args.{a.name} = &{a.name};' + decl += ind(1) + 'HookContext ctx;' + decl += ind(1) + 'ctx.env = env;' + decl += ind(1) + 'ctx.raw_args = &args;' + decl += ind(1) + f'ctx.{self.base_name()}_pre();' + decl += ind(1) + self.orig_method() + '(' + decl += ind(2) + f'env, clazz, {self.name_list()}' + decl += ind(1) + ');' + decl += ind(1) + f'ctx.{self.base_name()}_post();' + return decl + +class SpecApp(ForkAndSpec): + def __init__(self, ver, args): + super().__init__(ver, args) + self.ret = Return('', void) + + def base_name(self): + return 'nativeSpecializeAppProcess' + +class ForkServer(ForkAndSpec): + def base_name(self): + return 'nativeForkSystemServer' + + def init_args(self): + return 'ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' # Common args uid = Argument('uid', jint) @@ -77,125 +148,143 @@ whitelisted_data_info_list = Argument('whitelisted_data_info_list', JArray(jstri mount_data_dirs = Argument('mount_data_dirs', jboolean, True) mount_storage_dirs = Argument('mount_storage_dirs', jboolean, True) -# samsung (non-standard arguments) -i1 = Argument('i1', jint) -i2 = Argument('i2', jint) -i3 = Argument('i3', jint) - # server permitted_capabilities = Argument('permitted_capabilities', jlong) effective_capabilities = Argument('effective_capabilities', jlong) # Method definitions -fork_l = Method('l', [uid, gid, gids, runtime_flags, rlimits, mount_external, +fas_l = ForkAndSpec('l', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir]) -fork_o = Method('o', [uid, gid, gids, runtime_flags, rlimits, mount_external, +fas_o = ForkAndSpec('o', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) -fork_p = Method('p', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, +fas_p = ForkAndSpec('p', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir]) -fork_q_alt = Method('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, +fas_q_alt = ForkAndSpec('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app]) -fork_r = Method('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, +fas_r = ForkAndSpec('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, 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]) -fork_samsung_m = Method('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external, - se_info, i1, i2, nice_name, fds_to_close, instruction_set, app_data_dir]) +fas_samsung_m = ForkAndSpec('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir]) -fork_samsung_n = Method('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external, - se_info, i1, i2, nice_name, fds_to_close, instruction_set, app_data_dir, i3]) +fas_samsung_n = ForkAndSpec('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir, Anon(jint)]) -fork_samsung_o = Method('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external, - se_info, i1, i2, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) +fas_samsung_o = ForkAndSpec('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir]) -fork_samsung_p = Method('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external, - se_info, i1, i2, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir]) +fas_samsung_p = ForkAndSpec('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, is_child_zygote, + instruction_set, app_data_dir]) -spec_q = Method('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, +spec_q = SpecApp('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir]) -spec_q_alt = Method('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, +spec_q_alt = SpecApp('q_alt', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir, is_top_app]) -spec_r = Method('r', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, +spec_r = SpecApp('r', [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]) -spec_samsung_q = Method('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external, - se_info, i1, i2, nice_name, is_child_zygote, instruction_set, app_data_dir]) +spec_samsung_q = SpecApp('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external, + se_info, Anon(jint), Anon(jint), nice_name, is_child_zygote, instruction_set, app_data_dir]) -server_l = Method('l', [uid, gid, gids, runtime_flags, rlimits, +server_l = ForkServer('l', [uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities]) -server_samsung_q = Method('samsung_q', [uid, gid, gids, runtime_flags, i1, i2, rlimits, +server_samsung_q = ForkServer('samsung_q', [uid, gid, gids, runtime_flags, Anon(jint), Anon(jint), rlimits, permitted_capabilities, effective_capabilities]) +hook_map = {} -def ind(i): - return '\n' + ' ' * i +def gen_jni_def(clz, methods): + if clz not in hook_map: + hook_map[clz] = [] -def gen_definitions(methods, base_name): decl = '' - if base_name != 'nativeSpecializeAppProcess': - ret_stat = ind(1) + 'return ctx.pid;' - cpp_ret = 'jint' - jni_ret = 'I' - else: - ret_stat = '' - cpp_ret = 'void' - jni_ret = 'V' for m in methods: - func_name = f'{base_name}_{m.name}' - decl += ind(0) + f'{cpp_ret} {func_name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{' - if base_name == 'nativeForkSystemServer': - decl += ind(1) + 'ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);' - else: - decl += ind(1) + 'SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);' - for a in m.args: - if a.set_arg: - decl += ind(1) + f'args.{a.name} = &{a.name};' - decl += ind(1) + 'HookContext ctx;' - decl += ind(1) + 'ctx.env = env;' - decl += ind(1) + 'ctx.clazz = clazz;' - decl += ind(1) + 'ctx.raw_args = &args;' - decl += ind(1) + f'ctx.{base_name}_pre();' - decl += ind(1) + f'reinterpret_cast(HookContext::{base_name}_orig)(' - decl += ind(2) + f'env, clazz, {m.name_list()}' - decl += ind(1) + ');' - decl += ind(1) + f'ctx.{base_name}_post();' - decl += ret_stat + decl += ind(0) + f'{m.ret.type.cpp} {m.name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{' + decl += m.body() + if m.ret.value: + decl += ind(1) + f'return {m.ret.value};' decl += ind(0) + '}' - decl += ind(0) + f'const JNINativeMethod {base_name}_methods[] = {{' + decl += ind(0) + f'const JNINativeMethod {m.base_name()}_methods[] = {{' for m in methods: decl += ind(1) + '{' - decl += ind(2) + f'"{base_name}",' - decl += ind(2) + f'"({m.jni()}){jni_ret}",' - decl += ind(2) + f'(void *) &{base_name}_{m.name}' + 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'constexpr int {base_name}_methods_num = std::size({base_name}_methods);' + 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()) + return decl -def gen_fork(): - methods = [fork_l, fork_o, fork_p, fork_q_alt, fork_r, fork_samsung_m, fork_samsung_n, fork_samsung_o, fork_samsung_p] - return gen_definitions(methods, 'nativeForkAndSpecialize') +def gen_jni_hook(): + decl = '' + decl += ind(0) + 'unique_ptr hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {' + decl += ind(1) + 'unique_ptr newMethods;' + decl += ind(1) + 'int clz_id = -1;' + decl += ind(1) + 'int hook_cnt = 0;' + decl += ind(1) + 'do {' -def gen_spec(): - methods = [spec_q, spec_q_alt, spec_r, spec_samsung_q] - return gen_definitions(methods, 'nativeSpecializeAppProcess') + 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) + '}' -def gen_server(): - methods = [server_l, server_samsung_q] - return gen_definitions(methods, 'nativeForkSystemServer') + decl += ind(1) + '} while (false);' + + decl += ind(1) + 'if (hook_cnt) {' + decl += ind(2) + 'newMethods = make_unique(numMethods);' + decl += ind(2) + 'memcpy(newMethods.get(), 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) {' + decl += ind(2) + 'class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;' + decl += ind(2) + 'if (hook_cnt == 0) continue;' + + for index, methods in enumerate(hook_map.values()): + decl += ind(2) + f'if (clz_id == {index}) {{' + for m in methods: + decl += ind(3) + f'HOOK_JNI({m})' + decl += ind(3) + 'continue;' + decl += ind(2) + '}' + + decl += ind(1) + '}' + + decl += ind(1) + 'return newMethods;' + decl += ind(0) + '}' + return decl with open('jni_hooks.hpp', 'w') as f: f.write('// Generated by gen_jni_hooks.py\n') - f.write(gen_fork()) - f.write(gen_spec()) - f.write(gen_server()) + + zygote = 'com/android/internal/os/Zygote' + + methods = [fas_l, fas_o, fas_p, fas_q_alt, fas_r, fas_samsung_m, fas_samsung_n, fas_samsung_o, fas_samsung_p] + f.write(gen_jni_def(zygote, methods)) + + methods = [spec_q, spec_q_alt, spec_r, spec_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + methods = [server_l, server_samsung_q] + f.write(gen_jni_def(zygote, methods)) + + f.write(gen_jni_hook()) + + f.write('\n') diff --git a/native/jni/zygisk/hook.cpp b/native/jni/zygisk/hook.cpp index c63f8ec51..bc4e46c4b 100644 --- a/native/jni/zygisk/hook.cpp +++ b/native/jni/zygisk/hook.cpp @@ -15,25 +15,24 @@ using jni_hook::hash_map; using jni_hook::tree_map; using xstring = jni_hook::string; +// Extreme verbose logging +#define VLOG(...) LOGD(__VA_ARGS__) +//#define VLOG(...) + namespace { -#define DCL_JNI_FUNC(name) \ -static void *name##_orig; \ -void name##_pre(); \ -void name##_post(); - -#define DEF_STATIC(name) \ -void *HookContext::name##_orig = nullptr; - enum { DENY_FLAG, - APP_FORK, + FORK_AND_SPECIALIZE, FLAG_MAX }; +#define DCL_JNI_FUNC(name) \ +void name##_pre(); \ +void name##_post(); + struct HookContext { JNIEnv *env; - jclass clazz; union { SpecializeAppProcessArgs *args; ForkSystemServerArgs *server_args; @@ -43,23 +42,26 @@ struct HookContext { int pid; bitset flags; - HookContext() : process(nullptr), pid(0) {} + HookContext() : pid(-1) {} + + void pre_fork(); + static void post_fork(); - // JNI method declarations DCL_JNI_FUNC(nativeForkAndSpecialize) DCL_JNI_FUNC(nativeSpecializeAppProcess) DCL_JNI_FUNC(nativeForkSystemServer) }; -DEF_STATIC(nativeForkAndSpecialize) -DEF_STATIC(nativeSpecializeAppProcess) -DEF_STATIC(nativeForkSystemServer) #undef DCL_JNI_FUNC -#undef DEF_STATIC + +struct StringCmp { + using is_transparent = void; + bool operator()(string_view a, string_view b) const { return a < b; } +}; // Global variables vector> *xhook_list; -vector *jni_hook_list; +map, StringCmp> *jni_hook_list; hash_map>> *jni_method_map; // Current context @@ -67,46 +69,23 @@ HookContext *g_ctx; const JNINativeInterface *old_functions; JNINativeInterface *new_functions; -// JNI method definitions -// Includes method signatures of all supported Android versions -#include "jni_hooks.hpp" - #define HOOK_JNI(method) \ if (methods[i].name == #method##sv) { \ - jni_hook_list->push_back(methods[i]); \ - HookContext::method##_orig = methods[i].fnPtr; \ - for (int j = 0; j < method##_methods_num; ++j) { \ + for (int j = 0; j < method##_methods_num; ++j) { \ if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \ - newMethods[i] = method##_methods[j]; \ - LOGI("zygisk: replaced #" #method "\n"); \ - ++hooked; \ + jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \ + method##_orig = methods[i].fnPtr; \ + newMethods[i] = method##_methods[j]; \ + LOGI("zygisk: replaced %s#" #method "\n", className); \ + --hook_cnt; \ break; \ } \ } \ continue; \ } -unique_ptrhookAndSaveJNIMethods( - const char *className, const JNINativeMethod *methods, int numMethods) { - unique_ptr newMethods; - int hooked = numeric_limits::max(); - if (className == "com/android/internal/os/Zygote"sv) { - hooked = 0; - newMethods = make_unique(numMethods); - memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods); - } - - auto &class_map = (*jni_method_map)[className]; - for (int i = 0; i < numMethods; ++i) { - class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; - if (hooked < 3) { - HOOK_JNI(nativeForkAndSpecialize) - HOOK_JNI(nativeSpecializeAppProcess) - HOOK_JNI(nativeForkSystemServer) - } - } - return newMethods; -} +// JNI method hook definitions, auto generated +#include "jni_hooks.hpp" #undef HOOK_JNI @@ -133,29 +112,29 @@ string get_class_name(JNIEnv *env, jclass clazz) { ret (*old_##func)(__VA_ARGS__); \ ret new_##func(__VA_ARGS__) -jint jniRegisterNatives( +jint env_RegisterNatives( JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) { auto className = get_class_name(env, clazz); - LOGD("zygisk: JNIEnv->RegisterNatives [%s]\n", className.data()); + VLOG("zygisk: JNIEnv->RegisterNatives [%s]\n", className.data()); auto newMethods = hookAndSaveJNIMethods(className.data(), methods, numMethods); return old_functions->RegisterNatives(env, clazz, newMethods.get() ?: methods, numMethods); } DCL_HOOK_FUNC(int, jniRegisterNativeMethods, JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) { - LOGD("zygisk: jniRegisterNativeMethods [%s]\n", className); + VLOG("zygisk: jniRegisterNativeMethods [%s]\n", className); auto newMethods = hookAndSaveJNIMethods(className, methods, numMethods); return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods); } DCL_HOOK_FUNC(int, fork) { - return g_ctx ? g_ctx->pid : old_fork(); + return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork(); } DCL_HOOK_FUNC(int, selinux_android_setcontext, uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) { if (g_ctx && g_ctx->flags[DENY_FLAG]) { - // Ask magiskd to cleanup our mount namespace before switching context + // Request magiskd to cleanup our mount namespace before switching secontext // This is the latest point where we can still connect to the magiskd main socket if (remote_request_unmount() == 0) { LOGD("zygisk: mount namespace cleaned up\n"); @@ -180,7 +159,7 @@ void onVmCreated(void *self, JNIEnv* env) { new_functions = new JNINativeInterface(); memcpy(new_functions, env->functions, sizeof(*new_functions)); - new_functions->RegisterNatives = &jniRegisterNatives; + new_functions->RegisterNatives = &env_RegisterNatives; // Replace the function table in JNIEnv to hook RegisterNatives old_functions = env->functions; @@ -189,7 +168,7 @@ void onVmCreated(void *self, JNIEnv* env) { template void vtable_entry(void *self, JNIEnv* env) { - // The first invocation will be onVmCreated + // The first invocation will be onVmCreated. It will also restore the vtable. onVmCreated(self, env); // Call original function reinterpret_cast(gAppRuntimeVTable[N])(self, env); @@ -232,10 +211,10 @@ DCL_HOOK_FUNC(void, setArgv0, void *self, const char *argv0, bool setProcName) { void HookContext::nativeSpecializeAppProcess_pre() { g_ctx = this; process = env->GetStringUTFChars(args->nice_name, nullptr); - if (flags[APP_FORK]) { - LOGD("zygisk: pre app fork [%s]\n", process); + if (flags[FORK_AND_SPECIALIZE]) { + VLOG("zygisk: pre forkAndSpecialize [%s]\n", process); } else { - LOGD("zygisk: pre specialize [%s]\n", process); + VLOG("zygisk: pre specialize [%s]\n", process); } /* TODO: Handle MOUNT_EXTERNAL_NONE */ @@ -246,10 +225,10 @@ void HookContext::nativeSpecializeAppProcess_pre() { } void HookContext::nativeSpecializeAppProcess_post() { - if (flags[APP_FORK]) { - LOGD("zygisk: post app fork [%s]\n", process); + if (flags[FORK_AND_SPECIALIZE]) { + VLOG("zygisk: post forkAndSpecialize [%s]\n", process); } else { - LOGD("zygisk: post specialize [%s]\n", process); + VLOG("zygisk: post specialize [%s]\n", process); } env->ReleaseStringUTFChars(args->nice_name, process); @@ -258,6 +237,18 @@ void HookContext::nativeSpecializeAppProcess_post() { g_ctx = nullptr; } +void HookContext::nativeForkSystemServer_pre() { + pre_fork(); + if (pid) return; + VLOG("zygisk: pre forkSystemServer\n"); +} + +void HookContext::nativeForkSystemServer_post() { + run_finally f([]{ post_fork(); }); + if (pid) return; + VLOG("zygisk: post forkSystemServer\n"); +} + // ----------------------------------------------------------------- int sigmask(int how, int signum) { @@ -269,49 +260,34 @@ int sigmask(int how, int signum) { // Do our own fork before loading any 3rd party code // First block SIGCHLD, unblock after original fork is done -#define PRE_FORK() \ - g_ctx = this; \ - sigmask(SIG_BLOCK, SIGCHLD); \ - pid = old_fork(); \ - if (pid != 0) \ - return; +void HookContext::pre_fork() { + g_ctx = this; + sigmask(SIG_BLOCK, SIGCHLD); + pid = old_fork(); +} // Unblock SIGCHLD in case the original method didn't -#define POST_FORK() \ - run_finally f([]{g_ctx = nullptr;}); \ - sigmask(SIG_UNBLOCK, SIGCHLD); \ - if (pid != 0) \ - return; +void HookContext::post_fork() { + sigmask(SIG_UNBLOCK, SIGCHLD); + g_ctx = nullptr; +} void HookContext::nativeForkAndSpecialize_pre() { - PRE_FORK() - flags[APP_FORK] = true; - nativeSpecializeAppProcess_pre(); + pre_fork(); + flags[FORK_AND_SPECIALIZE] = true; + if (pid == 0) + nativeSpecializeAppProcess_pre(); } void HookContext::nativeForkAndSpecialize_post() { - POST_FORK() - nativeSpecializeAppProcess_post(); + if (pid == 0) + nativeSpecializeAppProcess_post(); + post_fork(); } -// ----------------------------------------------------------------- +} // namespace -void HookContext::nativeForkSystemServer_pre() { - PRE_FORK() - LOGD("zygisk: pre server fork\n"); -} - -void HookContext::nativeForkSystemServer_post() { - POST_FORK() - LOGD("zygisk: post server fork\n"); -} - -#undef PRE_FORK -#undef POST_FORK - -// ----------------------------------------------------------------- - -bool hook_refresh() { +static bool hook_refresh() { if (xhook_refresh(0) == 0) { xhook_clear(); LOGI("zygisk: xhook success\n"); @@ -322,7 +298,7 @@ bool hook_refresh() { } } -int hook_register(const char *path, const char *symbol, void *new_func, void **old_func) { +static int hook_register(const char *path, const char *symbol, void *new_func, void **old_func) { int ret = xhook_register(path, symbol, new_func, old_func); if (ret != 0) { LOGE("hook: Failed to register hook \"%s\"\n", symbol); @@ -332,8 +308,6 @@ int hook_register(const char *path, const char *symbol, void *new_func, void **o return 0; } -} // namespace - #define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \ hook_register(PATH_REGEX, SYM, (void*) new_##NAME, (void **) &old_##NAME) @@ -366,18 +340,20 @@ void hook_functions() { if (old_jniRegisterNativeMethods == nullptr) { LOGD("zygisk: jniRegisterNativeMethods not hooked, using fallback\n"); - // android::AndroidRuntime::setArgv0(const char *, bool) + // android::AndroidRuntime::setArgv0(const char*, bool) XHOOK_REGISTER_SYM(APP_PROCESS, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0); hook_refresh(); // We still need old_jniRegisterNativeMethods as other code uses it - // android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char *, const JNINativeMethod *, int) + // android::AndroidRuntime::registerNativeMethods(_JNIEnv*, const char*, const JNINativeMethod*, int) constexpr char sig[] = "_ZN7android14AndroidRuntime21registerNativeMethodsEP7_JNIEnvPKcPK15JNINativeMethodi"; *(void **) &old_jniRegisterNativeMethods = dlsym(RTLD_DEFAULT, sig); } } bool unhook_functions() { + bool success = true; + // Restore JNIEnv if (g_ctx->env->functions == new_functions) { g_ctx->env->functions = old_functions; @@ -394,21 +370,27 @@ bool unhook_functions() { jni_hook::memory_block::release(); // Unhook JNI methods - if (!jni_hook_list->empty() && old_jniRegisterNativeMethods(g_ctx->env, - "com/android/internal/os/Zygote", - jni_hook_list->data(), jni_hook_list->size()) != 0) { - LOGE("hook: Failed to register JNI hook\n"); - return false; + for (const auto &[clz, methods] : *jni_hook_list) { + if (!methods.empty() && old_jniRegisterNativeMethods( + g_ctx->env, clz.data(), methods.data(), methods.size()) != 0) { + LOGE("zygisk: Failed to restore JNI hook of class [%s]\n", clz.data()); + success = false; + } } delete jni_hook_list; // Unhook xhook - for (auto &[path, sym, old_func] : *xhook_list) { + for (const auto &[path, sym, old_func] : *xhook_list) { if (xhook_register(path, sym, *old_func, nullptr) != 0) { - LOGE("hook: Failed to register hook \"%s\"\n", sym); - return false; + LOGE("zygisk: Failed to register xhook [%s]\n", sym); + success = false; } } delete xhook_list; - return hook_refresh(); + if (!hook_refresh()) { + LOGE("zygisk: Failed to restore xhook\n"); + success = false; + } + + return success; } diff --git a/native/jni/zygisk/jni_hooks.hpp b/native/jni/zygisk/jni_hooks.hpp index 0fe01fdb0..3cc938c29 100644 --- a/native/jni/zygisk/jni_hooks.hpp +++ b/native/jni/zygisk/jni_hooks.hpp @@ -1,13 +1,13 @@ // Generated by gen_jni_hooks.py +void *nativeForkAndSpecialize_orig = nullptr; jint nativeForkAndSpecialize_l(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, jstring instruction_set, jstring app_data_dir) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( + reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); @@ -17,10 +17,9 @@ jint nativeForkAndSpecialize_o(JNIEnv *env, jclass clazz, jint uid, jint gid, ji SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( + reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); @@ -31,10 +30,9 @@ jint nativeForkAndSpecialize_p(JNIEnv *env, jclass clazz, jint uid, jint gid, ji args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( + reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); @@ -46,10 +44,9 @@ jint nativeForkAndSpecialize_q_alt(JNIEnv *env, jclass clazz, jint uid, jint gid args.is_top_app = &is_top_app; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( + reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir, is_top_app ); ctx.nativeForkAndSpecialize_post(); @@ -65,64 +62,59 @@ jint nativeForkAndSpecialize_r(JNIEnv *env, jclass clazz, jint uid, jint gid, ji args.mount_storage_dirs = &mount_storage_dirs; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( + reinterpret_cast(nativeForkAndSpecialize_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, fds_to_ignore, 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 ); ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint i1, jint i2, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { +jint nativeForkAndSpecialize_samsung_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( - env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, i1, i2, nice_name, fds_to_close, instruction_set, app_data_dir + reinterpret_cast(nativeForkAndSpecialize_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint i1, jint i2, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint i3) { +jint nativeForkAndSpecialize_samsung_n(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( - env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, i1, i2, nice_name, fds_to_close, instruction_set, app_data_dir, i3 + reinterpret_cast(nativeForkAndSpecialize_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4 ); ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint i1, jint i2, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { +jint nativeForkAndSpecialize_samsung_o(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( - env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, i1, i2, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir + reinterpret_cast(nativeForkAndSpecialize_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); return ctx.pid; } -jint nativeForkAndSpecialize_samsung_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint i1, jint i2, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { +jint nativeForkAndSpecialize_samsung_p(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkAndSpecialize_pre(); - reinterpret_cast(HookContext::nativeForkAndSpecialize_orig)( - env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, i1, i2, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir + reinterpret_cast(nativeForkAndSpecialize_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeForkAndSpecialize_post(); return ctx.pid; @@ -176,15 +168,15 @@ const JNINativeMethod nativeForkAndSpecialize_methods[] = { }; constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods); +void *nativeSpecializeAppProcess_orig = nullptr; 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) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeSpecializeAppProcess_pre(); - reinterpret_cast(HookContext::nativeSpecializeAppProcess_orig)( + reinterpret_cast(nativeSpecializeAppProcess_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeSpecializeAppProcess_post(); @@ -195,10 +187,9 @@ void nativeSpecializeAppProcess_q_alt(JNIEnv *env, jclass clazz, jint uid, jint args.is_top_app = &is_top_app; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeSpecializeAppProcess_pre(); - reinterpret_cast(HookContext::nativeSpecializeAppProcess_orig)( + reinterpret_cast(nativeSpecializeAppProcess_orig)( 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 ); ctx.nativeSpecializeAppProcess_post(); @@ -213,24 +204,22 @@ void nativeSpecializeAppProcess_r(JNIEnv *env, jclass clazz, jint uid, jint gid, args.mount_storage_dirs = &mount_storage_dirs; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeSpecializeAppProcess_pre(); - reinterpret_cast(HookContext::nativeSpecializeAppProcess_orig)( + reinterpret_cast(nativeSpecializeAppProcess_orig)( 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 ); ctx.nativeSpecializeAppProcess_post(); } -void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint i1, jint i2, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { +void nativeSpecializeAppProcess_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jint mount_external, jstring se_info, jint _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) { SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir); args.is_child_zygote = &is_child_zygote; HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeSpecializeAppProcess_pre(); - reinterpret_cast(HookContext::nativeSpecializeAppProcess_orig)( - env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, i1, i2, nice_name, is_child_zygote, instruction_set, app_data_dir + reinterpret_cast(nativeSpecializeAppProcess_orig)( + env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir ); ctx.nativeSpecializeAppProcess_post(); } @@ -258,28 +247,27 @@ const JNINativeMethod nativeSpecializeAppProcess_methods[] = { }; constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods); +void *nativeForkSystemServer_orig = nullptr; jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkSystemServer_pre(); - reinterpret_cast(HookContext::nativeForkSystemServer_orig)( + reinterpret_cast(nativeForkSystemServer_orig)( env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities ); ctx.nativeForkSystemServer_post(); return ctx.pid; } -jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint i1, jint i2, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { +jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) { ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities); HookContext ctx; ctx.env = env; - ctx.clazz = clazz; ctx.raw_args = &args; ctx.nativeForkSystemServer_pre(); - reinterpret_cast(HookContext::nativeForkSystemServer_orig)( - env, clazz, uid, gid, gids, runtime_flags, i1, i2, rlimits, permitted_capabilities, effective_capabilities + reinterpret_cast(nativeForkSystemServer_orig)( + env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities ); ctx.nativeForkSystemServer_post(); return ctx.pid; @@ -297,3 +285,32 @@ const JNINativeMethod nativeForkSystemServer_methods[] = { }, }; constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods); + +unique_ptr hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { + unique_ptr newMethods; + int clz_id = -1; + int hook_cnt = 0; + do { + if (className == "com/android/internal/os/Zygote"sv) { + clz_id = 0; + hook_cnt = 3; + break; + } + } while (false); + if (hook_cnt) { + newMethods = make_unique(numMethods); + memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods); + } + auto &class_map = (*jni_method_map)[className]; + for (int i = 0; i < numMethods; ++i) { + class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr; + if (hook_cnt == 0) continue; + if (clz_id == 0) { + HOOK_JNI(nativeForkAndSpecialize) + HOOK_JNI(nativeSpecializeAppProcess) + HOOK_JNI(nativeForkSystemServer) + continue; + } + } + return newMethods; +}