Load Zygisk modules

This commit is contained in:
topjohnwu
2021-10-13 04:52:02 -07:00
parent 27814e3015
commit c8ac6c07b0
10 changed files with 366 additions and 120 deletions

View File

@@ -172,7 +172,7 @@ struct Api {
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
void hookJniNativeMethods(const char *className, JNINativeMethod *methods, int numMethods);
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// For ELFs loaded in memory matching `regex`, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
@@ -241,7 +241,7 @@ struct api_table {
bool (*registerModule)(api_table *, module_abi *);
// Utility functions
void (*hookJniNativeMethods)(const char *, JNINativeMethod *, int);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
@@ -269,8 +269,8 @@ int Api::connectCompanion() {
void Api::forceDenyListUnmount() {
impl->forceDenyListUnmount(impl->_this);
}
void Api::hookJniNativeMethods(const char *className, JNINativeMethod *methods, int numMethods) {
impl->hookJniNativeMethods(className, methods, numMethods);
void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
impl->hookJniNativeMethods(env, className, methods, numMethods);
}
void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
impl->pltHookRegister(regex, symbol, newFunc, oldFunc);

View File

@@ -213,17 +213,21 @@ static int zygisk_log(int prio, const char *fmt, va_list ap) {
return ret;
}
void remote_get_app_info(int uid, const char *process, AppInfo *info) {
std::vector<int> remote_get_info(int uid, const char *process, AppInfo *info) {
vector<int> fds;
if (int fd = connect_daemon(); fd >= 0) {
write_int(fd, ZYGISK_REQUEST);
write_int(fd, ZYGISK_GET_APPINFO);
write_int(fd, ZYGISK_GET_INFO);
write_int(fd, uid);
write_string(fd, process);
xxread(fd, info, sizeof(*info));
if (!info->on_denylist) {
fds = recv_fds(fd);
}
close(fd);
}
return fds;
}
int remote_request_unmount() {
@@ -261,12 +265,12 @@ static void setup_files(int client, ucred *cred) {
int cached_manager_app_id = -1;
static time_t last_modified = 0;
static void get_app_info(int client) {
static void get_process_info(int client, ucred *cred) {
AppInfo info{};
int uid = read_int(client);
string process = read_string(client);
// This function is called on every single zygote app process specialization,
// This function is called on every single zygote process specialization,
// so performance is critical. get_manager_app_id() is expensive as it goes
// through a SQLite query and potentially multiple filesystem stats, so we
// really want to cache the app ID value. Check the last modify timestamp of
@@ -275,29 +279,40 @@ static void get_app_info(int client) {
// If denylist is enabled, inotify will invalidate the app ID cache for us.
// In this case, we can skip the timestamp check all together.
int manager_app_id = cached_manager_app_id;
if (uid != 1000) {
int manager_app_id = cached_manager_app_id;
// Denylist not enabled, check packages.xml timestamp
if (!denylist_enabled && manager_app_id > 0) {
struct stat st{};
stat("/data/system/packages.xml", &st);
if (st.st_atim.tv_sec > last_modified) {
manager_app_id = -1;
last_modified = st.st_atim.tv_sec;
// Denylist not enabled, check packages.xml timestamp
if (!denylist_enabled && manager_app_id > 0) {
struct stat st{};
stat("/data/system/packages.xml", &st);
if (st.st_atim.tv_sec > last_modified) {
manager_app_id = -1;
last_modified = st.st_atim.tv_sec;
}
}
if (manager_app_id < 0) {
manager_app_id = get_manager_app_id();
cached_manager_app_id = manager_app_id;
}
if (to_app_id(uid) == manager_app_id) {
info.is_magisk_app = true;
} else if (denylist_enabled) {
info.on_denylist = is_deny_target(uid, process);
}
}
if (manager_app_id < 0) {
manager_app_id = get_manager_app_id();
cached_manager_app_id = manager_app_id;
}
if (to_app_id(uid) == manager_app_id) {
info.is_magisk_app = true;
} else if (denylist_enabled) {
info.on_denylist = is_deny_target(uid, process);
}
xwrite(client, &info, sizeof(info));
if (!info.on_denylist) {
char buf[256];
snprintf(buf, sizeof(buf), "/proc/%d/exe", cred->pid);
xreadlink(buf, buf, sizeof(buf));
vector<int> fds = zygisk_module_fds(str_ends(buf, "64"));
send_fds(client, fds.data(), fds.size());
}
}
static void do_unmount(int client, ucred *cred) {
@@ -325,8 +340,8 @@ void zygisk_handler(int client, ucred *cred) {
case ZYGISK_SETUP:
setup_files(client, cred);
break;
case ZYGISK_GET_APPINFO:
get_app_info(client);
case ZYGISK_GET_INFO:
get_process_info(client, cred);
break;
case ZYGISK_UNMOUNT:
do_unmount(client, cred);

View File

@@ -8,7 +8,7 @@
#include "zygisk.hpp"
#include "memory.hpp"
#include "api.hpp"
#include "module.hpp"
using namespace std;
using jni_hook::hash_map;
@@ -22,57 +22,13 @@ using xstring = jni_hook::string;
namespace {
enum {
DENY_FLAG,
UNMOUNT_FLAG,
FORK_AND_SPECIALIZE,
APP_SPECIALIZE,
SERVER_SPECIALIZE,
FLAG_MAX
};
struct AppSpecializeArgsImpl {
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;
AppSpecializeArgsImpl(
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 ServerSpecializeArgsImpl {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgsImpl(
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) {}
};
#define DCL_PRE_POST(name) \
void name##_pre(); \
void name##_post();
@@ -88,14 +44,16 @@ struct HookContext {
int pid;
bitset<FLAG_MAX> flags;
AppInfo info;
vector<ZygiskModule> modules;
HookContext() : pid(-1), info{} {}
static void close_fds();
void toggle_unmount();
DCL_PRE_POST(fork)
DCL_PRE_POST(run_modules)
void run_modules_pre(const vector<int> &fds);
void run_modules_post();
DCL_PRE_POST(nativeForkAndSpecialize)
DCL_PRE_POST(nativeSpecializeAppProcess)
DCL_PRE_POST(nativeForkSystemServer)
@@ -184,7 +142,7 @@ DCL_HOOK_FUNC(int, fork) {
// This is the latest point where we can still connect to the magiskd main socket
DCL_HOOK_FUNC(int, selinux_android_setcontext,
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
if (g_ctx && g_ctx->flags[DENY_FLAG]) {
if (g_ctx && g_ctx->flags[UNMOUNT_FLAG]) {
if (remote_request_unmount() == 0) {
LOGD("zygisk: mount namespace cleaned up\n");
}
@@ -267,13 +225,121 @@ DCL_HOOK_FUNC(void, setArgv0, void *self, const char *argv0, bool setProcName) {
// -----------------------------------------------------------------
void HookContext::run_modules_pre() { /* TODO */ }
void HookContext::run_modules_post() { /* TODO */ }
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) {
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;
}
}
// No matching method found, set fnPtr to null
methods[i].fnPtr = nullptr;
}
if (hooks.empty())
return;
old_jniRegisterNativeMethods(env, clz, hooks.data(), hooks.size());
}
bool ZygiskModule::registerModule(ApiTable *table, long *module) {
long ver = *module;
// Unsupported version
if (ver > ZYGISK_API_VERSION)
return false;
// Set the actual module_abi*
table->module->ver = module;
// Fill in API accordingly with module API version
table->v1.hookJniNativeMethods = &hookJniNativeMethods;
table->v1.pltHookRegister = [](const char *path, const char *symbol, void *n, void **o) {
xhook_register(path, symbol, n, o);
};
table->v1.pltHookExclude = [](const char *path, const char *symbol) {
xhook_ignore(path, symbol);
};
table->v1.pltHookCommit = []() { return xhook_refresh(0) == 0; };
table->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
table->v1.forceDenyListUnmount = [](auto) { ZygiskModule::forceDenyListUnmount(); };
return true;
}
int ZygiskModule::connectCompanion() {
// TODO
(void) id;
return -1;
}
void ZygiskModule::forceDenyListUnmount() {
if (g_ctx == nullptr)
return;
g_ctx->toggle_unmount();
}
void HookContext::run_modules_pre(const vector<int> &fds) {
char buf[256];
for (int i = 0; i < fds.size(); ++i) {
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fds[i]);
if (void *h = dlopen(buf, RTLD_LAZY)) {
void (*module_entry)(void *, void *);
*(void **) &module_entry = dlsym(h, "zygisk_module_entry");
if (module_entry) {
modules.emplace_back(i);
auto api = new ApiTable(&modules.back());
module_entry(api, env);
}
}
close(fds[i]);
}
for (auto &m : modules) {
if (flags[APP_SPECIALIZE]) {
m.preAppSpecialize(args);
} else if (flags[SERVER_SPECIALIZE]) {
m.preServerSpecialize(server_args);
}
}
}
void HookContext::run_modules_post() {
for (auto &m : modules) {
if (flags[APP_SPECIALIZE]) {
m.postAppSpecialize(args);
} else if (flags[SERVER_SPECIALIZE]) {
m.postServerSpecialize(server_args);
}
}
}
void HookContext::close_fds() {
close(logd_fd.exchange(-1));
}
void HookContext::toggle_unmount() {
if (flags[APP_SPECIALIZE]) {
// TODO: Handle MOUNT_EXTERNAL_NONE
flags[UNMOUNT_FLAG] = args->mount_external != 0;
}
}
// -----------------------------------------------------------------
void HookContext::nativeSpecializeAppProcess_pre() {
@@ -286,14 +352,12 @@ void HookContext::nativeSpecializeAppProcess_pre() {
VLOG("zygisk: pre specialize [%s]\n", process);
}
remote_get_app_info(args->uid, process, &info);
/* TODO: Handle MOUNT_EXTERNAL_NONE */
if (args->mount_external != 0 && info.on_denylist) {
auto module_fds = remote_get_info(args->uid, process, &info);
if (info.on_denylist) {
LOGI("zygisk: [%s] is on the denylist\n", process);
flags[DENY_FLAG] = true;
toggle_unmount();
} else {
run_modules_pre();
run_modules_pre(module_fds);
}
}
@@ -305,7 +369,7 @@ void HookContext::nativeSpecializeAppProcess_post() {
}
env->ReleaseStringUTFChars(args->nice_name, process);
if (flags[DENY_FLAG]) {
if (info.on_denylist) {
self_unload();
} else {
run_modules_post();
@@ -325,7 +389,7 @@ void HookContext::nativeForkSystemServer_pre() {
flags[SERVER_SPECIALIZE] = true;
if (pid == 0) {
VLOG("zygisk: pre forkSystemServer\n");
run_modules_pre();
run_modules_pre(remote_get_info(1000, "system_server", &info));
close_fds();
}
}

View File

@@ -0,0 +1,117 @@
#pragma once
#include "api.hpp"
namespace {
using module_abi_v1 = zygisk::internal::module_abi;
struct HookContext;
struct ApiTable;
struct AppSpecializeArgsImpl {
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;
AppSpecializeArgsImpl(
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 ServerSpecializeArgsImpl {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgsImpl(
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) {}
};
template<typename T>
struct force_cast_wrapper {
template<typename U>
operator U() const { return reinterpret_cast<U>(mX); }
force_cast_wrapper(T &&x) : mX(std::forward<T>(x)) {}
force_cast_wrapper &operator=(const force_cast_wrapper &) = delete;
private:
T &&mX;
};
template<typename R>
force_cast_wrapper<R> force_cast(R &&x) {
return force_cast_wrapper<R>(std::forward<R>(x));
}
struct ZygiskModule {
void preAppSpecialize(AppSpecializeArgsImpl *args) {
v1->preAppSpecialize(v1->_this, force_cast(args));
}
void postAppSpecialize(const AppSpecializeArgsImpl *args) {
v1->postAppSpecialize(v1->_this, force_cast(args));
}
void preServerSpecialize(ServerSpecializeArgsImpl *args) {
v1->preServerSpecialize(v1->_this, force_cast(args));
}
void postServerSpecialize(const ServerSpecializeArgsImpl *args) {
v1->postServerSpecialize(v1->_this, force_cast(args));
}
int connectCompanion();
static void forceDenyListUnmount();
static bool registerModule(ApiTable *table, long *module);
ZygiskModule(int id) : id(id) {}
private:
int id;
union {
long *ver = nullptr;
module_abi_v1 *v1;
};
};
struct ApiTable {
ZygiskModule *module;
bool (*registerModule)(ApiTable *, long *);
union {
void *padding[6] = {};
struct {
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
int (*connectCompanion)(ZygiskModule *);
void (*forceDenyListUnmount)(ZygiskModule *);
} v1;
};
ApiTable(ZygiskModule *m) : module(m), registerModule(&ZygiskModule::registerModule) {}
};
} // namespace

View File

@@ -2,13 +2,14 @@
#include <stdint.h>
#include <jni.h>
#include <vector>
#define INJECT_ENV_1 "MAGISK_INJ_1"
#define INJECT_ENV_2 "MAGISK_INJ_2"
enum : int {
ZYGISK_SETUP,
ZYGISK_GET_APPINFO,
ZYGISK_GET_INFO,
ZYGISK_UNMOUNT,
ZYGISK_GET_LOG_PIPE,
};
@@ -33,5 +34,5 @@ struct AppInfo {
void self_unload();
void hook_functions();
bool unhook_functions();
void remote_get_app_info(int uid, const char *process, AppInfo *info);
std::vector<int> remote_get_info(int uid, const char *process, AppInfo *info);
int remote_request_unmount();