From de38c6201b3d4fe0387cb231d525c7a1889a91b5 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Sun, 12 Apr 2026 16:54:35 +0200 Subject: [PATCH 1/2] Skip module loading for isolated processes (#127) We implement a connection check to bypass process flag retrieval and module execution for isolated processes when the zygisk daemon is unreachable. --- loader/src/include/daemon.hpp | 2 ++ loader/src/injector/module.cpp | 13 ++++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/loader/src/include/daemon.hpp b/loader/src/include/daemon.hpp index f88445f..9b10702 100644 --- a/loader/src/include/daemon.hpp +++ b/loader/src/include/daemon.hpp @@ -73,6 +73,8 @@ void Init(const char* path); std::string GetTmpPath(); +int Connect(uint8_t retry); + bool PingHeartbeat(); std::vector ReadModules(); diff --git a/loader/src/injector/module.cpp b/loader/src/injector/module.cpp index fb0a8c0..997f42c 100644 --- a/loader/src/injector/module.cpp +++ b/loader/src/injector/module.cpp @@ -331,21 +331,24 @@ void ZygiskContext::run_modules_post() { void ZygiskContext::app_specialize_pre() { uid_t uid = args.app->uid; - // Correct uid for isolated services - if (uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END && args.app->app_data_dir) { + bool is_isolated_aid = uid >= AID_ISOLATED_START && uid <= AID_ISOLATED_END; + if (is_isolated_aid && args.app->app_data_dir) { const char *data_dir = nullptr; data_dir = env->GetStringUTFChars(args.app->app_data_dir, nullptr); if (data_dir != nullptr) { struct stat st; if (stat(data_dir, &st) != -1) { + // Correct uid for isolated services uid = st.st_uid; - LOGV("identify isolated service [uid:%d, data_dir:%s]", uid, data_dir); } + LOGV("Found isolated process [uid:%d, data_dir:%s]", uid, data_dir); env->ReleaseStringUTFChars(args.app->app_data_dir, data_dir); } } - if (info_flags == 0) info_flags = zygiskd::GetProcessFlags(uid); + bool skip_zygiskd = is_isolated_aid && zygiskd::Connect(1) == -1; + + if (!skip_zygiskd && info_flags == 0) info_flags = zygiskd::GetProcessFlags(uid); if ((info_flags & UNMOUNT_MASK) == UNMOUNT_MASK) { LOGI("[%s] is on the denylist", process); @@ -353,7 +356,7 @@ void ZygiskContext::app_specialize_pre() { } flags |= APP_SPECIALIZE; - run_modules_pre(); + if (!skip_zygiskd) run_modules_pre(); } void ZygiskContext::app_specialize_post() { From 2a75c68b0e5072d1eaa6b39b03d1068fe17be6eb Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Tue, 21 Apr 2026 23:43:30 +0200 Subject: [PATCH 2/2] Support BTI enabled devices [Pixel 10 Pro XL] During the entry point hijack phase on ARMv9 hardware with Branch Target Identification (BTI) enabled, dispatching remote calls to bionic functions (such as `dlopen`) resulted in an immediate Branch Target Exception (SIGILL). This crash is caused by a stale Branch Type (`BTYPE`) state. When the Android dynamic linker transfers control to the target's `AT_ENTRY`, it uses an indirect branch instruction (`BR`). This hardware action updates the CPU's `PSTATE` register, setting the `BTYPE` field to `0b11` (Indirect Jump). When the tracer pauses the process and redirects execution to `dlopen`, the CPU inherits this `PSTATE`. Modern Android bionic libraries are compiled with BTI and start with `PACIASP` or `BTI c` landing pads. These landing pads strictly require the incoming `BTYPE` to be `0b01` (Direct Call) or `0b10` (Indirect Call). Confronted with `0b11`, the CPU assumes a JOP (Jump-Oriented Programming) exploit and raises a SIGILL. This commit resolves the issue by clearing the `BTYPE` field (bits 10 and 11) in the `pstate` register structure prior to initiating any remote calls. Resetting the field to `0b00` prevents the hardware from enforcing BTI validation on the immediate next instruction. The original `PSTATE` is preserved in the register backup, ensuring that the target executable can securely validate its own BTI landing pad when execution is fully restored. References: https://developer.android.com/ndk/guides/abis#armv9_enabling_pac_and_bti_for_cc --- loader/src/ptracer/ptracer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/loader/src/ptracer/ptracer.cpp b/loader/src/ptracer/ptracer.cpp index 60aadfd..3be6893 100644 --- a/loader/src/ptracer/ptracer.cpp +++ b/loader/src/ptracer/ptracer.cpp @@ -184,7 +184,18 @@ bool inject_on_main(int pid, const char *lib_path) { } // Backup the current registers before we start making remote calls. + // It is vital we keep the original PSTATE intact in the backup, so + // the original executable can securely validate its own BTI pad upon final resume. memcpy(&backup, ®s, sizeof(regs)); + +#if defined(__aarch64__) + // Clear the BTYPE field (bits 10 and 11) in PSTATE. + // The previous indirect branch from the linker set BTYPE to 0b11. + // If we jump into BTI-protected bionic libraries (like libdl.so) with BTYPE=0b11, + // the CPU will throw a Branch Target Exception (SIGILL). + regs.pstate &= ~(3ULL << 10); +#endif + map = MapInfo::Scan(std::to_string(pid)); // Re-scan maps as they may have changed. auto local_map = MapInfo::Scan(); auto libc_return_addr = find_module_return_addr(map, "libc.so");