new parasitic manager

This commit is contained in:
5ec1cff 2024-02-07 23:16:40 +08:00 committed by JingMatrix
parent 9125b4b2f1
commit 41eb1c63ad
15 changed files with 175 additions and 538 deletions

View File

@ -34,7 +34,12 @@ import io.github.libxposed.api.annotations.XposedHooker;
@XposedHooker
public class HandleSystemServerProcessHooker implements XposedInterface.Hooker {
public interface Callback {
void onSystemServerLoaded(ClassLoader classLoader);
}
public static volatile ClassLoader systemServerCL;
public static volatile Callback callback = null;
@SuppressLint("PrivateApi")
@AfterInvocation
@ -47,6 +52,7 @@ public class HandleSystemServerProcessHooker implements XposedInterface.Hooker {
PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL);
var clazz = Class.forName("com.android.server.SystemServer", false, systemServerCL);
LSPosedHelper.hookAllMethods(StartBootstrapServicesHooker.class, clazz, "startBootstrapServices");
if (callback != null) callback.onSystemServerLoaded(systemServerCL);
} catch (Throwable t) {
Hookers.logE("error when hooking systemMain", t);
}

View File

@ -206,9 +206,11 @@ public class ConfigManager {
Log.e(TAG, "skip injecting into android because sepolicy was not loaded properly");
return true; // skip
}
/*
try (Cursor cursor = db.query("scope INNER JOIN modules ON scope.mid = modules.mid", new String[]{"modules.mid"}, "app_pkg_name=? AND enabled=1", new String[]{"system"}, null, null, null)) {
return cursor == null || !cursor.moveToNext();
}
}*/
return false;
}
@SuppressLint("BlockedPrivateApi")

View File

