Skip to content

Commit

Permalink
Refactor hookJniNativeMethods
Browse files Browse the repository at this point in the history
  • Loading branch information
yujincheng08 authored and Howard20181 committed Oct 14, 2023
1 parent 3d72657 commit 2817596
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 274 deletions.
1 change: 0 additions & 1 deletion native/src/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ LOCAL_SRC_FILES := \
zygisk/entry.cpp \
zygisk/main.cpp \
zygisk/hook.cpp \
zygisk/memory.cpp \
zygisk/native_bridge.cpp \
zygisk/deny/cli.cpp \
zygisk/deny/utils.cpp \
Expand Down
50 changes: 16 additions & 34 deletions native/src/zygisk/gen_jni_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand All @@ -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<JNINativeMethod> 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

Expand Down
185 changes: 63 additions & 122 deletions native/src/zygisk/hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,11 @@
#include <magisk.hpp>

#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;
using rust::MagiskD;
using rust::get_magiskd;

Expand All @@ -30,8 +26,7 @@ using rust::get_magiskd;

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 ReloadNativeBridge(const string &nb);

namespace {
Expand All @@ -52,14 +47,12 @@ enum {
// Global variables
vector<tuple<dev_t, ino_t, const char *, void **>> *plt_hook_list;
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
const android::NativeBridgeRuntimeCallbacks* runtime_callbacks;
bool should_unmap_zygisk = false;

// Current context
HookContext *g_ctx;
bitset<MAX_FD_SIZE> *g_allowed_fds = nullptr;
const JNINativeInterface *old_functions = nullptr;
JNINativeInterface *new_functions = nullptr;

#define DCL_PRE_POST(name) \
void name##_pre(); \
Expand Down Expand Up @@ -100,14 +93,7 @@ struct HookContext {

HookContext(JNIEnv *env, void *args) :
env(env), args{args}, magiskd(get_magiskd()), 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();

Expand Down Expand Up @@ -141,12 +127,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();
Expand Down Expand Up @@ -210,51 +190,68 @@ DCL_HOOK_FUNC(int, dlclose, void *handle) {
if (!kDone) {
ZLOGV("dlclose zygisk_loader\n");
kDone = true;
string nb = get_prop(NBPROP);
if (nb != ZYGISKLDR) {
ReloadNativeBridge(nb);
}
ReloadNativeBridge(get_prop(NBPROP));
}
[[clang::musttail]] return old_dlclose(handle);
}

DCL_HOOK_FUNC(bool, LoadNativeBridge, const char* nb_library_filename,
const android::NativeBridgeRuntimeCallbacks* runtime_cbs) {
ZLOGD("LoadNativeBridge: %s with cbs: %p\n", nb_library_filename, runtime_cbs);
runtime_callbacks = runtime_cbs;
auto len = strlen(ZYGISKLDR);
if (strlen(nb_library_filename) > len) {
return old_LoadNativeBridge(nb_library_filename + len, runtime_cbs);
}
return false;
}

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<JNINativeMethod> 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<JNINativeMethod[]>(total);
runtime_callbacks->getNativeMethods(env, clazz, old_methods.get(), total);
std::map<void *, JNINativeMethod *> 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<JNINativeMethod[]>(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<int>(hooks.size()));
}

ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
Expand Down Expand Up @@ -656,12 +653,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();
Expand Down Expand Up @@ -789,12 +780,13 @@ 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;
ino_t art_inode = 0;
dev_t art_dev = 0;

for (auto &map : lsplt::MapInfo::Scan()) {
if (map.path.ends_with("/libandroid_runtime.so")) {
Expand All @@ -803,13 +795,18 @@ void hook_functions() {
} else if (map.path.ends_with("/libnativebridge.so")) {
native_bridge_inode = map.inode;
native_bridge_dev = map.dev;
} else if (map.path.ends_with("/libart.so")) {
art_inode = map.inode;
art_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, strdup);
PLT_HOOK_REGISTER(art_dev, art_inode, LoadNativeBridge);
PLT_HOOK_REGISTER_SYM(art_dev, art_inode, "_ZN7android16LoadNativeBridgeEPKcPKNS_28NativeBridgeRuntimeCallbacksE", LoadNativeBridge);
PLT_HOOK_REGISTER_SYM(android_runtime_dev, android_runtime_inode, "__android_log_close", android_log_close);
hook_commit();

Expand Down Expand Up @@ -852,31 +849,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<JNINativeMethod[]>(
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<method_sig>(
dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));
Expand All @@ -897,7 +873,6 @@ static void hook_jni_env() {
return;
}
}

JavaVM *vm = nullptr;
jsize num = 0;
jint res = get_created_vms(&vm, 1, &num);
Expand All @@ -911,39 +886,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"
Loading

0 comments on commit 2817596

Please sign in to comment.