Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ val minKsuVersion by extra(10940)
val minKsudVersion by extra(11425)
val maxKsuVersion by extra(30000)
val minMagiskVersion by extra(26402)
val workDirectory by extra("/data/adb/neozygisk")
val workDirectory by extra("/data/system/neozygisk")
val updateJson by extra("https://raw.githubusercontent.com/JingMatrix/NeoZygisk/master/module/zygisk.json")

val androidMinSdkVersion by extra(26)
Expand Down
9 changes: 9 additions & 0 deletions loader/src/include/trace.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

enum TraceMode {
UNKNOWN = 0,
SPAWN = 1,
STANDALONE = 2,
SYSTEM_SERVER = 3,
};

19 changes: 15 additions & 4 deletions loader/src/injector/entry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

#include "daemon.hpp"
#include "logging.hpp"
#include "system_server.hpp"
#include "trace.hpp"
#include "zygisk.hpp"

using namespace std;

extern "C" [[gnu::visibility("default")]]
void entry(void* addr, size_t size, const char* path) {
LOGI("zygisk library injected, version %s", ZKSU_VERSION);
void entry(void* addr, size_t size, const char* path, TraceMode mode) {
LOGI("zygisk library injected, version %s [mode: %d]", ZKSU_VERSION, mode);

zygiskd::Init(path);

Expand All @@ -17,8 +19,17 @@ void entry(void* addr, size_t size, const char* path) {
return;
}

hook_entry(addr, size);
send_seccomp_event_if_needed();
if (mode == TraceMode::SYSTEM_SERVER) {
trigger_system_server_hooks();
} else {
hook_entry(addr, size);
send_seccomp_event_if_needed();
}

// Tiggering Zygote hooks
if (mode == TraceMode::STANDALONE) {
trigger_zygote_hooks();
}
}

/**
Expand Down
9 changes: 9 additions & 0 deletions loader/src/injector/hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "module.hpp"
#include "zygisk.hpp"

#define PROP_VALUE_MAX 92

using namespace std;

// *********************
Expand Down Expand Up @@ -175,6 +177,13 @@ DCL_HOOK_FUNC(static int, pthread_attr_setstacksize, void *target, size_t size)

#undef DCL_HOOK_FUNC

void trigger_zygote_hooks() {
LOGI("triggering zygote hook sequence");
new_strdup(kZygoteInit);
char buf[PROP_VALUE_MAX];
new_property_get("ro.product.cpu.abi", buf, "0");
}

// -----------------------------------------------------------------
static size_t get_fd_max() {
rlimit r{32768, 32768};
Expand Down
2 changes: 1 addition & 1 deletion loader/src/injector/module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ void ZygiskContext::run_modules_post() {
if (m.tryUnload()) modules_unloaded++;
}

if (modules.size() > 0) {
if (modules.size() > 0 && g_hook != nullptr) {
LOGV("modules unloaded: %zu/%zu", modules_unloaded, modules.size());
if (modules.size() == modules_unloaded) clean_libc_trace();
clean_linker_trace("jit-cache-zygisk", modules.size(), modules_unloaded, true);
Expand Down
173 changes: 173 additions & 0 deletions loader/src/injector/system_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#include "system_server.hpp"

#include <dlfcn.h>
#include <jni.h>
#include <linux/capability.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>

#include <cstring>
#include <vector>

#include "logging.hpp"
#include "module.hpp"

namespace {

/**
* @brief RAII wrapper to safely obtain JNIEnv and manage thread attachment lifecycle.
*/
class JniAttachment {
public:
JniAttachment() {
// auto cached_map_infos = lsplt::MapInfo::Scan();
// void* libart;
// for (auto& map : cached_map_infos) {
// if (map.path.ends_with("/libart.so")) {
// LOGV("found path %s", map.path.data());
// libart = dlopen(map.path.data(), RTLD_NOLOAD | RTLD_NOW);
// if (!libart) {
// libart = dlopen("libart.so", RTLD_NOW);
// }
// break;
// }
// }

// if (!libart) {
// LOGE("failed to get libart.so handle");
// return;
// }

using JNI_GetCreatedJavaVMs_t = jint (*)(JavaVM**, jsize, jsize*);

// Pass RTLD_DEFAULT instead of a specific library handle
auto get_vms =
reinterpret_cast<JNI_GetCreatedJavaVMs_t>(dlsym(RTLD_DEFAULT, "JNI_GetCreatedJavaVMs"));

if (!get_vms) {
LOGE("failed to find JNI_GetCreatedJavaVMs in libart");
return;
}

jsize num_vms = 0;
if (get_vms(&vm_, 1, &num_vms) != JNI_OK || num_vms == 0 || !vm_) {
LOGE("failed to get created JavaVM");
return;
}

jint env_res = vm_->GetEnv(reinterpret_cast<void**>(&env_), JNI_VERSION_1_6);
if (env_res == JNI_EDETACHED) {
LOGI("current thread is detached from JVM, attaching temporarily...");
JavaVMAttachArgs args{JNI_VERSION_1_6, "NeoZygisk-Injector", nullptr};
if (vm_->AttachCurrentThread(&env_, &args) == JNI_OK) {
attached_ = true;
} else {
LOGE("failed to attach current thread to JavaVM");
env_ = nullptr;
}
}
}

~JniAttachment() {
if (attached_ && vm_) {
LOGV("detaching temporary injector thread from JVM");
vm_->DetachCurrentThread();
}
}

JNIEnv* get_env() const { return env_; }

private:
JavaVM* vm_ = nullptr;
JNIEnv* env_ = nullptr;
bool attached_ = false;
};

/**
* @brief Retrieves current process capabilities and maps them to jlong.
*/
void fetch_capabilities(jlong& permitted, jlong& effective) {
struct __user_cap_header_struct capheader;
struct __user_cap_data_struct capdata[2];
memset(&capheader, 0, sizeof(capheader));
memset(&capdata, 0, sizeof(capdata));
capheader.version = _LINUX_CAPABILITY_VERSION_3; // 64-bit caps
capheader.pid = 0; // Self

if (syscall(__NR_capget, &capheader, &capdata) == 0) {
permitted = (static_cast<jlong>(capdata[1].permitted) << 32) | capdata[0].permitted;
effective = (static_cast<jlong>(capdata[1].effective) << 32) | capdata[0].effective;
} else {
LOGW("failed to read capabilities via capget, using 0");
permitted = 0;
effective = 0;
}
}

/**
* @brief Retrieves current supplementary groups and allocates a JNI IntArray.
*/
jintArray fetch_gids(JNIEnv* env) {
int count = getgroups(0, nullptr);
if (count <= 0) {
return env->NewIntArray(0);
}

std::vector<gid_t> gids(count);
getgroups(count, gids.data());

// Convert gid_t (usually 32-bit unsigned) to jint (32-bit signed)
std::vector<jint> j_gids(count);
for (int i = 0; i < count; ++i) {
j_gids[i] = static_cast<jint>(gids[i]);
}

jintArray array = env->NewIntArray(count);
if (array) {
env->SetIntArrayRegion(array, 0, count, j_gids.data());
}
return array;
}

} // anonymous namespace

