mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-10-19 07:52:23 +00:00
The name is Zygisk
This commit is contained in:
47
native/jni/zygisk/api.hpp
Normal file
47
native/jni/zygisk/api.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
struct SpecializeAppProcessArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jint &mount_external;
|
||||
jstring &se_info;
|
||||
jstring &nice_name;
|
||||
jstring &instruction_set;
|
||||
jstring &app_data_dir;
|
||||
|
||||
/* Optional */
|
||||
jboolean *is_child_zygote = nullptr;
|
||||
jboolean *is_top_app = nullptr;
|
||||
jobjectArray *pkg_data_info_list = nullptr;
|
||||
jobjectArray *whitelisted_data_info_list = nullptr;
|
||||
jboolean *mount_data_dirs = nullptr;
|
||||
jboolean *mount_storage_dirs = nullptr;
|
||||
|
||||
SpecializeAppProcessArgs(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jint &mount_external, jstring &se_info, jstring &nice_name,
|
||||
jstring &instruction_set, jstring &app_data_dir) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags),
|
||||
mount_external(mount_external), se_info(se_info), nice_name(nice_name),
|
||||
instruction_set(instruction_set), app_data_dir(app_data_dir) {}
|
||||
};
|
||||
|
||||
struct ForkSystemServerArgs {
|
||||
jint &uid;
|
||||
jint &gid;
|
||||
jintArray &gids;
|
||||
jint &runtime_flags;
|
||||
jlong &permitted_capabilities;
|
||||
jlong &effective_capabilities;
|
||||
|
||||
ForkSystemServerArgs(
|
||||
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
|
||||
jlong &permitted_capabilities, jlong &effective_capabilities) :
|
||||
uid(uid), gid(gid), gids(gids), runtime_flags(runtime_flags),
|
||||
permitted_capabilities(permitted_capabilities),
|
||||
effective_capabilities(effective_capabilities) {}
|
||||
};
|
152
native/jni/zygisk/entry.cpp
Normal file
152
native/jni/zygisk/entry.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <libgen.h>
|
||||
#include <dlfcn.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/sendfile.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <android/log.h>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static void *self_handle = nullptr;
|
||||
static atomic<int> active_threads = -1;
|
||||
|
||||
#define alog(prio) [](auto fmt, auto ap){ \
|
||||
return __android_log_vprint(ANDROID_LOG_##prio, "Magisk", fmt, ap); }
|
||||
static void inject_logging() {
|
||||
log_cb.d = alog(DEBUG);
|
||||
log_cb.i = alog(INFO);
|
||||
log_cb.w = alog(WARN);
|
||||
log_cb.e = alog(ERROR);
|
||||
log_cb.ex = nop_ex;
|
||||
}
|
||||
|
||||
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<thread_entry>(&dlclose), self_handle);
|
||||
active_threads--;
|
||||
}
|
||||
|
||||
static void *unload_first_stage(void *) {
|
||||
// Setup 1ms
|
||||
timespec ts = { .tv_sec = 0, .tv_nsec = 1000000L };
|
||||
|
||||
while (getenv(INJECT_ENV_1))
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
// Wait another 1ms to make sure all threads left our code
|
||||
nanosleep(&ts, nullptr);
|
||||
|
||||
unmap_all(INJECT_LIB_1);
|
||||
active_threads--;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make sure /proc/self/environ does not reveal our secrets
|
||||
// Copy all env to a contiguous memory and set the memory region as MM_ENV
|
||||
static void sanitize_environ() {
|
||||
static string env;
|
||||
|
||||
for (int i = 0; environ[i]; ++i) {
|
||||
if (str_starts(environ[i], INJECT_ENV_1 "="))
|
||||
continue;
|
||||
env += environ[i];
|
||||
env += '\0';
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
bool success = true;
|
||||
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_START, env.data(), 0, 0));
|
||||
success &= (0 <= prctl(PR_SET_MM, PR_SET_MM_ENV_END, env.data() + env.size(), 0, 0));
|
||||
if (success)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__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() {
|
||||
if (getenv(INJECT_ENV_2)) {
|
||||
inject_logging();
|
||||
LOGD("zygote: inject 2nd stage\n");
|
||||
active_threads = 1;
|
||||
unsetenv(INJECT_ENV_2);
|
||||
|
||||
// Get our own handle
|
||||
self_handle = dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
dlclose(self_handle);
|
||||
unlink(INJECT_LIB_2);
|
||||
|
||||
hook_functions();
|
||||
|
||||
// Some cleanup
|
||||
sanitize_environ();
|
||||
active_threads++;
|
||||
new_daemon_thread(&unload_first_stage);
|
||||
} else if (char *env = getenv(INJECT_ENV_1)) {
|
||||
inject_logging();
|
||||
LOGD("zygote: inject 1st stage\n");
|
||||
|
||||
if (env[0] == '1')
|
||||
unsetenv("LD_PRELOAD");
|
||||
else
|
||||
setenv("LD_PRELOAD", env, 1); // Restore original LD_PRELOAD
|
||||
|
||||
// Setup second stage
|
||||
setenv(INJECT_ENV_2, "1", 1);
|
||||
cp_afc(INJECT_LIB_1, INJECT_LIB_2);
|
||||
dlopen(INJECT_LIB_2, RTLD_LAZY);
|
||||
|
||||
unlink(INJECT_LIB_1);
|
||||
unsetenv(INJECT_ENV_1);
|
||||
}
|
||||
}
|
||||
|
||||
int app_process_main(int argc, char *argv[]) {
|
||||
inject_logging();
|
||||
char buf[4096];
|
||||
if (realpath("/proc/self/exe", buf) == nullptr)
|
||||
return 1;
|
||||
|
||||
int in = xopen(buf, O_RDONLY);
|
||||
int out = xopen(INJECT_LIB_1, O_WRONLY | O_CREAT, 0644);
|
||||
sendfile(out, in, nullptr, INT_MAX);
|
||||
close(in);
|
||||
close(out);
|
||||
|
||||
if (char *ld = getenv("LD_PRELOAD")) {
|
||||
char env[128];
|
||||
sprintf(env, "%s:" INJECT_LIB_1, ld);
|
||||
setenv("LD_PRELOAD", env, 1);
|
||||
setenv(INJECT_ENV_1, ld, 1); // Backup original LD_PRELOAD
|
||||
} else {
|
||||
setenv("LD_PRELOAD", INJECT_LIB_1, 1);
|
||||
setenv(INJECT_ENV_1, "1", 1);
|
||||
}
|
||||
|
||||
// Execute real app_process
|
||||
xumount2(buf, MNT_DETACH);
|
||||
execve(buf, argv, environ);
|
||||
return 1;
|
||||
}
|
199
native/jni/zygisk/gen_jni_hooks.py
Executable file
199
native/jni/zygisk/gen_jni_hooks.py
Executable file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
primitives = ['jint', 'jboolean', 'jlong']
|
||||
|
||||
class JType:
|
||||
def __init__(self, name, sig) -> None:
|
||||
self.name = name
|
||||
self.sig = sig
|
||||
|
||||
|
||||
class JArray(JType):
|
||||
def __init__(self, type) -> None:
|
||||
if type.name in primitives:
|
||||
name = type.name + 'Array'
|
||||
else:
|
||||
name = 'jobjectArray'
|
||||
super().__init__(name, '[' + type.sig)
|
||||
|
||||
|
||||
class Argument:
|
||||
def __init__(self, name, type, set_arg = False) -> None:
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.set_arg = set_arg
|
||||
|
||||
def cpp(self):
|
||||
return f'{self.type.name} {self.name}'
|
||||
|
||||
|
||||
class Method:
|
||||
def __init__(self, name, args) -> None:
|
||||
self.name = name
|
||||
self.args = args
|
||||
|
||||
def cpp(self):
|
||||
return ', '.join(map(lambda a: a.cpp(), self.args))
|
||||
|
||||
def name_list(self):
|
||||
return ', '.join(map(lambda a: a.name, self.args))
|
||||
|
||||
def jni(self):
|
||||
return ''.join(map(lambda a: a.type.sig, self.args))
|
||||
|
||||
|
||||
# Common types
|
||||
jint = JType('jint', 'I')
|
||||
jintArray = JArray(jint)
|
||||
jstring = JType('jstring', 'Ljava/lang/String;')
|
||||
jboolean = JType('jboolean', 'Z')
|
||||
jlong = JType('jlong', 'J')
|
||||
|
||||
# Common args
|
||||
uid = Argument('uid', jint)
|
||||
gid = Argument('gid', jint)
|
||||
gids = Argument('gids', jintArray)
|
||||
runtime_flags = Argument('runtime_flags', jint)
|
||||
rlimits = Argument('rlimits', JArray(jintArray))
|
||||
mount_external = Argument('mount_external', jint)
|
||||
se_info = Argument('se_info', jstring)
|
||||
nice_name = Argument('nice_name', jstring)
|
||||
fds_to_close = Argument('fds_to_close', jintArray)
|
||||
instruction_set = Argument('instruction_set', jstring)
|
||||
app_data_dir = Argument('app_data_dir', jstring)
|
||||
|
||||
# o
|
||||
fds_to_ignore = Argument('fds_to_ignore', jintArray)
|
||||
|
||||
# p
|
||||
is_child_zygote = Argument('is_child_zygote', jboolean, True)
|
||||
|
||||
# q_alt
|
||||
is_top_app = Argument('is_top_app', jboolean, True)
|
||||
|
||||
# r
|
||||
pkg_data_info_list = Argument('pkg_data_info_list', JArray(jstring), True)
|
||||
whitelisted_data_info_list = Argument('whitelisted_data_info_list', JArray(jstring), True)
|
||||
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,
|
||||
se_info, nice_name, fds_to_close, instruction_set, app_data_dir])
|
||||
|
||||
fork_o = Method('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,
|
||||
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,
|
||||
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,
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
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])
|
||||
|
||||
spec_q = Method('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,
|
||||
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,
|
||||
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])
|
||||
|
||||
server_m = Method('m', [uid, gid, gids, runtime_flags, rlimits,
|
||||
permitted_capabilities, effective_capabilities])
|
||||
|
||||
server_samsung_q = Method('samsung_q', [uid, gid, gids, runtime_flags, i1, i2, rlimits,
|
||||
permitted_capabilities, effective_capabilities])
|
||||
|
||||
|
||||
def ind(i):
|
||||
return '\n' + ' ' * i
|
||||
|
||||
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()}) {{'
|
||||
decl += ind(1) + 'HookContext ctx{};'
|
||||
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) + 'ctx.raw_args = &args;'
|
||||
decl += ind(1) + f'{base_name}_pre(&ctx, env, clazz);'
|
||||
decl += ind(1) + f'reinterpret_cast<decltype(&{func_name})>({base_name}_orig)('
|
||||
decl += ind(2) + f'env, clazz, {m.name_list()}'
|
||||
decl += ind(1) + ');'
|
||||
decl += ind(1) + f'{base_name}_post(&ctx, env, clazz);'
|
||||
decl += ret_stat
|
||||
decl += ind(0) + '}'
|
||||
|
||||
decl += ind(0) + f'const JNINativeMethod {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(1) + '},'
|
||||
decl += ind(0) + '};'
|
||||
decl += ind(0) + f'constexpr int {base_name}_methods_num = std::size({base_name}_methods);'
|
||||
decl += ind(0)
|
||||
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_spec():
|
||||
methods = [spec_q, spec_q_alt, spec_r, spec_samsung_q]
|
||||
return gen_definitions(methods, 'nativeSpecializeAppProcess')
|
||||
|
||||
def gen_server():
|
||||
methods = [server_m, server_samsung_q]
|
||||
return gen_definitions(methods, 'nativeForkSystemServer')
|
||||
|
||||
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())
|
397
native/jni/zygisk/hook.cpp
Normal file
397
native/jni/zygisk/hook.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
#include <dlfcn.h>
|
||||
#include <xhook.h>
|
||||
#include <utils.hpp>
|
||||
#include <flags.hpp>
|
||||
#include <daemon.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "api.hpp"
|
||||
|
||||
using namespace std;
|
||||
using jni_hook::hash_map;
|
||||
using jni_hook::tree_map;
|
||||
using xstring = jni_hook::string;
|
||||
|
||||
namespace {
|
||||
|
||||
struct HookContext {
|
||||
int pid;
|
||||
bool do_hide;
|
||||
union {
|
||||
SpecializeAppProcessArgs *args;
|
||||
ForkSystemServerArgs *server_args;
|
||||
void *raw_args;
|
||||
};
|
||||
};
|
||||
|
||||
// Global variables
|
||||
vector<tuple<const char *, const char *, void **>> *xhook_list;
|
||||
vector<JNINativeMethod> *jni_hook_list;
|
||||
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
|
||||
|
||||
HookContext *current_ctx;
|
||||
JavaVM *g_jvm;
|
||||
const JNINativeInterface *old_functions;
|
||||
JNINativeInterface *new_functions;
|
||||
|
||||
#define DCL_JNI_FUNC(name) \
|
||||
void *name##_orig; \
|
||||
void name##_pre(HookContext *ctx, JNIEnv *env, jclass clazz); \
|
||||
void name##_post(HookContext *ctx, JNIEnv *env, jclass clazz);
|
||||
|
||||
// JNI method declarations
|
||||
DCL_JNI_FUNC(nativeForkAndSpecialize)
|
||||
DCL_JNI_FUNC(nativeSpecializeAppProcess)
|
||||
DCL_JNI_FUNC(nativeForkSystemServer)
|
||||
|
||||
#undef DCL_JNI_FUNC
|
||||
|
||||
// JNI method definitions
|
||||
// Includes all 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]); \
|
||||
method##_orig = methods[i].fnPtr; \
|
||||
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; \
|
||||
}
|
||||
|
||||
unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(
|
||||
JNIEnv *env, const char *className, const JNINativeMethod *methods, int numMethods) {
|
||||
if (g_jvm == nullptr) {
|
||||
// Save for later unhooking
|
||||
env->GetJavaVM(&g_jvm);
|
||||
}
|
||||
|
||||
unique_ptr<JNINativeMethod[]> newMethods;
|
||||
int hooked = numeric_limits<int>::max();
|
||||
if (className == "com/android/internal/os/Zygote"sv) {
|
||||
hooked = 0;
|
||||
newMethods = make_unique<JNINativeMethod[]>(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;
|
||||
}
|
||||
|
||||
#undef HOOK_JNI
|
||||
|
||||
jclass gClassRef;
|
||||
jmethodID class_getName;
|
||||
string get_class_name(JNIEnv *env, jclass clazz) {
|
||||
if (!gClassRef) {
|
||||
jclass cls = env->FindClass("java/lang/Class");
|
||||
gClassRef = (jclass) env->NewGlobalRef(cls);
|
||||
env->DeleteLocalRef(cls);
|
||||
class_getName = env->GetMethodID(gClassRef, "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;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
#define DCL_HOOK_FUNC(ret, func, ...) \
|
||||
ret (*old_##func)(__VA_ARGS__); \
|
||||
ret new_##func(__VA_ARGS__)
|
||||
|
||||
jint jniRegisterNatives(
|
||||
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) {
|
||||
auto className = get_class_name(env, clazz);
|
||||
LOGD("hook: JNIEnv->RegisterNatives %s\n", className.data());
|
||||
auto newMethods = hookAndSaveJNIMethods(env, 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("hook: jniRegisterNativeMethods %s\n", className);
|
||||
auto newMethods = hookAndSaveJNIMethods(env, className, methods, numMethods);
|
||||
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(int, fork) {
|
||||
return current_ctx ? current_ctx->pid : old_fork();
|
||||
}
|
||||
|
||||
DCL_HOOK_FUNC(int, selinux_android_setcontext,
|
||||
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
|
||||
if (current_ctx && current_ctx->do_hide) {
|
||||
// Ask magiskd to hide ourselves before switching context
|
||||
// because magiskd socket is not accessible on Android 8.0+
|
||||
remote_request_hide();
|
||||
LOGD("hook: process successfully hidden\n");
|
||||
}
|
||||
return old_selinux_android_setcontext(uid, isSystemServer, seinfo, pkgname);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// The original android::AppRuntime virtual table
|
||||
void **gAppRuntimeVTable;
|
||||
|
||||
// This method is a trampoline for hooking JNIEnv->RegisterNatives
|
||||
void onVmCreated(void *self, JNIEnv* env) {
|
||||
LOGD("hook: AppRuntime::onVmCreated\n");
|
||||
|
||||
// Restore virtual table
|
||||
auto new_table = *reinterpret_cast<void***>(self);
|
||||
*reinterpret_cast<void***>(self) = gAppRuntimeVTable;
|
||||
delete[] new_table;
|
||||
|
||||
new_functions = new JNINativeInterface();
|
||||
memcpy(new_functions, env->functions, sizeof(*new_functions));
|
||||
new_functions->RegisterNatives = &jniRegisterNatives;
|
||||
|
||||
// Replace the function table in JNIEnv to hook RegisterNatives
|
||||
old_functions = env->functions;
|
||||
env->functions = new_functions;
|
||||
}
|
||||
|
||||
template<int N>
|
||||
void vtable_entry(void *self, JNIEnv* env) {
|
||||
// The first invocation will be onVmCreated
|
||||
onVmCreated(self, env);
|
||||
// Call original function
|
||||
reinterpret_cast<decltype(&onVmCreated)>(gAppRuntimeVTable[N])(self, env);
|
||||
}
|
||||
|
||||
// This method is a trampoline for swizzling android::AppRuntime vtable
|
||||
bool swizzled = false;
|
||||
DCL_HOOK_FUNC(void, setArgv0, void *self, const char *argv0, bool setProcName) {
|
||||
if (swizzled) {
|
||||
old_setArgv0(self, argv0, setProcName);
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("hook: AndroidRuntime::setArgv0\n");
|
||||
|
||||
// We don't know which entry is onVmCreated, so overwrite every one
|
||||
// We also don't know the size of the vtable, but 8 is more than enough
|
||||
auto new_table = new void*[8];
|
||||
new_table[0] = reinterpret_cast<void*>(&vtable_entry<0>);
|
||||
new_table[1] = reinterpret_cast<void*>(&vtable_entry<1>);
|
||||
new_table[2] = reinterpret_cast<void*>(&vtable_entry<2>);
|
||||
new_table[3] = reinterpret_cast<void*>(&vtable_entry<3>);
|
||||
new_table[4] = reinterpret_cast<void*>(&vtable_entry<4>);
|
||||
new_table[5] = reinterpret_cast<void*>(&vtable_entry<5>);
|
||||
new_table[6] = reinterpret_cast<void*>(&vtable_entry<6>);
|
||||
new_table[7] = reinterpret_cast<void*>(&vtable_entry<7>);
|
||||
|
||||
// Swizzle C++ vtable to hook virtual function
|
||||
gAppRuntimeVTable = *reinterpret_cast<void***>(self);
|
||||
*reinterpret_cast<void***>(self) = new_table;
|
||||
swizzled = true;
|
||||
|
||||
old_setArgv0(self, argv0, setProcName);
|
||||
}
|
||||
|
||||
#undef DCL_HOOK_FUNC
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void nativeSpecializeAppProcess_pre(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
current_ctx = ctx;
|
||||
const char *process = env->GetStringUTFChars(ctx->args->nice_name, nullptr);
|
||||
LOGD("hook: %s %s\n", __FUNCTION__, process);
|
||||
|
||||
if (ctx->args->mount_external != 0 /* TODO: Handle MOUNT_EXTERNAL_NONE cases */
|
||||
&& remote_check_hide(ctx->args->uid, process)) {
|
||||
ctx->do_hide = true;
|
||||
LOGI("hook: [%s] should be hidden\n", process);
|
||||
}
|
||||
|
||||
env->ReleaseStringUTFChars(ctx->args->nice_name, process);
|
||||
}
|
||||
|
||||
void nativeSpecializeAppProcess_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
current_ctx = nullptr;
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
|
||||
if (ctx->do_hide)
|
||||
self_unload();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
int sigmask(int how, int signum) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, signum);
|
||||
return sigprocmask(how, &set, nullptr);
|
||||
}
|
||||
|
||||
// Do our own fork before loading any 3rd party code
|
||||
// First block SIGCHLD, unblock after original fork is done
|
||||
#define PRE_FORK() \
|
||||
current_ctx = ctx; \
|
||||
sigmask(SIG_BLOCK, SIGCHLD); \
|
||||
ctx->pid = old_fork(); \
|
||||
if (ctx->pid != 0) \
|
||||
return;
|
||||
|
||||
// Unblock SIGCHLD in case the original method didn't
|
||||
#define POST_FORK() \
|
||||
current_ctx = nullptr; \
|
||||
sigmask(SIG_UNBLOCK, SIGCHLD); \
|
||||
if (ctx->pid != 0)\
|
||||
return;
|
||||
|
||||
void nativeForkAndSpecialize_pre(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
PRE_FORK();
|
||||
nativeSpecializeAppProcess_pre(ctx, env, clazz);
|
||||
}
|
||||
|
||||
void nativeForkAndSpecialize_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
POST_FORK();
|
||||
nativeSpecializeAppProcess_post(ctx, env, clazz);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void nativeForkSystemServer_pre(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
if (env->functions == new_functions) {
|
||||
// Restore JNIEnv
|
||||
env->functions = old_functions;
|
||||
if (gClassRef) {
|
||||
env->DeleteGlobalRef(gClassRef);
|
||||
gClassRef = nullptr;
|
||||
class_getName = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PRE_FORK();
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
void nativeForkSystemServer_post(HookContext *ctx, JNIEnv *env, jclass clazz) {
|
||||
POST_FORK();
|
||||
LOGD("hook: %s\n", __FUNCTION__);
|
||||
}
|
||||
|
||||
#undef PRE_FORK
|
||||
#undef POST_FORK
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
bool hook_refresh() {
|
||||
if (xhook_refresh(0) == 0) {
|
||||
xhook_clear();
|
||||
LOGI("hook: xhook success\n");
|
||||
return true;
|
||||
} else {
|
||||
LOGE("hook: xhook failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
return ret;
|
||||
}
|
||||
xhook_list->emplace_back(path, symbol, old_func);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(T *&p) { p = new T(); }
|
||||
|
||||
#define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \
|
||||
hook_register(PATH_REGEX, SYM, (void*) new_##NAME, (void **) &old_##NAME)
|
||||
|
||||
#define XHOOK_REGISTER(PATH_REGEX, NAME) \
|
||||
XHOOK_REGISTER_SYM(PATH_REGEX, #NAME, NAME)
|
||||
|
||||
#define ANDROID_RUNTIME ".*/libandroid_runtime.so$"
|
||||
#define APP_PROCESS "^/system/bin/app_process.*"
|
||||
|
||||
void hook_functions() {
|
||||
#ifdef MAGISK_DEBUG
|
||||
xhook_enable_debug(1);
|
||||
xhook_enable_sigsegv_protection(0);
|
||||
#endif
|
||||
default_new(xhook_list);
|
||||
default_new(jni_hook_list);
|
||||
default_new(jni_method_map);
|
||||
|
||||
XHOOK_REGISTER(ANDROID_RUNTIME, fork);
|
||||
XHOOK_REGISTER(ANDROID_RUNTIME, selinux_android_setcontext);
|
||||
XHOOK_REGISTER(ANDROID_RUNTIME, jniRegisterNativeMethods);
|
||||
hook_refresh();
|
||||
|
||||
// Remove unhooked methods
|
||||
xhook_list->erase(
|
||||
std::remove_if(xhook_list->begin(), xhook_list->end(),
|
||||
[](auto &t) { return *std::get<2>(t) == nullptr;}),
|
||||
xhook_list->end());
|
||||
|
||||
if (old_jniRegisterNativeMethods == nullptr) {
|
||||
LOGD("hook: jniRegisterNativeMethods not used\n");
|
||||
|
||||
// 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)
|
||||
constexpr char sig[] = "_ZN7android14AndroidRuntime21registerNativeMethodsEP7_JNIEnvPKcPK15JNINativeMethodi";
|
||||
*(void **) &old_jniRegisterNativeMethods = dlsym(RTLD_DEFAULT, sig);
|
||||
}
|
||||
}
|
||||
|
||||
bool unhook_functions() {
|
||||
JNIEnv* env;
|
||||
if (g_jvm->GetEnv(reinterpret_cast<void**>(&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_hook_list->empty() && old_jniRegisterNativeMethods(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;
|
||||
}
|
||||
delete jni_hook_list;
|
||||
|
||||
// Unhook xhook
|
||||
for (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;
|
||||
}
|
||||
}
|
||||
delete xhook_list;
|
||||
return hook_refresh();
|
||||
}
|
22
native/jni/zygisk/inject.hpp
Normal file
22
native/jni/zygisk/inject.hpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define INJECT_LIB_1 "/dev/tmp/magisk.1.so"
|
||||
#define INJECT_LIB_2 "/dev/tmp/magisk.2.so"
|
||||
#define INJECT_ENV_1 "MAGISK_INJ_1"
|
||||
#define INJECT_ENV_2 "MAGISK_INJ_2"
|
||||
|
||||
// Unmap all pages matching the name
|
||||
void unmap_all(const char *name);
|
||||
|
||||
// Get library name + offset (from start of ELF), given function address
|
||||
uintptr_t get_function_off(int pid, uintptr_t addr, char *lib);
|
||||
|
||||
// Get function address, given library name + offset
|
||||
uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off);
|
||||
|
||||
void self_unload();
|
||||
void hook_functions();
|
||||
bool unhook_functions();
|
269
native/jni/zygisk/jni_hooks.hpp
Normal file
269
native/jni/zygisk/jni_hooks.hpp
Normal file
@@ -0,0 +1,269 @@
|
||||
// Generated by gen_jni_hooks.py
|
||||
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_l)>(nativeForkAndSpecialize_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, fds_to_close, instruction_set, app_data_dir
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
jint nativeForkAndSpecialize_o(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, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
|
||||
HookContext ctx{};
|
||||
SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_o)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
jint nativeForkAndSpecialize_p(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, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_p)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
jint nativeForkAndSpecialize_q_alt(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, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
args.is_top_app = &is_top_app;
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_q_alt)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
jint nativeForkAndSpecialize_r(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, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_r)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
SpecializeAppProcessArgs args(uid, gid, gids, runtime_flags, mount_external, se_info, nice_name, instruction_set, app_data_dir);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_o)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
ctx.raw_args = &args;
|
||||
nativeForkAndSpecialize_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_p)>(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
|
||||
);
|
||||
nativeForkAndSpecialize_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
const JNINativeMethod nativeForkAndSpecialize_methods[] = {
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_l
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_o
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_p
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z)I",
|
||||
(void *) &nativeForkAndSpecialize_q_alt
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)I",
|
||||
(void *) &nativeForkAndSpecialize_r
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_m
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[ILjava/lang/String;Ljava/lang/String;I)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_n
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[ILjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_o
|
||||
},
|
||||
{
|
||||
"nativeForkAndSpecialize",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;[I[IZLjava/lang/String;Ljava/lang/String;)I",
|
||||
(void *) &nativeForkAndSpecialize_samsung_p
|
||||
},
|
||||
};
|
||||
constexpr int nativeForkAndSpecialize_methods_num = std::size(nativeForkAndSpecialize_methods);
|
||||
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
ctx.raw_args = &args;
|
||||
nativeSpecializeAppProcess_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q)>(nativeSpecializeAppProcess_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, is_child_zygote, instruction_set, app_data_dir
|
||||
);
|
||||
nativeSpecializeAppProcess_post(&ctx, env, clazz);
|
||||
}
|
||||
void nativeSpecializeAppProcess_q_alt(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, jboolean is_top_app) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
args.is_top_app = &is_top_app;
|
||||
ctx.raw_args = &args;
|
||||
nativeSpecializeAppProcess_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_q_alt)>(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
|
||||
);
|
||||
nativeSpecializeAppProcess_post(&ctx, env, clazz);
|
||||
}
|
||||
void nativeSpecializeAppProcess_r(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, jboolean is_top_app, jobjectArray pkg_data_info_list, jobjectArray whitelisted_data_info_list, jboolean mount_data_dirs, jboolean mount_storage_dirs) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
args.is_top_app = &is_top_app;
|
||||
args.pkg_data_info_list = &pkg_data_info_list;
|
||||
args.whitelisted_data_info_list = &whitelisted_data_info_list;
|
||||
args.mount_data_dirs = &mount_data_dirs;
|
||||
args.mount_storage_dirs = &mount_storage_dirs;
|
||||
ctx.raw_args = &args;
|
||||
nativeSpecializeAppProcess_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_r)>(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
|
||||
);
|
||||
nativeSpecializeAppProcess_post(&ctx, env, clazz);
|
||||
}
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
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;
|
||||
ctx.raw_args = &args;
|
||||
nativeSpecializeAppProcess_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeSpecializeAppProcess_samsung_q)>(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
|
||||
);
|
||||
nativeSpecializeAppProcess_post(&ctx, env, clazz);
|
||||
}
|
||||
const JNINativeMethod nativeSpecializeAppProcess_methods[] = {
|
||||
{
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
(void *) &nativeSpecializeAppProcess_q
|
||||
},
|
||||
{
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z)V",
|
||||
(void *) &nativeSpecializeAppProcess_q_alt
|
||||
},
|
||||
{
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;Ljava/lang/String;ZLjava/lang/String;Ljava/lang/String;Z[Ljava/lang/String;[Ljava/lang/String;ZZ)V",
|
||||
(void *) &nativeSpecializeAppProcess_r
|
||||
},
|
||||
{
|
||||
"nativeSpecializeAppProcess",
|
||||
"(II[II[[IILjava/lang/String;IILjava/lang/String;ZLjava/lang/String;Ljava/lang/String;)V",
|
||||
(void *) &nativeSpecializeAppProcess_samsung_q
|
||||
},
|
||||
};
|
||||
constexpr int nativeSpecializeAppProcess_methods_num = std::size(nativeSpecializeAppProcess_methods);
|
||||
|
||||
jint nativeForkSystemServer_m(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
|
||||
HookContext ctx{};
|
||||
ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkSystemServer_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_m)>(nativeForkSystemServer_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
|
||||
);
|
||||
nativeForkSystemServer_post(&ctx, env, clazz);
|
||||
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) {
|
||||
HookContext ctx{};
|
||||
ForkSystemServerArgs args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
|
||||
ctx.raw_args = &args;
|
||||
nativeForkSystemServer_pre(&ctx, env, clazz);
|
||||
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
|
||||
env, clazz, uid, gid, gids, runtime_flags, i1, i2, rlimits, permitted_capabilities, effective_capabilities
|
||||
);
|
||||
nativeForkSystemServer_post(&ctx, env, clazz);
|
||||
return ctx.pid;
|
||||
}
|
||||
const JNINativeMethod nativeForkSystemServer_methods[] = {
|
||||
{
|
||||
"nativeForkSystemServer",
|
||||
"(II[II[[IJJ)I",
|
||||
(void *) &nativeForkSystemServer_m
|
||||
},
|
||||
{
|
||||
"nativeForkSystemServer",
|
||||
"(II[IIII[[IJJ)I",
|
||||
(void *) &nativeForkSystemServer_samsung_q
|
||||
},
|
||||
};
|
||||
constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);
|
31
native/jni/zygisk/memory.cpp
Normal file
31
native/jni/zygisk/memory.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace jni_hook {
|
||||
|
||||
// We know our minimum alignment is WORD size (size of pointer)
|
||||
static constexpr size_t ALIGN = sizeof(long);
|
||||
|
||||
// 4MB is more than enough
|
||||
static constexpr size_t CAPACITY = (1 << 22);
|
||||
|
||||
// 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
|
40
native/jni/zygisk/memory.hpp
Normal file
40
native/jni/zygisk/memory.hpp
Normal 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
|
240
native/jni/zygisk/ptrace.cpp
Normal file
240
native/jni/zygisk/ptrace.cpp
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Original code: https://github.com/Chainfire/injectvm-binderjack/blob/master/app/src/main/jni/libinject/inject.cpp
|
||||
* The code is heavily modified and sublicensed to GPLv3 for incorporating into Magisk.
|
||||
*
|
||||
* Copyright (c) 2015, Simone 'evilsocket' Margaritelli
|
||||
* Copyright (c) 2015-2019, Jorrit 'Chainfire' Jongma
|
||||
* Copyright (c) 2021, John 'topjohnwu' Wu
|
||||
*
|
||||
* See original LICENSE file from the original project for additional details:
|
||||
* https://github.com/Chainfire/injectvm-binderjack/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
/*
|
||||
* NOTE:
|
||||
* The code in this file was originally planned to be used for some features,
|
||||
* but it turned out to be unsuitable for the task. However, this shall remain
|
||||
* in our arsenal in case it may be used in the future.
|
||||
*/
|
||||
|
||||
#include <elf.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/ptrace.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
#include "ptrace.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
#if defined(__arm__)
|
||||
#define CPSR_T_MASK (1u << 5)
|
||||
#define PARAMS_IN_REGS 4
|
||||
#elif defined(__aarch64__)
|
||||
#define CPSR_T_MASK (1u << 5)
|
||||
#define PARAMS_IN_REGS 8
|
||||
#define pt_regs user_pt_regs
|
||||
#define uregs regs
|
||||
#define ARM_pc pc
|
||||
#define ARM_sp sp
|
||||
#define ARM_cpsr pstate
|
||||
#define ARM_lr regs[30]
|
||||
#define ARM_r0 regs[0]
|
||||
#endif
|
||||
|
||||
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long data = xptrace(PTRACE_PEEKTEXT, pid, reinterpret_cast<void*>(addr + i));
|
||||
if (data < 0)
|
||||
return false;
|
||||
memcpy(static_cast<uint8_t *>(buf) + i, &data, std::min(len - i, sizeof(data)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len) {
|
||||
for (size_t i = 0; i < len; i += sizeof(long)) {
|
||||
long data = 0;
|
||||
memcpy(&data, static_cast<const uint8_t *>(buf) + i, std::min(len - i, sizeof(data)));
|
||||
if (xptrace(PTRACE_POKETEXT, pid, reinterpret_cast<void*>(addr + i), data) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get remote registers
|
||||
#define remote_getregs(regs) _remote_getregs(pid, regs)
|
||||
static void _remote_getregs(int pid, pt_regs *regs) {
|
||||
#if defined(__LP64__)
|
||||
uintptr_t regset = NT_PRSTATUS;
|
||||
iovec iov{};
|
||||
iov.iov_base = regs;
|
||||
iov.iov_len = sizeof(*regs);
|
||||
xptrace(PTRACE_GETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
|
||||
#else
|
||||
xptrace(PTRACE_GETREGS, pid, nullptr, regs);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Set remote registers
|
||||
#define remote_setregs(regs) _remote_setregs(pid, regs)
|
||||
static void _remote_setregs(int pid, pt_regs *regs) {
|
||||
#if defined(__LP64__)
|
||||
uintptr_t regset = NT_PRSTATUS;
|
||||
iovec iov{};
|
||||
iov.iov_base = regs;
|
||||
iov.iov_len = sizeof(*regs);
|
||||
xptrace(PTRACE_SETREGSET, pid, reinterpret_cast<void*>(regset), &iov);
|
||||
#else
|
||||
xptrace(PTRACE_SETREGS, pid, nullptr, regs);
|
||||
#endif
|
||||
}
|
||||
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va) {
|
||||
pt_regs regs, regs_bak;
|
||||
|
||||
// Get registers and save a backup
|
||||
remote_getregs(®s);
|
||||
memcpy(®s_bak, ®s, sizeof(regs));
|
||||
|
||||
// ABI dependent: Setup stack and registers to perform the call
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
// Fill R0-Rx with the first 4 (32-bit) or 8 (64-bit) parameters
|
||||
for (int i = 0; (i < nargs) && (i < PARAMS_IN_REGS); ++i) {
|
||||
regs.uregs[i] = va_arg(va, uintptr_t);
|
||||
}
|
||||
|
||||
// Push remaining parameters onto stack
|
||||
if (nargs > PARAMS_IN_REGS) {
|
||||
regs.ARM_sp -= sizeof(uintptr_t) * (nargs - PARAMS_IN_REGS);
|
||||
uintptr_t stack = regs.ARM_sp;
|
||||
for (int i = PARAMS_IN_REGS; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Set return address
|
||||
regs.ARM_lr = 0;
|
||||
|
||||
// Set function address to call
|
||||
regs.ARM_pc = func_addr;
|
||||
|
||||
// Setup the current processor status register
|
||||
if (regs.ARM_pc & 1u) {
|
||||
// thumb
|
||||
regs.ARM_pc &= (~1u);
|
||||
regs.ARM_cpsr |= CPSR_T_MASK;
|
||||
} else {
|
||||
// arm
|
||||
regs.ARM_cpsr &= ~CPSR_T_MASK;
|
||||
}
|
||||
#elif defined(__i386__)
|
||||
// Push all params onto stack
|
||||
regs.esp -= sizeof(uintptr_t) * nargs;
|
||||
uintptr_t stack = regs.esp;
|
||||
for (int i = 0; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
|
||||
// Push return address onto stack
|
||||
uintptr_t ret_addr = 0;
|
||||
regs.esp -= sizeof(uintptr_t);
|
||||
remote_write(regs.esp, &ret_addr, sizeof(uintptr_t));
|
||||
|
||||
// Set function address to call
|
||||
regs.eip = func_addr;
|
||||
#elif defined(__x86_64__)
|
||||
// Align, rsp - 8 must be a multiple of 16 at function entry point
|
||||
uintptr_t space = sizeof(uintptr_t);
|
||||
if (nargs > 6)
|
||||
space += sizeof(uintptr_t) * (nargs - 6);
|
||||
while (((regs.rsp - space - 8) & 0xF) != 0)
|
||||
regs.rsp--;
|
||||
|
||||
// Fill [RDI, RSI, RDX, RCX, R8, R9] with the first 6 parameters
|
||||
for (int i = 0; (i < nargs) && (i < 6); ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
switch (i) {
|
||||
case 0: regs.rdi = arg; break;
|
||||
case 1: regs.rsi = arg; break;
|
||||
case 2: regs.rdx = arg; break;
|
||||
case 3: regs.rcx = arg; break;
|
||||
case 4: regs.r8 = arg; break;
|
||||
case 5: regs.r9 = arg; break;
|
||||
}
|
||||
}
|
||||
|
||||
// Push remaining parameters onto stack
|
||||
if (nargs > 6) {
|
||||
regs.rsp -= sizeof(uintptr_t) * (nargs - 6);
|
||||
uintptr_t stack = regs.rsp;
|
||||
for(int i = 6; i < nargs; ++i) {
|
||||
uintptr_t arg = va_arg(va, uintptr_t);
|
||||
remote_write(stack, &arg, sizeof(uintptr_t));
|
||||
stack += sizeof(uintptr_t);
|
||||
}
|
||||
}
|
||||
|
||||
// Push return address onto stack
|
||||
uintptr_t ret_addr = 0;
|
||||
regs.rsp -= sizeof(uintptr_t);
|
||||
remote_write(regs.rsp, &ret_addr, sizeof(uintptr_t));
|
||||
|
||||
// Set function address to call
|
||||
regs.rip = func_addr;
|
||||
|
||||
// may be needed
|
||||
regs.rax = 0;
|
||||
regs.orig_rax = 0;
|
||||
#else
|
||||
#error Unsupported ABI
|
||||
#endif
|
||||
|
||||
// Resume process to do the call
|
||||
remote_setregs(®s);
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
|
||||
// Catch SIGSEGV caused by the 0 return address
|
||||
int status;
|
||||
while (waitpid(pid, &status, __WALL | __WNOTHREAD) == pid) {
|
||||
if (WIFSTOPPED(status) && (WSTOPSIG(status) == SIGSEGV))
|
||||
break;
|
||||
xptrace(PTRACE_CONT, pid);
|
||||
}
|
||||
|
||||
// Get registers again for return value
|
||||
remote_getregs(®s);
|
||||
|
||||
// Restore registers
|
||||
remote_setregs(®s_bak);
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
return regs.ARM_r0;
|
||||
#elif defined(__i386__)
|
||||
return regs.eax;
|
||||
#elif defined(__x86_64__)
|
||||
return regs.rax;
|
||||
#endif
|
||||
}
|
||||
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...) {
|
||||
char lib_name[4096];
|
||||
auto off = get_function_off(getpid(), addr, lib_name);
|
||||
if (off == 0)
|
||||
return 0;
|
||||
auto remote = get_function_addr(pid, lib_name, off);
|
||||
if (remote == 0)
|
||||
return 0;
|
||||
va_list va;
|
||||
va_start(va, nargs);
|
||||
auto result = remote_call_abi(pid, remote, nargs, va);
|
||||
va_end(va);
|
||||
return result;
|
||||
}
|
27
native/jni/zygisk/ptrace.hpp
Normal file
27
native/jni/zygisk/ptrace.hpp
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Write bytes to the remote process at addr
|
||||
bool _remote_write(int pid, uintptr_t addr, const void *buf, size_t len);
|
||||
#define remote_write(...) _remote_write(pid, __VA_ARGS__)
|
||||
|
||||
// Read bytes from the remote process at addr
|
||||
bool _remote_read(int pid, uintptr_t addr, void *buf, size_t len);
|
||||
#define remote_read(...) _remote_read(pid, __VA_ARGS__)
|
||||
|
||||
// Call a remote function
|
||||
// Arguments are expected to be only integer-like or pointer types
|
||||
// as other more complex C ABIs are not implemented.
|
||||
uintptr_t remote_call_abi(int pid, uintptr_t func_addr, int nargs, va_list va);
|
||||
|
||||
// Find remote offset and invoke function
|
||||
uintptr_t remote_call_vararg(int pid, uintptr_t addr, int nargs, ...);
|
||||
|
||||
// C++ wrapper for auto argument counting and casting function pointers
|
||||
template<class FuncPtr, class ...Args>
|
||||
static uintptr_t _remote_call(int pid, FuncPtr sym, Args && ...args) {
|
||||
auto addr = reinterpret_cast<uintptr_t>(sym);
|
||||
return remote_call_vararg(pid, addr, sizeof...(args), std::forward<Args>(args)...);
|
||||
}
|
||||
#define remote_call(...) _remote_call(pid, __VA_ARGS__)
|
100
native/jni/zygisk/utils.cpp
Normal file
100
native/jni/zygisk/utils.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include <cinttypes>
|
||||
#include <utils.hpp>
|
||||
|
||||
#include "inject.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
struct map_info {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
uintptr_t off;
|
||||
int perms;
|
||||
char *path;
|
||||
|
||||
map_info() : start(0), end(0), off(0), perms(0), path(nullptr) {}
|
||||
|
||||
enum {
|
||||
EXEC = (1 << 0),
|
||||
WRITE = (1 << 1),
|
||||
READ = (1 << 2),
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename Func>
|
||||
static void parse_maps(int pid, Func fn) {
|
||||
char file[32];
|
||||
|
||||
// format: start-end perms offset dev inode path
|
||||
sprintf(file, "/proc/%d/maps", pid);
|
||||
file_readline(true, file, [=](string_view l) -> bool {
|
||||
char *line = (char *) l.data();
|
||||
map_info info;
|
||||
char perm[5];
|
||||
int path_off;
|
||||
|
||||
if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %*x:%*x %*d %n%*s",
|
||||
&info.start, &info.end, perm, &info.off, &path_off) != 4)
|
||||
return true;
|
||||
|
||||
// Parse permissions
|
||||
if (perm[0] != '-')
|
||||
info.perms |= map_info::READ;
|
||||
if (perm[1] != '-')
|
||||
info.perms |= map_info::WRITE;
|
||||
if (perm[2] != '-')
|
||||
info.perms |= map_info::EXEC;
|
||||
|
||||
info.path = line + path_off;
|
||||
|
||||
return fn(info);
|
||||
});
|
||||
}
|
||||
|
||||
void unmap_all(const char *name) {
|
||||
vector<map_info> unmaps;
|
||||
parse_maps(getpid(), [=, &unmaps](map_info &info) -> bool {
|
||||
if (strcmp(info.path, name) == 0)
|
||||
unmaps.emplace_back(info);
|
||||
return true;
|
||||
});
|
||||
for (map_info &info : unmaps) {
|
||||
void *addr = reinterpret_cast<void *>(info.start);
|
||||
size_t size = info.end - info.start;
|
||||
munmap(addr, size);
|
||||
if (info.perms & map_info::READ) {
|
||||
// Make sure readable pages are still readable
|
||||
xmmap(addr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) {
|
||||
uintptr_t off = 0;
|
||||
parse_maps(pid, [=, &off](map_info &info) -> bool {
|
||||
if (addr >= info.start && addr < info.end) {
|
||||
if (lib)
|
||||
strcpy(lib, info.path);
|
||||
off = addr - info.start + info.off;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return off;
|
||||
}
|
||||
|
||||
uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) {
|
||||
uintptr_t addr = 0;
|
||||
parse_maps(pid, [=, &addr](map_info &info) -> bool {
|
||||
if (strcmp(info.path, lib) == 0 && (info.perms & map_info::EXEC)) {
|
||||
addr = info.start - info.off + off;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return addr;
|
||||
}
|
Reference in New Issue
Block a user