package com.topjohnwu.magisk; import android.app.AppComponentFactory; import android.app.Application; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.util.Log; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.reflect.Field; import io.michaelrocks.paranoid.Obfuscate; @Obfuscate public class DynLoad { static Object componentFactory; static final DynAPK.Data apkData = createApkData(); private static void copy(InputStream src, OutputStream dest) throws IOException { try (InputStream s = src) { try (OutputStream o = dest) { byte[] buf = new byte[8192]; for (int read; (read = s.read(buf)) >= 0;) { o.write(buf, 0, read); } } } } // 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); if (update.exists()) { // Rename from update update.renameTo(apk); } if (BuildConfig.DEBUG) { // Copy from external for easier development File external = new File(context.getExternalFilesDir(null), "magisk.apk"); if (external.exists()) { try { copy(new FileInputStream(external), new FileOutputStream(apk)); } catch (IOException e) { Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } finally { external.delete(); } } } if (!apk.exists() && !context.getPackageName().equals(BuildConfig.APPLICATION_ID)) { // Copy from previous app Uri uri = new Uri.Builder().scheme("content") .authority("com.topjohnwu.magisk.provider") .encodedPath("apk_file").build(); ContentResolver resolver = context.getContentResolver(); try { InputStream src = resolver.openInputStream(uri); if (src != null) { copy(src, new FileOutputStream(apk)); } } catch (IOException e) { Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } } if (apk.exists()) { ClassLoader cl = new InjectedClassLoader(apk); PackageManager pm = context.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0); try { return createApp(context, cl, pkgInfo.applicationInfo); } catch (ReflectiveOperationException e) { Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); } } 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) { ((DelegateComponentFactory) componentFactory).loader = cl; } } catch (Exception e) { Log.e(DynLoad.class.getSimpleName(), "", e); } return null; } private static Application createApp(Context context, ClassLoader cl, ApplicationInfo info) throws ReflectiveOperationException { // Create the receiver Application Object app = cl.loadClass(info.className) .getConstructor(Object.class) .newInstance(apkData.getObject()); // Create the receiver component factory 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); return (Application) app; } // Replace LoadedApk mClassLoader private static void setClassLoader(Context context, ClassLoader cl) throws NoSuchFieldException, IllegalAccessException { // Get ContextImpl while (context instanceof ContextWrapper) { context = ((ContextWrapper) context).getBaseContext(); } Field mInfo = context.getClass().getDeclaredField("mPackageInfo"); mInfo.setAccessible(true); Object loadedApk = mInfo.get(context); Field mcl = loadedApk.getClass().getDeclaredField("mClassLoader"); mcl.setAccessible(true); mcl.set(loadedApk, cl); } private static DynAPK.Data createApkData() { DynAPK.Data data = new DynAPK.Data(); data.setVersion(BuildConfig.STUB_VERSION); data.setClassToComponent(Mapping.inverseMap); data.setRootService(DelegateRootService.class); return data; } }