diff --git a/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java b/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java index 93a623a37..c688b7339 100644 --- a/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java +++ b/app/shared/src/main/java/com/topjohnwu/magisk/DynAPK.java @@ -13,11 +13,6 @@ import io.michaelrocks.paranoid.Obfuscate; @Obfuscate public class DynAPK { - - // Indices of the object array - private static final int STUB_VERSION_ENTRY = 0; - private static final int CLASS_COMPONENT_MAP = 1; - private static File dynDir; private static Method addAssetPath; @@ -41,21 +36,6 @@ public class DynAPK { return new File(getDynDir(c), "update.apk"); } - public static Data load(Object o) { - Object[] arr = (Object[]) o; - Data data = new Data(); - data.version = (int) arr[STUB_VERSION_ENTRY]; - data.classToComponent = (Map) arr[CLASS_COMPONENT_MAP]; - return data; - } - - public static Object pack(Data data) { - Object[] arr = new Object[2]; - arr[STUB_VERSION_ENTRY] = data.version; - arr[CLASS_COMPONENT_MAP] = data.classToComponent; - return arr; - } - public static void addAssetPath(AssetManager asset, String path) { try { if (addAssetPath == null) @@ -65,7 +45,28 @@ public class DynAPK { } public static class Data { - public int version; - public Map classToComponent; + // Indices of the object array + private static final int STUB_VERSION = 0; + private static final int CLASS_COMPONENT_MAP = 1; + private static final int ROOT_SERVICE = 2; + private static final int ARR_SIZE = 3; + + private final Object[] arr; + + public Data() { arr = new Object[ARR_SIZE]; } + public Data(Object o) { arr = (Object[]) o; } + public Object getObject() { return arr; } + + public int getVersion() { return (int) arr[STUB_VERSION]; } + public void setVersion(int version) { arr[STUB_VERSION] = version; } + public Map getClassToComponent() { + // noinspection unchecked + return (Map) arr[CLASS_COMPONENT_MAP]; + } + public void setClassToComponent(Map map) { + arr[CLASS_COMPONENT_MAP] = map; + } + public Class getRootService() { return (Class) arr[ROOT_SERVICE]; } + public void setRootService(Class service) { arr[ROOT_SERVICE] = service; } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/App.kt b/app/src/main/java/com/topjohnwu/magisk/core/App.kt index 801522c4b..a224c2836 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/App.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/App.kt @@ -22,16 +22,15 @@ import kotlin.system.exitProcess open class App() : Application() { constructor(o: Any) : this() { - Info.stub = DynAPK.load(o) + val data = DynAPK.Data(o) + // Add the root service name mapping + data.classToComponent[RootRegistry::class.java.name] = data.rootService.name + // Send back the actual root service class + data.rootService = RootRegistry::class.java + Info.stub = data } init { - Shell.setDefaultBuilder(Shell.Builder.create() - .setFlags(Shell.FLAG_MOUNT_MASTER) - .setInitializers(ShellInit::class.java) - .setTimeout(2)) - Shell.EXECUTOR = IODispatcherExecutor() - // Always log full stack trace with Timber Timber.plant(Timber.DebugTree()) Thread.setDefaultUncaughtExceptionHandler { _, e -> @@ -41,6 +40,12 @@ open class App() : Application() { } override fun attachBaseContext(base: Context) { + Shell.setDefaultBuilder(Shell.Builder.create() + .setFlags(Shell.FLAG_MOUNT_MASTER) + .setInitializers(ShellInit::class.java) + .setTimeout(2)) + Shell.EXECUTOR = IODispatcherExecutor() + // Some context magic val app: Application val impl: Context diff --git a/app/src/main/java/com/topjohnwu/magisk/core/utils/RootRegistry.kt b/app/src/main/java/com/topjohnwu/magisk/core/utils/RootRegistry.kt index 77853e5b6..ca79cf317 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/utils/RootRegistry.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/utils/RootRegistry.kt @@ -10,7 +10,11 @@ import timber.log.Timber import java.util.concurrent.CountDownLatch import kotlin.system.exitProcess -class RootRegistry : RootService() { +class RootRegistry(stub: Any?) : RootService() { + + constructor() : this(null) + + private val className: String? = stub?.javaClass?.name init { // Always log full stack trace with Timber @@ -26,6 +30,10 @@ class RootRegistry : RootService() { return Binder() } + override fun getComponentName(): ComponentName { + return ComponentName(packageName, className ?: javaClass.name) + } + // TODO: PLACEHOLDER object Connection : CountDownLatch(1), ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { diff --git a/stub/proguard-rules.pro b/stub/proguard-rules.pro index 99c641428..1aed13825 100644 --- a/stub/proguard-rules.pro +++ b/stub/proguard-rules.pro @@ -30,3 +30,4 @@ -keepclassmembers class com.topjohnwu.magisk.dummy.* { (); } -keepclassmembers class com.topjohnwu.magisk.DownloadActivity { (); } -keepclassmembers class com.topjohnwu.magisk.FileProvider { (); } +-keepclassmembers class com.topjohnwu.magisk.DelegateRootService { (); } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java index 3b4ad53d6..542663e46 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java @@ -18,7 +18,7 @@ public class DelegateApplication extends Application { protected void attachBaseContext(Context base) { super.attachBaseContext(base); - receiver = InjectAPK.setup(this); + receiver = DynLoad.setup(this); if (receiver != null) try { // Call attachBaseContext without ContextImpl to show it is being wrapped Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class); @@ -27,6 +27,13 @@ public class DelegateApplication extends Application { } catch (Exception ignored) { /* Impossible */ } } + @Override + public void onCreate() { + super.onCreate(); + if (receiver != null) + receiver.onCreate(); + } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java index d191b822c..e255fa96a 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java @@ -16,7 +16,7 @@ public class DelegateComponentFactory extends AppComponentFactory { AppComponentFactory receiver; public DelegateComponentFactory() { - InjectAPK.componentFactory = this; + DynLoad.componentFactory = this; } @Override diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java new file mode 100644 index 000000000..5be210edc --- /dev/null +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java @@ -0,0 +1,36 @@ +package com.topjohnwu.magisk; + +import android.content.Context; +import android.content.ContextWrapper; +import android.util.Log; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +import io.michaelrocks.paranoid.Obfuscate; + +@Obfuscate +public class DelegateRootService extends ContextWrapper { + + public DelegateRootService() { + super(null); + } + + @Override + protected void attachBaseContext(Context base) { + if (DynLoad.inject(base) == null) + return; + + // Create the actual RootService and call its attachBaseContext + try { + Constructor ctor = DynLoad.apkData.getRootService().getConstructor(Object.class); + ctor.setAccessible(true); + Object service = ctor.newInstance(this); + Method m = ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class); + m.setAccessible(true); + m.invoke(service, base); + } catch (Exception e) { + Log.e(DelegateRootService.class.getSimpleName(), "", e); + } + } +} diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index 1d9dcba2b..fa2810f80 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -115,7 +115,7 @@ public class DownloadActivity extends Activity { File apk = dynLoad ? DynAPK.current(this) : new File(getCacheDir(), "manager.apk"); request(apkLink).setExecutor(AsyncTask.THREAD_POOL_EXECUTOR).getAsFile(apk, file -> { if (dynLoad) { - InjectAPK.setup(this); + DynLoad.setup(this); runOnUiThread(() -> { dialog.dismiss(); Toast.makeText(themed, relaunch_app, Toast.LENGTH_LONG).show(); diff --git a/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java b/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java similarity index 71% rename from stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java rename to stub/src/main/java/com/topjohnwu/magisk/DynLoad.java index 53c653e16..15762ecc0 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/InjectAPK.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java @@ -23,13 +23,10 @@ import java.lang.reflect.Field; import io.michaelrocks.paranoid.Obfuscate; @Obfuscate -public class InjectAPK { +public class DynLoad { static Object componentFactory; - - private static DelegateComponentFactory getComponentFactory() { - return (DelegateComponentFactory) componentFactory; - } + static final DynAPK.Data apkData = createApkData(); private static void copy(InputStream src, OutputStream dest) throws IOException { try (InputStream s = src) { @@ -42,13 +39,9 @@ public class InjectAPK { } } - @SuppressWarnings("ResultOfMethodCallIgnored") - static Application setup(Context context) { - // Get ContextImpl - while (context instanceof ContextWrapper) { - context = ((ContextWrapper) context).getBaseContext(); - } - + // Dynamically load APK, inject ClassLoader into ContextImpl, then + // create the actual Application instance from the loaded APK + static Application inject(Context context) { File apk = DynAPK.current(context); File update = DynAPK.update(context); @@ -64,7 +57,7 @@ public class InjectAPK { try { copy(new FileInputStream(external), new FileOutputStream(apk)); } catch (IOException e) { - Log.e(InjectAPK.class.getSimpleName(), "", e); + Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } finally { external.delete(); @@ -84,7 +77,7 @@ public class InjectAPK { copy(src, new FileOutputStream(apk)); } } catch (IOException e) { - Log.e(InjectAPK.class.getSimpleName(), "", e); + Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } } @@ -95,21 +88,30 @@ public class InjectAPK { PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0); try { return createApp(context, cl, pkgInfo.applicationInfo); - } catch (Exception e) { - Log.e(InjectAPK.class.getSimpleName(), "", e); + } catch (ReflectiveOperationException e) { + Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } - // fallthrough + + } + return null; + } + + // Inject and create Application, or setup redirections for the current app + static Application setup(Context context) { + Application app = inject(context); + if (app != null) { + return app; } ClassLoader cl = new RedirectClassLoader(); try { setClassLoader(context, cl); if (Build.VERSION.SDK_INT >= 28) { - getComponentFactory().loader = cl; + ((DelegateComponentFactory) componentFactory).loader = cl; } } catch (Exception e) { - Log.e(InjectAPK.class.getSimpleName(), "", e); + Log.e(DynLoad.class.getSimpleName(), "", e); } return null; @@ -120,40 +122,42 @@ public class InjectAPK { // Create the receiver Application Object app = cl.loadClass(info.className) .getConstructor(Object.class) - .newInstance(DynAPK.pack(dynData())); + .newInstance(apkData.getObject()); // Create the receiver component factory - Object factory = null; - if (Build.VERSION.SDK_INT >= 28) { - factory = cl.loadClass(info.appComponentFactory).newInstance(); + if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) { + Object factory = cl.loadClass(info.appComponentFactory).newInstance(); + DelegateComponentFactory delegate = (DelegateComponentFactory) componentFactory; + delegate.loader = cl; + delegate.receiver = (AppComponentFactory) factory; } setClassLoader(context, cl); - // Finally, set variables - if (Build.VERSION.SDK_INT >= 28) { - getComponentFactory().loader = cl; - getComponentFactory().receiver = (AppComponentFactory) factory; - } - return (Application) app; } // Replace LoadedApk mClassLoader - private static void setClassLoader(Context impl, ClassLoader cl) + private static void setClassLoader(Context context, ClassLoader cl) throws NoSuchFieldException, IllegalAccessException { - Field mInfo = impl.getClass().getDeclaredField("mPackageInfo"); + // Get ContextImpl + while (context instanceof ContextWrapper) { + context = ((ContextWrapper) context).getBaseContext(); + } + + Field mInfo = context.getClass().getDeclaredField("mPackageInfo"); mInfo.setAccessible(true); - Object loadedApk = mInfo.get(impl); + Object loadedApk = mInfo.get(context); Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader"); mcl.setAccessible(true); mcl.set(loadedApk, cl); } - private static DynAPK.Data dynData() { + private static DynAPK.Data createApkData() { DynAPK.Data data = new DynAPK.Data(); - data.version = BuildConfig.STUB_VERSION; - data.classToComponent = Mapping.inverseMap; + data.setVersion(BuildConfig.STUB_VERSION); + data.setClassToComponent(Mapping.inverseMap); + data.setRootService(DelegateRootService.class); return data; } }