Store all native JNI methods in data structures

This commit is contained in:
topjohnwu 2021-08-01 14:35:16 -07:00
parent c59f8adc4a
commit 00a1e18959
10 changed files with 181 additions and 76 deletions

3
.gitmodules vendored
View File

@ -34,6 +34,9 @@
[submodule "zlib"] [submodule "zlib"]
path = native/jni/external/zlib path = native/jni/external/zlib
url = https://android.googlesource.com/platform/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"] [submodule "termux-elf-cleaner"]
path = tools/termux-elf-cleaner path = tools/termux-elf-cleaner
url = https://github.com/termux/termux-elf-cleaner.git url = https://github.com/termux/termux-elf-cleaner.git

View File

@ -5,13 +5,13 @@ LOCAL_PATH := $(call my-dir)
######################## ########################
# Global toggle for the WIP zygote injection features # Global toggle for the WIP zygote injection features
ENABLE_INJECT := 0 ENABLE_INJECT := 1
ifdef B_MAGISK ifdef B_MAGISK
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := magisk LOCAL_MODULE := magisk
LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils LOCAL_STATIC_LIBRARIES := libnanopb libsystemproperties libutils libphmap
LOCAL_C_INCLUDES := jni/include LOCAL_C_INCLUDES := jni/include
LOCAL_SRC_FILES := \ LOCAL_SRC_FILES := \
@ -42,7 +42,8 @@ LOCAL_STATIC_LIBRARIES += libxhook
LOCAL_SRC_FILES += \ LOCAL_SRC_FILES += \
inject/entry.cpp \ inject/entry.cpp \
inject/utils.cpp \ inject/utils.cpp \
inject/hook.cpp inject/hook.cpp \
inject/memory.cpp
else else
LOCAL_SRC_FILES += magiskhide/proc_monitor.cpp LOCAL_SRC_FILES += magiskhide/proc_monitor.cpp
endif endif
@ -146,7 +147,7 @@ ifneq (,$(wildcard jni/test.cpp))
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE := test LOCAL_MODULE := test
LOCAL_STATIC_LIBRARIES := libutils LOCAL_STATIC_LIBRARIES := libutils libphmap
LOCAL_C_INCLUDES := jni/include LOCAL_C_INCLUDES := jni/include
LOCAL_SRC_FILES := test.cpp LOCAL_SRC_FILES := test.cpp
include $(BUILD_EXECUTABLE) include $(BUILD_EXECUTABLE)

View File

@ -1,5 +1,12 @@
LOCAL_PATH := $(call my-dir) 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 # libxz.a
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_MODULE:= libxz LOCAL_MODULE:= libxz

@ -0,0 +1 @@
Subproject commit 7684faf186806e2c88554a78188c18185b21f127

View File

@ -4,7 +4,6 @@
#include <sys/sendfile.h> #include <sys/sendfile.h>
#include <sys/prctl.h> #include <sys/prctl.h>
#include <android/log.h> #include <android/log.h>
#include <atomic>
#include <utils.hpp> #include <utils.hpp>
@ -25,28 +24,12 @@ static void inject_logging() {
log_cb.ex = nop_ex; 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() { void self_unload() {
LOGD("hook: Request to self unload\n"); LOGD("hook: Request to self unload\n");
// If unhook failed, do not unload or else it will cause SIGSEGV // If unhook failed, do not unload or else it will cause SIGSEGV
if (!unhook_functions()) if (!unhook_functions())
return; return;
new_daemon_thread(reinterpret_cast<void *(*)(void *)>(&dlclose), self_handle); new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
active_threads--; 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)) __attribute__((constructor))
static void inject_init() { static void inject_init() {
inject_logging(); inject_logging();

View File

@ -1,82 +1,86 @@
#include <jni.h>
#include <xhook.h> #include <xhook.h>
#include <utils.hpp> #include <utils.hpp>
#include <flags.hpp> #include <flags.hpp>
#include <daemon.hpp> #include <daemon.hpp>
#include "inject.hpp" #include "inject.hpp"
#include "memory.hpp"
using namespace std; using namespace std;
using jni_hook::hash_map;
#define DCL_HOOK_FUNC(ret, func, ...) \ using jni_hook::tree_map;
static ret (*old_##func)(__VA_ARGS__); \ using xstring = jni_hook::string;
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 {
struct HookContext { struct HookContext {
int pid; int pid;
bool do_hide; 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<tuple<const char *, const char *, void **>> *xhook_list; static vector<tuple<const char *, const char *, void **>> *xhook_list;
static vector<JNINativeMethod> *jni_list; static vector<JNINativeMethod> *jni_hook_list;
static hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
static JavaVM *g_jvm; static JavaVM *g_jvm;
static int prev_fork_pid = -1; static int prev_fork_pid = -1;
static HookContext *current_ctx; 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) \ #define HOOK_JNI(method) \
if (newMethods[i].name == #method##sv) { \ if (hooked < 3 && methods[i].name == #method##sv) { \
auto orig = new JNINativeMethod(); \ jni_hook_list->push_back(methods[i]); \
memcpy(orig, &newMethods[i], sizeof(JNINativeMethod)); \ method##_orig_idx = jni_hook_list->size() - 1; \
method##_orig = orig; \ for (int j = 0; j < method##_methods_num; ++j) { \
jni_list->push_back(newMethods[i]); \ if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \
for (int j = 0; j < method##_methods_num; ++j) { \ newMethods[i] = method##_methods[j]; \
if (strcmp(newMethods[i].signature, method##_methods[j].signature) == 0) { \ LOGI("hook: replaced #" #method "\n"); \
newMethods[i] = method##_methods[j]; \ ++hooked; \
LOGI("hook: replaced #" #method "\n"); \ break; \
++hooked; \ } \
break; \ } \
} \ continue; \
} \
continue; \
} }
DCL_HOOK_FUNC(int, jniRegisterNativeMethods, DCL_HOOK_FUNC(int, jniRegisterNativeMethods,
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) { JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) {
LOGD("hook: jniRegisterNativeMethods %s", className); LOGD("hook: jniRegisterNativeMethods %s", className);
unique_ptr<JNINativeMethod[]> newMethods;
int hooked = 0;
if (g_jvm == nullptr) { if (g_jvm == nullptr) {
// Save for later unhooking // Save for later unhooking
env->GetJavaVM(&g_jvm); env->GetJavaVM(&g_jvm);
} }
unique_ptr<JNINativeMethod[]> newMethods;
int hooked = numeric_limits<int>::max();
if (className == "com/android/internal/os/Zygote"sv) { if (className == "com/android/internal/os/Zygote"sv) {
hooked = 0;
newMethods = make_unique<JNINativeMethod[]>(numMethods); newMethods = make_unique<JNINativeMethod[]>(numMethods);
memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods); memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);
for (int i = 0; i < numMethods && hooked < 3; ++i) { }
HOOK_JNI(nativeForkAndSpecialize);
HOOK_JNI(nativeSpecializeAppProcess); auto &class_map = (*jni_method_map)[className];
HOOK_JNI(nativeForkSystemServer); 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); return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
@ -239,7 +243,8 @@ void hook_functions() {
xhook_enable_sigsegv_protection(0); xhook_enable_sigsegv_protection(0);
#endif #endif
xhook_list = new remove_pointer_t<decltype(xhook_list)>(); xhook_list = new remove_pointer_t<decltype(xhook_list)>();
jni_list = new remove_pointer_t<decltype(jni_list)>(); jni_hook_list = new remove_pointer_t<decltype(jni_hook_list)>();
jni_method_map = new remove_pointer_t<decltype(jni_method_map)>();
XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods); XHOOK_REGISTER(".*\\libandroid_runtime.so$", jniRegisterNativeMethods);
XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork); XHOOK_REGISTER(".*\\libandroid_runtime.so$", fork);
@ -252,14 +257,19 @@ bool unhook_functions() {
if (g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) if (g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
return false; 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 // Unhook JNI methods
if (!jni_list->empty() && old_jniRegisterNativeMethods(env, if (!jni_hook_list->empty() && old_jniRegisterNativeMethods(env,
"com/android/internal/os/Zygote", "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"); LOGE("hook: Failed to register JNI hook\n");
return false; return false;
} }
delete jni_list; delete jni_hook_list;
// Unhook xhook // Unhook xhook
for (auto &[path, sym, old_func] : *xhook_list) { for (auto &[path, sym, old_func] : *xhook_list) {

View File

@ -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. * The code is modified and sublicensed to GPLv3 for incorporating into Magisk.
* *
* Copyright (c) 2018-2021, RikkaW * Copyright (c) 2018-2021, RikkaW
@ -38,7 +38,7 @@ static ret name(__VA_ARGS__)
#define orig_fork(ver, ...) \ #define orig_fork(ver, ...) \
reinterpret_cast<decltype(&nativeForkAndSpecialize_##ver)> \ reinterpret_cast<decltype(&nativeForkAndSpecialize_##ver)> \
(nativeForkAndSpecialize_orig->fnPtr)(__VA_ARGS__) (nativeForkAndSpecialize_orig().fnPtr)(__VA_ARGS__)
#define post_fork() \ #define post_fork() \
nativeForkAndSpecialize_post(&ctx, env, clazz); \ nativeForkAndSpecialize_post(&ctx, env, clazz); \
@ -202,7 +202,7 @@ DCL_FORK_AND_SPECIALIZE(samsung_p,
#define orig_spec(ver, ...) \ #define orig_spec(ver, ...) \
reinterpret_cast<decltype(&nativeSpecializeAppProcess_##ver)> \ reinterpret_cast<decltype(&nativeSpecializeAppProcess_##ver)> \
(nativeSpecializeAppProcess_orig->fnPtr)(__VA_ARGS__) (nativeSpecializeAppProcess_orig().fnPtr)(__VA_ARGS__)
#define post_spec() \ #define post_spec() \
nativeSpecializeAppProcess_post(&ctx, env, clazz) nativeSpecializeAppProcess_post(&ctx, env, clazz)
@ -301,7 +301,7 @@ DCL_SPECIALIZE_APP(samsung_q,
#define orig_server(ver, ...) \ #define orig_server(ver, ...) \
reinterpret_cast<decltype(&nativeForkSystemServer_##ver)> \ reinterpret_cast<decltype(&nativeForkSystemServer_##ver)> \
(nativeForkSystemServer_orig->fnPtr)(__VA_ARGS__) (nativeForkSystemServer_orig().fnPtr)(__VA_ARGS__)
#define post_server() \ #define post_server() \
nativeForkSystemServer_post(&ctx, env, clazz); \ nativeForkSystemServer_post(&ctx, env, clazz); \
@ -355,8 +355,7 @@ const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
DEF_SPEC(r_dp2), DEF_SPEC(r_dp3) DEF_SPEC(r_dp2), DEF_SPEC(r_dp3)
#endif #endif
}; };
const int nativeSpecializeAppProcess_methods_num = std::size( const int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods);
nativeSpecializeAppProcess_methods);
const JNINativeMethod nativeForkSystemServer_methods[] = { const JNINativeMethod nativeForkSystemServer_methods[] = {
DEF_SERVER(m), DEF_SERVER(samsung_q) DEF_SERVER(m), DEF_SERVER(samsung_q)

View File

@ -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<uint8_t *> _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<uint8_t *>(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

View File

@ -0,0 +1,40 @@
#pragma once
#include <map>
#include <parallel_hashmap/phmap.h>
#include <utils.hpp>
namespace jni_hook {
struct memory_block {
static void *allocate(size_t sz);
static void deallocate(void *, size_t) { /* Monotonic increase */ }
static void release();
};
template<class T>
using allocator = stateless_allocator<T, memory_block>;
using string = std::basic_string<char, std::char_traits<char>, allocator<char>>;
// Use node_hash_map since it will use less memory because we are using a monotonic allocator
template<class K, class V>
using hash_map = phmap::node_hash_map<K, V,
phmap::priv::hash_default_hash<K>,
phmap::priv::hash_default_eq<K>,
allocator<std::pair<const K, V>>
>;
template<class K, class V>
using tree_map = std::map<K, V,
std::less<K>,
allocator<std::pair<const K, V>>
>;
} // namespace jni_hook
// Provide heterogeneous lookup for jni_hook::string
namespace phmap::priv {
template <> struct HashEq<jni_hook::string> : StringHashEqT<char> {};
} // namespace phmap::priv

View File

@ -58,6 +58,21 @@ reversed_container<T> reversed(T &base) {
return reversed_container<T>(base); return reversed_container<T>(base);
} }
template<typename T, typename Impl>
class stateless_allocator {
public:
using value_type = T;
T *allocate(size_t num) { return static_cast<T*>(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 <typename U>
stateless_allocator(const stateless_allocator<U, Impl>&) {}
bool operator==(const stateless_allocator&) { return true; }
bool operator!=(const stateless_allocator&) { return false; }
};
int parse_int(const char *s); 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(const std::string &s) { return parse_int(s.data()); }
static inline int parse_int(std::string_view s) { return parse_int(s.data()); } static inline int parse_int(std::string_view s) { return parse_int(s.data()); }