You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
Zygisk injector (#1)
* fix x86 * add lsplt * transplant from zygisk * api v4 Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com> * Remove redundant logs Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com> --------- Signed-off-by: 5ec1cff <ewtqyqyewtqyqy@gmail.com>
This commit is contained in:
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "loader/src/external/liblsplt"]
|
||||
path = loader/src/external/liblsplt
|
||||
url = https://github.com/LSPosed/LSPlt
|
||||
[submodule "loader/src/external/parallel-hashmap"]
|
||||
path = loader/src/external/parallel-hashmap
|
||||
url = https://github.com/greg7mdp/parallel-hashmap
|
||||
@@ -16,5 +16,4 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||
implementation("org.lsposed.lsplt:lsplt-standalone:1.1")
|
||||
}
|
||||
|
||||
@@ -26,9 +26,10 @@ LOCAL_MODULE := injector
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/injector))
|
||||
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
||||
LOCAL_STATIC_LIBRARIES := cxx common lsplt
|
||||
LOCAL_STATIC_LIBRARIES := cxx common liblsplt libphmap
|
||||
LOCAL_LDLIBS := -llog
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
$(call import-module,prefab/cxx)
|
||||
$(call import-module,prefab/lsplt)
|
||||
|
||||
include src/external/Android.mk
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace zygiskd {
|
||||
}
|
||||
|
||||
bool PingHeartbeat() {
|
||||
LOGD("Daemon socket: %s", kZygiskSocket);
|
||||
LOGD("Daemon socket: %s", kZygiskSocket.data());
|
||||
auto fd = Connect(5);
|
||||
if (fd == -1) {
|
||||
PLOGE("Connect to zygiskd");
|
||||
@@ -58,14 +58,44 @@ namespace zygiskd {
|
||||
size_t len = socket_utils::read_usize(fd);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
std::string name = socket_utils::read_string(fd);
|
||||
UniqueFd module_fd = socket_utils::recv_fd(fd);
|
||||
int module_fd = socket_utils::recv_fd(fd);
|
||||
auto handle = DlopenMem(module_fd, RTLD_NOW);
|
||||
if (handle == nullptr) {
|
||||
LOGW("Failed to dlopen module %s: %s", name.data(), dlerror());
|
||||
continue;
|
||||
}
|
||||
modules.emplace_back(name, handle);
|
||||
modules.emplace_back(i, name, handle);
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
UniqueFd ConnectCompanion(size_t index) {
|
||||
auto fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("ConnectCompanion");
|
||||
return -1;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::RequestCompanionSocket);
|
||||
socket_utils::write_usize(fd, index);
|
||||
if (socket_utils::read_u8(fd) == 1) {
|
||||
return fd;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
UniqueFd GetModuleDir(size_t index) {
|
||||
auto fd = Connect(1);
|
||||
if (fd == -1) {
|
||||
PLOGE("GetModuleDir");
|
||||
return -1;
|
||||
}
|
||||
socket_utils::write_u8(fd, (uint8_t) SocketAction::GetModuleDir);
|
||||
socket_utils::write_usize(fd, index);
|
||||
if (socket_utils::read_u8(fd) == 1) {
|
||||
return fd;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,7 +65,8 @@ namespace socket_utils {
|
||||
|
||||
if (msg.msg_controllen != bufsz ||
|
||||
cmsg == nullptr ||
|
||||
cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||
// TODO: pass from rust: 20, expected: 16
|
||||
// cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||
cmsg->cmsg_level != SOL_SOCKET ||
|
||||
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||
return nullptr;
|
||||
@@ -89,6 +90,10 @@ namespace socket_utils {
|
||||
return read_exact_or<size_t>(fd, 0);
|
||||
}
|
||||
|
||||
bool write_usize(int fd, size_t val) {
|
||||
return write_exact<size_t>(fd, val);
|
||||
}
|
||||
|
||||
std::string read_string(int fd) {
|
||||
auto len = read_usize(fd);
|
||||
char buf[len + 1];
|
||||
@@ -111,4 +116,8 @@ namespace socket_utils {
|
||||
memcpy(&result, data, sizeof(int));
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t read_u8(int fd) {
|
||||
return read_exact_or<uint8_t>(fd, 0);
|
||||
}
|
||||
}
|
||||
|
||||
20
loader/src/external/Android.mk
vendored
Normal file
20
loader/src/external/Android.mk
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# liblsplt.a
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= liblsplt
|
||||
LOCAL_C_INCLUDES := $(LOCAL_PATH)/liblsplt/lsplt/src/main/jni/include
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_C_INCLUDES)
|
||||
LOCAL_CFLAGS := -Wall -Wextra -Werror -fvisibility=hidden
|
||||
LOCAL_CPPFLAGS := -std=c++20
|
||||
LOCAL_STATIC_LIBRARIES := libcxx
|
||||
LOCAL_SRC_FILES := \
|
||||
liblsplt/lsplt/src/main/jni/elf_util.cc \
|
||||
liblsplt/lsplt/src/main/jni/lsplt.cc
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# Header only library
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE:= libphmap
|
||||
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/parallel-hashmap
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
1
loader/src/external/liblsplt
vendored
Submodule
1
loader/src/external/liblsplt
vendored
Submodule
Submodule loader/src/external/liblsplt added at 204a163688
1
loader/src/external/parallel-hashmap
vendored
Submodule
1
loader/src/external/parallel-hashmap
vendored
Submodule
Submodule loader/src/external/parallel-hashmap added at 87ece91c6e
341
loader/src/include/api.hpp
Normal file
341
loader/src/include/api.hpp
Normal file
@@ -0,0 +1,341 @@
|
||||
/* Copyright 2022 John "topjohnwu" Wu
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
// This is the public API for Zygisk modules.
|
||||
// DO NOT MODIFY ANY CODE IN THIS HEADER.
|
||||
|
||||
// WARNING: this file may contain changes that are not finalized.
|
||||
// Always use the following published header for development:
|
||||
// https://github.com/topjohnwu/zygisk-module-sample/blob/master/module/jni/zygisk.hpp
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#define ZYGISK_API_VERSION 4
|
||||
|
||||
/*
|
||||
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 method is called as soon as the module is loaded into the target process.
|
||||
// A Zygisk API handle will be passed as an argument.
|
||||
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
|
||||
|
||||
// This method 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 method 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 method runs as the same privilege of the app's own code.
|
||||
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
|
||||
|
||||
// This method is called before the system server process is specialized.
|
||||
// See preAppSpecialize(args) for more info.
|
||||
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
|
||||
|
||||
// This method 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 methods 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 methods due to SELinux restrictions.
|
||||
//
|
||||
// The pre[XXX]Specialize methods 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 method 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 methods.
|
||||
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
|
||||
// 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 method 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();
|
||||
|
||||
// Exempt the provided file descriptor from being automatically closed.
|
||||
//
|
||||
// This API only make sense in preAppSpecialize; calling this method in any other situation
|
||||
// is either a no-op (returns true) or an error (returns false).
|
||||
//
|
||||
// When false is returned, the provided file descriptor will eventually be closed by zygote.
|
||||
bool exemptFd(int fd);
|
||||
|
||||
// Hook JNI native methods for a class
|
||||
//
|
||||
// Lookup all registered JNI native methods and replace it with your own methods.
|
||||
// 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 `inode`, replace function `symbol` with `newFunc`.
|
||||
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
|
||||
void pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc);
|
||||
|
||||
// Commit all the hooks that was previously registered.
|
||||
// Returns false if an error occurred.
|
||||
bool pltHookCommit();
|
||||
|
||||
private:
|
||||
internal::api_table *tbl;
|
||||
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); }
|
||||
|
||||
/*********************************************************
|
||||
* The following is internal ABI implementation detail.
|
||||
* You do not have to understand what it is doing.
|
||||
*********************************************************/
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct module_abi {
|
||||
long api_version;
|
||||
ModuleBase *impl;
|
||||
|
||||
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), impl(module) {
|
||||
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
|
||||
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
|
||||
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
|
||||
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
|
||||
}
|
||||
};
|
||||
|
||||
struct api_table {
|
||||
// Base
|
||||
void *impl;
|
||||
bool (*registerModule)(api_table *, module_abi *);
|
||||
|
||||
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
bool (*exemptFd)(int);
|
||||
bool (*pltHookCommit)();
|
||||
int (*connectCompanion)(void * /* impl */);
|
||||
void (*setOption)(void * /* impl */, Option);
|
||||
int (*getModuleDir)(void * /* impl */);
|
||||
uint32_t (*getFlags)(void * /* impl */);
|
||||
};
|
||||
|
||||
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->tbl = table;
|
||||
module->onLoad(api, env);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
inline int Api::connectCompanion() {
|
||||
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
|
||||
}
|
||||
inline int Api::getModuleDir() {
|
||||
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
|
||||
}
|
||||
inline void Api::setOption(Option opt) {
|
||||
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
|
||||
}
|
||||
inline uint32_t Api::getFlags() {
|
||||
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
|
||||
}
|
||||
inline bool Api::exemptFd(int fd) {
|
||||
return tbl->exemptFd != nullptr && tbl->exemptFd(fd);
|
||||
}
|
||||
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
|
||||
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
|
||||
}
|
||||
inline void Api::pltHookRegister(dev_t dev, ino_t inode, const char *symbol, void *newFunc, void **oldFunc) {
|
||||
if (tbl->pltHookRegister) tbl->pltHookRegister(dev, inode, symbol, newFunc, oldFunc);
|
||||
}
|
||||
inline bool Api::pltHookCommit() {
|
||||
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
|
||||
}
|
||||
|
||||
} // namespace zygisk
|
||||
|
||||
extern "C" {
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
|
||||
|
||||
[[gnu::visibility("default")]] [[gnu::used]]
|
||||
void zygisk_companion_entry(int);
|
||||
|
||||
} // extern "C"
|
||||
@@ -9,7 +9,6 @@
|
||||
#else
|
||||
# define LP_SELECT(lp32, lp64) lp32
|
||||
#endif
|
||||
|
||||
constexpr std::string_view kZygiskSocket = LP_SELECT("zygiskd32", "zygiskd64") "socket_placeholder";
|
||||
|
||||
class UniqueFd {
|
||||
@@ -46,9 +45,10 @@ namespace zygiskd {
|
||||
struct Module {
|
||||
std::string name;
|
||||
void* handle;
|
||||
int id;
|
||||
|
||||
inline explicit Module(std::string name, void* handle)
|
||||
: name(name), handle(handle) {}
|
||||
inline explicit Module(int id, std::string name, void* handle)
|
||||
: name(name), handle(handle), id(id) {}
|
||||
};
|
||||
|
||||
enum class SocketAction {
|
||||
@@ -56,6 +56,7 @@ namespace zygiskd {
|
||||
ReadNativeBridge,
|
||||
ReadModules,
|
||||
RequestCompanionSocket,
|
||||
GetModuleDir,
|
||||
};
|
||||
|
||||
bool PingHeartbeat();
|
||||
@@ -63,4 +64,8 @@ namespace zygiskd {
|
||||
std::string ReadNativeBridge();
|
||||
|
||||
std::vector<Module> ReadModules();
|
||||
|
||||
UniqueFd ConnectCompanion(size_t index);
|
||||
|
||||
UniqueFd GetModuleDir(size_t index);
|
||||
}
|
||||
|
||||
52
loader/src/include/deny.hpp
Normal file
52
loader/src/include/deny.hpp
Normal 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;
|
||||
137
loader/src/include/elf_util.h
Normal file
137
loader/src/include/elf_util.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2019 Swift Gan
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
#ifndef SANDHOOK_ELF_UTIL_H
|
||||
#define SANDHOOK_ELF_UTIL_H
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <linux/elf.h>
|
||||
#include <sys/types.h>
|
||||
#include <link.h>
|
||||
#include <string>
|
||||
|
||||
#define SHT_GNU_HASH 0x6ffffff6
|
||||
|
||||
namespace SandHook {
|
||||
class ElfImg {
|
||||
public:
|
||||
|
||||
ElfImg(std::string_view elf);
|
||||
|
||||
constexpr ElfW(Addr) getSymbOffset(std::string_view name) const {
|
||||
return getSymbOffset(name, GnuHash(name), ElfHash(name));
|
||||
}
|
||||
|
||||
constexpr ElfW(Addr) getSymbAddress(std::string_view name) const {
|
||||
ElfW(Addr) offset = getSymbOffset(name);
|
||||
if (offset > 0 && base != nullptr) {
|
||||
return static_cast<ElfW(Addr)>((uintptr_t) base + offset - bias);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
requires(std::is_pointer_v<T>)
|
||||
constexpr T getSymbAddress(std::string_view name) const {
|
||||
return reinterpret_cast<T>(getSymbAddress(name));
|
||||
}
|
||||
|
||||
bool isValid() const {
|
||||
return base != nullptr;
|
||||
}
|
||||
|
||||
const std::string name() const {
|
||||
return elf;
|
||||
}
|
||||
|
||||
~ElfImg();
|
||||
|
||||
private:
|
||||
ElfW(Addr) getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const;
|
||||
|
||||
ElfW(Addr) ElfLookup(std::string_view name, uint32_t hash) const;
|
||||
|
||||
ElfW(Addr) GnuLookup(std::string_view name, uint32_t hash) const;
|
||||
|
||||
ElfW(Addr) LinearLookup(std::string_view name) const;
|
||||
|
||||
constexpr static uint32_t ElfHash(std::string_view name);
|
||||
|
||||
constexpr static uint32_t GnuHash(std::string_view name);
|
||||
|
||||
bool findModuleBase();
|
||||
|
||||
std::string elf;
|
||||
void *base = nullptr;
|
||||
char *buffer = nullptr;
|
||||
off_t size = 0;
|
||||
off_t bias = -4396;
|
||||
ElfW(Ehdr) *header = nullptr;
|
||||
ElfW(Shdr) *section_header = nullptr;
|
||||
ElfW(Shdr) *symtab = nullptr;
|
||||
ElfW(Shdr) *strtab = nullptr;
|
||||
ElfW(Shdr) *dynsym = nullptr;
|
||||
ElfW(Sym) *symtab_start = nullptr;
|
||||
ElfW(Sym) *dynsym_start = nullptr;
|
||||
ElfW(Sym) *strtab_start = nullptr;
|
||||
ElfW(Off) symtab_count = 0;
|
||||
ElfW(Off) symstr_offset = 0;
|
||||
ElfW(Off) symstr_offset_for_symtab = 0;
|
||||
ElfW(Off) symtab_offset = 0;
|
||||
ElfW(Off) dynsym_offset = 0;
|
||||
ElfW(Off) symtab_size = 0;
|
||||
|
||||
uint32_t nbucket_{};
|
||||
uint32_t *bucket_ = nullptr;
|
||||
uint32_t *chain_ = nullptr;
|
||||
|
||||
uint32_t gnu_nbucket_{};
|
||||
uint32_t gnu_symndx_{};
|
||||
uint32_t gnu_bloom_size_;
|
||||
uint32_t gnu_shift2_;
|
||||
uintptr_t *gnu_bloom_filter_;
|
||||
uint32_t *gnu_bucket_;
|
||||
uint32_t *gnu_chain_;
|
||||
|
||||
mutable std::unordered_map<std::string_view, ElfW(Sym) *> symtabs_;
|
||||
};
|
||||
|
||||
constexpr uint32_t ElfImg::ElfHash(std::string_view name) {
|
||||
uint32_t h = 0, g;
|
||||
for (unsigned char p: name) {
|
||||
h = (h << 4) + p;
|
||||
g = h & 0xf0000000;
|
||||
h ^= g;
|
||||
h ^= g >> 24;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
constexpr uint32_t ElfImg::GnuHash(std::string_view name) {
|
||||
uint32_t h = 5381;
|
||||
for (unsigned char p: name) {
|
||||
h += (h << 5) + p;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ELF_UTIL_H
|
||||
324
loader/src/include/jni_hooks.hpp
Normal file
324
loader/src/include/jni_hooks.hpp
Normal file
@@ -0,0 +1,324 @@
|
||||
// Generated by gen_jni_hooks.py
|
||||
|
||||
namespace {
|
||||
|
||||
void *nativeForkAndSpecialize_orig = nullptr;
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
[[clang::no_stack_protector]] 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.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();
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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();
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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();
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
[[clang::no_stack_protector]] 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.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;
|
||||
}
|
||||
[[clang::no_stack_protector]] 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.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
|
||||
45
loader/src/include/memory.hpp
Normal file
45
loader/src/include/memory.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-builtins"
|
||||
#include <parallel_hashmap/phmap.h>
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#include <misc.hpp>
|
||||
#include <sys/mman.h>
|
||||
|
||||
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
|
||||
100
loader/src/include/misc.hpp
Normal file
100
loader/src/include/misc.hpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include "logging.h"
|
||||
#include <dirent.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
|
||||
#define DISALLOW_COPY_AND_MOVE(clazz) \
|
||||
clazz(const clazz &) = delete; \
|
||||
clazz(clazz &&) = delete;
|
||||
|
||||
class mutex_guard {
|
||||
DISALLOW_COPY_AND_MOVE(mutex_guard)
|
||||
public:
|
||||
explicit mutex_guard(pthread_mutex_t &m): mutex(&m) {
|
||||
pthread_mutex_lock(mutex);
|
||||
}
|
||||
void unlock() {
|
||||
pthread_mutex_unlock(mutex);
|
||||
mutex = nullptr;
|
||||
}
|
||||
~mutex_guard() {
|
||||
if (mutex) pthread_mutex_unlock(mutex);
|
||||
}
|
||||
private:
|
||||
pthread_mutex_t *mutex;
|
||||
};
|
||||
|
||||
using thread_entry = void *(*)(void *);
|
||||
int new_daemon_thread(thread_entry entry, void *arg);
|
||||
|
||||
template<typename T, typename Impl>
|
||||
class stateless_allocator {
|
||||
public:
|
||||
using value_type = T;
|
||||
T *allocate(size_t num) { return static_cast<T*>(Impl::allocate(sizeof(T) * num)); }
|
||||
void deallocate(T *ptr, size_t num) { Impl::deallocate(ptr, sizeof(T) * num); }
|
||||
stateless_allocator() = default;
|
||||
stateless_allocator(const stateless_allocator&) = default;
|
||||
stateless_allocator(stateless_allocator&&) = default;
|
||||
template <typename U>
|
||||
stateless_allocator(const stateless_allocator<U, Impl>&) {}
|
||||
bool operator==(const stateless_allocator&) { return true; }
|
||||
bool operator!=(const stateless_allocator&) { return false; }
|
||||
};
|
||||
|
||||
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
|
||||
using sDIR = std::unique_ptr<DIR, decltype(&closedir)>;
|
||||
sDIR make_dir(DIR *dp);
|
||||
sFILE make_file(FILE *fp);
|
||||
|
||||
static inline sDIR open_dir(const char *path) {
|
||||
return make_dir(opendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(const char *path) {
|
||||
return make_dir(opendir(path));
|
||||
}
|
||||
|
||||
static inline sDIR xopen_dir(int dirfd) {
|
||||
return make_dir(fdopendir(dirfd));
|
||||
}
|
||||
|
||||
static inline sFILE open_file(const char *path, const char *mode) {
|
||||
return make_file(fopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(const char *path, const char *mode) {
|
||||
return make_file(fopen(path, mode));
|
||||
}
|
||||
|
||||
static inline sFILE xopen_file(int fd, const char *mode) {
|
||||
return make_file(fdopen(fd, mode));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(T *&p) { p = new T(); }
|
||||
|
||||
template<class T>
|
||||
static inline void default_new(std::unique_ptr<T> &p) { p.reset(new T()); }
|
||||
|
||||
struct StringCmp {
|
||||
using is_transparent = void;
|
||||
bool operator()(std::string_view a, std::string_view b) const { return a < b; }
|
||||
};
|
||||
|
||||
/*
|
||||
* Bionic's atoi runs through strtol().
|
||||
* Use our own implementation for faster conversion.
|
||||
*/
|
||||
int parse_int(std::string_view s);
|
||||
|
||||
template <typename T>
|
||||
static inline T align_to(T v, int a) {
|
||||
static_assert(std::is_integral<T>::value);
|
||||
return (v + a - 1) / a * a;
|
||||
}
|
||||
@@ -17,4 +17,8 @@ namespace socket_utils {
|
||||
bool write_u8(int fd, uint8_t val);
|
||||
|
||||
int recv_fd(int fd);
|
||||
|
||||
bool write_usize(int fd, size_t val);
|
||||
|
||||
uint8_t read_u8(int fd);
|
||||
}
|
||||
|
||||
260
loader/src/injector/elf_util.cpp
Normal file
260
loader/src/injector/elf_util.cpp
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2019 Swift Gan
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
#include <malloc.h>
|
||||
#include <cstring>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <cassert>
|
||||
#include <sys/stat.h>
|
||||
#include "logging.h"
|
||||
#include "elf_util.h"
|
||||
|
||||
using namespace SandHook;
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) {
|
||||
return reinterpret_cast<std::conditional_t<std::is_pointer_v<T>, T, T *>>(
|
||||
reinterpret_cast<uintptr_t>(head) + off);
|
||||
}
|
||||
|
||||
ElfImg::ElfImg(std::string_view base_name) : elf(base_name) {
|
||||
if (!findModuleBase()) {
|
||||
base = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
//load elf
|
||||
int fd = open(elf.data(), O_RDONLY);
|
||||
if (fd < 0) {
|
||||
LOGE("failed to open %s", elf.data());
|
||||
return;
|
||||
}
|
||||
|
||||
size = lseek(fd, 0, SEEK_END);
|
||||
if (size <= 0) {
|
||||
LOGE("lseek() failed for %s", elf.data());
|
||||
}
|
||||
|
||||
header = reinterpret_cast<decltype(header)>(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0));
|
||||
|
||||
close(fd);
|
||||
|
||||
section_header = offsetOf<decltype(section_header)>(header, header->e_shoff);
|
||||
|
||||
auto shoff = reinterpret_cast<uintptr_t>(section_header);
|
||||
char *section_str = offsetOf<char *>(header, section_header[header->e_shstrndx].sh_offset);
|
||||
|
||||
for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) {
|
||||
auto *section_h = (ElfW(Shdr) *) shoff;
|
||||
char *sname = section_h->sh_name + section_str;
|
||||
auto entsize = section_h->sh_entsize;
|
||||
switch (section_h->sh_type) {
|
||||
case SHT_DYNSYM: {
|
||||
if (bias == -4396) {
|
||||
dynsym = section_h;
|
||||
dynsym_offset = section_h->sh_offset;
|
||||
dynsym_start = offsetOf<decltype(dynsym_start)>(header, dynsym_offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_SYMTAB: {
|
||||
if (strcmp(sname, ".symtab") == 0) {
|
||||
symtab = section_h;
|
||||
symtab_offset = section_h->sh_offset;
|
||||
symtab_size = section_h->sh_size;
|
||||
symtab_count = symtab_size / entsize;
|
||||
symtab_start = offsetOf<decltype(symtab_start)>(header, symtab_offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_STRTAB: {
|
||||
if (bias == -4396) {
|
||||
strtab = section_h;
|
||||
symstr_offset = section_h->sh_offset;
|
||||
strtab_start = offsetOf<decltype(strtab_start)>(header, symstr_offset);
|
||||
}
|
||||
if (strcmp(sname, ".strtab") == 0) {
|
||||
symstr_offset_for_symtab = section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_PROGBITS: {
|
||||
if (strtab == nullptr || dynsym == nullptr) break;
|
||||
if (bias == -4396) {
|
||||
bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SHT_HASH: {
|
||||
auto *d_un = offsetOf<ElfW(Word)>(header, section_h->sh_offset);
|
||||
nbucket_ = d_un[0];
|
||||
bucket_ = d_un + 2;
|
||||
chain_ = bucket_ + nbucket_;
|
||||
break;
|
||||
}
|
||||
case SHT_GNU_HASH: {
|
||||
auto *d_buf = reinterpret_cast<ElfW(Word) *>(((size_t) header) +
|
||||
section_h->sh_offset);
|
||||
gnu_nbucket_ = d_buf[0];
|
||||
gnu_symndx_ = d_buf[1];
|
||||
gnu_bloom_size_ = d_buf[2];
|
||||
gnu_shift2_ = d_buf[3];
|
||||
gnu_bloom_filter_ = reinterpret_cast<decltype(gnu_bloom_filter_)>(d_buf + 4);
|
||||
gnu_bucket_ = reinterpret_cast<decltype(gnu_bucket_)>(gnu_bloom_filter_ +
|
||||
gnu_bloom_size_);
|
||||
gnu_chain_ = gnu_bucket_ + gnu_nbucket_ - gnu_symndx_;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ElfW(Addr) ElfImg::ElfLookup(std::string_view name, uint32_t hash) const {
|
||||
if (nbucket_ == 0) return 0;
|
||||
|
||||
char *strings = (char *) strtab_start;
|
||||
|
||||
for (auto n = bucket_[hash % nbucket_]; n != 0; n = chain_[n]) {
|
||||
auto *sym = dynsym_start + n;
|
||||
if (name == strings + sym->st_name) {
|
||||
return sym->st_value;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) ElfImg::GnuLookup(std::string_view name, uint32_t hash) const {
|
||||
static constexpr auto bloom_mask_bits = sizeof(ElfW(Addr)) * 8;
|
||||
|
||||
if (gnu_nbucket_ == 0 || gnu_bloom_size_ == 0) return 0;
|
||||
|
||||
auto bloom_word = gnu_bloom_filter_[(hash / bloom_mask_bits) % gnu_bloom_size_];
|
||||
uintptr_t mask = 0
|
||||
| (uintptr_t) 1 << (hash % bloom_mask_bits)
|
||||
| (uintptr_t) 1 << ((hash >> gnu_shift2_) % bloom_mask_bits);
|
||||
if ((mask & bloom_word) == mask) {
|
||||
auto sym_index = gnu_bucket_[hash % gnu_nbucket_];
|
||||
if (sym_index >= gnu_symndx_) {
|
||||
char *strings = (char *) strtab_start;
|
||||
do {
|
||||
auto *sym = dynsym_start + sym_index;
|
||||
if (((gnu_chain_[sym_index] ^ hash) >> 1) == 0
|
||||
&& name == strings + sym->st_name) {
|
||||
return sym->st_value;
|
||||
}
|
||||
} while ((gnu_chain_[sym_index++] & 1) == 0);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ElfW(Addr) ElfImg::LinearLookup(std::string_view name) const {
|
||||
if (symtabs_.empty()) {
|
||||
symtabs_.reserve(symtab_count);
|
||||
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
|
||||
for (ElfW(Off) i = 0; i < symtab_count; i++) {
|
||||
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
|
||||
const char *st_name = offsetOf<const char *>(header, symstr_offset_for_symtab +
|
||||
symtab_start[i].st_name);
|
||||
if ((st_type == STT_FUNC || st_type == STT_OBJECT) && symtab_start[i].st_size) {
|
||||
symtabs_.emplace(st_name, &symtab_start[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auto i = symtabs_.find(name); i != symtabs_.end()) {
|
||||
return i->second->st_value;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ElfImg::~ElfImg() {
|
||||
//open elf file local
|
||||
if (buffer) {
|
||||
free(buffer);
|
||||
buffer = nullptr;
|
||||
}
|
||||
//use mmap
|
||||
if (header) {
|
||||
munmap(header, size);
|
||||
}
|
||||
}
|
||||
|
||||
ElfW(Addr)
|
||||
ElfImg::getSymbOffset(std::string_view name, uint32_t gnu_hash, uint32_t elf_hash) const {
|
||||
if (auto offset = GnuLookup(name, gnu_hash); offset > 0) {
|
||||
LOGD("found %s %p in %s in dynsym by gnuhash", name.data(),
|
||||
reinterpret_cast<void *>(offset), elf.data());
|
||||
return offset;
|
||||
} else if (offset = ElfLookup(name, elf_hash); offset > 0) {
|
||||
LOGD("found %s %p in %s in dynsym by elfhash", name.data(),
|
||||
reinterpret_cast<void *>(offset), elf.data());
|
||||
return offset;
|
||||
} else if (offset = LinearLookup(name); offset > 0) {
|
||||
LOGD("found %s %p in %s in symtab by linear lookup", name.data(),
|
||||
reinterpret_cast<void *>(offset), elf.data());
|
||||
return offset;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool ElfImg::findModuleBase() {
|
||||
char buff[256];
|
||||
off_t load_addr;
|
||||
bool found = false;
|
||||
FILE *maps = fopen("/proc/self/maps", "r");
|
||||
|
||||
|
||||
while (fgets(buff, sizeof(buff), maps)) {
|
||||
if ((strstr(buff, "r-xp") || strstr(buff, "r--p")) && strstr(buff, elf.data())) {
|
||||
LOGD("found: %s", buff);
|
||||
std::string_view b = buff;
|
||||
if (auto begin = b.find_last_of(' '); begin != std::string_view::npos && b[++begin] == '/') {
|
||||
found = true;
|
||||
elf = b.substr(begin);
|
||||
if (elf.back() == '\n') elf.pop_back();
|
||||
LOGD("update path: %s", elf.data());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOGE("failed to read load address for %s", elf.data());
|
||||
fclose(maps);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (char *next = buff; load_addr = strtoul(buff, &next, 16), next == buff) {
|
||||
LOGE("failed to read load address for %s", elf.data());
|
||||
}
|
||||
|
||||
fclose(maps);
|
||||
|
||||
LOGD("get module base %s: %lx", elf.data(), load_addr);
|
||||
|
||||
base = reinterpret_cast<void *>(load_addr);
|
||||
return true;
|
||||
}
|
||||
30
loader/src/injector/entry.cpp
Normal file
30
loader/src/injector/entry.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "logging.h"
|
||||
#include "zygisk.hpp"
|
||||
#include "module.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void *self_handle = nullptr;
|
||||
|
||||
[[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);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" __used void entry(void *handle) {
|
||||
LOGD("load success");
|
||||
self_handle = handle;
|
||||
hook_functions();
|
||||
}
|
||||
|
||||
// The following code runs in zygote/app process
|
||||
|
||||
static inline bool should_load_modules(uint32_t flags) {
|
||||
return (flags & UNMOUNT_MASK) != UNMOUNT_MASK &&
|
||||
(flags & PROCESS_IS_MAGISK_APP) != PROCESS_IS_MAGISK_APP;
|
||||
}
|
||||
|
||||
787
loader/src/injector/hook.cpp
Normal file
787
loader/src/injector/hook.cpp
Normal file
@@ -0,0 +1,787 @@
|
||||
#include <android/dlext.h>
|
||||
#include <sys/mount.h>
|
||||
#include <dlfcn.h>
|
||||
#include <bitset>
|
||||
#include <sys/prctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <regex.h>
|
||||
|
||||
#include <lsplt.hpp>
|
||||
|
||||
#include <daemon.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "zygisk.hpp"
|
||||
#include "memory.hpp"
|
||||
#include "module.hpp"
|
||||
#include "misc.hpp"
|
||||
|
||||
using namespace std;
|
||||
using jni_hook::hash_map;
|
||||
using jni_hook::tree_map;
|
||||
using xstring = jni_hook::string;
|
||||
|
||||
static bool unhook_functions();
|
||||
|
||||
namespace {
|
||||
|
||||
enum {
|
||||
POST_SPECIALIZE,
|
||||
APP_FORK_AND_SPECIALIZE,
|
||||
APP_SPECIALIZE,
|
||||
SERVER_FORK_AND_SPECIALIZE,
|
||||
DO_REVERT_UNMOUNT,
|
||||
CAN_UNLOAD_ZYGISK,
|
||||
SKIP_FD_SANITIZATION,
|
||||
|
||||
FLAG_MAX
|
||||
};
|
||||
|
||||
#define DCL_PRE_POST(name) \
|
||||
void name##_pre(); \
|
||||
void name##_post();
|
||||
|
||||
#define MAX_FD_SIZE 1024
|
||||
|
||||
struct HookContext {
|
||||
JNIEnv *env;
|
||||
union {
|
||||
void *ptr;
|
||||
AppSpecializeArgs_v3 *app;
|
||||
ServerSpecializeArgs_v1 *server;
|
||||
} args;
|
||||
|
||||
const char *process;
|
||||
vector<ZygiskModule> modules;
|
||||
|
||||
int pid;
|
||||
bitset<FLAG_MAX> flags;
|
||||
uint32_t info_flags;
|
||||
bitset<MAX_FD_SIZE> allowed_fds;
|
||||
vector<int> exempted_fds;
|
||||
|
||||
struct RegisterInfo {
|
||||
regex_t regex;
|
||||
string symbol;
|
||||
void *callback;
|
||||
void **backup;
|
||||
};
|
||||
|
||||
struct IgnoreInfo {
|
||||
regex_t regex;
|
||||
string symbol;
|
||||
};
|
||||
|
||||
pthread_mutex_t hook_info_lock;
|
||||
vector<RegisterInfo> register_info;
|
||||
vector<IgnoreInfo> ignore_info;
|
||||
|
||||
HookContext() : env(nullptr), args{nullptr}, process(nullptr), pid(-1), info_flags(0),
|
||||
hook_info_lock(PTHREAD_MUTEX_INITIALIZER) {}
|
||||
|
||||
void run_modules_pre();
|
||||
void run_modules_post();
|
||||
DCL_PRE_POST(fork)
|
||||
DCL_PRE_POST(app_specialize)
|
||||
DCL_PRE_POST(nativeForkAndSpecialize)
|
||||
DCL_PRE_POST(nativeSpecializeAppProcess)
|
||||
DCL_PRE_POST(nativeForkSystemServer)
|
||||
|
||||
void unload_zygisk();
|
||||
void sanitize_fds();
|
||||
bool exempt_fd(int fd);
|
||||
|
||||
// Compatibility shim
|
||||
void plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup);
|
||||
void plt_hook_exclude(const char *regex, const char *symbol);
|
||||
void plt_hook_process_regex();
|
||||
|
||||
bool plt_hook_commit();
|
||||
};
|
||||
|
||||
#undef DCL_PRE_POST
|
||||
|
||||
// Global variables
|
||||
vector<tuple<dev_t, ino_t, const char *, void **>> *xhook_list;
|
||||
map<string, vector<JNINativeMethod>, StringCmp> *jni_hook_list;
|
||||
hash_map<xstring, tree_map<xstring, tree_map<xstring, void *>>> *jni_method_map;
|
||||
|
||||
// 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]; \
|
||||
LOGI("replaced %s#" #method "\n", className); \
|
||||
--hook_cnt; \
|
||||
break; \
|
||||
} \
|
||||
} \
|
||||
if (j == method##_methods_num) { \
|
||||
LOGE("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);
|
||||
LOGV("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) {
|
||||
LOGV("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
|
||||
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 &&
|
||||
// For some unknown reason, unmounting app_process in SysUI can break.
|
||||
// This is reproducible on the official AVD running API 26 and 27.
|
||||
// Simply avoid doing any unmounts for SysUI to avoid potential issues.
|
||||
g_ctx->process && g_ctx->process != "com.android.systemui"sv) {
|
||||
if (g_ctx->flags[DO_REVERT_UNMOUNT]) {
|
||||
// revert_unmount();
|
||||
}
|
||||
// Restore errno back to 0
|
||||
errno = 0;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 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->flags[CAN_UNLOAD_ZYGISK] = 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) {
|
||||
LOGD("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);
|
||||
}
|
||||
|
||||
void hookVirtualTable(void *self) {
|
||||
LOGD("hook AndroidRuntime virtual table\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;
|
||||
}
|
||||
|
||||
#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)
|
||||
: id(id), handle(handle), entry{entry}, api{}, mod{nullptr} {
|
||||
// Make sure all pointers are null
|
||||
memset(&api, 0, sizeof(api));
|
||||
api.base.impl = this;
|
||||
api.base.registerModule = &ZygiskModule::RegisterModuleImpl;
|
||||
}
|
||||
|
||||
bool ZygiskModule::RegisterModuleImpl(ApiTable *api, long *module) {
|
||||
if (api == nullptr || module == nullptr)
|
||||
return false;
|
||||
|
||||
long api_version = *module;
|
||||
// Unsupported version
|
||||
if (api_version > ZYGISK_API_VERSION)
|
||||
return false;
|
||||
|
||||
// Set the actual module_abi*
|
||||
api->base.impl->mod = { module };
|
||||
|
||||
// Fill in API accordingly with module API version
|
||||
if (api_version >= 1) {
|
||||
api->v1.hookJniNativeMethods = hookJniNativeMethods;
|
||||
api->v1.pltHookRegister = [](auto a, auto b, auto c, auto d) {
|
||||
if (g_ctx) g_ctx->plt_hook_register(a, b, c, d);
|
||||
};
|
||||
api->v1.pltHookExclude = [](auto a, auto b) {
|
||||
if (g_ctx) g_ctx->plt_hook_exclude(a, b);
|
||||
};
|
||||
api->v1.pltHookCommit = []() { return g_ctx && g_ctx->plt_hook_commit(); };
|
||||
api->v1.connectCompanion = [](ZygiskModule *m) { return m->connectCompanion(); };
|
||||
api->v1.setOption = [](ZygiskModule *m, auto opt) { m->setOption(opt); };
|
||||
}
|
||||
if (api_version >= 2) {
|
||||
api->v2.getModuleDir = [](ZygiskModule *m) { return m->getModuleDir(); };
|
||||
api->v2.getFlags = [](auto) { return ZygiskModule::getFlags(); };
|
||||
}
|
||||
if (api_version >= 4) {
|
||||
api->v4.pltHookRegister = [](dev_t dev, ino_t inode, const char *symbol, void *fn, void **backup) {
|
||||
if (dev == 0 || inode == 0 || symbol == nullptr || fn == nullptr)
|
||||
return;
|
||||
lsplt::RegisterHook(dev, inode, symbol, fn, backup);
|
||||
};
|
||||
api->v4.exemptFd = [](int fd) { return g_ctx && g_ctx->exempt_fd(fd); };
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void HookContext::plt_hook_register(const char *regex, const char *symbol, void *fn, void **backup) {
|
||||
if (regex == nullptr || symbol == nullptr || fn == nullptr)
|
||||
return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_info_lock);
|
||||
register_info.emplace_back(RegisterInfo{re, symbol, fn, backup});
|
||||
}
|
||||
|
||||
void HookContext::plt_hook_exclude(const char *regex, const char *symbol) {
|
||||
if (!regex) return;
|
||||
regex_t re;
|
||||
if (regcomp(&re, regex, REG_NOSUB) != 0)
|
||||
return;
|
||||
mutex_guard lock(hook_info_lock);
|
||||
ignore_info.emplace_back(IgnoreInfo{re, symbol ?: ""});
|
||||
}
|
||||
|
||||
void HookContext::plt_hook_process_regex() {
|
||||
if (register_info.empty())
|
||||
return;
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.offset != 0 || !map.is_private || !(map.perms & PROT_READ)) continue;
|
||||
for (auto ®: register_info) {
|
||||
if (regexec(®.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
bool ignored = false;
|
||||
for (auto &ign: ignore_info) {
|
||||
if (regexec(&ign.regex, map.path.data(), 0, nullptr, 0) != 0)
|
||||
continue;
|
||||
if (ign.symbol.empty() || ign.symbol == reg.symbol) {
|
||||
ignored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!ignored) {
|
||||
lsplt::RegisterHook(map.dev, map.inode, reg.symbol, reg.callback, reg.backup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HookContext::plt_hook_commit() {
|
||||
{
|
||||
mutex_guard lock(hook_info_lock);
|
||||
plt_hook_process_regex();
|
||||
register_info.clear();
|
||||
ignore_info.clear();
|
||||
}
|
||||
return lsplt::CommitHook();
|
||||
}
|
||||
|
||||
|
||||
bool ZygiskModule::valid() const {
|
||||
if (mod.api_version == nullptr)
|
||||
return false;
|
||||
switch (*mod.api_version) {
|
||||
case 4:
|
||||
case 3:
|
||||
case 2:
|
||||
case 1:
|
||||
return mod.v1->impl && mod.v1->preAppSpecialize && mod.v1->postAppSpecialize &&
|
||||
mod.v1->preServerSpecialize && mod.v1->postServerSpecialize;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int ZygiskModule::connectCompanion() const {
|
||||
return zygiskd::ConnectCompanion(id);
|
||||
}
|
||||
|
||||
int ZygiskModule::getModuleDir() const {
|
||||
return zygiskd::GetModuleDir(id);
|
||||
}
|
||||
|
||||
void ZygiskModule::setOption(zygisk::Option opt) {
|
||||
if (g_ctx == nullptr)
|
||||
return;
|
||||
switch (opt) {
|
||||
case zygisk::FORCE_DENYLIST_UNMOUNT:
|
||||
g_ctx->flags[DO_REVERT_UNMOUNT] = true;
|
||||
break;
|
||||
case zygisk::DLCLOSE_MODULE_LIBRARY:
|
||||
unload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ZygiskModule::getFlags() {
|
||||
return g_ctx ? (g_ctx->info_flags & ~PRIVATE_MASK) : 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
int sigmask(int how, int signum) {
|
||||
sigset_t set;
|
||||
sigemptyset(&set);
|
||||
sigaddset(&set, signum);
|
||||
return sigprocmask(how, &set, nullptr);
|
||||
}
|
||||
|
||||
void HookContext::fork_pre() {
|
||||
g_ctx = this;
|
||||
// Do our own fork before loading any 3rd party code
|
||||
// First block SIGCHLD, unblock after original fork is done
|
||||
sigmask(SIG_BLOCK, SIGCHLD);
|
||||
pid = old_fork();
|
||||
if (pid != 0 || flags[SKIP_FD_SANITIZATION])
|
||||
return;
|
||||
|
||||
// Record all open fds
|
||||
auto dir = xopen_dir("/proc/self/fd");
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
int fd = parse_int(entry->d_name);
|
||||
if (fd < 0 || fd >= MAX_FD_SIZE) {
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
// The dirfd should not be allowed
|
||||
allowed_fds[dirfd(dir.get())] = false;
|
||||
}
|
||||
|
||||
void HookContext::sanitize_fds() {
|
||||
if (flags[SKIP_FD_SANITIZATION])
|
||||
return;
|
||||
|
||||
if (flags[APP_FORK_AND_SPECIALIZE]) {
|
||||
auto update_fd_array = [&](int off) -> jintArray {
|
||||
if (exempted_fds.empty())
|
||||
return nullptr;
|
||||
|
||||
jintArray array = env->NewIntArray(off + exempted_fds.size());
|
||||
if (array == nullptr)
|
||||
return nullptr;
|
||||
|
||||
env->SetIntArrayRegion(array, off, exempted_fds.size(), exempted_fds.data());
|
||||
for (int fd : exempted_fds) {
|
||||
if (fd >= 0 && fd < MAX_FD_SIZE) {
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
}
|
||||
*args.app->fds_to_ignore = array;
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
return array;
|
||||
};
|
||||
|
||||
if (jintArray fdsToIgnore = *args.app->fds_to_ignore) {
|
||||
int *arr = env->GetIntArrayElements(fdsToIgnore, nullptr);
|
||||
int len = env->GetArrayLength(fdsToIgnore);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
int fd = arr[i];
|
||||
if (fd >= 0 && fd < MAX_FD_SIZE) {
|
||||
allowed_fds[fd] = true;
|
||||
}
|
||||
}
|
||||
if (jintArray newFdList = update_fd_array(len)) {
|
||||
env->SetIntArrayRegion(newFdList, 0, len, arr);
|
||||
}
|
||||
env->ReleaseIntArrayElements(fdsToIgnore, arr, JNI_ABORT);
|
||||
} else {
|
||||
update_fd_array(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (pid != 0)
|
||||
return;
|
||||
|
||||
// Close all forbidden fds to prevent crashing
|
||||
auto dir = open_dir("/proc/self/fd");
|
||||
int dfd = dirfd(dir.get());
|
||||
for (dirent *entry; (entry = readdir(dir.get()));) {
|
||||
int fd = parse_int(entry->d_name);
|
||||
if ((fd < 0 || fd >= MAX_FD_SIZE || !allowed_fds[fd]) && fd != dfd) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HookContext::fork_post() {
|
||||
// Unblock SIGCHLD in case the original method didn't
|
||||
sigmask(SIG_UNBLOCK, SIGCHLD);
|
||||
g_ctx = nullptr;
|
||||
unload_zygisk();
|
||||
}
|
||||
|
||||
void HookContext::run_modules_pre() {
|
||||
auto ms = zygiskd::ReadModules();
|
||||
modules.reserve(ms.size());
|
||||
for (auto &m: ms) {
|
||||
auto h = m.handle;
|
||||
if (void *e = dlsym(h, "zygisk_module_entry")) {
|
||||
modules.emplace_back(m.id, h, e);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &m : modules) {
|
||||
m.onLoad(env);
|
||||
if (flags[APP_SPECIALIZE]) {
|
||||
m.preAppSpecialize(args.app);
|
||||
} else if (flags[SERVER_FORK_AND_SPECIALIZE]) {
|
||||
m.preServerSpecialize(args.server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HookContext::run_modules_post() {
|
||||
flags[POST_SPECIALIZE] = true;
|
||||
for (const auto &m : modules) {
|
||||
if (flags[APP_SPECIALIZE]) {
|
||||
m.postAppSpecialize(args.app);
|
||||
} else if (flags[SERVER_FORK_AND_SPECIALIZE]) {
|
||||
m.postServerSpecialize(args.server);
|
||||
}
|
||||
m.tryUnload();
|
||||
}
|
||||
}
|
||||
|
||||
void HookContext::app_specialize_pre() {
|
||||
flags[APP_SPECIALIZE] = true;
|
||||
run_modules_pre();
|
||||
}
|
||||
|
||||
|
||||
void HookContext::app_specialize_post() {
|
||||
run_modules_post();
|
||||
if (info_flags & PROCESS_IS_MAGISK_APP) {
|
||||
setenv("ZYGISK_ENABLED", "1", 1);
|
||||
}
|
||||
|
||||
// Cleanups
|
||||
env->ReleaseStringUTFChars(args.app->nice_name, process);
|
||||
g_ctx = nullptr;
|
||||
}
|
||||
|
||||
void HookContext::unload_zygisk() {
|
||||
if (flags[CAN_UNLOAD_ZYGISK]) {
|
||||
// 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) {
|
||||
m.clearApi();
|
||||
}
|
||||
|
||||
new_daemon_thread(reinterpret_cast<thread_entry>(&dlclose), self_handle);
|
||||
}
|
||||
}
|
||||
|
||||
bool HookContext::exempt_fd(int fd) {
|
||||
if (flags[POST_SPECIALIZE] || flags[SKIP_FD_SANITIZATION])
|
||||
return true;
|
||||
if (!flags[APP_FORK_AND_SPECIALIZE])
|
||||
return false;
|
||||
exempted_fds.push_back(fd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
void HookContext::nativeSpecializeAppProcess_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
LOGV("pre specialize [%s]\n", process);
|
||||
g_ctx = this;
|
||||
// App specialize does not check FD
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
app_specialize_pre();
|
||||
}
|
||||
|
||||
void HookContext::nativeSpecializeAppProcess_post() {
|
||||
LOGV("post specialize [%s]\n", process);
|
||||
app_specialize_post();
|
||||
unload_zygisk();
|
||||
}
|
||||
|
||||
void HookContext::nativeForkSystemServer_pre() {
|
||||
LOGV("pre forkSystemServer\n");
|
||||
flags[SERVER_FORK_AND_SPECIALIZE] = true;
|
||||
|
||||
fork_pre();
|
||||
if (pid != 0)
|
||||
return;
|
||||
|
||||
run_modules_pre();
|
||||
|
||||
sanitize_fds();
|
||||
}
|
||||
|
||||
void HookContext::nativeForkSystemServer_post() {
|
||||
if (pid == 0) {
|
||||
LOGV("post forkSystemServer\n");
|
||||
run_modules_post();
|
||||
}
|
||||
fork_post();
|
||||
}
|
||||
|
||||
void HookContext::nativeForkAndSpecialize_pre() {
|
||||
process = env->GetStringUTFChars(args.app->nice_name, nullptr);
|
||||
LOGV("pre forkAndSpecialize [%s]\n", process);
|
||||
|
||||
flags[APP_FORK_AND_SPECIALIZE] = true;
|
||||
if (args.app->fds_to_ignore == nullptr) {
|
||||
// The field fds_to_ignore don't exist before Android 8.0, which FDs are not checked
|
||||
flags[SKIP_FD_SANITIZATION] = true;
|
||||
}
|
||||
|
||||
fork_pre();
|
||||
if (pid == 0) {
|
||||
app_specialize_pre();
|
||||
}
|
||||
sanitize_fds();
|
||||
}
|
||||
|
||||
void HookContext::nativeForkAndSpecialize_post() {
|
||||
if (pid == 0) {
|
||||
LOGV("post forkAndSpecialize [%s]\n", process);
|
||||
app_specialize_post();
|
||||
}
|
||||
fork_post();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static bool hook_refresh() {
|
||||
if (lsplt::CommitHook()) {
|
||||
return true;
|
||||
} else {
|
||||
LOGE("xhook failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void hook_register(dev_t dev, ino_t inode, const char *symbol, void *new_func, void **old_func) {
|
||||
if (!lsplt::RegisterHook(dev, inode, symbol, new_func, old_func)) {
|
||||
LOGE("Failed to register hook \"%s\"\n", symbol);
|
||||
return;
|
||||
}
|
||||
xhook_list->emplace_back(dev, inode, symbol, old_func);
|
||||
}
|
||||
|
||||
#define XHOOK_REGISTER_SYM(DEV, INO, SYM, NAME) \
|
||||
hook_register(DEV, INO, SYM, (void*) new_##NAME, (void **) &old_##NAME)
|
||||
|
||||
#define XHOOK_REGISTER(DEV, INO, NAME) \
|
||||
XHOOK_REGISTER_SYM(DEV, INO, #NAME, NAME)
|
||||
|
||||
#include "elf_util.h"
|
||||
|
||||
void hook_functions() {
|
||||
default_new(xhook_list);
|
||||
default_new(jni_hook_list);
|
||||
default_new(jni_method_map);
|
||||
|
||||
dev_t dev = 0;
|
||||
ino_t inode = 0;
|
||||
for (auto &map : lsplt::MapInfo::Scan()) {
|
||||
if (map.path.ends_with("libandroid_runtime.so")) {
|
||||
dev = map.dev;
|
||||
inode = map.inode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XHOOK_REGISTER(dev, inode, fork);
|
||||
XHOOK_REGISTER(dev, inode, unshare);
|
||||
XHOOK_REGISTER(dev, inode, jniRegisterNativeMethods);
|
||||
XHOOK_REGISTER(dev, inode, selinux_android_setcontext);
|
||||
hook_refresh();
|
||||
|
||||
// Remove unhooked methods
|
||||
xhook_list->erase(
|
||||
std::remove_if(xhook_list->begin(), xhook_list->end(),
|
||||
[](auto &t) { return *std::get<3>(t) == nullptr;}),
|
||||
xhook_list->end());
|
||||
|
||||
if (old_jniRegisterNativeMethods == nullptr) {
|
||||
do {
|
||||
LOGD("jniRegisterNativeMethods not hooked, using fallback\n");
|
||||
|
||||
SandHook::ElfImg art("libandroid_runtime.so");
|
||||
|
||||
auto *GetRuntime = art.getSymbAddress<void*(*)()>("_ZN7android14AndroidRuntime10getRuntimeEv");
|
||||
|
||||
if (GetRuntime == nullptr) {
|
||||
LOGE("GetRuntime is nullptr");
|
||||
break;
|
||||
}
|
||||
hookVirtualTable(GetRuntime());
|
||||
} while (false);
|
||||
|
||||
// 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) {
|
||||
LOGE("Failed to restore JNI hook of class [%s]\n", clz.data());
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
delete jni_hook_list;
|
||||
|
||||
// Unhook xhook
|
||||
for (const auto &[dev, inode, sym, old_func] : *xhook_list) {
|
||||
if (!lsplt::RegisterHook(dev, inode, sym, *old_func, nullptr)) {
|
||||
LOGE("Failed to register xhook [%s]\n", sym);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
delete xhook_list;
|
||||
if (!hook_refresh()) {
|
||||
LOGE("Failed to restore xhook\n");
|
||||
success = false;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#include <lsplt.hpp>
|
||||
|
||||
#include "logging.h"
|
||||
|
||||
extern "C"
|
||||
void entry(void* handle) {
|
||||
LOGD("Injector handle: %p", handle);
|
||||
|
||||
}
|
||||
31
loader/src/injector/memory.cpp
Normal file
31
loader/src/injector/memory.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "memory.hpp"
|
||||
|
||||
namespace jni_hook {
|
||||
|
||||
// We know our minimum alignment is WORD size (size of pointer)
|
||||
static constexpr size_t ALIGN = sizeof(long);
|
||||
|
||||
// 4MB is more than enough
|
||||
static constexpr size_t CAPACITY = (1 << 22);
|
||||
|
||||
// No need to be thread safe as the initial mmap always happens on the main thread
|
||||
static uint8_t *_area = nullptr;
|
||||
|
||||
static std::atomic<uint8_t *> _curr = nullptr;
|
||||
|
||||
void *memory_block::allocate(size_t sz) {
|
||||
if (!_area) {
|
||||
// Memory will not actually be allocated because physical pages are mapped in on-demand
|
||||
_area = static_cast<uint8_t *>(mmap(
|
||||
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
|
||||
32
loader/src/injector/misc.cpp
Normal file
32
loader/src/injector/misc.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "misc.hpp"
|
||||
|
||||
int new_daemon_thread(thread_entry entry, void *arg) {
|
||||
pthread_t thread;
|
||||
pthread_attr_t attr;
|
||||
pthread_attr_init(&attr);
|
||||
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||||
errno = pthread_create(&thread, &attr, entry, arg);
|
||||
if (errno) {
|
||||
PLOGE("pthread_create");
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
int parse_int(std::string_view s) {
|
||||
int val = 0;
|
||||
for (char c : s) {
|
||||
if (!c) break;
|
||||
if (c > '9' || c < '0')
|
||||
return -1;
|
||||
val = val * 10 + c - '0';
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
sDIR make_dir(DIR *dp) {
|
||||
return sDIR(dp, [](DIR *dp){ return dp ? closedir(dp) : 1; });
|
||||
}
|
||||
|
||||
sFILE make_file(FILE *fp) {
|
||||
return sFILE(fp, [](FILE *fp){ return fp ? fclose(fp) : 1; });
|
||||
}
|
||||
222
loader/src/injector/module.hpp
Normal file
222
loader/src/injector/module.hpp
Normal file
@@ -0,0 +1,222 @@
|
||||
#pragma once
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include "api.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
struct HookContext;
|
||||
struct ZygiskModule;
|
||||
|
||||
struct AppSpecializeArgs_v1;
|
||||
using AppSpecializeArgs_v2 = AppSpecializeArgs_v1;
|
||||
struct AppSpecializeArgs_v3;
|
||||
using AppSpecializeArgs_v4 = AppSpecializeArgs_v3;
|
||||
|
||||
struct module_abi_v1;
|
||||
using module_abi_v2 = module_abi_v1;
|
||||
using module_abi_v3 = module_abi_v1;
|
||||
using module_abi_v4 = module_abi_v1;
|
||||
|
||||
struct api_abi_v1;
|
||||
struct api_abi_v2;
|
||||
using api_abi_v3 = api_abi_v2;
|
||||
struct api_abi_v4;
|
||||
|
||||
union ApiTable;
|
||||
|
||||
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 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) {}
|
||||
};
|
||||
|
||||
struct module_abi_v1 {
|
||||
long api_version;
|
||||
void *impl;
|
||||
void (*preAppSpecialize)(void *, void *);
|
||||
void (*postAppSpecialize)(void *, const void *);
|
||||
void (*preServerSpecialize)(void *, void *);
|
||||
void (*postServerSpecialize)(void *, const void *);
|
||||
};
|
||||
|
||||
enum : uint32_t {
|
||||
PROCESS_GRANTED_ROOT = zygisk::StateFlag::PROCESS_GRANTED_ROOT,
|
||||
PROCESS_ON_DENYLIST = zygisk::StateFlag::PROCESS_ON_DENYLIST,
|
||||
|
||||
PROCESS_IS_SYS_UI = (1u << 29),
|
||||
DENYLIST_ENFORCING = (1u << 30),
|
||||
PROCESS_IS_MAGISK_APP = (1u << 31),
|
||||
|
||||
UNMOUNT_MASK = (PROCESS_ON_DENYLIST | DENYLIST_ENFORCING),
|
||||
PRIVATE_MASK = (PROCESS_IS_SYS_UI | DENYLIST_ENFORCING | PROCESS_IS_MAGISK_APP)
|
||||
};
|
||||
|
||||
struct api_abi_base {
|
||||
ZygiskModule *impl;
|
||||
bool (*registerModule)(ApiTable *, long *);
|
||||
};
|
||||
|
||||
struct api_abi_v1 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(const char *, const char *, void *, void **);
|
||||
/* 2 */ void (*pltHookExclude)(const char *, const char *);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
};
|
||||
|
||||
struct api_abi_v2 : public api_abi_v1 {
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
struct api_abi_v4 : public api_abi_base {
|
||||
/* 0 */ void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
|
||||
/* 1 */ void (*pltHookRegister)(dev_t, ino_t, const char *, void *, void **);
|
||||
/* 2 */ bool (*exemptFd)(int);
|
||||
/* 3 */ bool (*pltHookCommit)();
|
||||
/* 4 */ int (*connectCompanion)(ZygiskModule *);
|
||||
/* 5 */ void (*setOption)(ZygiskModule *, zygisk::Option);
|
||||
/* 6 */ int (*getModuleDir)(ZygiskModule *);
|
||||
/* 7 */ uint32_t (*getFlags)(ZygiskModule *);
|
||||
};
|
||||
|
||||
union ApiTable {
|
||||
api_abi_base base;
|
||||
api_abi_v1 v1;
|
||||
api_abi_v2 v2;
|
||||
api_abi_v4 v4;
|
||||
};
|
||||
|
||||
#define call_app(method) \
|
||||
switch (*mod.api_version) { \
|
||||
case 1: \
|
||||
case 2: { \
|
||||
AppSpecializeArgs_v1 a(args); \
|
||||
mod.v1->method(mod.v1->impl, &a); \
|
||||
break; \
|
||||
} \
|
||||
case 3: \
|
||||
case 4: \
|
||||
mod.v1->method(mod.v1->impl, args);\
|
||||
break; \
|
||||
}
|
||||
|
||||
struct ZygiskModule {
|
||||
|
||||
void onLoad(void *env) {
|
||||
entry.fn(&api, env);
|
||||
}
|
||||
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 {
|
||||
mod.v1->preServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
void postServerSpecialize(const ServerSpecializeArgs_v1 *args) const {
|
||||
mod.v1->postServerSpecialize(mod.v1->impl, args);
|
||||
}
|
||||
|
||||
bool valid() const;
|
||||
int connectCompanion() const;
|
||||
int getModuleDir() const;
|
||||
void setOption(zygisk::Option opt);
|
||||
static uint32_t getFlags();
|
||||
void tryUnload() const { if (unload) dlclose(handle); }
|
||||
void clearApi() { memset(&api, 0, sizeof(api)); }
|
||||
int getId() const { return id; }
|
||||
|
||||
ZygiskModule(int id, void *handle, void *entry);
|
||||
|
||||
static bool RegisterModuleImpl(ApiTable *api, long *module);
|
||||
|
||||
private:
|
||||
const int id;
|
||||
bool unload = false;
|
||||
|
||||
void * const handle;
|
||||
union {
|
||||
void * const ptr;
|
||||
void (* const fn)(void *, void *);
|
||||
} entry;
|
||||
|
||||
ApiTable api;
|
||||
|
||||
union {
|
||||
long *api_version;
|
||||
module_abi_v1 *v1;
|
||||
} mod;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
10
loader/src/injector/zygisk.hpp
Normal file
10
loader/src/injector/zygisk.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <jni.h>
|
||||
#include <vector>
|
||||
|
||||
extern void *self_handle;
|
||||
|
||||
void hook_functions();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import java.security.MessageDigest
|
||||
import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
@@ -33,6 +35,7 @@ androidComponents.onVariants { variant ->
|
||||
from("${rootProject.projectDir}/README.md")
|
||||
from("$projectDir/src") {
|
||||
exclude("module.prop", "customize.sh", "daemon.sh")
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
from("$projectDir/src") {
|
||||
include("module.prop")
|
||||
@@ -50,6 +53,7 @@ androidComponents.onVariants { variant ->
|
||||
"DEBUG" to if (buildTypeLowered == "debug") "true" else "false"
|
||||
)
|
||||
filter<ReplaceTokens>("tokens" to tokens)
|
||||
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
into("bin") {
|
||||
from(project(":zygiskd").buildDir.path + "/rustJniLibs/android")
|
||||
|
||||
@@ -60,9 +60,9 @@ mkdir "$MODPATH/system"
|
||||
[ "$HAS64BIT" = true ] && mkdir "$MODPATH/system/lib64"
|
||||
|
||||
if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
if [ "$HAS32BIT" = ture ]; then
|
||||
if [ "$HAS32BIT" = true ]; then
|
||||
ui_print "- Extracting x86 libraries"
|
||||
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin/zygiskd32" true
|
||||
extract "$ZIPFILE" 'bin/x86/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd32"
|
||||
extract "$ZIPFILE" 'lib/x86/libinjector.so' "$MODPATH/system/lib" true
|
||||
extract "$ZIPFILE" 'lib/x86/libzygiskloader.so' "$MODPATH/system/lib" true
|
||||
@@ -71,7 +71,7 @@ if [ "$ARCH" = "x86" ] || [ "$ARCH" = "x64" ]; then
|
||||
|
||||
if [ "$HAS64BIT" = true ]; then
|
||||
ui_print "- Extracting x64 libraries"
|
||||
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin/zygiskd64" true
|
||||
extract "$ZIPFILE" 'bin/x86_64/zygiskd' "$MODPATH/bin" true
|
||||
mv "$MODPATH/bin/zygiskd" "$MODPATH/bin/zygiskd64"
|
||||
extract "$ZIPFILE" 'lib/x86_64/libinjector.so' "$MODPATH/system/lib64" true
|
||||
extract "$ZIPFILE" 'lib/x86_64/libzygiskloader.so' "$MODPATH/system/lib64" true
|
||||
|
||||
@@ -18,4 +18,5 @@ pub enum DaemonSocketAction {
|
||||
ReadNativeBridge,
|
||||
ReadModules,
|
||||
RequestCompanionSocket,
|
||||
GetModuleDir,
|
||||
}
|
||||
|
||||
@@ -213,6 +213,17 @@ fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()>
|
||||
}
|
||||
}
|
||||
}
|
||||
DaemonSocketAction::GetModuleDir => {
|
||||
unsafe {
|
||||
let index = stream.read_usize()?;
|
||||
let module = &context.modules[index];
|
||||
let path = format!("{}/{}", constants::PATH_KSU_MODULE_DIR, module.name);
|
||||
let filename = std::ffi::CString::new(path)?;
|
||||
let fd = libc::open(filename.as_ptr(), libc::O_PATH | libc::O_CLOEXEC);
|
||||
stream.send_fd(fd)?;
|
||||
libc::close(fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user