void trigger_system_server_hooks() {
LOGI("preparing to invoke modules for system_server");

// 1. Initialize JVM Context via RAII
JniAttachment jni;
JNIEnv* env = jni.get_env();
if (!env) {
LOGE("aborting system_server specialization: failed to obtain JNIEnv");
return;
}

// 2. Prepare JNI-compliant variables
jint uid = static_cast<jint>(getuid());
jint gid = static_cast<jint>(getgid());
jintArray gids = fetch_gids(env);
jint runtime_flags = RuntimeFlags::LATE_INJECT;
jlong permitted_capabilities = 0;
jlong effective_capabilities = 0;

fetch_capabilities(permitted_capabilities, effective_capabilities);

// 3. Construct the API contract using exact references
ServerSpecializeArgs_v1 args(uid, gid, gids, runtime_flags, permitted_capabilities,
effective_capabilities);
ZygiskContext ctx(env, &args);

// 4. Trigger the Zygisk API lifecycle
LOGV("triggering server_specialize_pre");
ctx.flags |= SERVER_FORK_AND_SPECIALIZE;
ctx.server_specialize_pre();

LOGV("triggering server_specialize_post");
ctx.server_specialize_post();

// 5. Clean up the local JNI reference to prevent memory leaks in the ART
// if (gids) {
// env->DeleteLocalRef(gids);
// }
}
17 changes: 17 additions & 0 deletions loader/src/injector/system_server.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include <cstdint>
/**
* @brief Triggers Zygisk module hooks for system_server in late-injection scenarios.
*
* Dynamically reconstructs the JNI environment and process state parameters
* (UID, GID, capabilities) required to fulfill the Zygisk API contract for
* system_server_specialize.
*/
void trigger_system_server_hooks();

enum RuntimeFlags : uint32_t {
// Safely out of the way of AOSP's flags (Bits 0, 14-26)
// https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/jni/com_android_internal_os_Zygote.cpp;
LATE_INJECT = 1 << 30,
};
2 changes: 2 additions & 0 deletions loader/src/injector/zygisk.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ void spoof_zygote_fossil(char *search_from, char *search_to, const char *anchor)

void send_seccomp_event_if_needed();

void trigger_zygote_hooks();

std::vector<mount_info> check_zygote_traces(uint32_t info_flags);
Loading
Loading