Add standalone mode to trace command for late zygote injection - #107
Open
JingMatrix wants to merge 9 commits into
Open
Add standalone mode to trace command for late zygote injection#107JingMatrix wants to merge 9 commits into
JingMatrix wants to merge 9 commits into
Conversation
JingMatrix
added a commit
to JingMatrix/Vector
that referenced
this pull request
Mar 10, 2026
In JingMatrix/NeoZygisk#107, NeoZygisk is modified to support Zygisk initialization without relying on the early init phase hooks of Magisk. This commit adds support for Vector to operate under this late injection model. Modifications include: - Daemon: Added parsing for the --late-inject flag in ServiceManager. When active, the daemon uses "serial_vector" as the proxy service name and LSPosedService manually dispatches the boot completed event. - IPC Bridge: Updated RequestSystemServerBinder to accept a dynamic rendezvous service name instead of hardcoding it. - Native Module: VectorModule now reads RuntimeFlags::LATE_INJECT during server specialization, adjusts the bridge service name accordingly, and passes the state to the Java payload. - Framework: Updated Main.forkCommon to manually bootstrap the system_server environment during late injection. This extracts the ClassLoader from the live activity service, deoptimizes system server methods, and synchronously fires Xposed and LibXposed load callbacks.
|
All good, but still detect ptrace, Do u have plan to hide its , bro |
Owner
Author
|
@huynhbaman Current hiding mechinism is already good. Please avoid commenting about something you don't truly understand. If you want to ask questions, open a thread in Discussions. |
JingMatrix
added a commit
to JingMatrix/Vector
that referenced
this pull request
Mar 11, 2026
In JingMatrix/NeoZygisk#107, NeoZygisk is modified to support Zygisk initialization without relying on the early init phase hooks of Magisk. This commit adds support for Vector to operate under this late injection model. Modifications include: - Daemon: Added parsing for the --late-inject flag in ServiceManager. When active, the daemon uses "serial_vector" as the proxy service name and LSPosedService manually dispatches the boot completed event. - IPC Bridge: Updated RequestSystemServerBinder to accept a dynamic rendezvous service name instead of hardcoding it. - Native Module: VectorModule now reads RuntimeFlags::LATE_INJECT during server specialization, adjusts the bridge service name accordingly, and passes the state to the Java payload. - Framework: Updated Main.forkCommon to manually bootstrap the system_server environment during late injection. This extracts the ClassLoader from the live activity service, deoptimizes system server methods, and synchronously fires Xposed and LibXposed load callbacks.
Owner
Author
|
This pull-request is not going to be merged for now, due to lack of test thus potentially vulnerable to attacks. We nevertheless provide docs for the standalone setup: https://github.com/JingMatrix/NeoZygisk/blob/init/docs/late-injection.md |
4 tasks
Enovale
pushed a commit
to Enovale/Vector
that referenced
this pull request
Jul 13, 2026
In JingMatrix/NeoZygisk#107, NeoZygisk is modified to support Zygisk initialization without relying on the early init phase hooks of Magisk. This commit adds support for Vector to operate under this late injection model. Modifications include: - Daemon: Added parsing for the --late-inject flag in ServiceManager. When active, the daemon uses "serial_vector" as the proxy service name and LSPosedService manually dispatches the boot completed event. - IPC Bridge: Updated RequestSystemServerBinder to accept a dynamic rendezvous service name instead of hardcoding it. - Native Module: VectorModule now reads RuntimeFlags::LATE_INJECT during server specialization, adjusts the bridge service name accordingly, and passes the state to the Java payload. - Framework: Updated Main.forkCommon to manually bootstrap the system_server environment during late injection. This extracts the ClassLoader from the live activity service, deoptimizes system server methods, and synchronously fires Xposed and LibXposed load callbacks.
This commit introduces the --standalone flag to the zygisk-ptrace64 binary, enabling the initialization of NeoZygisk after the zygote process has already started. This provides an alternative to relying strictly on Magisk's init phase injection. To support injecting into an active, running process, several structural adjustments were made to the injection flow: CLI and Daemon Initialization: - The trace command now parses the --standalone flag, which is mutually exclusive with --restart. - In standalone mode, the tracer resolves and changes the working directory to the module directory, then initializes the daemon controller prior to injection. This ensures the UNIX domain sockets are listening before the injected library attempts to connect. - Added SIGINT and SIGTERM handlers to safely detach and send SIGCONT to zygote if the tracer is aborted by the user, preventing system lockups. Ptrace Flow Adjustments: - Omitted PTRACE_O_EXITKILL in standalone mode to ensure the zygote process is not terminated if the tracer exits unexpectedly. - Updated the PTRACE_SEIZE strategy to explicitly issue PTRACE_INTERRUPT. Unlike standard Magisk mode where zygote is already suspended via SIGSTOP, a running process must be manually interrupted. - Handled the resulting SIGTRAP stop and bypassed the SIGCONT wake-up sequence during detach, as a clean detach is sufficient to resume a process stopped via PTRACE_INTERRUPT. Injection Logic: - Extracted the remote code execution sequence (dlopen, dlsym, entry) into a shared function to maintain DRY principles. - Created inject_standalone to bypass the AT_ENTRY stack parsing and memory hijacking. Since the dynamic linker is already initialized, the payload is invoked immediately. - Implemented a cross-architecture syscall restart mitigation. Interrupting a tracee inside a blocking system call (e.g., epoll_wait) and altering the instruction pointer triggers the kernel's ERESTARTSYS rewind logic, causing the instruction pointer to jump into invalid padding and throw a SIGTRAP. This is resolved by clearing orig_rax (and architecture equivalents) in the temporary registers before the remote call, while preserving the actual system call state in the backup registers for a seamless resumption.
Complicated modules, such as LSPosed / Vector, need to be inform about the injection mode, since many of their functions could reply on the exact launching sequence of system services.
Gate the control socket with a kernel SO_PEERCRED check: the daemon now admits a connection only if the peer uid is root (0) or system (1000), and rejects anything else with a warning. This is sound because every connection the daemon legitimately accepts is opened while the caller is still privileged -- zygote (uid 0) during injection and the whole pre-specialization window, system_server (uid 1000) on the late path, and the root tracer (uid 0). The loader makes no daemon connection after a process drops to its app uid, and the app/companion channel is a handed-off fd that never re-enters the accept path, so an app-uid connection is illegitimate by design. Add docs/late-injection.md covering the --standalone and --system_server modes: the permissive-SELinux requirement (NeoZygisk's sepolicy rules cannot be loaded in these environments, and the /data/system relocation narrows but cannot remove the gap), the emulated Magisk environment (magisk / resetprop / unshare shims and a crafted magisk.db), the module-side RuntimeFlags::LATE_INJECT contract, the socket peer admission and its residual risk, and a link to the reference implementation in JingMatrix/Vector#564. The 0777 socket mode is kept (it matches shipped releases) with a comment recording that admission is enforced by SO_PEERCRED, not by the DAC mode. Note: this branch is not intended to be merged. The residual risk under a permissive policy (a process already running as root or system may still supply unverified request data) is inherent and documented rather than papered over.
JingMatrix
force-pushed
the
init
branch
2 times, most recently
from
August 8, 2026 15:23
c20b64d to
24f3978
Compare
The BTI workaround added in 133cd99 sat in inject_before_start, so the new late-injection path jumped into BTI-protected bionic with whatever BTYPE the interrupted tracee happened to carry. Move it into execute_remote_injection, which both paths share; the callers still keep the original PSTATE in their register backup for the final resume.
The previous wording called the relocation futile because it does not lift the permissive-SELinux requirement. That is true but misses the actual reason for it: /data/adb is 0700 root:root, so a late-injected system_server (uid 1000) has no search permission on it. Being a DAC denial, setenforce 0 does not relax it, and the chcon on the work directory relabels the child without granting traversal of the parent. Both halves of the system_server path fail there -- the dlopen of libzygisk.so and the PingHeartbeat connect() to the control socket, the latter making the 0777 socket mode and the AID_SYSTEM branch of the SO_PEERCRED check moot. /data/system fixes both, since post-fs-data.sh chowns it to system:system. Zygote is uid 0 throughout injection, so --standalone never depended on this.
handle_trace's standalone branch declared `auto pid = fork()`, shadowing the tracee PID it had just parsed, and reported a failed fork with `return false` -- which is 0, i.e. EXIT_SUCCESS -- from a function returning int. The child also left its failure paths on `return`, so instead of exiting it unwound back into main; had monitor.run() ever returned, it would have fallen through and attached to zygote a second time. Name the fork result, return EXIT_FAILURE, and terminate the child with _exit() on every path. The child additionally inherited handle_interrupt with g_traced_pid already armed, so a stray SIGTERM made it detach a process it never attached to; clear it after the fork. Scripts: quote the pidof substitutions -- an empty result silently shifted argv and fed `trace --standalone` a missing pid -- pick the tracer binary once instead of hardcoding zygisk-ptrace64 in service.sh, and bail with a log line when no zygote or no tracer is found. The 32-bit branch looked for "zygote32", but app_process only ever names itself "zygote64" or "zygote". Dead code: drop the commented-out copy_to_temp call site along with the use_temp plumbing that could never fire, and the commented-out libart lookup in JniAttachment that RTLD_DEFAULT already covers. Keep copy_to_temp itself, marked maybe_unused, documented as a debugging aid. Restore the DeleteLocalRef on the gids array. Also fix the --spwan usage typo, `sucess`, "No moniter handler actived", a missing newline, and replace the local PROP_VALUE_MAX define with the bionic header that defines it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This commit introduces the --standalone flag to the zygisk-ptrace64 binary, enabling the initialization of NeoZygisk after the zygote process has already started. This provides an alternative to relying strictly on Magisk's init phase injection.
To support injecting into an active, running process, several structural adjustments were made to the injection flow:
CLI and Daemon Initialization:
Ptrace Flow Adjustments:
Injection Logic: