You've already forked ReZygisk
mirror of
https://github.com/PerformanC/ReZygisk.git
synced 2025-09-06 06:37:01 +00:00
Add injector
This commit is contained in:
17
loader/build.gradle.kts
Normal file
17
loader/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.library")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
buildFeatures {
|
||||||
|
prefab = true
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild.ndkBuild {
|
||||||
|
path("src/Android.mk")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("dev.rikka.ndk.thirdparty:cxx:1.2.0")
|
||||||
|
}
|
||||||
33
loader/src/Android.mk
Normal file
33
loader/src/Android.mk
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
define walk
|
||||||
|
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
|
||||||
|
endef
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_MODULE := common
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||||
|
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/common))
|
||||||
|
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
||||||
|
LOCAL_STATIC_LIBRARIES := cxx
|
||||||
|
LOCAL_LDLIBS := -llog
|
||||||
|
include $(BUILD_STATIC_LIBRARY)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
LOCAL_MODULE := zygiskloader
|
||||||
|
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
|
||||||
|
FILE_LIST := $(filter %.cpp, $(call walk, $(LOCAL_PATH)/loader))
|
||||||
|
LOCAL_SRC_FILES := $(FILE_LIST:COMMON_FILE_LIST:$(LOCAL_PATH)/%=%)
|
||||||
|
LOCAL_STATIC_LIBRARIES := cxx common
|
||||||
|
LOCAL_LDLIBS := -llog
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
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
|
||||||
|
LOCAL_LDLIBS := -llog
|
||||||
|
include $(BUILD_SHARED_LIBRARY)
|
||||||
|
|
||||||
|
$(call import-module,prefab/cxx)
|
||||||
14
loader/src/Application.mk
Normal file
14
loader/src/Application.mk
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
APP_CFLAGS := -Wall -Wextra
|
||||||
|
APP_CFLAGS += -fno-stack-protector -fomit-frame-pointer
|
||||||
|
APP_CFLAGS += -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__
|
||||||
|
APP_CPPFLAGS := -std=c++20
|
||||||
|
APP_CONLYFLAGS := -std=c18
|
||||||
|
APP_STL := none
|
||||||
|
|
||||||
|
ifneq ($(NDK_DEBUG),1)
|
||||||
|
APP_CFLAGS += -Oz -flto
|
||||||
|
APP_CFLAGS += -Wno-unused -Wno-unused-parameter -Werror
|
||||||
|
APP_CFLAGS += -fvisibility=hidden -fvisibility-inlines-hidden
|
||||||
|
APP_CFLAGS += -fno-unwind-tables -fno-asynchronous-unwind-tables
|
||||||
|
APP_LDFLAGS += -Wl,--exclude-libs,ALL -flto -Wl,--gc-sections -Wl,--strip-all
|
||||||
|
endif
|
||||||
79
loader/src/common/daemon.cpp
Normal file
79
loader/src/common/daemon.cpp
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
#include <linux/un.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "daemon.h"
|
||||||
|
#include "dl.h"
|
||||||
|
#include "socket_utils.h"
|
||||||
|
|
||||||
|
namespace zygiskd {
|
||||||
|
|
||||||
|
UniqueFd Connect(uint8_t retry) {
|
||||||
|
UniqueFd fd = socket(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
|
||||||
|
struct sockaddr_un addr{
|
||||||
|
.sun_family = AF_UNIX,
|
||||||
|
.sun_path={0},
|
||||||
|
};
|
||||||
|
strncpy(addr.sun_path + 1, kZygiskSocket.data(), kZygiskSocket.size());
|
||||||
|
socklen_t socklen = sizeof(sa_family_t) + strlen(addr.sun_path + 1) + 1;
|
||||||
|
|
||||||
|
while (retry--) {
|
||||||
|
int r = connect(fd, reinterpret_cast<struct sockaddr*>(&addr), socklen);
|
||||||
|
if (r != -1) return r;
|
||||||
|
LOGW("retrying to connect to zygiskd, sleep 1s");
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PingHeartbeat() {
|
||||||
|
auto fd = Connect(5);
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOGE("Connect to zygiskd");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ReadNativeBridge() {
|
||||||
|
auto fd = Connect(1);
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOGE("ReadNativeBridge");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadNativeBridge);
|
||||||
|
return socket_utils::read_string(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
UniqueFd ReadInjector() {
|
||||||
|
auto fd = Connect(1);
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOGE("ReadInjector");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadInjector);
|
||||||
|
return socket_utils::recv_fd(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Module> ReadModules() {
|
||||||
|
std::vector<Module> modules;
|
||||||
|
auto fd = Connect(1);
|
||||||
|
if (fd == -1) {
|
||||||
|
PLOGE("ReadModules");
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
socket_utils::write_u8(fd, (uint8_t) SocketAction::ReadModules);
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
loader/src/common/dl.cpp
Normal file
51
loader/src/common/dl.cpp
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#include <libgen.h>
|
||||||
|
#include <climits>
|
||||||
|
#include <cstring>
|
||||||
|
#include <android/dlext.h>
|
||||||
|
|
||||||
|
#include "dl.h"
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
extern "C" [[gnu::weak]] struct android_namespace_t*
|
||||||
|
//NOLINTNEXTLINE
|
||||||
|
__loader_android_create_namespace([[maybe_unused]] const char* name,
|
||||||
|
[[maybe_unused]] const char* ld_library_path,
|
||||||
|
[[maybe_unused]] const char* default_library_path,
|
||||||
|
[[maybe_unused]] uint64_t type,
|
||||||
|
[[maybe_unused]] const char* permitted_when_isolated_path,
|
||||||
|
[[maybe_unused]] android_namespace_t* parent,
|
||||||
|
[[maybe_unused]] const void* caller_addr);
|
||||||
|
|
||||||
|
void* DlopenExt(const char* path, int flags) {
|
||||||
|
auto info = android_dlextinfo{};
|
||||||
|
auto* dir = dirname(path);
|
||||||
|
auto* ns = &__loader_android_create_namespace == nullptr ? nullptr :
|
||||||
|
__loader_android_create_namespace(path, dir, nullptr,
|
||||||
|
2, /* ANDROID_NAMESPACE_TYPE_SHARED */
|
||||||
|
nullptr, nullptr,
|
||||||
|
reinterpret_cast<void*>(&DlopenExt));
|
||||||
|
if (ns) {
|
||||||
|
info.flags = ANDROID_DLEXT_USE_NAMESPACE;
|
||||||
|
info.library_namespace = ns;
|
||||||
|
|
||||||
|
LOGD("Open %s with namespace %p", path, ns);
|
||||||
|
} else {
|
||||||
|
LOGD("Cannot create namespace for %s", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* handle = android_dlopen_ext(path, flags, &info);
|
||||||
|
if (handle) {
|
||||||
|
LOGD("dlopen %s: %p", path, handle);
|
||||||
|
} else {
|
||||||
|
LOGE("dlopen %s: %s", path, dlerror());
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* DlopenMem(int memfd, int flags) {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
sprintf(path, "/proc/self/fd/%d", memfd);
|
||||||
|
return DlopenExt(path, flags);
|
||||||
|
}
|
||||||
114
loader/src/common/socket_utils.cpp
Normal file
114
loader/src/common/socket_utils.cpp
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#include <cstddef>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "socket_utils.h"
|
||||||
|
|
||||||
|
namespace socket_utils {
|
||||||
|
|
||||||
|
ssize_t xread(int fd, void* buf, size_t count) {
|
||||||
|
size_t read_sz = 0;
|
||||||
|
ssize_t ret;
|
||||||
|
do {
|
||||||
|
ret = read(fd, (std::byte*) buf + read_sz, count - read_sz);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
PLOGE("read");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
read_sz += ret;
|
||||||
|
} while (read_sz != count && ret != 0);
|
||||||
|
if (read_sz != count) {
|
||||||
|
PLOGE("read (%d != %d)", count, read_sz);
|
||||||
|
}
|
||||||
|
return read_sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t xwrite(int fd, const void* buf, size_t count) {
|
||||||
|
size_t write_sz = 0;
|
||||||
|
ssize_t ret;
|
||||||
|
do {
|
||||||
|
ret = write(fd, (std::byte*) buf + write_sz, count - write_sz);
|
||||||
|
if (ret < 0) {
|
||||||
|
if (errno == EINTR) continue;
|
||||||
|
PLOGE("write");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
write_sz += ret;
|
||||||
|
} while (write_sz != count && ret != 0);
|
||||||
|
if (write_sz != count) {
|
||||||
|
PLOGE("write (%d != %d)", count, write_sz);
|
||||||
|
}
|
||||||
|
return write_sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t xrecvmsg(int sockfd, struct msghdr* msg, int flags) {
|
||||||
|
int rec = recvmsg(sockfd, msg, flags);
|
||||||
|
if (rec < 0) PLOGE("recvmsg");
|
||||||
|
return rec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* recv_fds(int sockfd, char* cmsgbuf, size_t bufsz, int cnt) {
|
||||||
|
iovec iov = {
|
||||||
|
.iov_base = &cnt,
|
||||||
|
.iov_len = sizeof(cnt),
|
||||||
|
};
|
||||||
|
msghdr msg = {
|
||||||
|
.msg_iov = &iov,
|
||||||
|
.msg_iovlen = 1,
|
||||||
|
.msg_control = cmsgbuf,
|
||||||
|
.msg_controllen = bufsz
|
||||||
|
};
|
||||||
|
|
||||||
|
xrecvmsg(sockfd, &msg, MSG_WAITALL);
|
||||||
|
cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
||||||
|
|
||||||
|
if (msg.msg_controllen != bufsz ||
|
||||||
|
cmsg == nullptr ||
|
||||||
|
cmsg->cmsg_len != CMSG_LEN(sizeof(int) * cnt) ||
|
||||||
|
cmsg->cmsg_level != SOL_SOCKET ||
|
||||||
|
cmsg->cmsg_type != SCM_RIGHTS) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CMSG_DATA(cmsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline T read_exact(int fd) {
|
||||||
|
T res;
|
||||||
|
return sizeof(T) == xread(fd, &res, sizeof(T)) ? res : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline bool write_exact(int fd, T val) {
|
||||||
|
return sizeof(T) == xwrite(fd, &val, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_usize(int fd) {
|
||||||
|
return read_exact<size_t>(fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string read_string(int fd) {
|
||||||
|
auto len = read_exact<size_t>(fd);
|
||||||
|
char buf[len + 1];
|
||||||
|
buf[len] = '\0';
|
||||||
|
xread(fd, buf, len);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_u8(int fd, uint8_t val) {
|
||||||
|
return write_exact<uint8_t>(fd, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
int recv_fd(int sockfd) {
|
||||||
|
char cmsgbuf[CMSG_SPACE(sizeof(int))];
|
||||||
|
|
||||||
|
void* data = recv_fds(sockfd, cmsgbuf, sizeof(cmsgbuf), 1);
|
||||||
|
if (data == nullptr) return -1;
|
||||||
|
|
||||||
|
int result;
|
||||||
|
memcpy(&result, data, sizeof(int));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
69
loader/src/include/daemon.h
Normal file
69
loader/src/include/daemon.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#if defined(__LP64__)
|
||||||
|
# define LP_SELECT(lp32, lp64) lp64
|
||||||
|
#else
|
||||||
|
# define LP_SELECT(lp32, lp64) lp32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr std::string_view kZygiskSocket = LP_SELECT("zygisk32", "zygisk64") "placeholder123456";
|
||||||
|
|
||||||
|
class UniqueFd {
|
||||||
|
using Fd = int;
|
||||||
|
public:
|
||||||
|
UniqueFd() = default;
|
||||||
|
|
||||||
|
UniqueFd(Fd fd) : fd_(fd) {}
|
||||||
|
|
||||||
|
~UniqueFd() { close(fd_); }
|
||||||
|
|
||||||
|
// Disallow copy
|
||||||
|
UniqueFd(const UniqueFd&) = delete;
|
||||||
|
|
||||||
|
UniqueFd& operator=(const UniqueFd&) = delete;
|
||||||
|
|
||||||
|
// Allow move
|
||||||
|
UniqueFd(UniqueFd&& other) { std::swap(fd_, other.fd_); }
|
||||||
|
|
||||||
|
UniqueFd& operator=(UniqueFd&& other) {
|
||||||
|
std::swap(fd_, other.fd_);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implict cast to Fd
|
||||||
|
operator const Fd&() const { return fd_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Fd fd_ = -1;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace zygiskd {
|
||||||
|
|
||||||
|
struct Module {
|
||||||
|
std::string name;
|
||||||
|
void* handle;
|
||||||
|
|
||||||
|
inline explicit Module(std::string name, void* handle)
|
||||||
|
: name(name), handle(handle) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class SocketAction {
|
||||||
|
HeartBeat,
|
||||||
|
ReadNativeBridge,
|
||||||
|
ReadInjector,
|
||||||
|
ReadModules,
|
||||||
|
RequestCompanionSocket,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool PingHeartbeat();
|
||||||
|
|
||||||
|
std::string ReadNativeBridge();
|
||||||
|
|
||||||
|
UniqueFd ReadInjector();
|
||||||
|
|
||||||
|
std::vector<Module> ReadModules();
|
||||||
|
}
|
||||||
7
loader/src/include/dl.h
Normal file
7
loader/src/include/dl.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <dlfcn.h>
|
||||||
|
|
||||||
|
void *DlopenExt(const char *path, int flags);
|
||||||
|
|
||||||
|
void *DlopenMem(int memfd, int flags);
|
||||||
30
loader/src/include/logging.h
Normal file
30
loader/src/include/logging.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#ifndef LOG_TAG
|
||||||
|
#define LOG_TAG "zygisksu"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef LOG_DISABLED
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#define LOGI(...)
|
||||||
|
#define LOGW(...)
|
||||||
|
#define LOGE(...)
|
||||||
|
#else
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
|
||||||
|
#else
|
||||||
|
#define LOGD(...)
|
||||||
|
#define LOGV(...)
|
||||||
|
#endif
|
||||||
|
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__)
|
||||||
|
#define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno))
|
||||||
|
#endif
|
||||||
33
loader/src/include/native_bridge_callbacks.h
Normal file
33
loader/src/include/native_bridge_callbacks.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <android/api-level.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
template<unsigned>
|
||||||
|
struct NativeBridgeCallbacks;
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct NativeBridgeCallbacks<__ANDROID_API_Q__> {
|
||||||
|
[[maybe_unused]] uint32_t version;
|
||||||
|
[[maybe_unused]] void *initialize;
|
||||||
|
[[maybe_unused]] void *loadLibrary;
|
||||||
|
[[maybe_unused]] void *getTrampoline;
|
||||||
|
[[maybe_unused]] void *isSupported;
|
||||||
|
[[maybe_unused]] void *getAppEnv;
|
||||||
|
[[maybe_unused]] void *isCompatibleWith;
|
||||||
|
[[maybe_unused]] void *getSignalHandler;
|
||||||
|
[[maybe_unused]] void *unloadLibrary;
|
||||||
|
[[maybe_unused]] void *getError;
|
||||||
|
[[maybe_unused]] void *isPathSupported;
|
||||||
|
[[maybe_unused]] void *initAnonymousNamespace;
|
||||||
|
[[maybe_unused]] void *createNamespace;
|
||||||
|
[[maybe_unused]] void *linkNamespaces;
|
||||||
|
[[maybe_unused]] void *loadLibraryExt;
|
||||||
|
[[maybe_unused]] void *getVendorNamespace;
|
||||||
|
[[maybe_unused]] void *getExportedNamespace;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct NativeBridgeCallbacks<__ANDROID_API_R__> : NativeBridgeCallbacks<__ANDROID_API_Q__> {
|
||||||
|
[[maybe_unused]] void *preZygoteFork;
|
||||||
|
};
|
||||||
20
loader/src/include/socket_utils.h
Normal file
20
loader/src/include/socket_utils.h
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "logging.h"
|
||||||
|
|
||||||
|
namespace socket_utils {
|
||||||
|
|
||||||
|
ssize_t xread(int fd, void *buf, size_t count);
|
||||||
|
|
||||||
|
ssize_t xwrite(int fd, const void *buf, size_t count);
|
||||||
|
|
||||||
|
size_t read_usize(int fd);
|
||||||
|
|
||||||
|
std::string read_string(int fd);
|
||||||
|
|
||||||
|
bool write_u8(int fd, uint8_t val);
|
||||||
|
|
||||||
|
int recv_fd(int fd);
|
||||||
|
}
|
||||||
96
loader/src/loader/loader.cpp
Normal file
96
loader/src/loader/loader.cpp
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#include <string_view>
|
||||||
|
#include <sys/system_properties.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "daemon.h"
|
||||||
|
#include "dl.h"
|
||||||
|
#include "logging.h"
|
||||||
|
#include "native_bridge_callbacks.h"
|
||||||
|
|
||||||
|
extern "C" [[gnu::visibility("default")]]
|
||||||
|
uint8_t NativeBridgeItf[sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>) * 2]{0};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr std::array kZygoteProcesses = {"zygote", "zygote32""zygote64", "usap32", "usap64"};
|
||||||
|
|
||||||
|
void* sOriginalBridge = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
__used __attribute__((destructor))
|
||||||
|
void Destructor() {
|
||||||
|
if (sOriginalBridge) {
|
||||||
|
dlclose(sOriginalBridge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
__used __attribute__((constructor))
|
||||||
|
void Constructor() {
|
||||||
|
if (getuid() != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view cmdline = getprogname();
|
||||||
|
if (std::any_of(
|
||||||
|
kZygoteProcesses.begin(),
|
||||||
|
kZygoteProcesses.end(),
|
||||||
|
[&](const char* p) { return cmdline == p; }
|
||||||
|
)) {
|
||||||
|
LOGW("Not started as zygote (cmdline=%s)", cmdline.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string native_bridge;
|
||||||
|
do {
|
||||||
|
LOGD("Ping heartbeat");
|
||||||
|
if (!zygiskd::PingHeartbeat()) break;
|
||||||
|
|
||||||
|
LOGI("Read native bridge");
|
||||||
|
native_bridge = zygiskd::ReadNativeBridge();
|
||||||
|
|
||||||
|
LOGI("Load injector");
|
||||||
|
auto injector = zygiskd::ReadInjector();
|
||||||
|
if (injector != -1) break;
|
||||||
|
|
||||||
|
auto handle = DlopenMem(injector, RTLD_NOW);
|
||||||
|
if (handle == nullptr) {
|
||||||
|
LOGE("Failed to dlopen injector: %s", dlerror());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto entry = dlsym(handle, "entry");
|
||||||
|
if (entry == nullptr) {
|
||||||
|
LOGE("Failed to dlsym injector entry: %s", dlerror());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
reinterpret_cast<void (*)()>(entry)();
|
||||||
|
} while (false);
|
||||||
|
|
||||||
|
if (native_bridge.empty()) return;
|
||||||
|
LOGI("Load original native bridge: %s", native_bridge.data());
|
||||||
|
sOriginalBridge = dlopen(native_bridge.data(), RTLD_NOW);
|
||||||
|
if (sOriginalBridge == nullptr) {
|
||||||
|
LOGE("dlopen failed: %s", dlerror());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* original_native_bridge_itf = dlsym(sOriginalBridge, "NativeBridgeItf");
|
||||||
|
if (original_native_bridge_itf == nullptr) {
|
||||||
|
LOGE("dlsym failed: %s", dlerror());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long sdk = 0;
|
||||||
|
char value[PROP_VALUE_MAX + 1];
|
||||||
|
if (__system_property_get("ro.build.version.sdk", value) > 0) {
|
||||||
|
sdk = strtol(value, nullptr, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto callbacks_size = 0;
|
||||||
|
if (sdk >= __ANDROID_API_R__) {
|
||||||
|
callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_R__>);
|
||||||
|
} else if (sdk == __ANDROID_API_Q__) {
|
||||||
|
callbacks_size = sizeof(NativeBridgeCallbacks<__ANDROID_API_Q__>);
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(NativeBridgeItf, original_native_bridge_itf, callbacks_size);
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ dependencyResolutionManagement {
|
|||||||
|
|
||||||
rootProject.name = "ZygiskOnKernelSU"
|
rootProject.name = "ZygiskOnKernelSU"
|
||||||
include(
|
include(
|
||||||
|
":loader",
|
||||||
":module",
|
":module",
|
||||||
":zygiskd",
|
":zygiskd",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ memfd = "0.6.2"
|
|||||||
nix = "0.26.2"
|
nix = "0.26.2"
|
||||||
num_enum = "0.5.9"
|
num_enum = "0.5.9"
|
||||||
passfd = "0.1.5"
|
passfd = "0.1.5"
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
@@ -3,17 +3,22 @@ use num_enum::TryFromPrimitive;
|
|||||||
|
|
||||||
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
|
pub const PROP_NATIVE_BRIDGE: &str = "ro.dalvik.vm.native.bridge";
|
||||||
|
|
||||||
|
pub const SOCKET_PLACEHOLDER: &str = "placeholder123456";
|
||||||
|
|
||||||
pub const PATH_KSU_MODULE_DIR: &str = "/data/adb/ksu/modules";
|
pub const PATH_KSU_MODULE_DIR: &str = "/data/adb/ksu/modules";
|
||||||
pub const PATH_ZYGISKSU_DIR: &str = concatcp!(PATH_KSU_MODULE_DIR, "/zygisksu");
|
pub const PATH_ZYGISKSU_DIR: &str = concatcp!(PATH_KSU_MODULE_DIR, "/zygisksu");
|
||||||
pub const PATH_ZYGISKWD: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskwd");
|
pub const PATH_ZYGISKWD: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskwd");
|
||||||
pub const PATH_ZYGISKD32: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd32");
|
pub const PATH_ZYGISKD32: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd32");
|
||||||
pub const PATH_ZYGISKD64: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd64");
|
pub const PATH_ZYGISKD64: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd64");
|
||||||
|
pub const PATH_INJECTOR: &str = concatcp!(PATH_ZYGISKSU_DIR, "/libinjector.so");
|
||||||
pub const PATH_DAEMON_LOCK: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd.lock");
|
pub const PATH_DAEMON_LOCK: &str = concatcp!(PATH_ZYGISKSU_DIR, "/zygiskd.lock");
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
|
#[derive(Debug, Eq, PartialEq, TryFromPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum DaemonSocketAction {
|
pub enum DaemonSocketAction {
|
||||||
|
PingHeartbeat,
|
||||||
ReadNativeBridge,
|
ReadNativeBridge,
|
||||||
|
ReadInjector,
|
||||||
ReadModules,
|
ReadModules,
|
||||||
RequestCompanionSocket,
|
RequestCompanionSocket,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,14 @@ use crate::constants;
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nix::unistd::gettid;
|
use nix::unistd::gettid;
|
||||||
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
|
use std::{fs, io::{Read, Write}, os::unix::net::UnixStream, process::Command};
|
||||||
|
use std::os::fd::FromRawFd;
|
||||||
|
use std::os::unix::net::UnixListener;
|
||||||
|
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
|
||||||
|
use rand::distributions::{Alphanumeric, DistString};
|
||||||
|
|
||||||
|
pub fn random_string() -> String {
|
||||||
|
Alphanumeric.sample_string(&mut rand::thread_rng(), 8)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_socket_create_context(context: &str) -> Result<()> {
|
pub fn set_socket_create_context(context: &str) -> Result<()> {
|
||||||
let path = "/proc/thread-self/attr/sockcreate";
|
let path = "/proc/thread-self/attr/sockcreate";
|
||||||
@@ -16,7 +24,7 @@ pub fn set_socket_create_context(context: &str) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_native_bridge() -> String {
|
pub fn get_native_bridge() -> String {
|
||||||
std::env::var("NATIVE_BRIDGE").unwrap_or("0".to_string())
|
std::env::var("NATIVE_BRIDGE").unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restore_native_bridge() -> Result<()> {
|
pub fn restore_native_bridge() -> Result<()> {
|
||||||
@@ -29,6 +37,7 @@ pub trait UnixStreamExt {
|
|||||||
fn read_u8(&mut self) -> Result<u8>;
|
fn read_u8(&mut self) -> Result<u8>;
|
||||||
fn read_u32(&mut self) -> Result<u32>;
|
fn read_u32(&mut self) -> Result<u32>;
|
||||||
fn read_usize(&mut self) -> Result<usize>;
|
fn read_usize(&mut self) -> Result<usize>;
|
||||||
|
fn write_u8(&mut self, value: u8) -> Result<()>;
|
||||||
fn write_usize(&mut self, value: usize) -> Result<()>;
|
fn write_usize(&mut self, value: usize) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,8 +60,23 @@ impl UnixStreamExt for UnixStream {
|
|||||||
Ok(usize::from_ne_bytes(buf))
|
Ok(usize::from_ne_bytes(buf))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_u8(&mut self, value: u8) -> Result<()> {
|
||||||
|
self.write_all(&value.to_ne_bytes())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn write_usize(&mut self, value: usize) -> Result<()> {
|
fn write_usize(&mut self, value: usize) -> Result<()> {
|
||||||
self.write_all(&value.to_ne_bytes())?;
|
self.write_all(&value.to_ne_bytes())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
|
||||||
|
pub fn abstract_namespace_socket(name: &str) -> Result<UnixListener> {
|
||||||
|
let addr = UnixAddr::new_abstract(name.as_bytes())?;
|
||||||
|
let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
|
||||||
|
nix::sys::socket::bind(socket, &addr)?;
|
||||||
|
nix::sys::socket::listen(socket, 2)?;
|
||||||
|
let listener = unsafe { UnixListener::from_raw_fd(socket) };
|
||||||
|
Ok(listener)
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ use std::sync::Arc;
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::fd::FromRawFd;
|
use std::os::fd::IntoRawFd;
|
||||||
use std::os::unix::{
|
use std::os::unix::{
|
||||||
net::{UnixListener, UnixStream},
|
net::{UnixListener, UnixStream},
|
||||||
prelude::AsRawFd,
|
prelude::AsRawFd,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use nix::sys::socket::{AddressFamily, SockFlag, SockType, UnixAddr};
|
|
||||||
|
|
||||||
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
|
type ZygiskCompanionEntryFn = unsafe extern "C" fn(i32);
|
||||||
|
|
||||||
@@ -32,8 +31,7 @@ struct Module {
|
|||||||
|
|
||||||
struct Context {
|
struct Context {
|
||||||
native_bridge: String,
|
native_bridge: String,
|
||||||
modules: Arc<Vec<Module>>,
|
modules: Vec<Module>,
|
||||||
listener: UnixListener,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(is64: bool) -> Result<()> {
|
pub fn start(is64: bool) -> Result<()> {
|
||||||
@@ -44,17 +42,25 @@ pub fn start(is64: bool) -> Result<()> {
|
|||||||
log::info!("Load modules");
|
log::info!("Load modules");
|
||||||
let modules = load_modules(arch)?;
|
let modules = load_modules(arch)?;
|
||||||
|
|
||||||
|
let context = Context {
|
||||||
|
native_bridge: utils::get_native_bridge(),
|
||||||
|
modules,
|
||||||
|
};
|
||||||
|
let context = Arc::new(context);
|
||||||
|
|
||||||
log::info!("Create socket");
|
log::info!("Create socket");
|
||||||
let listener = create_daemon_socket(is64)?;
|
let listener = create_daemon_socket(is64)?;
|
||||||
|
|
||||||
let context = Context {
|
log::info!("Handle zygote connections");
|
||||||
native_bridge: utils::get_native_bridge(),
|
for stream in listener.incoming() {
|
||||||
modules: Arc::new(modules),
|
let stream = stream?;
|
||||||
listener,
|
let context = Arc::clone(&context);
|
||||||
};
|
thread::spawn(move || {
|
||||||
|
if let Err(e) = handle_daemon_action(stream, &context) {
|
||||||
log::info!("Start to listen zygote connections");
|
log::warn!("Error handling daemon action: {e}");
|
||||||
handle_daemon_actions(context)?;
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -166,68 +172,52 @@ fn preload_module(memfd: &Memfd) -> Result<Option<ZygiskCompanionEntryFn>> {
|
|||||||
|
|
||||||
fn create_daemon_socket(is64: bool) -> Result<UnixListener> {
|
fn create_daemon_socket(is64: bool) -> Result<UnixListener> {
|
||||||
utils::set_socket_create_context("u:r:zygote:s0")?;
|
utils::set_socket_create_context("u:r:zygote:s0")?;
|
||||||
let name = if is64 { "zygiskd64" } else { "zygiskd32" };
|
let suffix = if is64 { "zygiskd64" } else { "zygiskd32" };
|
||||||
// TODO: Replace with SockAddrExt::from_abstract_name when it's stable
|
let name = String::from(suffix) + constants::SOCKET_PLACEHOLDER;
|
||||||
let addr = UnixAddr::new_abstract(name.as_bytes())?;
|
let listener = utils::abstract_namespace_socket(&name)?;
|
||||||
let socket = nix::sys::socket::socket(AddressFamily::Unix, SockType::Stream, SockFlag::empty(), None)?;
|
|
||||||
nix::sys::socket::bind(socket, &addr)?;
|
|
||||||
nix::sys::socket::listen(socket, 2)?;
|
|
||||||
log::debug!("Listening on {}", addr);
|
|
||||||
log::debug!("Socket fd: {}", socket);
|
|
||||||
let listener = unsafe { UnixListener::from_raw_fd(socket) };
|
|
||||||
Ok(listener)
|
Ok(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_daemon_actions(context: Context) -> Result<()> {
|
fn handle_daemon_action(mut stream: UnixStream, context: &Context) -> Result<()> {
|
||||||
loop {
|
let action = stream.read_u8()?;
|
||||||
let (mut stream, _) = context.listener.accept()?;
|
match DaemonSocketAction::try_from(action) {
|
||||||
let action = stream.read_u8()?;
|
Ok(DaemonSocketAction::PingHeartbeat) => {
|
||||||
match DaemonSocketAction::try_from(action) {
|
// Do nothing
|
||||||
// First connection from zygote
|
}
|
||||||
Ok(DaemonSocketAction::ReadNativeBridge) => {
|
Ok(DaemonSocketAction::ReadNativeBridge) => {
|
||||||
restore_native_bridge()?;
|
restore_native_bridge()?;
|
||||||
stream.write_usize(context.native_bridge.len())?;
|
stream.write_usize(context.native_bridge.len())?;
|
||||||
stream.write_all(context.native_bridge.as_bytes())?;
|
stream.write_all(context.native_bridge.as_bytes())?;
|
||||||
|
}
|
||||||
|
Ok(DaemonSocketAction::ReadInjector) => {
|
||||||
|
let so_path = PathBuf::from(constants::PATH_INJECTOR);
|
||||||
|
let memfd = create_memfd("injector", &so_path)?;
|
||||||
|
stream.send_fd(memfd.into_raw_fd())?;
|
||||||
|
}
|
||||||
|
Ok(DaemonSocketAction::ReadModules) => {
|
||||||
|
stream.write_usize(context.modules.len())?;
|
||||||
|
for module in context.modules.iter() {
|
||||||
|
stream.write_usize(module.name.len())?;
|
||||||
|
stream.write_all(module.name.as_bytes())?;
|
||||||
|
stream.send_fd(module.memfd.as_raw_fd())?;
|
||||||
}
|
}
|
||||||
Ok(DaemonSocketAction::ReadModules) => {
|
}
|
||||||
stream.write_usize(context.modules.len())?;
|
Ok(DaemonSocketAction::RequestCompanionSocket) => {
|
||||||
for module in context.modules.iter() {
|
let index = stream.read_usize()?;
|
||||||
stream.write_usize(module.name.len())?;
|
let module = &context.modules[index];
|
||||||
stream.write_all(module.name.as_bytes())?;
|
log::debug!("New companion request from module {}", module.name);
|
||||||
stream.send_fd(module.memfd.as_raw_fd())?;
|
|
||||||
|
match module.companion_entry {
|
||||||
|
Some(entry) => {
|
||||||
|
stream.write_u8(1)?;
|
||||||
|
unsafe { entry(stream.as_raw_fd()); }
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
stream.write_u8(0)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(DaemonSocketAction::RequestCompanionSocket) => {
|
|
||||||
let (server, client) = UnixStream::pair()?;
|
|
||||||
stream.send_fd(client.as_raw_fd())?;
|
|
||||||
let modules_ref = Arc::clone(&context.modules);
|
|
||||||
thread::spawn(move || {
|
|
||||||
if let Err(e) = create_companion(server, modules_ref.as_ref()) {
|
|
||||||
log::warn!("Companion thread exited: {e}");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(_) => bail!("Invalid action code: {action}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_companion(mut server: UnixStream, modules: &Vec<Module>) -> Result<()> {
|
|
||||||
loop {
|
|
||||||
let index = match server.read_usize() {
|
|
||||||
Ok(index) => index,
|
|
||||||
Err(_) => return Ok(()), // EOF
|
|
||||||
};
|
|
||||||
let module = &modules[index];
|
|
||||||
log::debug!("New companion request from module {}", module.name);
|
|
||||||
|
|
||||||
match module.companion_entry {
|
|
||||||
Some(entry) => {
|
|
||||||
let (sock_app, sock_companion) = UnixStream::pair()?;
|
|
||||||
server.send_fd(sock_app.as_raw_fd())?;
|
|
||||||
unsafe { entry(sock_companion.as_raw_fd()); }
|
|
||||||
}
|
|
||||||
None => (),
|
|
||||||
}
|
}
|
||||||
|
Err(_) => bail!("Invalid action code: {action}")
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user