You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Add inode plt hook APIs
This commit is contained in:
@@ -236,6 +236,14 @@ struct Api {
|
||||
// If `symbol` is nullptr, then all symbols will be excluded.
|
||||
void pltHookExclude(const char *regex, const char *symbol);
|
||||
|
||||
// For ELFs loaded in memory matching `inode`, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void pltHookRegisterInode(ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// For ELFs loaded in memory matching `inode`, exclude hooks registered for `symbol`.
|
||||
// If `symbol` is nullptr, then all symbols will be excluded.
|
||||
void pltHookExcludeInode(ino_t inode, const char *symbol);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
@@ -303,6 +311,8 @@ struct api_table {
|
||||
int (*getModuleDir)(void * /* impl */);
|
||||
uint32_t (*getFlags)(void * /* impl */);
|
||||
bool (*exemptFd)(int);
|
||||
void (*pltHookRegisterInode)(ino_t, const char *, void *, void **);
|
||||
void (*pltHookExcludeInode)(ino_t, const char *);
|
||||
};
|
||||
|
||||
template <class T>
|
||||
@@ -332,6 +342,12 @@ inline uint32_t Api::getFlags() {
|
||||
inline bool Api::exemptFd(int fd) {
|
||||
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
|
||||
}
|
||||
inline void Api::pltHookRegisterInode(ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
|
||||
if (tbl->pltHookExcludeInode) tbl->pltHookRegisterInode(inode, symbol, newFunc, oldFunc);
|
||||
}
|
||||
inline void Api::pltHookExcludeInode(ino_t inode, const char *symbol) {
|
||||
if (tbl->pltHookExcludeInode) tbl->pltHookExcludeInode(inode, symbol);
|
||||
}
|
||||
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
|
||||
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
|
||||
+95
-44
@@ -4,7 +4,7 @@
|
||||
#include <bitset>
|
||||
#include <list>
|
||||
|
||||
#include <xhook.h>
|
||||
#include <lsplt.hpp>
|
||||
|
||||
#include <base.hpp>
|
||||
#include <flags.h>
|
||||
@@ -22,7 +22,7 @@ using xstring = jni_hook::string;
|
||||
|
||||
// Extreme verbose logging
|
||||
//#define ZLOGV(...) ZLOGD(__VA_ARGS__)
|
||||
#define ZLOGV(...)
|
||||
#define ZLOGV(...) (void*)0
|
||||
|
||||
static bool unhook_functions();
|
||||
|
||||
@@ -81,7 +81,7 @@ struct HookContext {
|
||||
#undef DCL_PRE_POST
|
||||
|
||||
// Global variables
|
||||
vector<tuple<const char *, const char *, void **>> *xhook_list;
|
||||
vector<tuple<ino_t, const char *, void **>> *xhook_list;
|
||||
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
|
||||
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
|
||||
|
||||
@@ -328,25 +328,19 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
||||
switch (api_version) {
|
||||
case 4:
|
||||
api->v4.exemptFd = [](int fd) { return g_ctx != nullptr && g_ctx->exempt_fd(fd); };
|
||||
// fallthrough
|
||||
api->v4.pltHookRegisterInode = PltHookRegister;
|
||||
api->v4.pltHookExcludeInode = PltHookExclude;
|
||||
[[fallthrough]];
|
||||
case 3:
|
||||
case 2:
|
||||
api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };
|
||||
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
||||
// fallthrough
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
api->v1.hookJniNativeMethods = &hookJniNativeMethods;
|
||||
api->v1.pltHookRegister = [](const char *p, const char *s, void *n, void **o) {
|
||||
xhook_register(p, s, n, o);
|
||||
};
|
||||
api->v1.pltHookExclude = [](const char *p, const char *s) {
|
||||
xhook_ignore(p, s);
|
||||
};
|
||||
api->v1.pltHookCommit = [] {
|
||||
bool r = xhook_refresh(0) == 0;
|
||||
xhook_clear();
|
||||
return r;
|
||||
};
|
||||
api->v1.hookJniNativeMethods = hookJniNativeMethods;
|
||||
api->v1.pltHookRegister = PltHookRegister;
|
||||
api->v1.pltHookExclude = PltHookExclude;
|
||||
api->v1.pltHookCommit = CommitPltHook;
|
||||
api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
|
||||
api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };
|
||||
break;
|
||||
@@ -358,16 +352,74 @@ bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void ZygiskModule::PltHookRegister(const char* regex, const char *symbol, void *callback, void **backup) {
|
||||
if (regex == nullptr || symbol == nullptr || callback == nullptr)
|
||||
return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_lock);
|
||||
register_info.emplace_back(RegisterInfo{re, 0, symbol, callback, backup});
|
||||
}
|
||||
|
||||
void ZygiskModule::PltHookRegister(ino_t inode, const char *symbol, void *callback, void **backup) {
|
||||
if (inode == 0 || symbol == nullptr || callback == nullptr)
|
||||
return;
|
||||
mutex_guard lock(hook_lock);
|
||||
register_info.emplace_back(RegisterInfo{{}, inode, symbol, callback, backup});
|
||||
}
|
||||
|
||||
void ZygiskModule::PltHookExclude(const char* regex, const char *symbol) {
|
||||
if (!regex) return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_lock);
|
||||
ignore_info.emplace_back(IgnoreInfo{re, 0, symbol ? symbol : ""});
|
||||
}
|
||||
|
||||
void ZygiskModule::PltHookExclude(ino_t inode, const char *symbol) {
|
||||
if (inode == 0) return;
|
||||
mutex_guard lock(hook_lock);
|
||||
ignore_info.emplace_back(IgnoreInfo{{}, inode, symbol ? symbol : ""});
|
||||
}
|
||||
|
||||
bool ZygiskModule::CommitPltHook() {
|
||||
mutex_guard lock(hook_lock);
|
||||
for (auto &map: lsplt::MapInfo::Scan()) {
|
||||
if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;
|
||||
for (auto ®: register_info) {
|
||||
if ((reg.inode != 0 && reg.inode != map.inode)||
|
||||
(reg.inode == 0 && regexec(®.regex, map.path.c_str(), 0, nullptr, 0) != 0))
|
||||
continue;
|
||||
bool ignored = false;
|
||||
for (auto &ign: ignore_info) {
|
||||
if ((ign.inode != 0 && ign.inode != map.inode) ||
|
||||
(ign.inode == 0 && regexec(&ign.regex, map.path.c_str(), 0, nullptr, 0) != 0))
|
||||
continue;
|
||||
if (ign.symbol.empty() || ign.symbol == reg.symbol) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignored) {
|
||||
lsplt::RegisterHook(map.inode, reg.symbol, reg.callback, reg.backup);
|
||||
}
|
||||
}
|
||||
}
|
||||
register_info.clear();
|
||||
ignore_info.clear();
|
||||
return lsplt::CommitHook();
|
||||
}
|
||||
|
||||
|
||||
bool ZygiskModule::valid() const {
|
||||
if (mod.api_version == nullptr)
|
||||
return false;
|
||||
switch (*mod.api_version) {
|
||||
case 4:
|
||||
// fallthrough
|
||||
case 3:
|
||||
// fallthrough
|
||||
case 2:
|
||||
// fallthrough
|
||||
case 1:
|
||||
return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize &&
|
||||
mod.v1->preServerSpecialize && mod.v1->postServerSpecialize;
|
||||
@@ -697,8 +749,7 @@ void HookContext::nativeForkAndSpecialize_post() {
|
||||
} // namespace
|
||||
|
||||
static bool hook_refresh() {
|
||||
if (xhook_refresh(0) == 0) {
|
||||
xhook_clear();
|
||||
if (lsplt::CommitHook()) {
|
||||
return true;
|
||||
} else {
|
||||
ZLOGE("xhook failed\n");
|
||||
@@ -706,14 +757,12 @@ static bool hook_refresh() {
|
||||
}
|
||||
}
|
||||
|
||||
static 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) {
|
||||
static void hook_register(ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
||||
if (!lsplt::RegisterHook(inode, symbol, new_func, old_func)) {
|
||||
ZLOGE("Failed to register hook \"%s\"\n", symbol);
|
||||
return ret;
|
||||
return;
|
||||
}
|
||||
xhook_list->emplace_back(path, symbol, old_func);
|
||||
return 0;
|
||||
xhook_list->emplace_back(inode, symbol, old_func);
|
||||
}
|
||||
|
||||
#define XHOOK_REGISTER_SYM(PATH_REGEX, SYM, NAME) \
|
||||
@@ -722,23 +771,24 @@ static int hook_register(const char *path, const char *symbol, void *new_func, v
|
||||
#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() {
|
||||
#if 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, unshare);
|
||||
XHOOK_REGISTER(ANDROID_RUNTIME, jniRegisterNativeMethods);
|
||||
XHOOK_REGISTER(ANDROID_RUNTIME, selinux_android_setcontext);
|
||||
XHOOK_REGISTER_SYM(ANDROID_RUNTIME, "__android_log_close", android_log_close);
|
||||
ino_t android_runtime_inode = 0;
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.path.ends_with("libandroid_runtime.so")) {
|
||||
android_runtime_inode = map.inode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XHOOK_REGISTER(android_runtime_inode, fork);
|
||||
XHOOK_REGISTER(android_runtime_inode, unshare);
|
||||
XHOOK_REGISTER(android_runtime_inode, jniRegisterNativeMethods);
|
||||
XHOOK_REGISTER(android_runtime_inode, selinux_android_setcontext);
|
||||
XHOOK_REGISTER_SYM(android_runtime_inode, "__android_log_close", android_log_close);
|
||||
hook_refresh();
|
||||
|
||||
// Remove unhooked methods
|
||||
@@ -749,9 +799,10 @@ void hook_functions() {
|
||||
|
||||
if (old_jniRegisterNativeMethods == nullptr) {
|
||||
ZLOGD("jniRegisterNativeMethods not hooked, using fallback\n");
|
||||
|
||||
struct stat self_stat{};
|
||||
stat("/proc/self/exe", &self_stat);
|
||||
// android::AndroidRuntime::setArgv0(const char*, bool)
|
||||
XHOOK_REGISTER_SYM(APP_PROCESS, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0);
|
||||
XHOOK_REGISTER_SYM(self_stat.st_ino, "_ZN7android14AndroidRuntime8setArgv0EPKcb", setArgv0);
|
||||
hook_refresh();
|
||||
|
||||
// We still need old_jniRegisterNativeMethods as other code uses it
|
||||
@@ -785,8 +836,8 @@ static bool unhook_functions() {
|
||||
delete jni_hook_list;
|
||||
|
||||
// Unhook xhook
|
||||
for (const auto &[path, sym, old_func] : *xhook_list) {
|
||||
if (xhook_register(path, sym, *old_func, nullptr) != 0) {
|
||||
for (const auto &[inode, sym, old_func] : *xhook_list) {
|
||||
if (!lsplt::RegisterHook(inode, sym, *old_func, nullptr)) {
|
||||
ZLOGE("Failed to register xhook [%s]\n", sym);
|
||||
success = false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "api.hpp"
|
||||
#include <list>
|
||||
#include <regex.h>
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -140,6 +142,8 @@ struct api_abi_v2 : public api_abi_v1 {
|
||||
|
||||
struct api_abi_v4 : public api_abi_v2 {
|
||||
bool (*exemptFd)(int);
|
||||
void (*pltHookRegisterInode)(ino_t, const char *, void *, void **);
|
||||
void (*pltHookExcludeInode)(ino_t, const char *);
|
||||
};
|
||||
|
||||
union ApiTable {
|
||||
@@ -210,6 +214,29 @@ private:
|
||||
long *api_version;
|
||||
module_abi_v1 *v1;
|
||||
} mod;
|
||||
|
||||
struct RegisterInfo {
|
||||
regex_t regex;
|
||||
ino_t inode;
|
||||
std::string symbol;
|
||||
void *callback;
|
||||
void **backup;
|
||||
};
|
||||
|
||||
struct IgnoreInfo {
|
||||
regex_t regex;
|
||||
ino_t inode;
|
||||
std::string symbol;
|
||||
};
|
||||
inline static pthread_mutex_t hook_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
inline static std::list<RegisterInfo> register_info {};
|
||||
inline static std::list<IgnoreInfo> ignore_info {};
|
||||
|
||||
static void PltHookRegister(const char *lib, const char *symbol, void *callback, void **backup);
|
||||
static void PltHookRegister(ino_t inode, const char *symbol, void *callback, void **backup);
|
||||
static void PltHookExclude(const char *lib, const char *symbol);
|
||||
static void PltHookExclude(ino_t inode, const char *symbol);
|
||||
static bool CommitPltHook();
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
+25
-71
@@ -2,63 +2,23 @@
|
||||
#include <base.hpp>
|
||||
|
||||
#include "zygisk.hpp"
|
||||
#include <lsplt.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace {
|
||||
|
||||
struct map_info {
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
uintptr_t off;
|
||||
int perms;
|
||||
unsigned long inode;
|
||||
|
||||
map_info() : start(0), end(0), off(0), perms(0), inode(0) {}
|
||||
};
|
||||
|
||||
} // 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 %lu %n%*s",
|
||||
&info.start, &info.end, perm, &info.off, &info.inode, &path_off) != 5)
|
||||
return true;
|
||||
|
||||
// Parse permissions
|
||||
if (perm[0] != '-')
|
||||
info.perms |= PROT_READ;
|
||||
if (perm[1] != '-')
|
||||
info.perms |= PROT_WRITE;
|
||||
if (perm[2] != '-')
|
||||
info.perms |= PROT_EXEC;
|
||||
|
||||
return fn(info, line + path_off);
|
||||
});
|
||||
}
|
||||
|
||||
static vector<map_info> find_maps(const char *name) {
|
||||
vector<map_info> maps;
|
||||
parse_maps(getpid(), [=, &maps](const map_info &info, const char *path) -> bool {
|
||||
if (strcmp(path, name) == 0)
|
||||
maps.emplace_back(info);
|
||||
return true;
|
||||
});
|
||||
static vector<lsplt::MapInfo> find_maps(const char *name) {
|
||||
auto maps = lsplt::MapInfo::Scan();
|
||||
for (auto iter = maps.begin(); iter != maps.end();) {
|
||||
if (iter->path != name) {
|
||||
iter = maps.erase(iter);
|
||||
} else {
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
return maps;
|
||||
}
|
||||
|
||||
std::pair<void *, size_t> find_map_range(const char *name, unsigned long inode) {
|
||||
vector<map_info> maps = find_maps(name);
|
||||
auto maps = find_maps(name);
|
||||
uintptr_t start = 0u;
|
||||
uintptr_t end = 0u;
|
||||
for (const auto &map : maps) {
|
||||
@@ -76,8 +36,8 @@ std::pair<void *, size_t> find_map_range(const char *name, unsigned long inode)
|
||||
}
|
||||
|
||||
void unmap_all(const char *name) {
|
||||
vector<map_info> maps = find_maps(name);
|
||||
for (map_info &info : maps) {
|
||||
auto maps = find_maps(name);
|
||||
for (auto &info : maps) {
|
||||
void *addr = reinterpret_cast<void *>(info.start);
|
||||
size_t size = info.end - info.start;
|
||||
if (info.perms & PROT_READ) {
|
||||
@@ -91,8 +51,8 @@ void unmap_all(const char *name) {
|
||||
}
|
||||
|
||||
void remap_all(const char *name) {
|
||||
vector<map_info> maps = find_maps(name);
|
||||
for (map_info &info : maps) {
|
||||
auto maps = find_maps(name);
|
||||
for (auto &info : maps) {
|
||||
void *addr = reinterpret_cast<void *>(info.start);
|
||||
size_t size = info.end - info.start;
|
||||
void *copy = xmmap(nullptr, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
@@ -106,27 +66,21 @@ void remap_all(const char *name) {
|
||||
}
|
||||
|
||||
uintptr_t get_function_off(int pid, uintptr_t addr, char *lib) {
|
||||
uintptr_t off = 0;
|
||||
parse_maps(pid, [=, &off](const map_info &info, const char *path) -> bool {
|
||||
for (auto &info : lsplt::MapInfo::Scan()) {
|
||||
if (addr >= info.start && addr < info.end) {
|
||||
if (lib)
|
||||
strcpy(lib, path);
|
||||
off = addr - info.start + info.off;
|
||||
return false;
|
||||
strcpy(lib, info.path.data());
|
||||
return addr - info.start + info.offset;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return off;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uintptr_t get_function_addr(int pid, const char *lib, uintptr_t off) {
|
||||
uintptr_t addr = 0;
|
||||
parse_maps(pid, [=, &addr](const map_info &info, const char *path) -> bool {
|
||||
if (strcmp(path, lib) == 0 && (info.perms & PROT_EXEC)) {
|
||||
addr = info.start - info.off + off;
|
||||
return false;
|
||||
for (auto &info : lsplt::MapInfo::Scan()) {
|
||||
if (info.path == lib && (info.perms & PROT_EXEC)) {
|
||||
return info.start - info.offset + off;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return addr;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user