diff --git a/.gitmodules b/.gitmodules index 97db6d054..92ffedc27 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,6 +34,9 @@ [submodule "zlib"] path = native/jni/external/zlib url = https://android.googlesource.com/platform/external/zlib +[submodule "parallel-hashmap"] + path = native/jni/external/parallel-hashmap + url = https://github.com/greg7mdp/parallel-hashmap.git [submodule "termux-elf-cleaner"] path = tools/termux-elf-cleaner url = https://github.com/termux/termux-elf-cleaner.git diff --git a/native/jni/Android.mk b/native/jni/Android.mk index 134ae1d9f..741cce03e 100644 --- a/native/jni/Android.mk +++ b/native/jni/Android.mk @@ -5,13 +5,13 @@ LOCAL_PATH := $(call my-dir) ######################## # Global toggle for the WIP zygote injection features -ENABLE_INJECT := 0 +ENABLE_INJECT := 1 ifdef B_MAGISK include $(CLEAR_VARS) LOCAL_MODULE := magisk -LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils +LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils libphmap LOCAL_C_INCLUDES := jni/include LOCAL_SRC_FILES := \ @@ -42,7 +42,8 @@ LOCAL_STATIC_LIBRARIES += libxhook LOCAL_SRC_FILES += \ inject/entry.cpp \ inject/utils.cpp \ - inject/hook.cpp + inject/hook.cpp \ + inject/memory.cpp else LOCAL_SRC_FILES += magiskhide/proc_monitor.cpp endif @@ -146,7 +147,7 @@ ifneq (,$(wildcard jni/test.cpp)) include $(CLEAR_VARS) LOCAL_MODULE := test -LOCAL_STATIC_LIBRARIES := libutils +LOCAL_STATIC_LIBRARIES := libutils libphmap LOCAL_C_INCLUDES := jni/include LOCAL_SRC_FILES := test.cpp include $(BUILD_EXECUTABLE) diff --git a/native/jni/external/Android.mk b/native/jni/external/Android.mk index eae7fe334..4dab7ee39 100644 --- a/native/jni/external/Android.mk +++ b/native/jni/external/Android.mk @@ -1,5 +1,12 @@ LOCAL_PATH := $(call my-dir) +# Header only library +include $(CLEAR_VARS) +LOCAL_MODULE:= libphmap +LOCAL_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap +LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES) +include $(BUILD_STATIC_LIBRARY) + # libxz.a include $(CLEAR_VARS) LOCAL_MODULE:= libxz diff --git a/native/jni/external/parallel-hashmap b/native/jni/external/parallel-hashmap new file mode 160000 index 000000000..7684faf18 --- /dev/null +++ b/native/jni/external/parallel-hashmap @@ -0,0 +1 @@ +Subproject commit 7684faf186806e2c88554a78188c18185b21f127 diff --git a/native/jni/inject/entry.cpp b/native/jni/inject/entry.cpp index caebb2f60..c6b45b596 100644 --- a/native/jni/inject/entry.cpp +++ b/native/jni/inject/entry.cpp @@ -4,7 +4,6 @@ #include #include #include -#include #include @@ -25,28 +24,12 @@ static void inject_logging() { log_cb.ex = nop_ex; } -__attribute__((destructor)) -static void inject_cleanup() { - if (active_threads < 0) - return; - - // Setup 1ms - timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L }; - - // Check flag in busy loop - while (active_threads) - nanosleep(&ts, nullptr); - - // Wait another 1ms to make sure all threads left our code - nanosleep(&ts, nullptr); -} - void self_unload() { LOGD("hook: Request to self unload\n"); // If unhook failed, do not unload or else it will cause SIGSEGV if (!unhook_functions()) return; - new_daemon_thread(reinterpret_cast(&dlclose), self_handle); + new_daemon_thread(reinterpret_cast(&dlclose), self_handle); active_threads--; } @@ -86,6 +69,22 @@ static void sanitize_environ() { } } +__attribute__((destructor)) +static void inject_cleanup_wait() { + if (active_threads < 0) + return; + + // Setup 1ms + timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L }; + + // Check flag in busy loop + while (active_threads) + nanosleep(&ts, nullptr); + + // Wait another 1ms to make sure all threads left our code + nanosleep(&ts, nullptr); +} + __attribute__((constructor)) static void inject_init() { inject_logging(); diff --git a/native/jni/inject/hook.cpp b/native/jni/inject/hook.cpp index 6974629da..71aa41c09 100644 --- a/native/jni/inject/hook.cpp +++ b/native/jni/inject/hook.cpp @@ -1,82 +1,86 @@ -#include - #include #include #include #include #include "inject.hpp" +#include "memory.hpp" using namespace std; - -#define DCL_HOOK_FUNC(ret, func, ...) \ - static ret (*old_##func)(__VA_ARGS__); \ - static ret new_##func(__VA_ARGS__) - -#define DCL_JNI_FUNC(name) \ - static const JNINativeMethod *name##_orig = nullptr; \ - extern const JNINativeMethod name##_methods[]; \ - extern const int name##_methods_num; - -namespace { +using jni_hook::hash_map; +using jni_hook::tree_map; +using xstring = jni_hook::string; struct HookContext { int pid; bool do_hide; }; -// JNI method declarations -DCL_JNI_FUNC(nativeForkAndSpecialize) -DCL_JNI_FUNC(nativeSpecializeAppProcess) -DCL_JNI_FUNC(nativeForkSystemServer) - -} - -// For some reason static vectors won't work, use pointers instead static vector> *xhook_list; -static vector *jni_list; +static vector *jni_hook_list; +static hash_map>> *jni_method_map; static JavaVM *g_jvm; static int prev_fork_pid = -1; static HookContext *current_ctx; +#define DCL_HOOK_FUNC(ret, func, ...) \ + static ret (*old_##func)(__VA_ARGS__); \ + static ret new_##func(__VA_ARGS__) + +#define DCL_JNI_FUNC(name) \ + static int name##_orig_idx; \ + static inline JNINativeMethod &name##_orig() { \ + return (*jni_hook_list)[name##_orig_idx]; \ + } \ + extern const JNINativeMethod name##_methods[]; \ + extern const int name##_methods_num; + +namespace { +// JNI method declarations +DCL_JNI_FUNC(nativeForkAndSpecialize) +DCL_JNI_FUNC(nativeSpecializeAppProcess) +DCL_JNI_FUNC(nativeForkSystemServer) +} + #define HOOK_JNI(method) \ -if (newMethods[i].name == #method##sv) { \ - auto orig = new JNINativeMethod(); \ - memcpy(orig, &newMethods[i], sizeof(JNINativeMethod)); \ - method##_orig = orig; \ - jni_list->push_back(newMethods[i]); \ - for (int j = 0; j < method##_methods_num; ++j) { \ - if (strcmp(newMethods[i].signature, method##_methods[j].signature) == 0) { \ - newMethods[i] = method##_methods[j]; \ - LOGI("hook: replaced #" #method "\n"); \ - ++hooked; \ - break; \ - } \ - } \ - continue; \ +if (hooked < 3 && methods[i].name == #method##sv) { \ + jni_hook_list->push_back(methods[i]); \ + method##_orig_idx = jni_hook_list->size() - 1; \ + 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("hook: replaced #" #method "\n"); \ + ++hooked; \ + break; \ + } \ + } \ + continue; \ } DCL_HOOK_FUNC(int, jniRegisterNativeMethods, JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) { LOGD("hook: jniRegisterNativeMethods %s", className); - unique_ptr newMethods; - int hooked = 0; - if (g_jvm == nullptr) { // Save for later unhooking env->GetJavaVM(&g_jvm); } + 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); - for (int i = 0; i < numMethods && hooked < 3; ++i) { - HOOK_JNI(nativeForkAndSpecialize); - HOOK_JNI(nativeSpecializeAppProcess); - HOOK_JNI(nativeForkSystemServer); - } + } + + 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; + HOOK_JNI(nativeForkAndSpecialize); + HOOK_JNI(nativeSpecializeAppProcess); + HOOK_JNI(nativeForkSystemServer); } return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods); @@ -239,7 +243,8 @@ void hook_functions() { xhook_enable_sigsegv_protection(0); #endif xhook_list = new remove_pointer_t(); - jni_list = new remove_pointer_t(); + jni_hook_list = new remove_pointer_t(); + jni_method_map = new remove_pointer_t(); XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods); XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork); @@ -252,14 +257,19 @@ bool unhook_functions() { if (g_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return false; + // Do NOT call any destructors + operator delete(jni_method_map); + // Directly unmap the whole memory block + jni_hook::memory_block::release(); + // Unhook JNI methods - if (!jni_list->empty() && old_jniRegisterNativeMethods(env, + if (!jni_hook_list->empty() && old_jniRegisterNativeMethods(env, "com/android/internal/os/Zygote", - jni_list->data(), jni_list->size()) != 0) { + jni_hook_list->data(), jni_hook_list->size()) != 0) { LOGE("hook: Failed to register JNI hook\n"); return false; } - delete jni_list; + delete jni_hook_list; // Unhook xhook for (auto &[path, sym, old_func] : *xhook_list) { diff --git a/native/jni/inject/jni_hooks.hpp b/native/jni/inject/jni_hooks.hpp index d93b5e5bb..d7b2e21a4 100644 --- a/native/jni/inject/jni_hooks.hpp +++ b/native/jni/inject/jni_hooks.hpp @@ -1,5 +1,5 @@ /* - * Original code: https://github.com/RikkaApps/Riru/blob/master/riru/src/main/cpp/jni_native_method.cpp + * Original code from: https://github.com/RikkaApps/Riru * The code is modified and sublicensed to GPLv3 for incorporating into Magisk. * * Copyright (c) 2018-2021, RikkaW @@ -38,7 +38,7 @@ static ret name(__VA_ARGS__) #define orig_fork(ver, ...) \ reinterpret_cast \ - (nativeForkAndSpecialize_orig->fnPtr)(__VA_ARGS__) + (nativeForkAndSpecialize_orig().fnPtr)(__VA_ARGS__) #define post_fork() \ nativeForkAndSpecialize_post(&ctx, env, clazz); \ @@ -202,7 +202,7 @@ DCL_FORK_AND_SPECIALIZE(samsung_p, #define orig_spec(ver, ...) \ reinterpret_cast \ - (nativeSpecializeAppProcess_orig->fnPtr)(__VA_ARGS__) + (nativeSpecializeAppProcess_orig().fnPtr)(__VA_ARGS__) #define post_spec() \ nativeSpecializeAppProcess_post(&ctx, env, clazz) @@ -301,7 +301,7 @@ DCL_SPECIALIZE_APP(samsung_q, #define orig_server(ver, ...) \ reinterpret_cast \ - (nativeForkSystemServer_orig->fnPtr)(__VA_ARGS__) + (nativeForkSystemServer_orig().fnPtr)(__VA_ARGS__) #define post_server() \ nativeForkSystemServer_post(&ctx, env, clazz); \ @@ -355,8 +355,7 @@ const JNINativeMethod nativeSpecializeAppProcess_methods[] = { DEF_SPEC(r_dp2), DEF_SPEC(r_dp3) #endif }; -const int nativeSpecializeAppProcess_methods_num = std::size( - nativeSpecializeAppProcess_methods); +const int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods); const JNINativeMethod nativeForkSystemServer_methods[] = { DEF_SERVER(m), DEF_SERVER(samsung_q) diff --git a/native/jni/inject/memory.cpp b/native/jni/inject/memory.cpp new file mode 100644 index 000000000..7b77c996c --- /dev/null +++ b/native/jni/inject/memory.cpp @@ -0,0 +1,30 @@ +#include "memory.hpp" + +namespace jni_hook { + +// We know our minimum alignment is WORD size (size of pointer) +static constexpr size_t ALIGN = sizeof(long); + +static constexpr size_t CAPACITY = (1 << 24); + +// 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(do_align(sz, ALIGN)); +} + +void memory_block::release() { + if (_area) + munmap(_area, CAPACITY); +} + +} // namespace jni_hook diff --git a/native/jni/inject/memory.hpp b/native/jni/inject/memory.hpp new file mode 100644 index 000000000..35521ef1d --- /dev/null +++ b/native/jni/inject/memory.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#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/jni/utils/misc.hpp b/native/jni/utils/misc.hpp index 0854ba314..4e28b83d5 100644 --- a/native/jni/utils/misc.hpp +++ b/native/jni/utils/misc.hpp @@ -58,6 +58,21 @@ reversed_container reversed(T &base) { return reversed_container(base); } +template +class stateless_allocator { +public: + using value_type = T; + T *allocate(size_t num) { return static_cast(Impl::allocate(sizeof(T) * num)); } + void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); } + stateless_allocator() = default; + stateless_allocator(const stateless_allocator&) = default; + stateless_allocator(stateless_allocator&&) = default; + template + stateless_allocator(const stateless_allocator&) {} + bool operator==(const stateless_allocator&) { return true; } + bool operator!=(const stateless_allocator&) { return false; } +}; + int parse_int(const char *s); static inline int parse_int(const std::string &s) { return parse_int(s.data()); } static inline int parse_int(std::string_view s) { return parse_int(s.data()); }