From b8678720fbe921e1d9609be1e616c8ba27959d27 Mon Sep 17 00:00:00 2001 From: 5ec1cff <56485584+5ec1cff@users.noreply.github.com> Date: Tue, 31 Jan 2023 15:03:01 +0800 Subject: [PATCH] Zygisk injector (#1) * fix x86 * add lsplt * transplant from zygisk * api v4 Signed-off-by: 5ec1cff * Remove redundant logs Signed-off-by: 5ec1cff --------- Signed-off-by: 5ec1cff --- .gitmodules | 6 + loader/build.gradle.kts | 1 - loader/src/Android.mk | 5 +- loader/src/common/daemon.cpp | 36 +- loader/src/common/socket_utils.cpp | 11 +- loader/src/external/Android.mk | 20 + loader/src/external/liblsplt | 1 + loader/src/external/parallel-hashmap | 1 + loader/src/include/api.hpp | 341 ++++++++++++ loader/src/include/daemon.h | 11 +- loader/src/include/deny.hpp | 52 ++ loader/src/include/elf_util.h | 137 +++++ loader/src/include/jni_hooks.hpp | 324 +++++++++++ loader/src/include/memory.hpp | 45 ++ loader/src/include/misc.hpp | 100 ++++ loader/src/include/socket_utils.h | 4 + loader/src/injector/elf_util.cpp | 260 +++++++++ loader/src/injector/entry.cpp | 30 + loader/src/injector/hook.cpp | 787 +++++++++++++++++++++++++++ loader/src/injector/injector.cpp | 9 - loader/src/injector/memory.cpp | 31 ++ loader/src/injector/misc.cpp | 32 ++ loader/src/injector/module.hpp | 222 ++++++++ loader/src/injector/zygisk.hpp | 10 + module/build.gradle.kts | 4 + module/src/customize.sh | 6 +- zygiskd/src/constants.rs | 1 + zygiskd/src/zygisk.rs | 11 + 28 files changed, 2476 insertions(+), 22 deletions(-) create mode 100644 .gitmodules create mode 100644 loader/src/external/Android.mk create mode 160000 loader/src/external/liblsplt create mode 160000 loader/src/external/parallel-hashmap create mode 100644 loader/src/include/api.hpp create mode 100644 loader/src/include/deny.hpp create mode 100644 loader/src/include/elf_util.h create mode 100644 loader/src/include/jni_hooks.hpp create mode 100644 loader/src/include/memory.hpp create mode 100644 loader/src/include/misc.hpp create mode 100644 loader/src/injector/elf_util.cpp create mode 100644 loader/src/injector/entry.cpp create mode 100644 loader/src/injector/hook.cpp delete mode 100644 loader/src/injector/injector.cpp create mode 100644 loader/src/injector/memory.cpp create mode 100644 loader/src/injector/misc.cpp create mode 100644 loader/src/injector/module.hpp create mode 100644 loader/src/injector/zygisk.hpp diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..01ccb65 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/loader/build.gradle.kts b/loader/build.gradle.kts index 2c3ef40..39d6b0b 100644 --- a/loader/build.gradle.kts +++ b/loader/build.gradle.kts @@ -16,5 +16,4 @@ android { dependencies { implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0") - implementation("org.lsposed.lsplt:lsplt-standalone:1.1") } diff --git a/loader/src/Android.mk b/loader/src/Android.mk index 1ea2a81..74441a6 100644 --- a/loader/src/Android.mk +++ b/loader/src/Android.mk @@ -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 diff --git a/loader/src/common/daemon.cpp b/loader/src/common/daemon.cpp index cdb430f..d0dea38 100644 --- a/loader/src/common/daemon.cpp +++ b/loader/src/common/daemon.cpp @@ -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; + } + } } diff --git a/loader/src/common/socket_utils.cpp b/loader/src/common/socket_utils.cpp index 80a4be2..bb85c2a 100644 --- a/loader/src/common/socket_utils.cpp +++ b/loader/src/common/socket_utils.cpp @@ -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(fd, 0); } + bool write_usize(int fd, size_t val) { + return write_exact(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(fd, 0); + } } diff --git a/loader/src/external/Android.mk b/loader/src/external/Android.mk new file mode 100644 index 0000000..ce44805 --- /dev/null +++ b/loader/src/external/Android.mk @@ -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) diff --git a/loader/src/external/liblsplt b/loader/src/external/liblsplt new file mode 160000 index 0000000..204a163 --- /dev/null +++ b/loader/src/external/liblsplt @@ -0,0 +1 @@ +Subproject commit 204a1636884d397617101e2bae3a78dd3febb163 diff --git a/loader/src/external/parallel-hashmap b/loader/src/external/parallel-hashmap new file mode 160000 index 0000000..87ece91 --- /dev/null +++ b/loader/src/external/parallel-hashmap @@ -0,0 +1 @@ +Subproject commit 87ece91c6e4c457c5faac179dae6e11e2cd39b16 diff --git a/loader/src/include/api.hpp b/loader/src/include/api.hpp new file mode 100644 index 0000000..ab77a74 --- /dev/null +++ b/loader/src/include/api.hpp @@ -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 + +#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 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 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(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 + 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" diff --git a/loader/src/include/daemon.h b/loader/src/include/daemon.h index 0f8117a..b811f1c 100644 --- a/loader/src/include/daemon.h +++ b/loader/src/include/daemon.h @@ -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 ReadModules(); + + UniqueFd ConnectCompanion(size_t index); + + UniqueFd GetModuleDir(size_t index); } diff --git a/loader/src/include/deny.hpp b/loader/src/include/deny.hpp new file mode 100644 index 0000000..656b1dc --- /dev/null +++ b/loader/src/include/deny.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#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 denylist_enforced; diff --git a/loader/src/include/elf_util.h b/loader/src/include/elf_util.h new file mode 100644 index 0000000..99317ae --- /dev/null +++ b/loader/src/include/elf_util.h @@ -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 . + * + * Copyright (C) 2019 Swift Gan + * Copyright (C) 2021 LSPosed Contributors + */ +#ifndef SANDHOOK_ELF_UTIL_H +#define SANDHOOK_ELF_UTIL_H + +#include +#include +#include +#include +#include +#include + +#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((uintptr_t) base + offset - bias); + } else { + return 0; + } + } + + template + requires(std::is_pointer_v) + constexpr T getSymbAddress(std::string_view name) const { + return reinterpret_cast(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 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 diff --git a/loader/src/include/jni_hooks.hpp b/loader/src/include/jni_hooks.hpp new file mode 100644 index 0000000..4445ad6 --- /dev/null +++ b/loader/src/include/jni_hooks.hpp @@ -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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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 hookAndSaveJNIMethods(const char *className, const JNINativeMethod *methods, int numMethods) { + unique_ptr 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(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 diff --git a/loader/src/include/memory.hpp b/loader/src/include/memory.hpp new file mode 100644 index 0000000..a09654e --- /dev/null +++ b/loader/src/include/memory.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-builtins" +#include +#pragma clang diagnostic pop + +#include +#include + +namespace jni_hook { + +struct memory_block { + static void *allocate(size_t sz); + static void deallocate(void *, size_t) { /* Monotonic increase */ } + static void release(); +}; + +template +using allocator = stateless_allocator; + +using string = std::basic_string, allocator>; + +// Use node_hash_map since it will use less memory because we are using a monotonic allocator +template +using hash_map = phmap::node_hash_map, + phmap::priv::hash_default_eq, + allocator> +>; + +template +using tree_map = std::map, + allocator> +>; + +} // namespace jni_hook + +// Provide heterogeneous lookup for jni_hook::string +namespace phmap::priv { +template <> struct HashEq : StringHashEqT {}; +} // namespace phmap::priv diff --git a/loader/src/include/misc.hpp b/loader/src/include/misc.hpp new file mode 100644 index 0000000..6f45852 --- /dev/null +++ b/loader/src/include/misc.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include +#include "logging.h" +#include +#include + +#include +#include + +#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 +class stateless_allocator { +public: + using value_type = T; + T *allocate(size_t num) { return static_cast(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 + stateless_allocator(const stateless_allocator&) {} + bool operator==(const stateless_allocator&) { return true; } + bool operator!=(const stateless_allocator&) { return false; } +}; + +using sFILE = std::unique_ptr; +using sDIR = std::unique_ptr; +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 +static inline void default_new(T *&p) { p = new T(); } + +template +static inline void default_new(std::unique_ptr &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 +static inline T align_to(T v, int a) { + static_assert(std::is_integral::value); + return (v + a - 1) / a * a; +} diff --git a/loader/src/include/socket_utils.h b/loader/src/include/socket_utils.h index ae6321d..e186d47 100644 --- a/loader/src/include/socket_utils.h +++ b/loader/src/include/socket_utils.h @@ -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); } diff --git a/loader/src/injector/elf_util.cpp b/loader/src/injector/elf_util.cpp new file mode 100644 index 0000000..83c8030 --- /dev/null +++ b/loader/src/injector/elf_util.cpp @@ -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 . + * + * Copyright (C) 2019 Swift Gan + * Copyright (C) 2021 LSPosed Contributors + */ +#include +#include +#include +#include +#include +#include +#include +#include "logging.h" +#include "elf_util.h" + +using namespace SandHook; + +template +inline constexpr auto offsetOf(ElfW(Ehdr) *head, ElfW(Off) off) { + return reinterpret_cast, T, T *>>( + reinterpret_cast(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(mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0)); + + close(fd); + + section_header = offsetOf(header, header->e_shoff); + + auto shoff = reinterpret_cast(section_header); + char *section_str = offsetOf(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(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(header, symtab_offset); + } + break; + } + case SHT_STRTAB: { + if (bias == -4396) { + strtab = section_h; + symstr_offset = section_h->sh_offset; + strtab_start = offsetOf(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(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(((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(d_buf + 4); + gnu_bucket_ = reinterpret_cast(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(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(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(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(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(load_addr); + return true; +} diff --git a/loader/src/injector/entry.cpp b/loader/src/injector/entry.cpp new file mode 100644 index 0000000..116469a --- /dev/null +++ b/loader/src/injector/entry.cpp @@ -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; +} + diff --git a/loader/src/injector/hook.cpp b/loader/src/injector/hook.cpp new file mode 100644 index 0000000..16b8c9f --- /dev/null +++ b/loader/src/injector/hook.cpp @@ -0,0 +1,787 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#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 modules; + + int pid; + bitset flags; + uint32_t info_flags; + bitset allowed_fds; + vector 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 register_info; + vector 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> *xhook_list; +map, StringCmp> *jni_hook_list; +hash_map>> *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(self); + *reinterpret_cast(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 +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(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(&vtable_entry<0>); + new_table[1] = reinterpret_cast(&vtable_entry<1>); + new_table[2] = reinterpret_cast(&vtable_entry<2>); + new_table[3] = reinterpret_cast(&vtable_entry<3>); + new_table[4] = reinterpret_cast(&vtable_entry<4>); + new_table[5] = reinterpret_cast(&vtable_entry<5>); + new_table[6] = reinterpret_cast(&vtable_entry<6>); + new_table[7] = reinterpret_cast(&vtable_entry<7>); + + // Swizzle C++ vtable to hook virtual function + gAppRuntimeVTable = *reinterpret_cast(self); + *reinterpret_cast(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 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(&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("_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; +} diff --git a/loader/src/injector/injector.cpp b/loader/src/injector/injector.cpp deleted file mode 100644 index 4af3df5..0000000 --- a/loader/src/injector/injector.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -#include "logging.h" - -extern "C" -void entry(void* handle) { - LOGD("Injector handle: %p", handle); - -} diff --git a/loader/src/injector/memory.cpp b/loader/src/injector/memory.cpp new file mode 100644 index 0000000..1ebcf52 --- /dev/null +++ b/loader/src/injector/memory.cpp @@ -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 _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(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 diff --git a/loader/src/injector/misc.cpp b/loader/src/injector/misc.cpp new file mode 100644 index 0000000..959c733 --- /dev/null +++ b/loader/src/injector/misc.cpp @@ -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; }); +} diff --git a/loader/src/injector/module.hpp b/loader/src/injector/module.hpp new file mode 100644 index 0000000..e1d13bc --- /dev/null +++ b/loader/src/injector/module.hpp @@ -0,0 +1,222 @@ +#pragma once + +#include +#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 \ No newline at end of file diff --git a/loader/src/injector/zygisk.hpp b/loader/src/injector/zygisk.hpp new file mode 100644 index 0000000..4880af9 --- /dev/null +++ b/loader/src/injector/zygisk.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include +#include + +extern void *self_handle; + +void hook_functions(); + diff --git a/module/build.gradle.kts b/module/build.gradle.kts index 14ac0c9..0636523 100644 --- a/module/build.gradle.kts +++ b/module/build.gradle.kts @@ -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("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("tokens" to tokens) + filter("eol" to FixCrLfFilter.CrLf.newInstance("lf")) } into("bin") { from(project(":zygiskd").buildDir.path + "/rustJniLibs/android") diff --git a/module/src/customize.sh b/module/src/customize.sh index 015d194..d9cfd85 100644 --- a/module/src/customize.sh +++ b/module/src/customize.sh @@ -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 diff --git a/zygiskd/src/constants.rs b/zygiskd/src/constants.rs index e1f7e0a..1096ca5 100644 --- a/zygiskd/src/constants.rs +++ b/zygiskd/src/constants.rs @@ -18,4 +18,5 @@ pub enum DaemonSocketAction { ReadNativeBridge, ReadModules, RequestCompanionSocket, + GetModuleDir, } diff --git a/zygiskd/src/zygisk.rs b/zygiskd/src/zygisk.rs index 4419b53..0b8bc3e 100644 --- a/zygiskd/src/zygisk.rs +++ b/zygiskd/src/zygisk.rs @@ -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(()) }