diff --git a/core/src/main/java/org/lsposed/lspd/core/Startup.java b/core/src/main/java/org/lsposed/lspd/core/Startup.java index e4d08b96d..3970de86d 100644 --- a/core/src/main/java/org/lsposed/lspd/core/Startup.java +++ b/core/src/main/java/org/lsposed/lspd/core/Startup.java @@ -24,6 +24,7 @@ import android.app.LoadedApk; import android.content.pm.ApplicationInfo; import android.content.res.CompatibilityInfo; +import android.os.IBinder; import com.android.internal.os.ZygoteInit; @@ -34,6 +35,7 @@ import org.lsposed.lspd.hooker.LoadedApkCtorHooker; import org.lsposed.lspd.hooker.LoadedApkCreateCLHooker; import org.lsposed.lspd.hooker.OpenDexFileHooker; +import org.lsposed.lspd.hooker.StartBootstrapServicesHooker; import org.lsposed.lspd.impl.LSPosedContext; import org.lsposed.lspd.impl.LSPosedHelper; import org.lsposed.lspd.service.ILSPApplicationService; @@ -63,7 +65,7 @@ private static void startBootstrapHook(boolean isSystem) { LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach"); } - public static void bootstrapXposed() { + public static void bootstrapXposed(boolean systemServerStarted) { // Initialize the Xposed framework try { startBootstrapHook(XposedInit.startsSystemServer); @@ -71,6 +73,23 @@ public static void bootstrapXposed() { } catch (Throwable t) { Utils.logE("error during Xposed initialization", t); } + + if (systemServerStarted) { + Utils.logD("Manually triggering system_server module load for late injection"); + + IBinder activityService = android.os.ServiceManager.getService("activity"); + if (activityService == null) { + Utils.logE("Activity service not found! Cannot get SystemServer ClassLoader."); + return; + } + + // Maintain state consistency for the rest of the Vector framework + HandleSystemServerProcessHooker.systemServerCL = activityService.getClass().getClassLoader(); + HandleSystemServerProcessHooker.after(); + StartBootstrapServicesHooker.before(); + + Utils.logI("Late system_server injection successfully completed."); + } } public static void initXposed(boolean isSystem, String processName, String appDir, ILSPApplicationService service) { diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java index 0a67995cd..ef93feb19 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java @@ -35,15 +35,17 @@ public interface Callback { void onSystemServerLoaded(ClassLoader classLoader); } - public static volatile ClassLoader systemServerCL; + public static volatile ClassLoader systemServerCL = null; public static volatile Callback callback = null; @SuppressLint("PrivateApi") public static void after() { Hookers.logD("ZygoteInit#handleSystemServerProcess() starts"); try { - // get system_server classLoader - systemServerCL = Thread.currentThread().getContextClassLoader(); + if (systemServerCL == null) { + // get system_server classLoader + systemServerCL = Thread.currentThread().getContextClassLoader(); + } // deopt methods in SYSTEMSERVERCLASSPATH PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL); var clazz = Class.forName("com.android.server.SystemServer", false, systemServerCL); diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java index 25057f54b..facf75417 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPSystemServerService.java @@ -32,8 +32,7 @@ public class LSPSystemServerService extends ILSPSystemServerService.Stub implements IBinder.DeathRecipient { - public static final String PROXY_SERVICE_NAME = "serial"; - + private final String proxyServiceName; private IBinder originService = null; private int requested; @@ -42,12 +41,13 @@ public boolean systemServerRequested() { } public void putBinderForSystemServer() { - android.os.ServiceManager.addService(PROXY_SERVICE_NAME, this); + android.os.ServiceManager.addService(proxyServiceName, this); binderDied(); } - public LSPSystemServerService(int maxRetry) { - Log.d(TAG, "LSPSystemServerService::LSPSystemServerService"); + public LSPSystemServerService(int maxRetry, String serviceName) { + Log.d(TAG, "LSPSystemServerService::LSPSystemServerService with proxy " + serviceName); + proxyServiceName = serviceName; requested = -maxRetry; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { // Registers a callback when system is registering an authentic "serial" service @@ -56,7 +56,7 @@ public LSPSystemServerService(int maxRetry) { @Override public void onRegistration(String name, IBinder binder) { Log.d(TAG, "LSPSystemServerService::LSPSystemServerService onRegistration: " + name + " " + binder); - if (name.equals(PROXY_SERVICE_NAME) && binder != null && binder != LSPSystemServerService.this) { + if (name.equals(proxyServiceName) && binder != null && binder != LSPSystemServerService.this) { Log.d(TAG, "Register " + name + " " + binder); originService = binder; LSPSystemServerService.this.linkToDeath(); @@ -69,7 +69,7 @@ public IBinder asBinder() { } }; try { - getSystemServiceManager().registerForNotifications(PROXY_SERVICE_NAME, serviceCallback); + getSystemServiceManager().registerForNotifications(proxyServiceName, serviceCallback); } catch (Throwable e) { Log.e(TAG, "unregister: ", e); } diff --git a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java index b2e0e81a2..198957362 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/LSPosedService.java @@ -479,6 +479,11 @@ public void dispatchSystemServerContext(IBinder appThread, IBinder activityToken registerOpenManagerReceiver(); registerModuleScopeReceiver(); registerUidObserver(); + + if (ServiceManager.isLateInject) { + Log.i(TAG, "System already booted during late injection. Manually triggering boot completed."); + dispatchBootCompleted(null); + } } @Override diff --git a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java b/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java index 1e890081a..e0fbc16af 100644 --- a/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java +++ b/daemon/src/main/java/org/lsposed/lspd/service/ServiceManager.java @@ -66,6 +66,9 @@ public class ServiceManager { private static LogcatService logcatService = null; private static Dex2OatService dex2OatService = null; + public static boolean isLateInject = false; + public static String proxyServiceName = "serial"; + private static final ExecutorService executorService = Executors.newSingleThreadExecutor(); @RequiresApi(Build.VERSION_CODES.Q) @@ -104,9 +107,13 @@ public static void start(String[] args) { systemServerMaxRetry = Integer.parseInt(arg.substring(arg.lastIndexOf('=') + 1)); } catch (Throwable ignored) { } + } else if (arg.equals("--late-inject")) { + isLateInject = true; + proxyServiceName = "serial_vector"; } } - Log.i(TAG, "starting server..."); + + Log.i(TAG, "Vector daemon started: lateInject: " + isLateInject); Log.i(TAG, String.format("version %s (%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)); Thread.setDefaultUncaughtExceptionHandler((t, e) -> { @@ -136,7 +143,7 @@ public static void start(String[] args) { mainService = new LSPosedService(); applicationService = new LSPApplicationService(); managerService = new LSPManagerService(); - systemServerService = new LSPSystemServerService(systemServerMaxRetry); + systemServerService = new LSPSystemServerService(systemServerMaxRetry, proxyServiceName); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { dex2OatService = new Dex2OatService(); dex2OatService.start(); diff --git a/zygisk/proguard-rules.pro b/zygisk/proguard-rules.pro index 7eee05bdc..f908e5529 100644 --- a/zygisk/proguard-rules.pro +++ b/zygisk/proguard-rules.pro @@ -1,5 +1,5 @@ -keepclasseswithmembers class org.matrix.vector.core.Main { - public static void forkCommon(boolean, java.lang.String, java.lang.String, android.os.IBinder); + public static void forkCommon(boolean, boolean, java.lang.String, java.lang.String, android.os.IBinder); } -keepclasseswithmembers,includedescriptorclasses class * { native ; diff --git a/zygisk/src/main/cpp/include/ipc_bridge.h b/zygisk/src/main/cpp/include/ipc_bridge.h index 2a1d0a4eb..748840671 100644 --- a/zygisk/src/main/cpp/include/ipc_bridge.h +++ b/zygisk/src/main/cpp/include/ipc_bridge.h @@ -49,9 +49,11 @@ class IPCBridge { /** * @brief Requests the system_server's dedicated Binder from the host service. * @param env JNI environment pointer. + * @param bridgeServiceName rendezvous point used by the system_server * @return A ScopedLocalRef to the Binder object, or nullptr on failure. */ - lsplant::ScopedLocalRef RequestSystemServerBinder(JNIEnv *env); + lsplant::ScopedLocalRef RequestSystemServerBinder(JNIEnv *env, + std::string bridgeServiceName); /** * @brief Asks the system_server binder for the application manager binder. diff --git a/zygisk/src/main/cpp/ipc_bridge.cpp b/zygisk/src/main/cpp/ipc_bridge.cpp index 4e81f96a9..1cf79917a 100644 --- a/zygisk/src/main/cpp/ipc_bridge.cpp +++ b/zygisk/src/main/cpp/ipc_bridge.cpp @@ -84,8 +84,6 @@ class BinderCaller { // The name of the system service we use as a rendezvous point to find our manager service. // Using "activity" is a common technique as it's always available. constexpr auto kBridgeServiceName = "activity"sv; -// A different rendezvous point used only by the system_server. -constexpr auto kSystemServerBridgeServiceName = "serial"sv; // Transaction codes for specific actions. constexpr jint kBridgeTransactionCode = ('_' << 24) | ('V' << 16) | ('E' << 8) | 'C'; @@ -299,14 +297,14 @@ lsplant::ScopedLocalRef IPCBridge::RequestAppBinder(JNIEnv *env, jstrin return result_binder; } -lsplant::ScopedLocalRef IPCBridge::RequestSystemServerBinder(JNIEnv *env) { +lsplant::ScopedLocalRef IPCBridge::RequestSystemServerBinder( + JNIEnv *env, std::string bridgeServiceName) { if (!initialized_) { LOGE("RequestSystemServerBinder failed: IPCBridge not initialized."); return {env, nullptr}; } - auto service_name = - lsplant::ScopedLocalRef(env, env->NewStringUTF(kSystemServerBridgeServiceName.data())); + auto service_name = lsplant::ScopedLocalRef(env, env->NewStringUTF(bridgeServiceName.data())); lsplant::ScopedLocalRef binder = {env, nullptr}; // The system_server might start its services slightly after Zygisk injects us. @@ -315,11 +313,12 @@ lsplant::ScopedLocalRef IPCBridge::RequestSystemServerBinder(JNIEnv *en binder = lsplant::JNI_CallStaticObjectMethod(env, service_manager_class_, get_service_method_, service_name.get()); if (binder) { - LOGI("Got system server binder on attempt {}.", i + 1); + LOGI("Got system server binder via {} on attempt {}.", bridgeServiceName.data(), i + 1); return binder; } if (i < 2) { - LOGW("Failed to get system server binder, will retry in 1 second..."); + LOGW("Failed to get system server binder via {}, will retry in 1 second...", + bridgeServiceName.data()); std::this_thread::sleep_for(std::chrono::seconds(1)); } } diff --git a/zygisk/src/main/cpp/module.cpp b/zygisk/src/main/cpp/module.cpp index c25a53018..f35efcf69 100644 --- a/zygisk/src/main/cpp/module.cpp +++ b/zygisk/src/main/cpp/module.cpp @@ -38,6 +38,11 @@ const char *const kHostPackageName = INJECTED_PACKAGE_NAME; const char *const kManagePackageName = MANAGER_PACKAGE_NAME; constexpr uid_t GID_INET = 3003; // Android's Internet group ID. +enum RuntimeFlags : uint32_t { + // Flags defined by NeoZygisk + LATE_INJECT = 1 << 30, +}; + // A simply ConfigBridge implemnetation holding obfuscation maps in memory using obfuscation_map_t = std::map; class ConfigImpl : public ConfigBridge { @@ -338,9 +343,9 @@ void VectorModule::postAppSpecialize(const zygisk::AppSpecializeArgs *args) { this->SetupEntryClass(env_); // Hand off control to the Java side of the framework. - this->FindAndCall(env_, "forkCommon", - "(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_FALSE, - args->nice_name, args->app_data_dir, binder.get(), is_manager_app_); + this->FindAndCall( + env_, "forkCommon", "(ZZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", + JNI_FALSE, JNI_FALSE, args->nice_name, args->app_data_dir, binder.get(), is_manager_app_); LOGV("Injected Vector framework into '{}'.", nice_name_str.get()); SetAllowUnload(false); // We are injected, PREVENT module unloading. @@ -385,7 +390,10 @@ void VectorModule::postServerSpecialize(const zygisk::ServerSpecializeArgs *args // --- Framework Injection for System Server --- auto &ipc_bridge = IPCBridge::GetInstance(); - auto system_binder = ipc_bridge.RequestSystemServerBinder(env_); + std::string bridgeServiceName = "serial"; + bool is_late_inject = (args->runtime_flags & RuntimeFlags::LATE_INJECT) != 0; + if (is_late_inject) bridgeServiceName = "serial_vector"; + auto system_binder = ipc_bridge.RequestSystemServerBinder(env_, bridgeServiceName); if (!system_binder) { LOGE("Failed to get system server IPC binder. Aborting injection."); SetAllowUnload(true); // Allow unload on failure. @@ -423,8 +431,9 @@ void VectorModule::postServerSpecialize(const zygisk::ServerSpecializeArgs *args auto system_name = lsplant::ScopedLocalRef(env_, env_->NewStringUTF("system")); this->FindAndCall(env_, "forkCommon", - "(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_TRUE, - system_name.get(), nullptr, manager_binder.get(), is_manager_app_); + "(ZZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V", JNI_TRUE, + is_late_inject, system_name.get(), nullptr, manager_binder.get(), + is_manager_app_); LOGI("Injected Vector framework into system_server."); SetAllowUnload(false); // We are injected, PREVENT module unloading. diff --git a/zygisk/src/main/kotlin/org/matrix/vector/core/Main.kt b/zygisk/src/main/kotlin/org/matrix/vector/core/Main.kt index 894298ab4..164d86d84 100644 --- a/zygisk/src/main/kotlin/org/matrix/vector/core/Main.kt +++ b/zygisk/src/main/kotlin/org/matrix/vector/core/Main.kt @@ -17,12 +17,19 @@ object Main { * Shared initialization logic for both System Server and Application processes. * * @param isSystem True if this is the system_server process. + * @param isLateInject True if Zygisk APIs are not invoked via hooks * @param niceName The process name (e.g., package name or "system"). * @param appDir The application's data directory. * @param binder The Binder token associated with the application service. */ @JvmStatic - fun forkCommon(isSystem: Boolean, niceName: String, appDir: String?, binder: IBinder) { + fun forkCommon( + isSystem: Boolean, + isLateInject: Boolean, + niceName: String, + appDir: String?, + binder: IBinder, + ) { // Initialize system-specific resolution hooks if in system_server if (isSystem) { ParasiticManagerSystemHooker.start() @@ -46,6 +53,6 @@ object Main { // Standard Xposed module loading for third-party apps Utils.logI("Loading Vector/Xposed for $niceName (UID: ${Process.myUid()})") - Startup.bootstrapXposed() + Startup.bootstrapXposed(isSystem && isLateInject) } }