Skip to content

Commit fe8e069

Browse files
authored
Allow standalone launch via late inject (#564)
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.
1 parent de010c4 commit fe8e069

10 files changed

Lines changed: 80 additions & 30 deletions

File tree

core/src/main/java/org/lsposed/lspd/core/Startup.java

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import android.app.LoadedApk;
2525
import android.content.pm.ApplicationInfo;
2626
import android.content.res.CompatibilityInfo;
27+
import android.os.IBinder;
2728

2829
import com.android.internal.os.ZygoteInit;
2930

@@ -34,6 +35,7 @@
3435
import org.lsposed.lspd.hooker.LoadedApkCtorHooker;
3536
import org.lsposed.lspd.hooker.LoadedApkCreateCLHooker;
3637
import org.lsposed.lspd.hooker.OpenDexFileHooker;
38+
import org.lsposed.lspd.hooker.StartBootstrapServicesHooker;
3739
import org.lsposed.lspd.impl.LSPosedContext;
3840
import org.lsposed.lspd.impl.LSPosedHelper;
3941
import org.lsposed.lspd.service.ILSPApplicationService;
@@ -63,14 +65,31 @@ private static void startBootstrapHook(boolean isSystem) {
6365
LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach");
6466
}
6567

66-
public static void bootstrapXposed() {
68+
public static void bootstrapXposed(boolean systemServerStarted) {
6769
// Initialize the Xposed framework
6870
try {
6971
startBootstrapHook(XposedInit.startsSystemServer);
7072
XposedInit.loadLegacyModules();
7173
} catch (Throwable t) {
7274
Utils.logE("error during Xposed initialization", t);
7375
}
76+
77+
if (systemServerStarted) {
78+
Utils.logD("Manually triggering system_server module load for late injection");
79+
80+
IBinder activityService = android.os.ServiceManager.getService("activity");
81+
if (activityService == null) {
82+
Utils.logE("Activity service not found! Cannot get SystemServer ClassLoader.");
83+
return;
84+
}
85+
86+
// Maintain state consistency for the rest of the Vector framework
87+
HandleSystemServerProcessHooker.systemServerCL = activityService.getClass().getClassLoader();
88+
HandleSystemServerProcessHooker.after();
89+
StartBootstrapServicesHooker.before();
90+
91+
Utils.logI("Late system_server injection successfully completed.");
92+
}
7493
}
7594

7695
public static void initXposed(boolean isSystem, String processName, String appDir, ILSPApplicationService service) {

core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,17 @@ public interface Callback {
3535
void onSystemServerLoaded(ClassLoader classLoader);
3636
}
3737

38-
public static volatile ClassLoader systemServerCL;
38+
public static volatile ClassLoader systemServerCL = null;
3939
public static volatile Callback callback = null;
4040

4141
@SuppressLint("PrivateApi")
4242
public static void after() {
4343
Hookers.logD("ZygoteInit#handleSystemServerProcess() starts");
4444
try {
45-
// get system_server classLoader
46-
systemServerCL = Thread.currentThread().getContextClassLoader();
45+
if (systemServerCL == null) {
46+
// get system_server classLoader
47+
systemServerCL = Thread.currentThread().getContextClassLoader();
48+
}
4749
// deopt methods in SYSTEMSERVERCLASSPATH
4850
PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL);
4951
var clazz = Class.forName("com.android.server.SystemServer", false, systemServerCL);

daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@
3232

3333
public class LSPSystemServerService extends ILSPSystemServerService.Stub implements IBinder.DeathRecipient {
3434

35-
public static final String PROXY_SERVICE_NAME = "serial";
36-
35+
private final String proxyServiceName;
3736
private IBinder originService = null;
3837
private int requested;
3938

@@ -42,12 +41,13 @@ public boolean systemServerRequested() {
4241
}
4342

4443
public void putBinderForSystemServer() {
45-
android.os.ServiceManager.addService(PROXY_SERVICE_NAME, this);
44+
android.os.ServiceManager.addService(proxyServiceName, this);
4645
binderDied();
4746
}
4847

49-
public LSPSystemServerService(int maxRetry) {
50-
Log.d(TAG, "LSPSystemServerService::LSPSystemServerService");
48+
public LSPSystemServerService(int maxRetry, String serviceName) {
49+
Log.d(TAG, "LSPSystemServerService::LSPSystemServerService with proxy " + serviceName);
50+
proxyServiceName = serviceName;
5151
requested = -maxRetry;
5252
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
5353
// Registers a callback when system is registering an authentic "serial" service
@@ -56,7 +56,7 @@ public LSPSystemServerService(int maxRetry) {
5656
@Override
5757
public void onRegistration(String name, IBinder binder) {
5858
Log.d(TAG, "LSPSystemServerService::LSPSystemServerService onRegistration: " + name + " " + binder);
59-
if (name.equals(PROXY_SERVICE_NAME) && binder != null && binder != LSPSystemServerService.this) {
59+
if (name.equals(proxyServiceName) && binder != null && binder != LSPSystemServerService.this) {
6060
Log.d(TAG, "Register " + name + " " + binder);
6161
originService = binder;
6262
LSPSystemServerService.this.linkToDeath();
@@ -69,7 +69,7 @@ public IBinder asBinder() {
6969
}
7070
};
7171
try {
72-
getSystemServiceManager().registerForNotifications(PROXY_SERVICE_NAME, serviceCallback);
72+
getSystemServiceManager().registerForNotifications(proxyServiceName, serviceCallback);
7373
} catch (Throwable e) {
7474
Log.e(TAG, "unregister: ", e);
7575
}

daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,11 @@ public void dispatchSystemServerContext(IBinder appThread, IBinder activityToken
479479
registerOpenManagerReceiver();
480480
registerModuleScopeReceiver();
481481
registerUidObserver();
482+
483+
if (ServiceManager.isLateInject) {
484+
Log.i(TAG, "System already booted during late injection. Manually triggering boot completed.");
485+
dispatchBootCompleted(null);
486+
}
482487
}
483488

484489
@Override

daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ public class ServiceManager {
6666
private static LogcatService logcatService = null;
6767
private static Dex2OatService dex2OatService = null;
6868

69+
public static boolean isLateInject = false;
70+
public static String proxyServiceName = "serial";
71+
6972
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();
7073

7174
@RequiresApi(Build.VERSION_CODES.Q)
@@ -104,9 +107,13 @@ public static void start(String[] args) {
104107
systemServerMaxRetry = Integer.parseInt(arg.substring(arg.lastIndexOf('=') + 1));
105108
} catch (Throwable ignored) {
106109
}
110+
} else if (arg.equals("--late-inject")) {
111+
isLateInject = true;
112+
proxyServiceName = "serial_vector";
107113
}
108114
}
109-
Log.i(TAG, "starting server...");
115+
116+
Log.i(TAG, "Vector daemon started: lateInject: " + isLateInject);
110117
Log.i(TAG, String.format("version %s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE));
111118

112119
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
@@ -136,7 +143,7 @@ public static void start(String[] args) {
136143
mainService = new LSPosedService();
137144
applicationService = new LSPApplicationService();
138145
managerService = new LSPManagerService();
139-
systemServerService = new LSPSystemServerService(systemServerMaxRetry);
146+
systemServerService = new LSPSystemServerService(systemServerMaxRetry, proxyServiceName);
140147
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
141148
dex2OatService = new Dex2OatService();
142149
dex2OatService.start();

zygisk/proguard-rules.pro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-keepclasseswithmembers class org.matrix.vector.core.Main {
2-
public static void forkCommon(boolean, java.lang.String, java.lang.String, android.os.IBinder);
2+
public static void forkCommon(boolean, boolean, java.lang.String, java.lang.String, android.os.IBinder);
33
}
44
-keepclasseswithmembers,includedescriptorclasses class * {
55
native <methods>;

zygisk/src/main/cpp/include/ipc_bridge.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,11 @@ class IPCBridge {
4949
/**
5050
* @brief Requests the system_server's dedicated Binder from the host service.
5151
* @param env JNI environment pointer.
52+
* @param bridgeServiceName rendezvous point used by the system_server
5253
* @return A ScopedLocalRef to the Binder object, or nullptr on failure.
5354
*/
54-
lsplant::ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env);
55+
lsplant::ScopedLocalRef<jobject> RequestSystemServerBinder(JNIEnv *env,
56+
std::string bridgeServiceName);
5557

5658
/**
5759
* @brief Asks the system_server binder for the application manager binder.

zygisk/src/main/cpp/ipc_bridge.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ class BinderCaller {
8484
// The name of the system service we use as a rendezvous point to find our manager service.
8585
// Using "activity" is a common technique as it's always available.
8686
constexpr auto kBridgeServiceName = "activity"sv;
87-
// A different rendezvous point used only by the system_server.
88-
constexpr auto kSystemServerBridgeServiceName = "serial"sv;
8987

9088
// Transaction codes for specific actions.
9189
constexpr jint kBridgeTransactionCode = ('_' << 24) | ('V' << 16) | ('E' << 8) | 'C';
@@ -299,14 +297,14 @@ lsplant::ScopedLocalRef<jobject> IPCBridge::RequestAppBinder(JNIEnv *env, jstrin
299297
return result_binder;
300298
}
301299

302-
lsplant::ScopedLocalRef<jobject> IPCBridge::RequestSystemServerBinder(JNIEnv *env) {
300+
lsplant::ScopedLocalRef<jobject> IPCBridge::RequestSystemServerBinder(
301+
JNIEnv *env, std::string bridgeServiceName) {
303302
if (!initialized_) {
304303
LOGE("RequestSystemServerBinder failed: IPCBridge not initialized.");
305304
return {env, nullptr};
306305
}
307306

308-
auto service_name =
309-
lsplant::ScopedLocalRef(env, env->NewStringUTF(kSystemServerBridgeServiceName.data()));
307+
auto service_name = lsplant::ScopedLocalRef(env, env->NewStringUTF(bridgeServiceName.data()));
310308
lsplant::ScopedLocalRef<jobject> binder = {env, nullptr};
311309

312310
// The system_server might start its services slightly after Zygisk injects us.
@@ -315,11 +313,12 @@ lsplant::ScopedLocalRef<jobject> IPCBridge::RequestSystemServerBinder(JNIEnv *en
315313
binder = lsplant::JNI_CallStaticObjectMethod(env, service_manager_class_,
316314
get_service_method_, service_name.get());
317315
if (binder) {
318-
LOGI("Got system server binder on attempt {}.", i + 1);
316+
LOGI("Got system server binder via {} on attempt {}.", bridgeServiceName.data(), i + 1);
319317
return binder;
320318
}
321319
if (i < 2) {
322-
LOGW("Failed to get system server binder, will retry in 1 second...");
320+
LOGW("Failed to get system server binder via {}, will retry in 1 second...",
321+
bridgeServiceName.data());
323322
std::this_thread::sleep_for(std::chrono::seconds(1));
324323
}
325324
}

zygisk/src/main/cpp/module.cpp

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ const char *const kHostPackageName = INJECTED_PACKAGE_NAME;
3838
const char *const kManagePackageName = MANAGER_PACKAGE_NAME;
3939
constexpr uid_t GID_INET = 3003; // Android's Internet group ID.
4040

41+
enum RuntimeFlags : uint32_t {
42+
// Flags defined by NeoZygisk
43+
LATE_INJECT = 1 << 30,
44+
};
45+
4146
// A simply ConfigBridge implemnetation holding obfuscation maps in memory
4247
using obfuscation_map_t = std::map<std::string, std::string>;
4348
class ConfigImpl : public ConfigBridge {
@@ -338,9 +343,9 @@ void VectorModule::postAppSpecialize(const zygisk::AppSpecializeArgs *args) {
338343
this->SetupEntryClass(env_);
339344

340345
// Hand off control to the Java side of the framework.
341-
this->FindAndCall(env_, "forkCommon",
342-
"(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_FALSE,
343-
args->nice_name, args->app_data_dir, binder.get(), is_manager_app_);
346+
this->FindAndCall(
347+
env_, "forkCommon", "(ZZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
348+
JNI_FALSE, JNI_FALSE, args->nice_name, args->app_data_dir, binder.get(), is_manager_app_);
344349

345350
LOGV("Injected Vector framework into '{}'.", nice_name_str.get());
346351
SetAllowUnload(false); // We are injected, PREVENT module unloading.
@@ -385,7 +390,10 @@ void VectorModule::postServerSpecialize(const zygisk::ServerSpecializeArgs *args
385390

386391
// --- Framework Injection for System Server ---
387392
auto &ipc_bridge = IPCBridge::GetInstance();
388-
auto system_binder = ipc_bridge.RequestSystemServerBinder(env_);
393+
std::string bridgeServiceName = "serial";
394+
bool is_late_inject = (args->runtime_flags & RuntimeFlags::LATE_INJECT) != 0;
395+
if (is_late_inject) bridgeServiceName = "serial_vector";
396+
auto system_binder = ipc_bridge.RequestSystemServerBinder(env_, bridgeServiceName);
389397
if (!system_binder) {
390398
LOGE("Failed to get system server IPC binder. Aborting injection.");
391399
SetAllowUnload(true); // Allow unload on failure.
@@ -423,8 +431,9 @@ void VectorModule::postServerSpecialize(const zygisk::ServerSpecializeArgs *args
423431

424432
auto system_name = lsplant::ScopedLocalRef(env_, env_->NewStringUTF("system"));
425433
this->FindAndCall(env_, "forkCommon",
426-
"(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_TRUE,
427-
system_name.get(), nullptr, manager_binder.get(), is_manager_app_);
434+
"(ZZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_TRUE,
435+
is_late_inject, system_name.get(), nullptr, manager_binder.get(),
436+
is_manager_app_);
428437

429438
LOGI("Injected Vector framework into system_server.");
430439
SetAllowUnload(false); // We are injected, PREVENT module unloading.

zygisk/src/main/kotlin/org/matrix/vector/core/Main.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,19 @@ object Main {
1717
* Shared initialization logic for both System Server and Application processes.
1818
*
1919
* @param isSystem True if this is the system_server process.
20+
* @param isLateInject True if Zygisk APIs are not invoked via hooks
2021
* @param niceName The process name (e.g., package name or "system").
2122
* @param appDir The application's data directory.
2223
* @param binder The Binder token associated with the application service.
2324
*/
2425
@JvmStatic
25-
fun forkCommon(isSystem: Boolean, niceName: String, appDir: String?, binder: IBinder) {
26+
fun forkCommon(
27+
isSystem: Boolean,
28+
isLateInject: Boolean,
29+
niceName: String,
30+
appDir: String?,
31+
binder: IBinder,
32+
) {
2633
// Initialize system-specific resolution hooks if in system_server
2734
if (isSystem) {
2835
ParasiticManagerSystemHooker.start()
@@ -46,6 +53,6 @@ object Main {
4653

4754
// Standard Xposed module loading for third-party apps
4855
Utils.logI("Loading Vector/Xposed for $niceName (UID: ${Process.myUid()})")
49-
Startup.bootstrapXposed()
56+
Startup.bootstrapXposed(isSystem && isLateInject)
5057
}
5158
}

0 commit comments

Comments
 (0)