mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-22 07:57:39 +00:00
Obtain NativeBridgeRuntimeCallbacks for future use
NativeBridgeRuntimeCallbacks can be used for better JNI method hooking Co-authored-by: topjohnwu <topjohnwu@gmail.com>
This commit is contained in:
parent
cbc6d40b2c
commit
fce1bf2365
@ -29,7 +29,7 @@ static void hook_unloader();
|
|||||||
static void unhook_functions();
|
static void unhook_functions();
|
||||||
static void hook_jni_env();
|
static void hook_jni_env();
|
||||||
static void restore_jni_env(JNIEnv *env);
|
static void restore_jni_env(JNIEnv *env);
|
||||||
static void ReloadNativeBridge(const string &nb);
|
static void reload_native_bridge(const string &nb);
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@ -57,6 +57,7 @@ HookContext *g_ctx;
|
|||||||
bitset<MAX_FD_SIZE> *g_allowed_fds = nullptr;
|
bitset<MAX_FD_SIZE> *g_allowed_fds = nullptr;
|
||||||
const JNINativeInterface *old_functions = nullptr;
|
const JNINativeInterface *old_functions = nullptr;
|
||||||
JNINativeInterface *new_functions = nullptr;
|
JNINativeInterface *new_functions = nullptr;
|
||||||
|
const NativeBridgeRuntimeCallbacks *runtime_callbacks = nullptr;
|
||||||
|
|
||||||
#define DCL_PRE_POST(name) \
|
#define DCL_PRE_POST(name) \
|
||||||
void name##_pre(); \
|
void name##_pre(); \
|
||||||
@ -213,11 +214,7 @@ DCL_HOOK_FUNC(int, dlclose, void *handle) {
|
|||||||
if (!kDone) {
|
if (!kDone) {
|
||||||
ZLOGV("dlclose zygisk_loader\n");
|
ZLOGV("dlclose zygisk_loader\n");
|
||||||
kDone = true;
|
kDone = true;
|
||||||
string nb = get_prop(NBPROP);
|
reload_native_bridge(get_prop(NBPROP));
|
||||||
if (nb != ZYGISKLDR) {
|
|
||||||
auto orig_nb_path = nb.substr(sizeof(ZYGISKLDR) - 1);
|
|
||||||
ReloadNativeBridge(orig_nb_path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[[clang::musttail]] return old_dlclose(handle);
|
[[clang::musttail]] return old_dlclose(handle);
|
||||||
}
|
}
|
||||||
@ -730,6 +727,8 @@ void HookContext::nativeForkAndSpecialize_post() {
|
|||||||
inline void *unwind_get_region_start(_Unwind_Context *ctx) {
|
inline void *unwind_get_region_start(_Unwind_Context *ctx) {
|
||||||
auto fp = _Unwind_GetRegionStart(ctx);
|
auto fp = _Unwind_GetRegionStart(ctx);
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
|
// On arm32, we need to check if the pc is in thumb mode,
|
||||||
|
// if so, we need to set the lowest bit of fp to 1
|
||||||
auto pc = _Unwind_GetGR(ctx, 15); // r15 is pc
|
auto pc = _Unwind_GetGR(ctx, 15); // r15 is pc
|
||||||
if (pc & 1) {
|
if (pc & 1) {
|
||||||
// Thumb mode
|
// Thumb mode
|
||||||
@ -739,23 +738,87 @@ inline void *unwind_get_region_start(_Unwind_Context *ctx) {
|
|||||||
return reinterpret_cast<void *>(fp);
|
return reinterpret_cast<void *>(fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ReloadNativeBridge(const string &nb) {
|
// As we use NativeBridgeRuntimeCallbacks to reload native bridge and to hook jni functions,
|
||||||
// Use unwind to find the address of LoadNativeBridge
|
// we need to find it by the native bridge's unwind context.
|
||||||
// and call it to get NativeBridgeRuntimeCallbacks
|
// For abis that use registers to pass arguments, i.e. arm32, arm64, x86_64, the registers are
|
||||||
void *load_native_bridge = nullptr;
|
// caller-saved, and they are not preserved in the unwind context. However, they will be saved
|
||||||
|
// into the callee-saved registers, so we will search the callee-saved registers for the second
|
||||||
|
// argument, which is the pointer to NativeBridgeRuntimeCallbacks.
|
||||||
|
// For x86, whose abi uses stack to pass arguments, we can directly get the pointer to
|
||||||
|
// NativeBridgeRuntimeCallbacks from the stack.
|
||||||
|
static const NativeBridgeRuntimeCallbacks* find_runtime_callbacks(struct _Unwind_Context *ctx) {
|
||||||
|
// Find the writable memory region of libart.so, where the NativeBridgeRuntimeCallbacks is located.
|
||||||
|
auto [start, end] = []()-> tuple<uintptr_t, uintptr_t> {
|
||||||
|
for (const auto &map : lsplt::MapInfo::Scan()) {
|
||||||
|
if (map.path.ends_with("/libart.so") && map.perms == (PROT_WRITE | PROT_READ)) {
|
||||||
|
ZLOGV("libart.so: start=%p, end=%p\n",
|
||||||
|
reinterpret_cast<void *>(map.start), reinterpret_cast<void *>(map.end));
|
||||||
|
return {map.start, map.end};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {0, 0};
|
||||||
|
}();
|
||||||
|
#if defined(__aarch64__)
|
||||||
|
// r19-r28 are callee-saved registers
|
||||||
|
for (int i = 19; i <= 28; ++i) {
|
||||||
|
auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));
|
||||||
|
ZLOGV("r%d = %p\n", i, reinterpret_cast<void *>(val));
|
||||||
|
if (val >= start && val < end)
|
||||||
|
return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);
|
||||||
|
}
|
||||||
|
#elif defined(__arm__)
|
||||||
|
// r4-r10 are callee-saved registers
|
||||||
|
for (int i = 4; i <= 10; ++i) {
|
||||||
|
auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));
|
||||||
|
ZLOGV("r%d = %p\n", i, reinterpret_cast<void *>(val));
|
||||||
|
if (val >= start && val < end)
|
||||||
|
return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);
|
||||||
|
}
|
||||||
|
#elif defined(__i386__)
|
||||||
|
// get ebp, which points to the bottom of the stack frame
|
||||||
|
auto ebp = static_cast<uintptr_t>(_Unwind_GetGR(ctx, 5));
|
||||||
|
// 1 pointer size above ebp is the old ebp
|
||||||
|
// 2 pointer sizes above ebp is the return address
|
||||||
|
// 3 pointer sizes above ebp is the 2nd arg
|
||||||
|
auto val = *reinterpret_cast<uintptr_t *>(ebp + 3 * sizeof(void *));
|
||||||
|
ZLOGV("ebp + 3 * ptr_size = %p\n", reinterpret_cast<void *>(val));
|
||||||
|
if (val >= start && val < end)
|
||||||
|
return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);
|
||||||
|
#elif defined(__x86_64__)
|
||||||
|
// r12-r15 and rbx are callee-saved registers, but the compiler is likely to use them reversely
|
||||||
|
for (int i : {3, 15, 14, 13, 12}) {
|
||||||
|
auto val = static_cast<uintptr_t>(_Unwind_GetGR(ctx, i));
|
||||||
|
ZLOGV("r%d = %p\n", i, reinterpret_cast<void *>(val));
|
||||||
|
if (val >= start && val < end)
|
||||||
|
return reinterpret_cast<const NativeBridgeRuntimeCallbacks*>(val);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error "Unsupported architecture"
|
||||||
|
#endif
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reload_native_bridge(const string &nb) {
|
||||||
|
// Use unwind to find the address of android::LoadNativeBridge and NativeBridgeRuntimeCallbacks
|
||||||
|
bool (*load_native_bridge)(const char *nb_library_filename,
|
||||||
|
const NativeBridgeRuntimeCallbacks *runtime_cbs) = nullptr;
|
||||||
_Unwind_Backtrace(+[](struct _Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code {
|
_Unwind_Backtrace(+[](struct _Unwind_Context *ctx, void *arg) -> _Unwind_Reason_Code {
|
||||||
void *fp = unwind_get_region_start(ctx);
|
void *fp = unwind_get_region_start(ctx);
|
||||||
Dl_info info{};
|
Dl_info info{};
|
||||||
dladdr(fp, &info);
|
dladdr(fp, &info);
|
||||||
ZLOGV("backtrace: %p %s\n", fp, info.dli_fname ? info.dli_fname : "???");
|
ZLOGV("backtrace: %p %s\n", fp, info.dli_fname ? info.dli_fname : "???");
|
||||||
if (info.dli_fname && std::string_view(info.dli_fname).ends_with("/libart.so")) {
|
if (info.dli_fname && std::string_view(info.dli_fname).ends_with("/libnativebridge.so")) {
|
||||||
ZLOGV("LoadNativeBridge: %p\n", fp);
|
|
||||||
*reinterpret_cast<void **>(arg) = fp;
|
*reinterpret_cast<void **>(arg) = fp;
|
||||||
|
runtime_callbacks = find_runtime_callbacks(ctx);
|
||||||
|
ZLOGD("cbs: %p\n", runtime_callbacks);
|
||||||
return _URC_END_OF_STACK;
|
return _URC_END_OF_STACK;
|
||||||
}
|
}
|
||||||
return _URC_NO_REASON;
|
return _URC_NO_REASON;
|
||||||
}, &load_native_bridge);
|
}, &load_native_bridge);
|
||||||
reinterpret_cast<bool (*)(const string &)>(load_native_bridge)(nb);
|
auto len = sizeof(ZYGISKLDR) - 1;
|
||||||
|
if (nb.size() > len) {
|
||||||
|
load_native_bridge(nb.data() + len, runtime_callbacks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
||||||
|
@ -3,16 +3,9 @@
|
|||||||
#include <unwind.h>
|
#include <unwind.h>
|
||||||
|
|
||||||
#include <magisk.hpp>
|
#include <magisk.hpp>
|
||||||
|
#include "zygisk.hpp"
|
||||||
#include "../core/core.hpp"
|
#include "../core/core.hpp"
|
||||||
|
|
||||||
// The reference layout of this struct
|
|
||||||
// https://cs.android.com/android/platform/superproject/main/+/main:art/libnativebridge/include/nativebridge/native_bridge.h
|
|
||||||
struct NativeBridgeCallbacks {
|
|
||||||
uint32_t version;
|
|
||||||
void *padding[5];
|
|
||||||
bool (*isCompatibleWith)(uint32_t);
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool is_compatible_with(uint32_t) {
|
static bool is_compatible_with(uint32_t) {
|
||||||
auto name = get_prop(NBPROP);
|
auto name = get_prop(NBPROP);
|
||||||
android_dlextinfo info = {
|
android_dlextinfo info = {
|
||||||
|
@ -38,3 +38,19 @@ inline int zygisk_request(int req) {
|
|||||||
write_int(fd, req);
|
write_int(fd, req);
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The reference of the following structs
|
||||||
|
// https://cs.android.com/android/platform/superproject/main/+/main:art/libnativebridge/include/nativebridge/native_bridge.h
|
||||||
|
|
||||||
|
struct NativeBridgeRuntimeCallbacks {
|
||||||
|
const char* (*getMethodShorty)(JNIEnv* env, jmethodID mid);
|
||||||
|
uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz);
|
||||||
|
uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
|
||||||
|
uint32_t method_count);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct NativeBridgeCallbacks {
|
||||||
|
uint32_t version;
|
||||||
|
void *padding[5];
|
||||||
|
bool (*isCompatibleWith)(uint32_t);
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user