@ -21,7 +21,6 @@ package org.lsposed.lspd.service;
import static android.content.Context.BIND_AUTO_CREATE;
import static org.lsposed.lspd.service.ServiceManager.TAG;
import static org.lsposed.lspd.service.ServiceManager.getExecutorService;
import android.annotation.SuppressLint;
import android.app.IServiceConnection;
@ -59,7 +58,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import hidden.HiddenApiBridge;
@ -67,10 +65,9 @@ import io.github.libxposed.service.IXposedService;
import rikka.parcelablelist.ParcelableListSlice;
public class LSPManagerService extends ILSPManagerService.Stub {
// this maybe useful when obtaining the manager binder
private static String RANDOM_UUID = null;
private static Intent managerIntent = null;
private boolean enabled = true;
public class ManagerGuard implements IBinder.DeathRecipient {
private final @NonNull
@ -163,7 +160,11 @@ public class LSPManagerService extends ILSPManagerService.Stub {
if (intent == null) return;
intent = new Intent(intent);
intent.setData(withData);
ServiceManager.getManagerService().preStartManager(BuildConfig.MANAGER_INJECTED_PKG_NAME, intent, true);
try {
ActivityManagerService.startActivityAsUserWithFeature("android", null, intent, intent.getType(), null, null, 0, 0, null, null, 0);
} catch (RemoteException e) {
Log.e(TAG, "failed to open manager");
}
}
@SuppressLint("WrongConstant")
@ -188,11 +189,6 @@ public class LSPManagerService extends ILSPManagerService.Stub {
}
}
public ManagerGuard guardSnapshot() {
var snapshot = guard;
return snapshot != null && snapshot.isAlive() ? snapshot : null;
}
private void ensureWebViewPermission(File f) {
if (!f.exists()) return;
SELinux.setFileContext(f.getAbsolutePath(), "u:object_r:magisk_file:s0");
@ -222,136 +218,46 @@ public class LSPManagerService extends ILSPManagerService.Stub {
}
}
// To start injected manager, we should take care about conflict
// with the target app since we won't inject into it
// if we are not going to display manager.
// Thus, when someone launching manager, we should no matter
// stop any process of the target app
// Ideally we should call force stop package here,
// however it's not feasible because it will cause deadlock
// Thus we will cancel the launch of the activity
// and manually start activity with force stopping
// However, the intent we got here is not complete since
// there's no extras. We cannot do the same thing
// where starting the target app while the manager is
// still running.
// We instead let the manager to restart the activity.
synchronized boolean preStartManager(String pkgName, Intent intent, boolean doResume) {
// first, check if it's our target app, if not continue the start
if (BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkgName)) {
Log.d(TAG, "starting target app of parasitic manager");
// check if it's launching our manager
if (intent.getCategories() != null &&
intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) {
Log.d(TAG, "requesting launch of manager");
// a new launch for the manager
// check if there's one running
// or it's run by ourselves after force stopping
var snapshot = guardSnapshot();
if ((RANDOM_UUID != null && intent.getCategories().contains(RANDOM_UUID)) ||
(snapshot != null && snapshot.isAlive() && snapshot.uid == BuildConfig.MANAGER_INJECTED_UID)) {
Log.d(TAG, "manager is still running or is on its way");
// there's one running parasitic manager
// or it's run by ourself after killing, resume it
if (doResume) {
// if doResume is true, we help do the resumption
try {
ActivityManagerService.startActivityAsUserWithFeature("android", null, intent, intent.getType(), null, null, 0, 0, null, null, 0);
} catch (Throwable e) {
Log.w(TAG, "resume manager", e);
}
}
return true;
} else if (pendingManager) {
// Check the flag in case new launch comes before finishing
// the previous one to avoid racing.
Log.d(TAG, "manager is still on its way when new launch comes, skipping");
return false;
} else {
// new parasitic manager launch, set the flag and kill
// old processes
// we do it by cancelling the launch (return false)
// and start activity in a new thread
pendingManager = true;
getExecutorService().submit(() -> {
ensureWebViewPermission();
stopAndStartActivity(pkgName, intent, true);
});
Log.d(TAG, "requested to launch manager");
return false;
}
} else if (pendingManager) {
// there's still parasitic manager, cancel a normal launch until
// the parasitic manager is launch
Log.d(TAG, "previous request is not yet done");
return false;
}
// this is a normal launch of the target app
// send it to the manager and let it to restart the package
// if the manager is running
// or normally restart without injecting
Log.d(TAG, "launching the target app normally");
return true;
}
synchronized boolean preStartManager() {
pendingManager = true;
managerPid = -1;
return true;
}
synchronized void stopAndStartActivity(String packageName, Intent intent, boolean addUUID) {
try {
ActivityManagerService.forceStopPackage(packageName, 0);
Log.d(TAG, "stopped old package");
if (addUUID) {
intent = new Intent(intent);
RANDOM_UUID = UUID.randomUUID().toString();
intent.addCategory(RANDOM_UUID);
}
ActivityManagerService.startActivityAsUserWithFeature("android", null, intent, intent.getType(), null, null, 0, 0, null, null, 0);
Log.d(TAG, "relaunching");
} catch (RemoteException e) {
Log.e(TAG, "stop and start activity", e);
}
}
// return true to inject manager
synchronized boolean shouldStartManager(int pid, int uid, String processName) {
if (uid != BuildConfig.MANAGER_INJECTED_UID || !BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(processName) || !pendingManager)
if (!enabled || uid != BuildConfig.MANAGER_INJECTED_UID || !BuildConfig.DEFAULT_MANAGER_PACKAGE_NAME.equals(processName) || !pendingManager)
return false;
// pending parasitic manager launch it processes
// now we have its pid so we allow it to be killed
// and thus reset the pending flag and mark its pid
pendingManager = false;
managerPid = pid;
Log.d(TAG, "starting injected manager: pid = " + pid + " uid = " + uid + " processName = " + processName);
return true;
}
synchronized boolean setEnabled(boolean newValue) {
enabled = newValue;
Log.i(TAG, "manager enabled = " + enabled);
return enabled;
}
// return true to send manager binder
synchronized boolean postStartManager(int pid, int uid) {
if (pid == managerPid && uid == BuildConfig.MANAGER_INJECTED_UID) {
RANDOM_UUID = null;
return true;
}
return false;
boolean postStartManager(int pid, int uid) {
return enabled && uid == BuildConfig.MANAGER_INJECTED_UID && pid == managerPid;
}
public @NonNull
IBinder obtainManagerBinder(@NonNull IBinder heartbeat, int pid, int uid) {
new ManagerGuard(heartbeat, pid, uid);
if (postStartManager(pid, uid)) {
managerPid = 0;
}
if (uid == BuildConfig.MANAGER_INJECTED_UID)
ensureWebViewPermission();
return this;
}
public boolean isRunningManager(int pid, int uid) {
var snapshotPid = managerPid;
var snapshotGuard = guardSnapshot();
return (pid == snapshotPid && uid == BuildConfig.MANAGER_INJECTED_UID) || (snapshotGuard != null && snapshotGuard.pid == pid && snapshotGuard.uid == uid);
return false;
}
void onSystemServerDied() {
pendingManager = false;
managerPid = 0;
guard = null;
}
@ -564,8 +470,6 @@ public class LSPManagerService extends ILSPManagerService.Stub {
@Override
public void restartFor(Intent intent) throws RemoteException {
forceStopPackage(BuildConfig.MANAGER_INJECTED_PKG_NAME, 0);
stopAndStartActivity(BuildConfig.MANAGER_INJECTED_PKG_NAME, intent, false);
}
@Override

View File

@ -221,12 +221,6 @@ public class LSPosedService extends ILSPosedService.Stub {
private void dispatchBootCompleted(Intent intent) {
bootCompleted = true;
try {
var am = ActivityManagerService.getActivityManager();
if (am != null) am.setActivityController(null, false);
} catch (RemoteException e) {
Log.e(TAG, "setActivityController", e);
}
var configManager = ConfigManager.getInstance();
if (configManager.enableStatusNotification()) {
LSPNotificationManager.notifyStatusNotification();
@ -463,8 +457,12 @@ public class LSPosedService extends ILSPosedService.Stub {
}
@Override
public boolean preStartManager(String pkgName, Intent intent) {
Log.d(TAG, "checking manager intent");
return ServiceManager.getManagerService().preStartManager(pkgName, intent, false);
public boolean preStartManager() {
return ServiceManager.getManagerService().preStartManager();
}
@Override
public boolean setManagerEnabled(boolean enabled) throws RemoteException {
return ServiceManager.getManagerService().setEnabled(enabled);
}
}

View File

@ -24,15 +24,18 @@ import android.os.Process;
import org.lsposed.lspd.service.ILSPApplicationService;
import org.lsposed.lspd.util.ParasiticManagerHooker;
import org.lsposed.lspd.util.ParasiticManagerSystemHooker;
import org.lsposed.lspd.util.Utils;
import org.lsposed.lspd.BuildConfig;
public class Main {
public static void forkCommon(boolean isSystem, String niceName, String appDir, IBinder binder) {
if (isSystem) {
ParasiticManagerSystemHooker.start();
}
Startup.initXposed(isSystem, niceName, appDir, ILSPApplicationService.Stub.asInterface(binder));
if ((niceName.equals(BuildConfig.MANAGER_INJECTED_PKG_NAME) || niceName.equals(BuildConfig.DEFAULT_MANAGER_PACKAGE_NAME))
&& ParasiticManagerHooker.start()) {
if (niceName.equals(BuildConfig.DEFAULT_MANAGER_PACKAGE_NAME) && ParasiticManagerHooker.start()) {
Utils.logI("Loaded manager, skipping next steps");
return;
}

View File

@ -1,260 +0,0 @@
package org.lsposed.lspd.service;
import static org.lsposed.lspd.service.BridgeService.TAG;
import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.app.IActivityController;
import android.app.IActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.ShellCommand;
import android.util.Log;
import androidx.annotation.NonNull;
import org.lsposed.lspd.BuildConfig;
import java.io.FileDescriptor;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ActivityController extends IActivityController.Stub {
private static Constructor<?> myActivityControllerConstructor = null;
private static Method myActivityControllerRunner = null;
private static boolean inited = false;
private static int fdSize = -1;
private static IActivityController controller = null;
static private ActivityController instance;
static {
try {
Context ctx = ActivityThread.currentActivityThread().getSystemContext();
var systemClassLoader = ctx.getClassLoader();
@SuppressLint("PrivateApi") var myActivityControllerClass = Class.forName("com.android.server.am.ActivityManagerShellCommand$MyActivityController", false, systemClassLoader);
try {
myActivityControllerConstructor = myActivityControllerClass.getDeclaredConstructor(IActivityManager.class, PrintWriter.class, InputStream.class,
String.class, boolean.class);
} catch (NoSuchMethodException e1) {
try {
myActivityControllerConstructor = myActivityControllerClass.getDeclaredConstructor(IActivityManager.class, PrintWriter.class, InputStream.class,
String.class, boolean.class, boolean.class, String.class, boolean.class);
} catch (NoSuchMethodException e2) {
myActivityControllerConstructor = myActivityControllerClass.getDeclaredConstructor(IActivityManager.class, PrintWriter.class, InputStream.class,
String.class, boolean.class, boolean.class, String.class, boolean.class, boolean.class);
}
}
myActivityControllerConstructor.setAccessible(true);
myActivityControllerRunner = myActivityControllerClass.getDeclaredMethod("run");
myActivityControllerRunner.setAccessible(true);
var tmp = Parcel.obtain();
tmp.writeFileDescriptor(FileDescriptor.in);
fdSize = tmp.dataPosition();
tmp.recycle();
inited = true;
} catch (Throwable e) {
Log.e(TAG, "Failed to init ActivityController", e);
}
}
private ActivityController() {
instance = this;
}
static private @NonNull
ActivityController getInstance() {
if (instance == null) new ActivityController();
return instance;
}
static boolean replaceShellCommand(IBinder am, Parcel data, Parcel reply) {
if (!inited) return false;
try {
data.setDataPosition(fdSize * 3);
String[] args = data.createStringArray();
if (args.length > 0 && "monitor".equals(args[0])) {
data.setDataPosition(0);
try (var in = data.readFileDescriptor();
var out = data.readFileDescriptor();
var err = data.readFileDescriptor()) {
data.createStringArray();
ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);
ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);
new ShellCommand() {
@Override
public int onCommand(String cmd) {
final PrintWriter pw = getOutPrintWriter();
String opt;
String gdbPort = null;
boolean monkey = false;
boolean simpleMode = false;
String target = null;
boolean alwaysContinue = false;
boolean alwaysKill = false;
while ((opt = getNextOption()) != null) {
if (opt.equals("--gdb")) {
gdbPort = getNextArgRequired();
} else if (opt.equals("-m")) {
monkey = true;
} else if (myActivityControllerConstructor.getParameterCount() == 8) {
switch (opt) {
case "-p":
target = getNextArgRequired();
break;
case "-s":
simpleMode = true;
break;
case "-c":
alwaysContinue = true;
break;
}
} else if (myActivityControllerConstructor.getParameterCount() > 8) {
switch (opt) {
case "-k":
alwaysKill = true;
break;
}
} else {
getErrPrintWriter().println("Error: Unknown option: " + opt);
return -1;
}
}
return replaceMyControllerActivity(pw, getRawInputStream(), gdbPort, monkey, simpleMode, target, alwaysContinue, alwaysKill);
}
@Override
public void onHelp() {
}
}.exec((Binder) am, in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args, shellCallback, resultReceiver);
} catch (Throwable e) {
Log.e(TAG, "replace shell command", e);
} finally {
if (reply != null) reply.writeNoException();
}
return true;
}
} finally {
data.setDataPosition(0);
}
return false;
}
static boolean replaceActivityController(Parcel data) {
if (!inited) return false;
try {
var position = data.dataPosition();
var controller = replaceActivityController(IActivityController.Stub.asInterface(data.readStrongBinder()));
var b = data.readInt();
data.setDataSize(position);
data.setDataPosition(position);
data.writeStrongInterface(controller);
data.writeInt(b);
} catch (Throwable e) {
Log.e(TAG, "replace activity controller", e);
} finally {
data.setDataPosition(0);
}
return false;
}
static private int replaceMyControllerActivity(PrintWriter pw, InputStream stream, String gdbPort, boolean monkey, boolean simpleMode, String target, boolean alwaysContinue, boolean alwaysKill) {
try {
InvocationHandler handler = (proxy, method, args1) -> {
if (method.getName().equals("setActivityController")) {
try {
args1[0] = replaceActivityController((IActivityController) args1[0]);
} catch (Throwable e) {
Log.e(TAG, "replace activity controller", e);
}
}
return method.invoke(ServiceManager.getService("activity"), args1);
};
var amProxy = Proxy.newProxyInstance(BridgeService.class.getClassLoader(),
new Class[]{myActivityControllerConstructor.getParameterTypes()[0]}, handler);
Object ctrl;
if (myActivityControllerConstructor.getParameterCount() == 5) {
ctrl = myActivityControllerConstructor.newInstance(amProxy, pw, stream, gdbPort, monkey);
} else if (myActivityControllerConstructor.getParameterCount() == 8){
ctrl = myActivityControllerConstructor.newInstance(amProxy, pw, stream, gdbPort, monkey, simpleMode, target, alwaysContinue);
} else {
ctrl = myActivityControllerConstructor.newInstance(amProxy, pw, stream, gdbPort, monkey, simpleMode, target, alwaysContinue, alwaysKill);
}
myActivityControllerRunner.invoke(ctrl);
return 0;
} catch (Throwable e) {
Log.e(TAG, "run monitor", e);
return 1;
}
}
static private IActivityController replaceActivityController(IActivityController controller) {
Log.d(TAG, "android.app.IActivityManager.setActivityController is called");
ActivityController.controller = controller;
return getInstance();
}
@Override
public boolean activityStarting(Intent intent, String pkg) {
Log.d(TAG, "activity from " + pkg + " with " + intent + " with extras " + intent.getExtras() + " is starting");
var snapshot = BridgeService.getService();
if (snapshot != null && BuildConfig.MANAGER_INJECTED_PKG_NAME.equals(pkg)) {
try {
return snapshot.preStartManager(pkg, intent);
} catch (Throwable e) {
Log.e(TAG, "request manager", e);
}
}
return controller == null || controller.activityStarting(intent, pkg);
}
@Override
public boolean activityResuming(String pkg) {
return controller == null || controller.activityResuming(pkg);
}
@Override
public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg, long timeMillis, String stackTrace) {
return controller == null || controller.appCrashed(processName, pid, shortMsg, longMsg, timeMillis, stackTrace);
}
@Override
public int appEarlyNotResponding(String processName, int pid, String annotation) {
return controller == null ? 0 : controller.appNotResponding(processName, pid, annotation);
}
@Override
public int appNotResponding(String processName, int pid, String processStats) {
return controller == null ? 0 : controller.appNotResponding(processName, pid, processStats);
}
@Override
public int systemNotResponding(String msg) {
return controller == null ? -1 : controller.systemNotResponding(msg);
}
@Override
public boolean moveTaskToFront(String pkg, int task, int flags, Bundle options) {
return controller == null || controller.moveTaskToFront(pkg, task, flags, options);
}
@Override
public IBinder asBinder() {
return this;
}
}

View File

@ -47,6 +47,7 @@ public class BridgeService {
ACTION_UNKNOWN,
ACTION_SEND_BINDER,
ACTION_GET_BINDER,
ACTION_ENABLE_MANAGER,
}
// for client
@ -149,6 +150,15 @@ public class BridgeService {
}
return false;
}
case ACTION_ENABLE_MANAGER: {
var uid = Binder.getCallingUid();
if ((uid == 0 || uid == 2000 || uid == 1000) && service != null) {
var result = service.setManagerEnabled(data.readInt() == 1);
if (reply != null) reply.writeInt(result ? 1 : 0);
return true;
}
return false;
}
}
} catch (Throwable e) {
Log.e(TAG, "onTransact", e);
@ -156,52 +166,6 @@ public class BridgeService {
return false;
}
@SuppressWarnings("unused")
public static boolean replaceShellCommand(IBinder obj, int code, long dataObj, long replyObj, int flags) {
Parcel data = ParcelUtils.fromNativePointer(dataObj);
Parcel reply = ParcelUtils.fromNativePointer(replyObj);
if (data == null || reply == null) {
Log.w(TAG, "Got transaction with null data or reply");
return false;
}
try {
String descriptor = obj.getInterfaceDescriptor();
if (!"android.app.IActivityManager".equals(descriptor) &&
!"com.sonymobile.hookservice.HookActivityService".equals(descriptor)) {
return false;
}
return ActivityController.replaceShellCommand(obj, data, reply);
} catch (Throwable e) {
Log.e(TAG, "replace shell command", e);
return false;
} finally {
data.setDataPosition(0);
}
}
@SuppressWarnings("unused")
public static boolean replaceActivityController(IBinder obj, int code, long dataObj, long replyObj, int flags) {
Parcel data = ParcelUtils.fromNativePointer(dataObj);
Parcel reply = ParcelUtils.fromNativePointer(replyObj);
if (data == null || reply == null) {
Log.w(TAG, "Got transaction with null data or reply");
return false;
}
try {
if (!ParcelUtils.safeEnforceInterface(data, "android.app.IActivityManager") &&
!ParcelUtils.safeEnforceInterface(data, "com.sonymobile.hookservice.HookActivityService")) {
return false;
}
return ActivityController.replaceActivityController(data);
} finally {
data.setDataPosition(0);
}
}
@SuppressWarnings("unused")
public static boolean execTransact(IBinder obj, int code, long dataObj, long replyObj, int flags) {
if (code != TRANSACTION_CODE) return false;

View File

@ -17,15 +17,12 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteException;
import android.util.AndroidRuntimeException;
import android.util.ArrayMap;
import android.webkit.WebViewDelegate;
import android.webkit.WebViewFactory;
import android.webkit.WebViewFactoryProvider;
import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.ILSPManagerService;
import java.io.FileInputStream;
@ -157,7 +154,6 @@ public class ParasiticManagerHooker {
}
if (param.args[i] instanceof Intent) {
var intent = (Intent) param.args[i];
checkIntent(managerService, intent);
intent.setComponent(new ComponentName(intent.getComponent().getPackageName(), "org.lsposed.manager.ui.activity.MainActivity"));
}
}
@ -257,16 +253,6 @@ public class ParasiticManagerHooker {
}
});
XposedHelpers.findAndHookMethod(ActivityThread.class, "deliverNewIntents", activityClientRecordClass, List.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (param.args[1] == null) return;
for (var intent : (List<?>) param.args[1]) {
checkIntent(managerService, (Intent) intent);
}
}
});
XposedHelpers.findAndHookMethod(WebViewFactory.class, "getProvider", new XC_MethodReplacement() {
@Override
protected Object replaceHookedMethod(MethodHookParam param) {
@ -323,21 +309,6 @@ public class ParasiticManagerHooker {
XposedHelpers.findAndHookMethod(ActivityThread.class, "performDestroyActivity", IBinder.class, boolean.class, int.class, boolean.class, stateHooker);
}
private static void checkIntent(ILSPManagerService managerService, Intent intent) {
if (managerService == null) return;
if (Process.myUid() != BuildConfig.MANAGER_INJECTED_UID) return;
if (intent.getCategories() == null || !intent.getCategories().contains("org.lsposed.manager.LAUNCH_MANAGER")) {
Hookers.logD("Launching the original app, restarting");
try {
managerService.restartFor(intent);
} catch (RemoteException e) {
Hookers.logE("restart failed", e);
} finally {
Process.killProcess(Process.myPid());
}
}
}
static public boolean start() {
List<IBinder> binder = new ArrayList<>(1);

View File

@ -0,0 +1,77 @@
package org.lsposed.lspd.util;
import android.annotation.SuppressLint;
import android.app.ProfilerInfo;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
import android.util.Log;
import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
import org.lsposed.lspd.impl.LSPosedHelper;
import org.lsposed.lspd.service.BridgeService;
import io.github.libxposed.api.XposedInterface;
import io.github.libxposed.api.annotations.AfterInvocation;
import io.github.libxposed.api.annotations.XposedHooker;
public class ParasiticManagerSystemHooker implements HandleSystemServerProcessHooker.Callback {
public static void start() {
HandleSystemServerProcessHooker.callback = new ParasiticManagerSystemHooker();
}
/*@XposedHooker
private static class Hooker2 implements XposedInterface.Hooker {
@BeforeInvocation
public static void beforeHookedMethod(XposedInterface.BeforeHookCallback callback) throws Throwable {
Log.d("LSPosed", "checking new activity");
var self = callback.getThisObject();
if (self == null) return;
var request = XposedHelpers.getObjectField(self, "mRequest");
Log.d("LSPosed", "start activity intent=" + XposedHelpers.getObjectField(request, "intent") + " ai=" + XposedHelpers.getObjectField(request, "activityInfo"), new Throwable());
}
}*/
@XposedHooker
private static class Hooker implements XposedInterface.Hooker {
@AfterInvocation
public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) throws Throwable {
Log.d("LSPosed", "checking new activity");
var intent = (Intent) callback.getArgs()[0];
Log.d("LSPosed", "intent=" + intent);
if (intent == null) return;
// TODO: keep sync with LSPManagerService getManagerIntent
if (!intent.hasCategory("org.lsposed.manager.LAUNCH_MANAGER")) return;
var aInfo = (ActivityInfo) callback.getResult();
if (aInfo == null || !"com.android.shell".equals(aInfo.packageName)) return;
aInfo.processName = "org.lsposed.manager";
aInfo.theme = android.R.style.Theme_Material_Light_NoActionBar;
aInfo.flags = aInfo.flags & ~(ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS);
BridgeService.getService().preStartManager();
Log.d("LSPosed", "replaced activity");
}
}
@SuppressLint("PrivateApi")
@Override
public void onSystemServerLoaded(ClassLoader classLoader) {
try {
Class<?> supervisorClass;
try {
supervisorClass = Class.forName("com.android.server.wm.ActivityTaskSupervisor", false, classLoader);
} catch (ClassNotFoundException ignore) {
supervisorClass = Class.forName("com.android.server.wm.ActivityStackSupervisor", false, classLoader);
}
LSPosedHelper.hookMethod(Hooker.class, supervisorClass, "resolveActivity", Intent.class, ResolveInfo.class, int.class, ProfilerInfo.class);
/*
for (var method: Class.forName("com.android.server.wm.ActivityStarter", false, classLoader).getDeclaredMethods()) {
if ("execute".equals(method.getName()))
HookBridge.deoptimizeMethod(method);
}
LSPosedHelper.hookAllMethods(Hooker2.class, Class.forName("com.android.server.wm.ActivityStarter", false, classLoader), "execute");*/
Log.d("LSPosed", "hooked activity starter");
} catch (Throwable e) {
Log.e("LSPosed", "onSystemServerLoaded: ", e);
}
}
}

View File

@ -63,6 +63,7 @@ namespace lspd {
nice_name,
*start_child_zygote,
*_app_data_dir);
*_nice_name = nice_name;
}
void nativeForkAndSpecializePost(JNIEnv *env, jclass, jint res) {

View File

@ -37,6 +37,7 @@ static_assert(FS_IOC_SETFLAGS == LP_SELECT(0x40046602, 0x40086602));
namespace lspd {
extern int *allowUnload;
jboolean is_parasitic_manager = JNI_FALSE;
constexpr int FIRST_ISOLATED_UID = 99000;
constexpr int LAST_ISOLATED_UID = 99999;
@ -114,52 +115,55 @@ namespace lspd {
close(dex_fd);
instance->HookBridge(*this, env);
if (application_binder) {
lsplant::InitInfo initInfo{
.inline_hooker = [](auto t, auto r) {
void* bk = nullptr;
return HookFunction(t, r, &bk) == 0 ? bk : nullptr;
},
.inline_unhooker = [](auto t) {
return UnhookFunction(t) == 0 ;
},
.art_symbol_resolver = [](auto symbol) {
return GetArt()->getSymbAddress(symbol);
},
.art_symbol_prefix_resolver = [](auto symbol) {
return GetArt()->getSymbPrefixFirstAddress(symbol);
},
};
InitArtHooker(env, initInfo);
InitHooks(env);
SetupEntryClass(env);
FindAndCall(env, "forkCommon",
"(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
JNI_TRUE, JNI_NewStringUTF(env, "system"), nullptr, application_binder);
GetArt(true);
} else {
LOGI("skipped system server");
GetArt(true);
}
// always inject into system server
lsplant::InitInfo initInfo{
.inline_hooker = [](auto t, auto r) {
void* bk = nullptr;
return HookFunction(t, r, &bk) == 0 ? bk : nullptr;
},
.inline_unhooker = [](auto t) {
return UnhookFunction(t) == 0 ;
},
.art_symbol_resolver = [](auto symbol) {
return GetArt()->getSymbAddress(symbol);
},
.art_symbol_prefix_resolver = [](auto symbol) {
return GetArt()->getSymbPrefixFirstAddress(symbol);
},
};
InitArtHooker(env, initInfo);
InitHooks(env);
SetupEntryClass(env);
FindAndCall(env, "forkCommon",
"(ZLjava/lang/String;Ljava/lang/String;Landroid/os/IBinder;)V",
JNI_TRUE, JNI_NewStringUTF(env, "system"), nullptr, application_binder, is_parasitic_manager);
GetArt(true);
}
}
void MagiskLoader::OnNativeForkAndSpecializePre(JNIEnv *env,
jint uid,
jintArray &gids,
jstring nice_name,
jstring &nice_name,
jboolean is_child_zygote,
jstring app_data_dir) {
jboolean is_manager = JNI_FALSE;
if (uid == kAidInjected) {
int array_size = gids ? env->GetArrayLength(gids) : 0;
auto region = std::make_unique<jint[]>(array_size + 1);
auto *new_gids = env->NewIntArray(array_size + 1);
if (gids) env->GetIntArrayRegion(gids, 0, array_size, region.get());
region.get()[array_size] = kAidInet;
env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
if (gids) env->SetIntArrayRegion(gids, 0, 1, region.get() + array_size);
gids = new_gids;
const JUTFString name(env, nice_name);
if (name.get() == "org.lsposed.manager"sv) {
int array_size = gids ? env->GetArrayLength(gids) : 0;
auto region = std::make_unique<jint[]>(array_size + 1);
auto *new_gids = env->NewIntArray(array_size + 1);
if (gids) env->GetIntArrayRegion(gids, 0, array_size, region.get());
region.get()[array_size] = kAidInet;
env->SetIntArrayRegion(new_gids, 0, array_size + 1, region.get());
if (gids) env->SetIntArrayRegion(gids, 0, 1, region.get() + array_size);
gids = new_gids;
nice_name = JNI_NewStringUTF(env, "com.android.shell").release();
is_manager = JNI_TRUE;
}
}
is_parasitic_manager = is_manager;
Service::instance()->InitService(env);
const auto app_id = uid % PER_USER_RANGE;
JUTFString process_name(env, nice_name);
@ -187,6 +191,7 @@ namespace lspd {
MagiskLoader::OnNativeForkAndSpecializePost(JNIEnv *env, jstring nice_name, jstring app_dir) {
const JUTFString process_name(env, nice_name);
auto *instance = Service::instance();
if (is_parasitic_manager) nice_name = JNI_NewStringUTF(env, "org.lsposed.manager").release();
auto binder = skip_ ? ScopedLocalRef<jobject>{env, nullptr}
: instance->RequestBinder(env, nice_name);
if (binder) {

View File

@ -36,7 +36,7 @@ namespace lspd {
return static_cast<MagiskLoader*>(instance_.get());
}
void OnNativeForkAndSpecializePre(JNIEnv *env, jint uid, jintArray &gids, jstring nice_name,
void OnNativeForkAndSpecializePre(JNIEnv *env, jint uid, jintArray &gids, jstring &nice_name,
jboolean is_child_zygote, jstring app_data_dir);
void OnNativeForkAndSpecializePost(JNIEnv *env, jstring nice_name, jstring app_dir);

View File

@ -95,25 +95,6 @@ namespace lspd {
instance()->exec_transact_replace_methodID_,
obj, code, data_obj, reply_obj, flags);
return true;
} else if (SET_ACTIVITY_CONTROLLER_CODE != -1 &&
code == SET_ACTIVITY_CONTROLLER_CODE) [[unlikely]] {
va_copy(copy, args);
if (instance()->replace_activity_controller_methodID_) {
*res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_,
instance()->replace_activity_controller_methodID_,
obj, code, data_obj, reply_obj, flags);
}
va_end(copy);
// fallback the backup
} else if (code == (('_' << 24) | ('C' << 16) | ('M' << 8) | 'D')) {
va_copy(copy, args);
if (instance()->replace_shell_command_methodID_) {
*res = JNI_CallStaticBooleanMethod(env, instance()->bridge_service_class_,
instance()->replace_shell_command_methodID_,
obj, code, data_obj, reply_obj, flags);
}
va_end(copy);
return *res;
}
return false;
}
@ -247,21 +228,6 @@ namespace lspd {
return;
}
replace_activity_controller_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_,
"replaceActivityController",
hooker_sig);
if (!replace_activity_controller_methodID_) {
LOGE("replaceActivityShell class not found");
}
replace_shell_command_methodID_ = JNI_GetStaticMethodID(env, bridge_service_class_,
"replaceShellCommand",
hooker_sig);
if (!replace_shell_command_methodID_) {
LOGE("replaceShellCommand class not found");
}
auto binder_class = JNI_FindClass(env, "android/os/Binder");
exec_transact_backup_methodID_ = JNI_GetMethodID(env, binder_class, "execTransact",
"(IJJI)Z");

View File

@ -107,8 +107,6 @@ namespace lspd {
jclass bridge_service_class_ = nullptr;
jmethodID exec_transact_replace_methodID_ = nullptr;
jmethodID replace_activity_controller_methodID_ = nullptr;
jmethodID replace_shell_command_methodID_ = nullptr;
jclass binder_class_ = nullptr;
jmethodID binder_ctor_ = nullptr;

View File

@ -7,5 +7,7 @@ interface ILSPosedService {
oneway void dispatchSystemServerContext(in IBinder activityThread, in IBinder activityToken, String api);
boolean preStartManager(String pkgName, in Intent intent);
boolean preStartManager();
boolean setManagerEnabled(boolean enabled);
}