Restructure the native module

Consolidate all code into the src folder
This commit is contained in:
topjohnwu
2022-07-23 13:51:56 -07:00
parent c7c9fb9576
commit b9e89a1a2d
198 changed files with 52 additions and 45 deletions
+332
View File
@@ -0,0 +1,332 @@
// All content of this file is released to the public domain.
// This file is the public API for Zygisk modules.
// DO NOT use this file for developing Zygisk modules as it might contain WIP changes.
// Always use the following header for development as those are finalized APIs:
// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp
#pragma once
#include <jni.h>
#define ZYGISK_API_VERSION 3
/*
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM SERVER PROCESS, NOT THE ZYGOTE DAEMON!
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
static void example_handler(int socket) { ... }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This function is called when the module is loaded into the target process.
// A Zygisk API handle will be sent as an argument; call utility functions or interface
// with Zygisk through this handle.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This function is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
//
// All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized.
//
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
// get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This function is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this function runs as the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This function is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This function is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jobjectArray &rlimits;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jintArray *const fds_to_ignore;
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
//
// Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization.
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t {
// The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API functions will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
//
// This API only works in the pre[XXX]Specialize functions due to SELinux restrictions.
//
// The pre[XXX]Specialize functions run with the same privilege of zygote.
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
//
// The root companion process is ABI aware; that is, when calling this function from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
//
// This API only works in the pre[XXX]Specialize functions.
// Accessing the directory returned is only possible in the pre[XXX]Specialize functions
// or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID.
//
// Returns -1 if errors occurred.
int getModuleDir();
// Set various options for your module.
// Please note that this function accepts one single option at a time.
// Check zygisk::Option for the full list of options available.
void setOption(Option opt);
// Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags();
// Hook JNI native methods for a class
//
// Lookup all registered JNI native methods and replace it with your own functions.
// 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(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`.
void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc);
// For ELFs loaded in memory matching `regex`, exclude hooks registered for `symbol`.
// If `symbol` is nullptr, then all symbols will be excluded.
void pltHookExclude(const char *regex, const char *symbol);
// Commit all the hooks that was previously registered.
// Returns false if an error occurred.
bool pltHookCommit();
private:
internal::api_table *impl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz) \
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
zygisk::internal::entry_impl<clazz>(table, env); \
}
// Register a root companion request handler function for your module
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have a globally shared resource.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/************************************************************************************
* All the code after this point is internal code used to interface with Zygisk
* and guarantee ABI stability. You do not have to understand what it is doing.
************************************************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *_this;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), _this(module) {
preAppSpecialize = [](auto self, auto args) { self->preAppSpecialize(args); };
postAppSpecialize = [](auto self, auto args) { self->postAppSpecialize(args); };
preServerSpecialize = [](auto self, auto args) { self->preServerSpecialize(args); };
postServerSpecialize = [](auto self, auto args) { self->postServerSpecialize(args); };
}
};
struct api_table {
// These first 2 entries are permanent, shall never change
void *_this;
bool (*registerModule)(api_table *, module_abi *);
// Utility functions
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
// Zygisk functions
int (*connectCompanion)(void * /* _this */);
void (*setOption)(void * /* _this */, Option);
int (*getModuleDir)(void * /* _this */);
uint32_t (*getFlags)(void * /* _this */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
ModuleBase *module = new T();
if (!table->registerModule(table, new module_abi(module)))
return;
auto api = new Api();
api->impl = table;
module->onLoad(api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return impl->connectCompanion ? impl->connectCompanion(impl->_this) : -1;
}
inline int Api::getModuleDir() {
return impl->getModuleDir ? impl->getModuleDir(impl->_this) : -1;
}
inline void Api::setOption(Option opt) {
if (impl->setOption) impl->setOption(impl->_this, opt);
}
inline uint32_t Api::getFlags() {
return impl->getFlags ? impl->getFlags(impl->_this) : 0;
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (impl->hookJniNativeMethods) impl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
if (impl->pltHookRegister) impl->pltHookRegister(regex, symbol, newFunc, oldFunc);
}
inline void Api::pltHookExclude(const char *regex, const char *symbol) {
if (impl->pltHookExclude) impl->pltHookExclude(regex, symbol);
}
inline bool Api::pltHookCommit() {
return impl->pltHookCommit != nullptr && impl->pltHookCommit();
}
} // namespace zygisk
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default")]] [[gnu::used]]
extern "C" void zygisk_companion_entry(int);
+149
View File
@@ -0,0 +1,149 @@
#include <sys/wait.h>
#include <sys/mount.h>
#include <magisk.hpp>
#include <base.hpp>
#include "deny.hpp"
using namespace std;
[[noreturn]] static void usage() {
fprintf(stderr,
R"EOF(DenyList Config CLI
Usage: magisk --denylist [action [arguments...] ]
Actions:
status Return the enforcement status
enable Enable denylist enforcement
disable Disable denylist enforcement
add PKG [PROC] Add a new target to the denylist
rm PKG [PROC] Remove target(s) from the denylist
ls Print the current denylist
exec CMDs... Execute commands in isolated mount
namespace and do all unmounts
)EOF");
exit(1);
}
void denylist_handler(int client, const sock_cred *cred) {
if (client < 0) {
revert_unmount();
return;
}
int req = read_int(client);
int res = DenyResponse::ERROR;
switch (req) {
case DenyRequest::ENFORCE:
res = enable_deny();
break;
case DenyRequest::DISABLE:
res = disable_deny();
break;
case DenyRequest::ADD:
res = add_list(client);
break;
case DenyRequest::REMOVE:
res = rm_list(client);
break;
case DenyRequest::LIST:
ls_list(client);
return;
case DenyRequest::STATUS:
res = (zygisk_enabled && denylist_enforced)
? DenyResponse::ENFORCED : DenyResponse::NOT_ENFORCED;
break;
default:
// Unknown request code
break;
}
write_int(client, res);
close(client);
}
int denylist_cli(int argc, char **argv) {
if (argc < 2)
usage();
int req;
if (argv[1] == "enable"sv)
req = DenyRequest::ENFORCE;
else if (argv[1] == "disable"sv)
req = DenyRequest::DISABLE;
else if (argv[1] == "add"sv)
req = DenyRequest::ADD;
else if (argv[1] == "rm"sv)
req = DenyRequest::REMOVE;
else if (argv[1] == "ls"sv)
req = DenyRequest::LIST;
else if (argv[1] == "status"sv)
req = DenyRequest::STATUS;
else if (argv[1] == "exec"sv && argc > 2) {
xunshare(CLONE_NEWNS);
xmount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr);
int fd = connect_daemon(MainRequest::GET_PATH);
MAGISKTMP = read_string(fd);
close(fd);
revert_unmount();
execvp(argv[2], argv + 2);
exit(1);
} else {
usage();
}
// Send request
int fd = connect_daemon(MainRequest::DENYLIST);
write_int(fd, req);
if (req == DenyRequest::ADD || req == DenyRequest::REMOVE) {
write_string(fd, argv[2]);
write_string(fd, argv[3] ? argv[3] : "");
}
// Get response
int res = read_int(fd);
if (res < 0 || res >= DenyResponse::END)
res = DenyResponse::ERROR;
switch (res) {
case DenyResponse::NOT_ENFORCED:
fprintf(stderr, "Denylist is not enforced\n");
goto return_code;
case DenyResponse::ENFORCED:
fprintf(stderr, "Denylist is enforced\n");
goto return_code;
case DenyResponse::ITEM_EXIST:
fprintf(stderr, "Target already exists in denylist\n");
goto return_code;
case DenyResponse::ITEM_NOT_EXIST:
fprintf(stderr, "Target does not exist in denylist\n");
goto return_code;
case DenyResponse::NO_NS:
fprintf(stderr, "The kernel does not support mount namespace\n");
goto return_code;
case DenyResponse::INVALID_PKG:
fprintf(stderr, "Invalid package / process name\n");
goto return_code;
case DenyResponse::ERROR:
fprintf(stderr, "deny: Daemon error\n");
return -1;
case DenyResponse::OK:
break;
default:
__builtin_unreachable();
}
if (req == DenyRequest::LIST) {
string out;
for (;;) {
read_string(fd, out);
if (out.empty())
break;
printf("%s\n", out.data());
}
}
return_code:
return req == DenyRequest::STATUS ? res != DenyResponse::ENFORCED : res != DenyResponse::OK;
}
+52
View File
@@ -0,0 +1,52 @@
#pragma once
#include <pthread.h>
#include <string_view>
#include <functional>
#include <map>
#include <atomic>
#include <daemon.hpp>
#define ISOLATED_MAGIC "isolated"
namespace DenyRequest {
enum : int {
ENFORCE,
DISABLE,
ADD,
REMOVE,
LIST,
STATUS,
END
};
}
namespace DenyResponse {
enum : int {
OK,
ENFORCED,
NOT_ENFORCED,
ITEM_EXIST,
ITEM_NOT_EXIST,
INVALID_PKG,
NO_NS,
ERROR,
END
};
}
// CLI entries
int enable_deny();
int disable_deny();
int add_list(int client);
int rm_list(int client);
void ls_list(int client);
// Utility functions
bool is_deny_target(int uid, std::string_view process);
void revert_unmount();
extern std::atomic<bool> denylist_enforced;
+41
View File
@@ -0,0 +1,41 @@
#include <sys/mount.h>
#include <magisk.hpp>
#include <base.hpp>
#include "deny.hpp"
using namespace std;
static void lazy_unmount(const char* mountpoint) {
if (umount2(mountpoint, MNT_DETACH) != -1)
LOGD("denylist: Unmounted (%s)\n", mountpoint);
}
#define TMPFS_MNT(dir) (mentry->mnt_type == "tmpfs"sv && str_starts(mentry->mnt_dir, "/" #dir))
void revert_unmount() {
vector<string> targets;
// Unmount dummy skeletons and MAGISKTMP
targets.push_back(MAGISKTMP);
parse_mnt("/proc/self/mounts", [&](mntent *mentry) {
if (TMPFS_MNT(system) || TMPFS_MNT(vendor) || TMPFS_MNT(product) || TMPFS_MNT(system_ext))
targets.emplace_back(mentry->mnt_dir);
return true;
});
for (auto &s : reversed(targets))
lazy_unmount(s.data());
targets.clear();
// Unmount all Magisk created mounts
parse_mnt("/proc/self/mounts", [&](mntent *mentry) {
if (str_contains(mentry->mnt_fsname, BLOCKDIR))
targets.emplace_back(mentry->mnt_dir);
return true;
});
for (auto &s : reversed(targets))
lazy_unmount(s.data());
}
+415
View File
@@ -0,0 +1,415 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <set>
#include <magisk.hpp>
#include <base.hpp>
#include <db.hpp>
#include "deny.hpp"
using namespace std;
atomic_flag skip_pkg_rescan;
// For the following data structures:
// If package name == ISOLATED_MAGIC, or app ID == -1, it means isolated service
// Package name -> list of process names
static unique_ptr<map<string, set<string, StringCmp>, StringCmp>> pkg_to_procs_;
#define pkg_to_procs (*pkg_to_procs_)
// app ID -> list of pkg names (string_view points to a pkg_to_procs key)
static unique_ptr<map<int, set<string_view>>> app_id_to_pkgs_;
#define app_id_to_pkgs (*app_id_to_pkgs_)
// Locks the data structures above
static pthread_mutex_t data_lock = PTHREAD_MUTEX_INITIALIZER;
atomic<bool> denylist_enforced = false;
#define do_kill (zygisk_enabled && denylist_enforced)
static void rescan_apps() {
LOGD("denylist: rescanning apps\n");
app_id_to_pkgs.clear();
auto data_dir = xopen_dir(APP_DATA_DIR);
if (!data_dir)
return;
dirent *entry;
while ((entry = xreaddir(data_dir.get()))) {
// For each user
int dfd = xopenat(dirfd(data_dir.get()), entry->d_name, O_RDONLY);
if (auto dir = xopen_dir(dfd)) {
while ((entry = xreaddir(dir.get()))) {
// For each package
struct stat st{};
xfstatat(dfd, entry->d_name, &st, 0);
int app_id = to_app_id(st.st_uid);
if (auto it = pkg_to_procs.find(entry->d_name); it != pkg_to_procs.end()) {
app_id_to_pkgs[app_id].insert(it->first);
}
}
} else {
close(dfd);
}
}
}
static void update_pkg_uid(const string &pkg, bool remove) {
auto data_dir = xopen_dir(APP_DATA_DIR);
if (!data_dir)
return;
dirent *entry;
struct stat st{};
char buf[PATH_MAX] = {0};
// For each user
while ((entry = xreaddir(data_dir.get()))) {
snprintf(buf, sizeof(buf), "%s/%s", entry->d_name, pkg.data());
if (fstatat(dirfd(data_dir.get()), buf, &st, 0) == 0) {
int app_id = to_app_id(st.st_uid);
if (remove) {
if (auto it = app_id_to_pkgs.find(app_id); it != app_id_to_pkgs.end()) {
it->second.erase(pkg);
if (it->second.empty()) {
app_id_to_pkgs.erase(it);
}
}
} else {
app_id_to_pkgs[app_id].insert(pkg);
}
break;
}
}
}
// Leave /proc fd opened as we're going to read from it repeatedly
static DIR *procfp;
template<class F>
static void crawl_procfs(const F &fn) {
rewinddir(procfp);
dirent *dp;
int pid;
while ((dp = readdir(procfp))) {
pid = parse_int(dp->d_name);
if (pid > 0 && !fn(pid))
break;
}
}
static inline bool str_eql(string_view a, string_view b) { return a == b; }
template<bool str_op(string_view, string_view) = &str_eql>
static bool proc_name_match(int pid, string_view name) {
char buf[4019];
sprintf(buf, "/proc/%d/cmdline", pid);
if (auto fp = open_file(buf, "re")) {
fgets(buf, sizeof(buf), fp.get());
if (str_op(buf, name)) {
return true;
}
}
return false;
}
static bool proc_context_match(int pid, string_view context) {
char buf[PATH_MAX];
sprintf(buf, "/proc/%d/attr/current", pid);
if (auto fp = open_file(buf, "re")) {
fgets(buf, sizeof(buf), fp.get());
if (str_starts(buf, context)) {
return true;
}
}
return false;
}
template<bool matcher(int, string_view) = &proc_name_match>
static void kill_process(const char *name, bool multi = false) {
crawl_procfs([=](int pid) -> bool {
if (matcher(pid, name)) {
kill(pid, SIGKILL);
LOGD("denylist: kill PID=[%d] (%s)\n", pid, name);
return multi;
}
return true;
});
}
static bool validate(const char *pkg, const char *proc) {
bool pkg_valid = false;
bool proc_valid = true;
if (str_eql(pkg, ISOLATED_MAGIC)) {
pkg_valid = true;
for (char c; (c = *proc); ++proc) {
if (isalnum(c) || c == '_' || c == '.')
continue;
if (c == ':')
break;
proc_valid = false;
break;
}
} else {
for (char c; (c = *pkg); ++pkg) {
if (isalnum(c) || c == '_')
continue;
if (c == '.') {
pkg_valid = true;
continue;
}
pkg_valid = false;
break;
}
for (char c; (c = *proc); ++proc) {
if (isalnum(c) || c == '_' || c == ':' || c == '.')
continue;
proc_valid = false;
break;
}
}
return pkg_valid && proc_valid;
}
static auto add_hide_set(const char *pkg, const char *proc) {
auto p = pkg_to_procs[pkg].emplace(proc);
if (!p.second)
return p;
LOGI("denylist add: [%s/%s]\n", pkg, proc);
if (!do_kill)
return p;
if (str_eql(pkg, ISOLATED_MAGIC)) {
// Kill all matching isolated processes
kill_process<&proc_name_match<str_starts>>(proc, true);
} else {
kill_process(proc);
}
return p;
}
static void clear_data() {
pkg_to_procs_.reset(nullptr);
app_id_to_pkgs_.reset(nullptr);
}
static bool ensure_data() {
if (pkg_to_procs_)
return true;
LOGI("denylist: initializing internal data structures\n");
default_new(pkg_to_procs_);
char *err = db_exec("SELECT * FROM denylist", [](db_row &row) -> bool {
add_hide_set(row["package_name"].data(), row["process"].data());
return true;
});
db_err_cmd(err, goto error)
default_new(app_id_to_pkgs_);
rescan_apps();
return true;
error:
clear_data();
return false;
}
static int add_list(const char *pkg, const char *proc) {
if (proc[0] == '\0')
proc = pkg;
if (!validate(pkg, proc))
return DenyResponse::INVALID_PKG;
{
mutex_guard lock(data_lock);
if (!ensure_data())
return DenyResponse::ERROR;
auto p = add_hide_set(pkg, proc);
if (!p.second)
return DenyResponse::ITEM_EXIST;
update_pkg_uid(*p.first, false);
}
// Add to database
char sql[4096];
snprintf(sql, sizeof(sql),
"INSERT INTO denylist (package_name, process) VALUES('%s', '%s')", pkg, proc);
char *err = db_exec(sql);
db_err_cmd(err, return DenyResponse::ERROR)
return DenyResponse::OK;
}
int add_list(int client) {
string pkg = read_string(client);
string proc = read_string(client);
return add_list(pkg.data(), proc.data());
}
static int rm_list(const char *pkg, const char *proc) {
{
mutex_guard lock(data_lock);
if (!ensure_data())
return DenyResponse::ERROR;
bool remove = false;
auto it = pkg_to_procs.find(pkg);
if (it != pkg_to_procs.end()) {
if (proc[0] == '\0') {
update_pkg_uid(it->first, true);
pkg_to_procs.erase(it);
remove = true;
LOGI("denylist rm: [%s]\n", pkg);
} else if (it->second.erase(proc) != 0) {
remove = true;
LOGI("denylist rm: [%s/%s]\n", pkg, proc);
if (it->second.empty()) {
update_pkg_uid(it->first, true);
pkg_to_procs.erase(it);
}
}
}
if (!remove)
return DenyResponse::ITEM_NOT_EXIST;
}
char sql[4096];
if (proc[0] == '\0')
snprintf(sql, sizeof(sql), "DELETE FROM denylist WHERE package_name='%s'", pkg);
else
snprintf(sql, sizeof(sql),
"DELETE FROM denylist WHERE package_name='%s' AND process='%s'", pkg, proc);
char *err = db_exec(sql);
db_err_cmd(err, return DenyResponse::ERROR)
return DenyResponse::OK;
}
int rm_list(int client) {
string pkg = read_string(client);
string proc = read_string(client);
return rm_list(pkg.data(), proc.data());
}
void ls_list(int client) {
{
mutex_guard lock(data_lock);
if (!ensure_data()) {
write_int(client, static_cast<int>(DenyResponse::ERROR));
return;
}
write_int(client,static_cast<int>(DenyResponse::OK));
for (const auto &[pkg, procs] : pkg_to_procs) {
for (const auto &proc : procs) {
write_int(client, pkg.size() + proc.size() + 1);
xwrite(client, pkg.data(), pkg.size());
xwrite(client, "|", 1);
xwrite(client, proc.data(), proc.size());
}
}
}
write_int(client, 0);
close(client);
}
static void update_deny_config() {
char sql[64];
sprintf(sql, "REPLACE INTO settings (key,value) VALUES('%s',%d)",
DB_SETTING_KEYS[DENYLIST_CONFIG], denylist_enforced.load());
char *err = db_exec(sql);
db_err(err);
}
int enable_deny() {
if (denylist_enforced) {
return DenyResponse::OK;
} else {
mutex_guard lock(data_lock);
if (access("/proc/self/ns/mnt", F_OK) != 0) {
LOGW("The kernel does not support mount namespace\n");
return DenyResponse::NO_NS;
}
if (procfp == nullptr && (procfp = opendir("/proc")) == nullptr)
return DenyResponse::ERROR;
LOGI("* Enable DenyList\n");
denylist_enforced = true;
if (!ensure_data()) {
denylist_enforced = false;
return DenyResponse::ERROR;
}
// On Android Q+, also kill blastula pool and all app zygotes
if (SDK_INT >= 29 && zygisk_enabled) {
kill_process("usap32", true);
kill_process("usap64", true);
kill_process<&proc_context_match>("u:r:app_zygote:s0", true);
}
}
update_deny_config();
return DenyResponse::OK;
}
int disable_deny() {
if (denylist_enforced) {
denylist_enforced = false;
LOGI("* Disable DenyList\n");
}
update_deny_config();
return DenyResponse::OK;
}
void initialize_denylist() {
if (!denylist_enforced) {
db_settings dbs;
get_db_settings(dbs, DENYLIST_CONFIG);
if (dbs[DENYLIST_CONFIG])
enable_deny();
}
}
bool is_deny_target(int uid, string_view process) {
mutex_guard lock(data_lock);
if (!ensure_data())
return false;
if (!skip_pkg_rescan.test_and_set())
rescan_apps();
int app_id = to_app_id(uid);
if (app_id >= 90000) {
if (auto it = pkg_to_procs.find(ISOLATED_MAGIC); it != pkg_to_procs.end()) {
for (const auto &s : it->second) {
if (str_starts(process, s))
return true;
}
}
return false;
} else {
auto it = app_id_to_pkgs.find(app_id);
if (it == app_id_to_pkgs.end())
return false;
for (const auto &pkg : it->second) {
if (pkg_to_procs.find(pkg)->second.count(process))
return true;
}
}
return false;
}
+400
View File
@@ -0,0 +1,400 @@
#include <libgen.h>
#include <dlfcn.h>
#include <sys/prctl.h>
#include <sys/mount.h>
#include <android/log.h>
#include <android/dlext.h>
#include <base.hpp>
#include <daemon.hpp>
#include <magisk.hpp>
#include "zygisk.hpp"
#include "module.hpp"
#include "deny/deny.hpp"
using namespace std;
void *self_handle = nullptr;
// Make sure /proc/self/environ is sanitized
// Filter env and reset MM_ENV_END
static void sanitize_environ() {
char *cur = environ[0];
for (int i = 0; environ[i]; ++i) {
// Copy all env onto the original stack
size_t len = strlen(environ[i]);
memmove(cur, environ[i], len + 1);
environ[i] = cur;
cur += len + 1;
}
prctl(PR_SET_MM, PR_SET_MM_ENV_END, cur, 0, 0);
}
[[gnu::destructor]] [[maybe_unused]]
static void zygisk_cleanup_wait() {
if (self_handle) {
// Wait 10us to make sure none of our code is executing
timespec ts = { .tv_sec = 0, .tv_nsec = 10000L };
nanosleep(&ts, nullptr);
}
}
static void *unload_first_stage(void *) {
// Wait 10us to make sure 1st stage is done
timespec ts = { .tv_sec = 0, .tv_nsec = 10000L };
nanosleep(&ts, nullptr);
unmap_all(HIJACK_BIN);
xumount2(HIJACK_BIN, MNT_DETACH);
return nullptr;
}
static void second_stage_entry() {
zygisk_logging();
ZLOGD("inject 2nd stage\n");
MAGISKTMP = getenv(MAGISKTMP_ENV);
#if defined(__LP64__)
self_handle = dlopen("/system/bin/app_process", RTLD_NOLOAD);
#else
self_handle = dlopen("/system/bin/app_process32", RTLD_NOLOAD);
#endif
dlclose(self_handle);
unsetenv(MAGISKTMP_ENV);
sanitize_environ();
hook_functions();
new_daemon_thread(&unload_first_stage, nullptr);
}
static void first_stage_entry() {
ZLOGD("inject 1st stage\n");
char *ld = getenv("LD_PRELOAD");
if (char *c = strrchr(ld, ':')) {
*c = '\0';
setenv("LD_PRELOAD", ld, 1); // Restore original LD_PRELOAD
} else {
unsetenv("LD_PRELOAD");
}
// Load second stage
setenv(INJECT_ENV_2, "1", 1);
#if defined(__LP64__)
dlopen("/system/bin/app_process", RTLD_LAZY);
#else
dlopen("/system/bin/app_process32", RTLD_LAZY);
#endif
}
[[gnu::constructor]] [[maybe_unused]]
static void zygisk_init() {
android_logging();
if (getenv(INJECT_ENV_1)) {
unsetenv(INJECT_ENV_1);
first_stage_entry();
} else if (getenv(INJECT_ENV_2)) {
unsetenv(INJECT_ENV_2);
second_stage_entry();
}
}
// The following code runs in zygote/app process
extern "C" void zygisk_log_write(int prio, const char *msg, int len) {
// If we don't have log pipe set, ask magiskd for it
// This could happen multiple times in zygote because it was closed to prevent crashing
if (logd_fd < 0) {
// Change logging temporarily to prevent infinite recursion and stack overflow
android_logging();
if (int fd = zygisk_request(ZygiskRequest::GET_LOG_PIPE); fd >= 0) {
if (read_int(fd) == 0) {
logd_fd = recv_fd(fd);
}
close(fd);
}
zygisk_logging();
}
sigset_t mask;
sigset_t orig_mask;
bool sig = false;
// Make sure SIGPIPE won't crash zygote
if (logd_fd >= 0) {
sig = true;
sigemptyset(&mask);
sigaddset(&mask, SIGPIPE);
pthread_sigmask(SIG_BLOCK, &mask, &orig_mask);
}
magisk_log_write(prio, msg, len);
if (sig) {
timespec ts{};
sigtimedwait(&mask, nullptr, &ts);
pthread_sigmask(SIG_SETMASK, &orig_mask, nullptr);
}
}
static inline bool should_load_modules(uint32_t flags) {
return (flags & UNMOUNT_MASK) != UNMOUNT_MASK &&
(flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP;
}
int remote_get_info(int uid, const char *process, uint32_t *flags, vector<int> &fds) {
if (int fd = zygisk_request(ZygiskRequest::GET_INFO); fd >= 0) {
write_int(fd, uid);
write_string(fd, process);
xxread(fd, flags, sizeof(*flags));
if (should_load_modules(*flags)) {
fds = recv_fds(fd);
}
return fd;
}
return -1;
}
// The following code runs in magiskd
static vector<int> get_module_fds(bool is_64_bit) {
vector<int> fds;
// All fds passed to send_fds have to be valid file descriptors.
// To workaround this issue, send over STDOUT_FILENO as an indicator of an
// invalid fd as it will always be /dev/null in magiskd
if (is_64_bit) {
#if defined(__LP64__)
std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds),
[](const module_info &info) { return info.z64 < 0 ? STDOUT_FILENO : info.z64; });
#endif
} else {
std::transform(module_list->begin(), module_list->end(), std::back_inserter(fds),
[](const module_info &info) { return info.z32 < 0 ? STDOUT_FILENO : info.z32; });
}
return fds;
}
static bool get_exe(int pid, char *buf, size_t sz) {
snprintf(buf, sz, "/proc/%d/exe", pid);
return xreadlink(buf, buf, sz) > 0;
}
static pthread_mutex_t zygiskd_lock = PTHREAD_MUTEX_INITIALIZER;
static int zygiskd_sockets[] = { -1, -1 };
#define zygiskd_socket zygiskd_sockets[is_64_bit]
static void connect_companion(int client, bool is_64_bit) {
mutex_guard g(zygiskd_lock);
if (zygiskd_socket >= 0) {
// Make sure the socket is still valid
pollfd pfd = { zygiskd_socket, 0, 0 };
poll(&pfd, 1, 0);
if (pfd.revents) {
// Any revent means error
close(zygiskd_socket);
zygiskd_socket = -1;
}
}
if (zygiskd_socket < 0) {
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
zygiskd_socket = fds[0];
if (fork_dont_care() == 0) {
string exe = MAGISKTMP + "/magisk" + (is_64_bit ? "64" : "32");
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
char buf[16];
snprintf(buf, sizeof(buf), "%d", fds[1]);
execl(exe.data(), "zygisk", "companion", buf, (char *) nullptr);
exit(-1);
}
close(fds[1]);
vector<int> module_fds = get_module_fds(is_64_bit);
send_fds(zygiskd_socket, module_fds.data(), module_fds.size());
// Wait for ack
if (read_int(zygiskd_socket) != 0) {
LOGE("zygiskd startup error\n");
return;
}
}
send_fd(zygiskd_socket, client);
}
static timespec last_zygote_start;
static int zygote_start_counts[] = { 0, 0 };
#define zygote_start_count zygote_start_counts[is_64_bit]
#define zygote_started (zygote_start_counts[0] + zygote_start_counts[1])
#define zygote_start_reset(val) { zygote_start_counts[0] = val; zygote_start_counts[1] = val; }
static void setup_files(int client, const sock_cred *cred) {
LOGD("zygisk: setup files for pid=[%d]\n", cred->pid);
char buf[256];
if (!get_exe(cred->pid, buf, sizeof(buf))) {
write_int(client, 1);
return;
}
bool is_64_bit = str_ends(buf, "64");
if (!zygote_started) {
// First zygote launch, record time
clock_gettime(CLOCK_MONOTONIC, &last_zygote_start);
}
if (zygote_start_count) {
// This zygote ABI had started before, kill existing zygiskd
close(zygiskd_sockets[0]);
close(zygiskd_sockets[1]);
zygiskd_sockets[0] = -1;
zygiskd_sockets[1] = -1;
}
++zygote_start_count;
if (zygote_start_count >= 5) {
// Bootloop prevention
timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
if (ts.tv_sec - last_zygote_start.tv_sec > 60) {
// This is very likely manual soft reboot
memcpy(&last_zygote_start, &ts, sizeof(ts));
zygote_start_reset(1);
} else {
// If any zygote relaunched more than 5 times within a minute,
// don't do any setups further to prevent bootloop.
zygote_start_reset(999);
write_int(client, 1);
return;
}
}
// Hijack some binary in /system/bin to host 1st stage
const char *hbin;
string mbin;
int app_fd;
if (is_64_bit) {
hbin = HIJACK_BIN64;
mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk64";
app_fd = app_process_64;
} else {
hbin = HIJACK_BIN32;
mbin = MAGISKTMP + "/" ZYGISKBIN "/magisk32";
app_fd = app_process_32;
}
xmount(mbin.data(), hbin, nullptr, MS_BIND, nullptr);
write_int(client, 0);
send_fd(client, app_fd);
write_string(client, MAGISKTMP);
}
static void magiskd_passthrough(int client) {
bool is_64_bit = read_int(client);
write_int(client, 0);
send_fd(client, is_64_bit ? app_process_64 : app_process_32);
}
extern bool uid_granted_root(int uid);
static void get_process_info(int client, const sock_cred *cred) {
int uid = read_int(client);
string process = read_string(client);
uint32_t flags = 0;
check_pkg_refresh();
if (is_deny_target(uid, process)) {
flags |= PROCESS_ON_DENYLIST;
}
int manager_app_id = get_manager();
if (to_app_id(uid) == manager_app_id) {
flags |= PROCESS_IS_MAGISK_APP;
}
if (denylist_enforced) {
flags |= DENYLIST_ENFORCING;
}
if (uid_granted_root(uid)) {
flags |= PROCESS_GRANTED_ROOT;
}
xwrite(client, &flags, sizeof(flags));
if (should_load_modules(flags)) {
char buf[256];
get_exe(cred->pid, buf, sizeof(buf));
vector<int> fds = get_module_fds(str_ends(buf, "64"));
send_fds(client, fds.data(), fds.size());
}
if (uid != 1000 || process != "system_server")
return;
// Collect module status from system_server
int slots = read_int(client);
dynamic_bitset bits;
for (int i = 0; i < slots; ++i) {
dynamic_bitset::slot_type l = 0;
xxread(client, &l, sizeof(l));
bits.emplace_back(l);
}
for (int id = 0; id < module_list->size(); ++id) {
if (!as_const(bits)[id]) {
// Either not a zygisk module, or incompatible
char buf[4096];
snprintf(buf, sizeof(buf), MODULEROOT "/%s/zygisk",
module_list->operator[](id).name.data());
if (int dirfd = open(buf, O_RDONLY | O_CLOEXEC); dirfd >= 0) {
close(xopenat(dirfd, "unloaded", O_CREAT | O_RDONLY, 0644));
close(dirfd);
}
}
}
}
static void send_log_pipe(int fd) {
// There is race condition here, but we can't really do much about it...
if (logd_fd >= 0) {
write_int(fd, 0);
send_fd(fd, logd_fd);
} else {
write_int(fd, 1);
}
}
static void get_moddir(int client) {
int id = read_int(client);
char buf[4096];
snprintf(buf, sizeof(buf), MODULEROOT "/%s", module_list->operator[](id).name.data());
int dfd = xopen(buf, O_RDONLY | O_CLOEXEC);
send_fd(client, dfd);
close(dfd);
}
void zygisk_handler(int client, const sock_cred *cred) {
int code = read_int(client);
char buf[256];
switch (code) {
case ZygiskRequest::SETUP:
setup_files(client, cred);
break;
case ZygiskRequest::PASSTHROUGH:
magiskd_passthrough(client);
break;
case ZygiskRequest::GET_INFO:
get_process_info(client, cred);
break;
case ZygiskRequest::GET_LOG_PIPE:
send_log_pipe(client);
break;
case ZygiskRequest::CONNECT_COMPANION:
get_exe(cred->pid, buf, sizeof(buf));
connect_companion(client, str_ends(buf, "64"));
break;
case ZygiskRequest::GET_MODDIR:
get_moddir(client);
break;
default:
// Unknown code
break;
}
close(client);
}
+289
View File
@@ -0,0 +1,289 @@
#!/usr/bin/env python3
primitives = ['jint', 'jboolean', 'jlong']
class JType:
def __init__(self, cpp, jni):
self.cpp = cpp
self.jni = jni
class JArray(JType):
def __init__(self, type):
if type.cpp in primitives:
name = type.cpp + 'Array'
else:
name = 'jobjectArray'
super().__init__(name, '[' + type.jni)
class Argument:
def __init__(self, name, type, set_arg = False):
self.name = name
self.type = type
self.set_arg = set_arg
def cpp(self):
return f'{self.type.cpp} {self.name}'
# Args we don't care, give it an auto generated name
class Anon(Argument):
cnt = 0
def __init__(self, type):
super().__init__(f'_{Anon.cnt}', type)
Anon.cnt += 1
class Return:
def __init__(self, value, type):
self.value = value
self.type = type
class Method:
def __init__(self, name, ret, args):
self.name = name
self.ret = ret
self.args = args
def cpp(self):
return ', '.join(map(lambda x: x.cpp(), self.args))
def name_list(self):
return ', '.join(map(lambda x: x.name, self.args))
def jni(self):
args = ''.join(map(lambda x: x.type.jni, self.args))
return f'({args}){self.ret.type.jni}'
def body(self):
return ''
class JNIHook(Method):
def __init__(self, ver, ret, args):
name = f'{self.base_name()}_{ver}'
super().__init__(name, ret, args)
def base_name(self):
return ''
def orig_method(self):
return f'reinterpret_cast<decltype(&{self.name})>({self.base_name()}_orig)'
def ind(i):
return '\n' + ' ' * i
# Common types
jint = JType('jint', 'I')
jintArray = JArray(jint)
jstring = JType('jstring', 'Ljava/lang/String;')
jboolean = JType('jboolean', 'Z')
jlong = JType('jlong', 'J')
void = JType('void', 'V')
class ForkAndSpec(JNIHook):
def __init__(self, ver, args):
super().__init__(ver, Return('ctx.pid', jint), args)
def base_name(self):
return 'nativeForkAndSpecialize'
def init_args(self):
return 'AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);'
def body(self):
decl = ''
decl += ind(1) + self.init_args()
for a in self.args:
if a.set_arg:
decl += ind(1) + f'args.{a.name} = &{a.name};'
decl += ind(1) + 'HookContext ctx;'
decl += ind(1) + 'ctx.env = env;'
decl += ind(1) + 'ctx.raw_args = &args;'
decl += ind(1) + f'ctx.{self.base_name()}_pre();'
decl += ind(1) + self.orig_method() + '('
decl += ind(2) + f'env, clazz, {self.name_list()}'
decl += ind(1) + ');'
decl += ind(1) + f'ctx.{self.base_name()}_post();'
return decl
class SpecApp(ForkAndSpec):
def __init__(self, ver, args):
super().__init__(ver, args)
self.ret = Return('', void)
def base_name(self):
return 'nativeSpecializeAppProcess'
class ForkServer(ForkAndSpec):
def base_name(self):
return 'nativeForkSystemServer'
def init_args(self):
return 'ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);'
# 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, True)
# 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)
# server
permitted_capabilities = Argument('permitted_capabilities', jlong)
effective_capabilities = Argument('effective_capabilities', jlong)
# Method definitions
fas_l = ForkAndSpec('l', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, nice_name, fds_to_close, instruction_set, app_data_dir])
fas_o = ForkAndSpec('o', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir])
fas_p = ForkAndSpec('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])
fas_q_alt = ForkAndSpec('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])
fas_r = ForkAndSpec('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])
fas_samsung_m = ForkAndSpec('samsung_m', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir])
fas_samsung_n = ForkAndSpec('samsung_n', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, instruction_set, app_data_dir, Anon(jint)])
fas_samsung_o = ForkAndSpec('samsung_o', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir])
fas_samsung_p = ForkAndSpec('samsung_p', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, Anon(jint), Anon(jint), nice_name, fds_to_close, fds_to_ignore, is_child_zygote,
instruction_set, app_data_dir])
spec_q = SpecApp('q', [uid, gid, gids, runtime_flags, rlimits, mount_external, se_info,
nice_name, is_child_zygote, instruction_set, app_data_dir])
spec_q_alt = SpecApp('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 = SpecApp('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 = SpecApp('samsung_q', [uid, gid, gids, runtime_flags, rlimits, mount_external,
se_info, Anon(jint), Anon(jint), nice_name, is_child_zygote, instruction_set, app_data_dir])
server_l = ForkServer('l', [uid, gid, gids, runtime_flags, rlimits,
permitted_capabilities, effective_capabilities])
server_samsung_q = ForkServer('samsung_q', [uid, gid, gids, runtime_flags, Anon(jint), Anon(jint), rlimits,
permitted_capabilities, effective_capabilities])
hook_map = {}
def gen_jni_def(clz, methods):
if clz not in hook_map:
hook_map[clz] = []
decl = ''
for m in methods:
decl += ind(0) + f'{m.ret.type.cpp} {m.name}(JNIEnv *env, jclass clazz, {m.cpp()}) {{'
decl += m.body()
if m.ret.value:
decl += ind(1) + f'return {m.ret.value};'
decl += ind(0) + '}'
decl += ind(0) + f'const JNINativeMethod {m.base_name()}_methods[] = {{'
for m in methods:
decl += ind(1) + '{'
decl += ind(2) + f'"{m.base_name()}",'
decl += ind(2) + f'"{m.jni()}",'
decl += ind(2) + f'(void *) &{m.name}'
decl += ind(1) + '},'
decl += ind(0) + '};'
decl = ind(0) + f'void *{m.base_name()}_orig = nullptr;' + decl
decl += ind(0) + f'constexpr int {m.base_name()}_methods_num = std::size({m.base_name()}_methods);'
decl += ind(0)
hook_map[clz].append(m.base_name())
return decl
def gen_jni_hook():
decl = ''
decl += ind(0) + 'unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {'
decl += ind(1) + 'unique_ptr<JNINativeMethod[]> newMethods;'
decl += ind(1) + 'int clz_id = -1;'
decl += ind(1) + 'int hook_cnt = 0;'
decl += ind(1) + 'do {'
for index, (clz, methods) in enumerate(hook_map.items()):
decl += ind(2) + f'if (className == "{clz}"sv) {{'
decl += ind(3) + f'clz_id = {index};'
decl += ind(3) + f'hook_cnt = {len(methods)};'
decl += ind(3) + 'break;'
decl += ind(2) + '}'
decl += ind(1) + '} while (false);'
decl += ind(1) + 'if (hook_cnt) {'
decl += ind(2) + 'newMethods = make_unique<JNINativeMethod[]>(numMethods);'
decl += ind(2) + 'memcpy(newMethods.get(), methods, sizeof(JNINativeMethod) * numMethods);'
decl += ind(1) + '}'
decl += ind(1) + 'auto &class_map = (*jni_method_map)[className];'
decl += ind(1) + 'for (int i = 0; i < numMethods; ++i) {'
for index, methods in enumerate(hook_map.values()):
decl += ind(2) + f'if (hook_cnt && clz_id == {index}) {{'
for m in methods:
decl += ind(3) + f'HOOK_JNI({m})'
decl += ind(2) + '}'
decl += ind(2) + 'class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;'
decl += ind(1) + '}'
decl += ind(1) + 'return newMethods;'
decl += ind(0) + '}'
return decl
with open('jni_hooks.hpp', 'w') as f:
f.write('// Generated by gen_jni_hooks.py\n')
f.write('\nnamespace {\n')
zygote = 'com/android/internal/os/Zygote'
methods = [fas_l, fas_o, fas_p, fas_q_alt, fas_r, fas_samsung_m, fas_samsung_n, fas_samsung_o, fas_samsung_p]
f.write(gen_jni_def(zygote, methods))
methods = [spec_q, spec_q_alt, spec_r, spec_samsung_q]
f.write(gen_jni_def(zygote, methods))
methods = [server_l, server_samsung_q]
f.write(gen_jni_def(zygote, methods))
f.write(gen_jni_hook())
f.write('\n\n} // namespace\n')
+685
View File
@@ -0,0 +1,685 @@
#include <android/dlext.h>
#include <sys/mount.h>
#include <dlfcn.h>
#include <bitset>
#include <xhook.h>
#include <base.hpp>
#include <flags.h>
#include <daemon.hpp>
#include "zygisk.hpp"
#include "memory.hpp"
#include "module.hpp"
#include "deny/deny.hpp"
using namespace std;
using jni_hook::hash_map;
using jni_hook::tree_map;
using xstring = jni_hook::string;
// Extreme verbose logging
//#define ZLOGV(...) ZLOGD(__VA_ARGS__)
#define ZLOGV(...)
static bool unhook_functions();
namespace {
enum {
DO_UNMOUNT,
FORK_AND_SPECIALIZE,
APP_SPECIALIZE,
SERVER_SPECIALIZE,
CAN_DLCLOSE,
FLAG_MAX
};
#define DCL_PRE_POST(name) \
void name##_pre(); \
void name##_post();
struct HookContext {
JNIEnv *env;
union {
AppSpecializeArgs_v3 *args;
ServerSpecializeArgs_v1 *server_args;
void *raw_args;
};
const char *process;
vector<ZygiskModule> modules;
bitset<FLAG_MAX> state;
int pid;
uint32_t flags;
HookContext() : pid(-1), flags(0) {}
static void close_fds();
void unload_zygisk();
DCL_PRE_POST(fork)
void run_modules_pre(const vector<int> &fds);
void run_modules_post();
DCL_PRE_POST(nativeForkAndSpecialize)
DCL_PRE_POST(nativeSpecializeAppProcess)
DCL_PRE_POST(nativeForkSystemServer)
};
#undef DCL_PRE_POST
// Global variables
vector<tuple<const char *, 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;
// Current context
HookContext *g_ctx;
const JNINativeInterface *old_functions;
JNINativeInterface *new_functions;
} // namespace
#define HOOK_JNI(method) \
if (methods[i].name == #method##sv) { \
int j = 0; \
for (; j < method##_methods_num; ++j) { \
if (strcmp(methods[i].signature, method##_methods[j].signature) == 0) { \
jni_hook_list->try_emplace(className).first->second.push_back(methods[i]); \
method##_orig = methods[i].fnPtr; \
newMethods[i] = method##_methods[j]; \
ZLOGI("replaced %s#" #method "\n", className); \
--hook_cnt; \
break; \
} \
} \
if (j == method##_methods_num) { \
ZLOGE("unknown signature of %s#" #method ": %s\n", className, methods[i].signature); \
} \
continue; \
}
// JNI method hook definitions, auto generated
#include "jni_hooks.hpp"
#undef HOOK_JNI
namespace {
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 env_RegisterNatives(
JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint numMethods) {
auto className = get_class_name(env, clazz);
ZLOGV("JNIEnv->RegisterNatives [%s]\n", className.data());
auto newMethods = hookAndSaveJNIMethods(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) {
ZLOGV("jniRegisterNativeMethods [%s]\n", className);
auto newMethods = hookAndSaveJNIMethods(className, methods, numMethods);
return old_jniRegisterNativeMethods(env, className, newMethods.get() ?: methods, numMethods);
}
// Skip actual fork and return cached result if applicable
// Also unload first stage zygisk if necessary
DCL_HOOK_FUNC(int, fork) {
return (g_ctx && g_ctx->pid >= 0) ? g_ctx->pid : old_fork();
}
// Unmount stuffs in the process's private mount namespace
DCL_HOOK_FUNC(int, unshare, int flags) {
int res = old_unshare(flags);
if (g_ctx && (flags & CLONE_NEWNS) != 0 && res == 0) {
if (g_ctx->state[DO_UNMOUNT]) {
revert_unmount();
} else {
umount2("/system/bin/app_process64", MNT_DETACH);
umount2("/system/bin/app_process32", MNT_DETACH);
}
}
return res;
}
// A place to clean things up before zygote evaluates fd table
DCL_HOOK_FUNC(void, android_log_close) {
HookContext::close_fds();
old_android_log_close();
}
// Last point before process secontext changes
DCL_HOOK_FUNC(int, selinux_android_setcontext,
uid_t uid, int isSystemServer, const char *seinfo, const char *pkgname) {
if (g_ctx) {
g_ctx->state[CAN_DLCLOSE] = unhook_functions();
}
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) {
ZLOGD("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 = &env_RegisterNatives;
// 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. It will also restore the vtable.
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;
}
ZLOGD("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 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());
}
ZygiskModule::ZygiskModule(int id, void *handle, void *entry)
: raw_entry(entry), api(this), id(id), handle(handle) {}
ApiTable::ApiTable(ZygiskModule *m)
: module(m), registerModule(&ZygiskModule::RegisterModule) {}
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
switch (ver) {
case 3:
case 2:
table->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };
table->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
// fallthrough
case 1:
table->v1.hookJniNativeMethods = &hookJniNativeMethods;
table->v1.pltHookRegister = [](const char *p, const char *s, void *n, void **o) {
xhook_register(p, s, n, o);
};
table->v1.pltHookExclude = [](const char *p, const char *s) {
xhook_ignore(p, s);
};
table->v1.pltHookCommit = []{ bool r = xhook_refresh(0) == 0; xhook_clear(); return r; };
table->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
table->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };
break;
default:
// Unknown version number
return false;
}
return true;
}
int ZygiskModule::connectCompanion() const {
if (int fd = zygisk_request(ZygiskRequest::CONNECT_COMPANION); fd >= 0) {
write_int(fd, id);
return fd;
}
return -1;
}
int ZygiskModule::getModuleDir() const {
if (int fd = zygisk_request(ZygiskRequest::GET_MODDIR); fd >= 0) {
write_int(fd, id);
int dfd = recv_fd(fd);
close(fd);
return dfd;
}
return -1;
}
void ZygiskModule::setOption(zygisk::Option opt) {
if (g_ctx == nullptr)
return;
switch (opt) {
case zygisk::FORCE_DENYLIST_UNMOUNT:
g_ctx->state[DO_UNMOUNT] = true;
break;
case zygisk::DLCLOSE_MODULE_LIBRARY:
unload = true;
break;
}
}
uint32_t ZygiskModule::getFlags() {
return g_ctx ? (g_ctx->flags & ~PRIVATE_MASK) : 0;
}
void HookContext::run_modules_pre(const vector<int> &fds) {
// Since we directly use the pointer to elements in the vector, in order to prevent dangling
// pointers, the vector has to be pre-allocated to ensure reallocation does not occur
modules.reserve(fds.size());
for (int i = 0; i < fds.size(); ++i) {
struct stat s{};
if (fstat(fds[i], &s) != 0 || !S_ISREG(s.st_mode)) {
close(fds[i]);
continue;
}
android_dlextinfo info {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fds[i],
};
if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) {
if (void *e = dlsym(h, "zygisk_module_entry")) {
modules.emplace_back(i, h, e);
}
} else if (g_ctx->state[SERVER_SPECIALIZE]) {
LOGW("Failed to dlopen zygisk module: %s\n", dlerror());
}
close(fds[i]);
}
// Record all open fds
bitset<1024> open_fds;
auto dir = open_dir("/proc/self/fd");
for (dirent *entry; (entry = xreaddir(dir.get()));) {
int fd = parse_int(entry->d_name);
if (fd < 0 || fd >= 1024) {
close(fd);
continue;
}
open_fds[fd] = true;
}
for (auto &m : modules) {
m.entry(&m.api, env);
if (state[APP_SPECIALIZE]) {
m.preAppSpecialize(args);
} else if (state[SERVER_SPECIALIZE]) {
m.preServerSpecialize(server_args);
}
}
// Add all ignored fd onto whitelist
if (state[APP_SPECIALIZE] && args->fds_to_ignore) {
if (jintArray fdsToIgnore = *args->fds_to_ignore) {
int len = env->GetArrayLength(fdsToIgnore);
int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr);
for (int i = 0; i < len; ++i) {
int fd = arr[i];
if (fd >= 0 && fd < 1024) {
open_fds[fd] = true;
}
}
env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT);
}
}
// Close all unrecorded fds
rewinddir(dir.get());
for (dirent *entry; (entry = xreaddir(dir.get()));) {
int fd = parse_int(entry->d_name);
if (fd < 0 || fd >= 1024 || !open_fds[fd]) {
close(fd);
}
}
}
void HookContext::run_modules_post() {
for (const auto &m : modules) {
if (state[APP_SPECIALIZE]) {
m.postAppSpecialize(args);
} else if (state[SERVER_SPECIALIZE]) {
m.postServerSpecialize(server_args);
}
m.doUnload();
}
}
void HookContext::close_fds() {
close(logd_fd.exchange(-1));
}
void HookContext::unload_zygisk() {
if (state[CAN_DLCLOSE]) {
// Do NOT call the destructor
operator delete(jni_method_map);
// Directly unmap the whole memory block
jni_hook::memory_block::release();
// Strip out all API function pointers
for (auto &m : modules) {
memset(&m.api, 0, sizeof(m.api));
}
new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
}
}
// -----------------------------------------------------------------
void HookContext::nativeSpecializeAppProcess_pre() {
g_ctx = this;
state[APP_SPECIALIZE] = true;
process = env->GetStringUTFChars(args->nice_name, nullptr);
if (state[FORK_AND_SPECIALIZE]) {
ZLOGV("pre forkAndSpecialize [%s]\n", process);
} else {
ZLOGV("pre specialize [%s]\n", process);
}
vector<int> module_fds;
int fd = remote_get_info(args->uid, process, &flags, module_fds);
if ((flags & UNMOUNT_MASK) == UNMOUNT_MASK) {
ZLOGI("[%s] is on the denylist\n", process);
state[DO_UNMOUNT] = true;
} else if (fd >= 0) {
run_modules_pre(module_fds);
}
close(fd);
close_fds();
android_logging();
}
void HookContext::nativeSpecializeAppProcess_post() {
if (state[FORK_AND_SPECIALIZE]) {
ZLOGV("post forkAndSpecialize [%s]\n", process);
} else {
ZLOGV("post specialize [%s]\n", process);
}
env->ReleaseStringUTFChars(args->nice_name, process);
run_modules_post();
if (flags & PROCESS_IS_MAGISK_APP) {
setenv("ZYGISK_ENABLED", "1", 1);
}
g_ctx = nullptr;
if (!state[FORK_AND_SPECIALIZE]) {
unload_zygisk();
}
}
void HookContext::nativeForkSystemServer_pre() {
fork_pre();
state[SERVER_SPECIALIZE] = true;
if (pid == 0) {
ZLOGV("pre forkSystemServer\n");
vector<int> module_fds;
int fd = remote_get_info(1000, "system_server", &flags, module_fds);
if (fd >= 0) {
if (module_fds.empty()) {
write_int(fd, 0);
} else {
run_modules_pre(module_fds);
// Send the bitset of module status back to magiskd from system_server
dynamic_bitset bits;
for (const auto &m : modules)
bits[m.getId()] = true;
write_int(fd, bits.slots());
for (int i = 0; i < bits.slots(); ++i) {
auto l = bits.get_slot(i);
xwrite(fd, &l, sizeof(l));
}
}
close(fd);
}
close_fds();
android_logging();
}
}
void HookContext::nativeForkSystemServer_post() {
if (pid == 0) {
ZLOGV("post forkSystemServer\n");
run_modules_post();
}
fork_post();
}
void HookContext::nativeForkAndSpecialize_pre() {
fork_pre();
state[FORK_AND_SPECIALIZE] = true;
if (pid == 0) {
nativeSpecializeAppProcess_pre();
}
}
void HookContext::nativeForkAndSpecialize_post() {
if (pid == 0) {
nativeSpecializeAppProcess_post();
}
fork_post();
}
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
void HookContext::fork_pre() {
g_ctx = this;
sigmask(SIG_BLOCK, SIGCHLD);
pid = old_fork();
}
// Unblock SIGCHLD in case the original method didn't
void HookContext::fork_post() {
sigmask(SIG_UNBLOCK, SIGCHLD);
g_ctx = nullptr;
unload_zygisk();
}
} // namespace
static bool hook_refresh() {
if (xhook_refresh(0) == 0) {
xhook_clear();
return true;
} else {
ZLOGE("xhook failed\n");
return false;
}
}
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) {
ZLOGE("Failed to register hook \"%s\"\n", symbol);
return ret;
}
xhook_list->emplace_back(path, symbol, old_func);
return 0;
}
#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() {
#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);
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) {
ZLOGD("jniRegisterNativeMethods not hooked, using fallback\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);
}
}
static bool unhook_functions() {
bool success = true;
// Restore JNIEnv
if (g_ctx->env->functions == new_functions) {
g_ctx->env->functions = old_functions;
if (gClassRef) {
g_ctx->env->DeleteGlobalRef(gClassRef);
gClassRef = nullptr;
class_getName = nullptr;
}
}
// Unhook JNI methods
for (const auto &[clz, methods] : *jni_hook_list) {
if (!methods.empty() && old_jniRegisterNativeMethods(
g_ctx->env, clz.data(), methods.data(), methods.size()) != 0) {
ZLOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
success = false;
}
}
delete jni_hook_list;
// Unhook xhook
for (const auto &[path, sym, old_func] : *xhook_list) {
if (xhook_register(path, sym, *old_func, nullptr) != 0) {
ZLOGE("Failed to register xhook [%s]\n", sym);
success = false;
}
}
delete xhook_list;
if (!hook_refresh()) {
ZLOGE("Failed to restore xhook\n");
success = false;
}
return success;
}
+324
View File
@@ -0,0 +1,324 @@
// Generated by gen_jni_hooks.py
namespace {
void *nativeForkAndSpecialize_orig = nullptr;
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
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
);
ctx.nativeForkAndSpecialize_post();
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
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
);
ctx.nativeForkAndSpecialize_post();
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
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
);
ctx.nativeForkAndSpecialize_post();
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
args.is_top_app = &is_top_app;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
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
);
ctx.nativeForkAndSpecialize_post();
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
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;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
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
);
ctx.nativeForkAndSpecialize_post();
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 _0, jint _1, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_m)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _0, _1, nice_name, fds_to_close, instruction_set, app_data_dir
);
ctx.nativeForkAndSpecialize_post();
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 _2, jint _3, jstring nice_name, jintArray fds_to_close, jstring instruction_set, jstring app_data_dir, jint _4) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_n)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _2, _3, nice_name, fds_to_close, instruction_set, app_data_dir, _4
);
ctx.nativeForkAndSpecialize_post();
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 _5, jint _6, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_o)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _5, _6, nice_name, fds_to_close, fds_to_ignore, instruction_set, app_data_dir
);
ctx.nativeForkAndSpecialize_post();
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 _7, jint _8, jstring nice_name, jintArray fds_to_close, jintArray fds_to_ignore, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.fds_to_ignore = &fds_to_ignore;
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkAndSpecialize_pre();
reinterpret_cast<decltype(&nativeForkAndSpecialize_samsung_p)>(nativeForkAndSpecialize_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _7, _8, nice_name, fds_to_close, fds_to_ignore, is_child_zygote, instruction_set, app_data_dir
);
ctx.nativeForkAndSpecialize_post();
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_orig = nullptr;
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeSpecializeAppProcess_pre();
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
);
ctx.nativeSpecializeAppProcess_post();
}
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, 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;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeSpecializeAppProcess_pre();
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
);
ctx.nativeSpecializeAppProcess_post();
}
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) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, 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;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeSpecializeAppProcess_pre();
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
);
ctx.nativeSpecializeAppProcess_post();
}
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 _9, jint _10, jstring nice_name, jboolean is_child_zygote, jstring instruction_set, jstring app_data_dir) {
AppSpecializeArgs_v3 args(uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, nice_name, instruction_set, app_data_dir);
args.is_child_zygote = &is_child_zygote;
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeSpecializeAppProcess_pre();
reinterpret_cast<decltype(&nativeSpecializeAppProcess_samsung_q)>(nativeSpecializeAppProcess_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, mount_external, se_info, _9, _10, nice_name, is_child_zygote, instruction_set, app_data_dir
);
ctx.nativeSpecializeAppProcess_post();
}
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);
void *nativeForkSystemServer_orig = nullptr;
jint nativeForkSystemServer_l(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkSystemServer_pre();
reinterpret_cast<decltype(&nativeForkSystemServer_l)>(nativeForkSystemServer_orig)(
env, clazz, uid, gid, gids, runtime_flags, rlimits, permitted_capabilities, effective_capabilities
);
ctx.nativeForkSystemServer_post();
return ctx.pid;
}
jint nativeForkSystemServer_samsung_q(JNIEnv *env, jclass clazz, jint uid, jint gid, jintArray gids, jint runtime_flags, jint _11, jint _12, jobjectArray rlimits, jlong permitted_capabilities, jlong effective_capabilities) {
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities, effective_capabilities);
HookContext ctx;
ctx.env = env;
ctx.raw_args = &args;
ctx.nativeForkSystemServer_pre();
reinterpret_cast<decltype(&nativeForkSystemServer_samsung_q)>(nativeForkSystemServer_orig)(
env, clazz, uid, gid, gids, runtime_flags, _11, _12, rlimits, permitted_capabilities, effective_capabilities
);
ctx.nativeForkSystemServer_post();
return ctx.pid;
}
const JNINativeMethod nativeForkSystemServer_methods[] = {
{
"nativeForkSystemServer",
"(II[II[[IJJ)I",
(void *) &nativeForkSystemServer_l
},
{
"nativeForkSystemServer",
"(II[IIII[[IJJ)I",
(void *) &nativeForkSystemServer_samsung_q
},
};
constexpr int nativeForkSystemServer_methods_num = std::size(nativeForkSystemServer_methods);
unique_ptr<JNINativeMethod[]> hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) {
unique_ptr<JNINativeMethod[]> newMethods;
int clz_id = -1;
int hook_cnt = 0;
do {
if (className == "com/android/internal/os/Zygote"sv) {
clz_id = 0;
hook_cnt = 3;
break;
}
} while (false);
if (hook_cnt) {
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) {
if (hook_cnt && clz_id == 0) {
HOOK_JNI(nativeForkAndSpecialize)
HOOK_JNI(nativeSpecializeAppProcess)
HOOK_JNI(nativeForkSystemServer)
}
class_map[methods[i].name][methods[i].signature] = methods[i].fnPtr;
}
return newMethods;
}
} // namespace
+212
View File
@@ -0,0 +1,212 @@
#include <sys/mount.h>
#include <android/dlext.h>
#include <dlfcn.h>
#include <magisk.hpp>
#include <base.hpp>
#include <socket.hpp>
#include <daemon.hpp>
#include <selinux.hpp>
#include "zygisk.hpp"
using namespace std;
// Entrypoint for app_process overlay
int app_process_main(int argc, char *argv[]) {
android_logging();
char buf[PATH_MAX];
bool zygote = false;
if (!selinux_enabled()) {
for (int i = 0; i < argc; ++i) {
if (argv[i] == "--zygote"sv) {
zygote = true;
break;
}
}
} else if (auto fp = open_file("/proc/self/attr/current", "r")) {
fscanf(fp.get(), "%s", buf);
zygote = (buf == "u:r:zygote:s0"sv);
}
if (!zygote) {
// For the non zygote case, we need to get real app_process via passthrough
// We have to connect magiskd via exec-ing magisk due to SELinux restrictions
// This is actually only relevant for calling app_process via ADB shell
// because zygisk shall already have the app_process overlays unmounted
// during app process specialization within its private mount namespace.
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
if (fork_dont_care() == 0) {
// This fd has to survive exec
fcntl(fds[1], F_SETFD, 0);
snprintf(buf, sizeof(buf), "%d", fds[1]);
#if defined(__LP64__)
execlp("magisk", "zygisk", "passthrough", buf, "1", (char *) nullptr);
#else
execlp("magisk", "zygisk", "passthrough", buf, "0", (char *) nullptr);
#endif
exit(-1);
}
close(fds[1]);
if (read_int(fds[0]) != 0) {
fprintf(stderr, "Failed to connect magisk daemon, "
"try umount %s or reboot.\n", argv[0]);
return 1;
}
int app_proc_fd = recv_fd(fds[0]);
if (app_proc_fd < 0)
return 1;
close(fds[0]);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
fexecve(app_proc_fd, argv, environ);
return 1;
}
if (int socket = zygisk_request(ZygiskRequest::SETUP); socket >= 0) {
do {
if (read_int(socket) != 0)
break;
int app_proc_fd = recv_fd(socket);
if (app_proc_fd < 0)
break;
string tmp = read_string(socket);
if (char *ld = getenv("LD_PRELOAD")) {
string env = ld;
env += ':';
env += HIJACK_BIN;
setenv("LD_PRELOAD", env.data(), 1);
} else {
setenv("LD_PRELOAD", HIJACK_BIN, 1);
}
setenv(INJECT_ENV_1, "1", 1);
setenv(MAGISKTMP_ENV, tmp.data(), 1);
close(socket);
fcntl(app_proc_fd, F_SETFD, FD_CLOEXEC);
fexecve(app_proc_fd, argv, environ);
} while (false);
close(socket);
}
// If encountering any errors, unmount and execute the original app_process
xreadlink("/proc/self/exe", buf, sizeof(buf));
xumount2("/proc/self/exe", MNT_DETACH);
execve(buf, argv, environ);
return 1;
}
static void zygiskd(int socket) {
if (getuid() != 0 || fcntl(socket, F_GETFD) < 0)
exit(-1);
#if defined(__LP64__)
set_nice_name("zygiskd64");
LOGI("* Launching zygiskd64\n");
#else
set_nice_name("zygiskd32");
LOGI("* Launching zygiskd32\n");
#endif
// Load modules
using comp_entry = void(*)(int);
vector<comp_entry> modules;
{
vector<int> module_fds = recv_fds(socket);
for (int fd : module_fds) {
comp_entry entry = nullptr;
struct stat s{};
if (fstat(fd, &s) == 0 && S_ISREG(s.st_mode)) {
android_dlextinfo info {
.flags = ANDROID_DLEXT_USE_LIBRARY_FD,
.library_fd = fd,
};
if (void *h = android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)) {
*(void **) &entry = dlsym(h, "zygisk_companion_entry");
} else {
LOGW("Failed to dlopen zygisk module: %s\n", dlerror());
}
}
modules.push_back(entry);
close(fd);
}
}
// ack
write_int(socket, 0);
// Start accepting requests
pollfd pfd = { socket, POLLIN, 0 };
for (;;) {
poll(&pfd, 1, -1);
if (pfd.revents && !(pfd.revents & POLLIN)) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int client = recv_fd(socket);
if (client < 0) {
// Something bad happened in magiskd, terminate zygiskd
exit(0);
}
int module_id = read_int(client);
if (module_id >= 0 && module_id < modules.size() && modules[module_id]) {
exec_task([=, entry = modules[module_id]] {
struct stat s1;
fstat(client, &s1);
entry(client);
// Only close client if it is the same file so we don't
// accidentally close a re-used file descriptor.
// This check is required because the module companion
// handler could've closed the file descriptor already.
if (struct stat s2; fstat(client, &s2) == 0) {
if (s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino) {
close(client);
}
}
});
} else {
close(client);
}
}
}
// Entrypoint where we need to re-exec ourselves
// This should only ever be called internally
int zygisk_main(int argc, char *argv[]) {
android_logging();
if (argc == 3 && argv[1] == "companion"sv) {
zygiskd(parse_int(argv[2]));
} else if (argc == 4 && argv[1] == "passthrough"sv) {
int client = parse_int(argv[2]);
int is_64_bit = parse_int(argv[3]);
if (fcntl(client, F_GETFD) < 0)
return 1;
if (int magiskd = connect_daemon(MainRequest::ZYGISK_PASSTHROUGH); magiskd >= 0) {
write_int(magiskd, ZygiskRequest::PASSTHROUGH);
write_int(magiskd, is_64_bit);
if (read_int(magiskd) != 0) {
write_int(client, 1);
return 0;
}
write_int(client, 0);
int real_app_fd = recv_fd(magiskd);
send_fd(client, real_app_fd);
} else {
write_int(client, 1);
return 0;
}
}
return 0;
}
+31
View 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(align_to(sz, ALIGN));
}
void memory_block::release() {
if (_area)
munmap(_area, CAPACITY);
}
} // namespace jni_hook
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <map>
#include <parallel_hashmap/phmap.h>
#include <base.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
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "api.hpp"
namespace {
struct HookContext;
struct ZygiskModule;
struct AppSpecializeArgs_v3 {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jobjectArray &rlimits;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
jintArray *fds_to_ignore = nullptr;
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;
AppSpecializeArgs_v3(
jint &uid, jint &gid, jintArray &gids, jint &runtime_flags,
jobjectArray &rlimits, 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), rlimits(rlimits),
mount_external(mount_external), se_info(se_info), nice_name(nice_name),
instruction_set(instruction_set), app_data_dir(app_data_dir) {}
};
struct AppSpecializeArgs_v1 {
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;
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs_v1(const AppSpecializeArgs_v3 *v3) :
uid(v3->uid), gid(v3->gid), gids(v3->gids), runtime_flags(v3->runtime_flags),
mount_external(v3->mount_external), se_info(v3->se_info), nice_name(v3->nice_name),
instruction_set(v3->instruction_set), app_data_dir(v3->app_data_dir),
is_child_zygote(v3->is_child_zygote), is_top_app(v3->is_top_app),
pkg_data_info_list(v3->pkg_data_info_list),
whitelisted_data_info_list(v3->whitelisted_data_info_list),
mount_data_dirs(v3->mount_data_dirs), mount_storage_dirs(v3->mount_storage_dirs) {}
};
struct module_abi_raw {
long api_version;
void *_this;
void (*preAppSpecialize)(void *, void *);
void (*postAppSpecialize)(void *, const void *);
void (*preServerSpecialize)(void *, void *);
void (*postServerSpecialize)(void *, const void *);
};
using module_abi_v1 = module_abi_raw;
struct ServerSpecializeArgs_v1 {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs_v1(
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) {}
};
enum : uint32_t {
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
DENYLIST_ENFORCING = (1u << 30),
PROCESS_IS_MAGISK_APP = (1u << 31),
UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING),
PRIVATE_MASK = (0x3u << 30)
};
struct ApiTable {
// These first 2 entries are permanent
ZygiskModule *module;
bool (*registerModule)(ApiTable *, long *);
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 (*setOption)(ZygiskModule *, zygisk::Option);
} v1{};
struct {
int (*getModuleDir)(ZygiskModule *);
uint32_t (*getFlags)(ZygiskModule *);
} v2{};
ApiTable(ZygiskModule *m);
};
#define call_app(method) \
switch (*ver) { \
case 1: \
case 2: { \
AppSpecializeArgs_v1 a(args); \
v1->method(v1->_this, &a); \
break; \
} \
case 3: \
v1->method(v1->_this, args); \
break; \
}
struct ZygiskModule {
void preAppSpecialize(AppSpecializeArgs_v3 *args) const {
call_app(preAppSpecialize)
}
void postAppSpecialize(const AppSpecializeArgs_v3 *args) const {
call_app(postAppSpecialize)
}
void preServerSpecialize(ServerSpecializeArgs_v1 *args) const {
v1->preServerSpecialize(v1->_this, args);
}
void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const {
v1->postServerSpecialize(v1->_this, args);
}
int connectCompanion() const;
int getModuleDir() const;
void setOption(zygisk::Option opt);
static uint32_t getFlags();
void doUnload() const { if (unload) dlclose(handle); }
int getId() const { return id; }
ZygiskModule(int id, void *handle, void *entry);
static bool RegisterModule(ApiTable *table, long *module);
union {
void (* const entry)(void *, void *);
void * const raw_entry;
};
ApiTable api;
private:
const int id;
bool unload = false;
void * const handle;
union {
long *ver = nullptr;
module_abi_v1 *v1;
};
};
} // namespace
+240
View 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 <base.hpp>
#include "zygisk.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(&regs);
memcpy(&regs_bak, &regs, 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(&regs);
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(&regs);
// Restore registers
remote_setregs(&regs_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
View 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__)
+132
View File
@@ -0,0 +1,132 @@
#include <cinttypes>
#include <base.hpp>
#include "zygisk.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;
});
return maps;
}
std::pair<void *, size_t> find_map_range(const char *name, unsigned long inode) {
vector<map_info> maps = find_maps(name);
uintptr_t start = 0u;
uintptr_t end = 0u;
for (const auto &map : maps) {
if (map.inode == inode) {
if (start == 0) {
start = map.start;
end = map.end;
} else if (map.start == end) {
end = map.end;
}
}
}
LOGD("found map %s with start = %zx, end = %zx\n", name, start, end);
return make_pair(reinterpret_cast<void *>(start), end - start);
}
void unmap_all(const char *name) {
vector<map_info> maps = find_maps(name);
for (map_info &info : maps) {
void *addr = reinterpret_cast<void *>(info.start);
size_t size = info.end - info.start;
if (info.perms & PROT_READ) {
// Make sure readable pages are still readable
void *dummy = xmmap(nullptr, size, PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
mremap(dummy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
} else {
munmap(addr, size);
}
}
}
void remap_all(const char *name) {
vector<map_info> maps = find_maps(name);
for (map_info &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);
if ((info.perms & PROT_READ) == 0) {
mprotect(addr, size, PROT_READ);
}
memcpy(copy, addr, size);
mremap(copy, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, addr);
mprotect(addr, size, info.perms);
}
}
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 {
if (addr >= info.start && addr < info.end) {
if (lib)
strcpy(lib, 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](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;
}
return true;
});
return addr;
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <stdint.h>
#include <jni.h>
#include <vector>
#include <daemon.hpp>
#define INJECT_ENV_1 "MAGISK_INJ_1"
#define INJECT_ENV_2 "MAGISK_INJ_2"
#define MAGISKTMP_ENV "MAGISKTMP"
#define HIJACK_BIN64 "/system/bin/appwidget"
#define HIJACK_BIN32 "/system/bin/bu"
namespace ZygiskRequest {
enum : int {
SETUP,
GET_INFO,
GET_LOG_PIPE,
CONNECT_COMPANION,
GET_MODDIR,
PASSTHROUGH,
END
};
}
#if defined(__LP64__)
#define ZLOGD(...) LOGD("zygisk64: " __VA_ARGS__)
#define ZLOGE(...) LOGE("zygisk64: " __VA_ARGS__)
#define ZLOGI(...) LOGI("zygisk64: " __VA_ARGS__)
#define HIJACK_BIN HIJACK_BIN64
#else
#define ZLOGD(...) LOGD("zygisk32: " __VA_ARGS__)
#define ZLOGE(...) LOGE("zygisk32: " __VA_ARGS__)
#define ZLOGI(...) LOGI("zygisk32: " __VA_ARGS__)
#define HIJACK_BIN HIJACK_BIN32
#endif
// Find the memory address + size of the pages matching name + inode
std::pair<void*, size_t> find_map_range(const char *name, unsigned long inode);
// Unmap all pages matching the name
void unmap_all(const char *name);
// Remap all matching pages with anonymous pages
void remap_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);
extern void *self_handle;
void hook_functions();
int remote_get_info(int uid, const char *process, uint32_t *flags, std::vector<int> &fds);
int remote_request_unmount();
inline int zygisk_request(int req) {
int fd = connect_daemon(MainRequest::ZYGISK);
write_int(fd, req);
return fd;
}