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:
5ec1cff
2023-01-31 15:03:01 +08:00
committed by GitHub
parent 50a50a8d39
commit b8678720fb
28 changed files with 2476 additions and 22 deletions

6
.gitmodules vendored Normal file
View 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

View File

@@ -16,5 +16,4 @@ android {
dependencies {
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
implementation("org.lsposed.lsplt:lsplt-standalone:1.1")
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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
View 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

341
loader/src/include/api.hpp Normal file
View 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"

View File

@@ -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);
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include <pthread.h>
#include <string_view>
#include <functional>
#include <map>
#include <atomic>
#include <daemon.hpp>
#define ISOLATED_MAGIC "isolated"
namespace DenyRequest {
enum : int {
ENFORCE,
DISABLE,
ADD,
REMOVE,
LIST,
STATUS,
END
};
}
namespace DenyResponse {
enum : int {
OK,
ENFORCED,
NOT_ENFORCED,
ITEM_EXIST,
ITEM_NOT_EXIST,
INVALID_PKG,
NO_NS,
ERROR,
END
};
}
// CLI entries
int enable_deny();
int disable_deny();
int add_list(int client);
int rm_list(int client);
void ls_list(int client);
// Utility functions
bool is_deny_target(int uid, std::string_view process);
void revert_unmount();
extern std::atomic<bool> denylist_enforced;

View 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

View 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

View 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
View 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;
}

View File

@@ -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);
}

View 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;
}

View 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;
}

View 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 &reg: register_info) {
if (regexec(&reg.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;
}

View File

@@ -1,9 +0,0 @@
#include <lsplt.hpp>
#include "logging.h"
extern "C"
void entry(void* handle) {
LOGD("Injector handle: %p", handle);
}

View File

@@ -0,0 +1,31 @@
#include "memory.hpp"
namespace jni_hook {
// We know our minimum alignment is WORD size (size of pointer)
static constexpr size_t ALIGN = sizeof(long);
// 4MB is more than enough
static constexpr size_t CAPACITY = (1 << 22);
// No need to be thread safe as the initial mmap always happens on the main thread
static uint8_t *_area = nullptr;
static std::atomic<uint8_t *> _curr = nullptr;
void *memory_block::allocate(size_t sz) {
if (!_area) {
// Memory will not actually be allocated because physical pages are mapped in on-demand
_area = static_cast<uint8_t *>(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

View 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; });
}

View 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

View File

@@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
#include <jni.h>
#include <vector>
extern void *self_handle;
void hook_functions();

View File

@@ -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")

View File

@@ -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

View File

@@ -18,4 +18,5 @@ pub enum DaemonSocketAction {
ReadNativeBridge,
ReadModules,
RequestCompanionSocket,
GetModuleDir,
}

View File

@@ -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(())
}