new parasitic manager
This commit is contained in:
parent
9125b4b2f1
commit
41eb1c63ad
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue