diff --git a/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java b/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java index c0afc2753..95071a945 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java +++ b/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java @@ -1,33 +1,124 @@ package com.topjohnwu.magisk; +import android.app.job.JobService; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.ServiceInfo; + +import com.topjohnwu.magisk.dummy.DummyProvider; +import com.topjohnwu.magisk.dummy.DummyReceiver; +import com.topjohnwu.magisk.dummy.DummyService; import com.topjohnwu.magisk.utils.DynamicClassLoader; import java.io.File; +import java.util.HashMap; import java.util.Map; +import io.michaelrocks.paranoid.Obfuscate; + // Wrap the actual classloader as we only want to resolve classname // mapping when loading from platform (via LoadedApk.mClassLoader) -class InjectedClassLoader extends ClassLoader { +@Obfuscate +class AppClassLoader extends ClassLoader { - InjectedClassLoader(File apk) { + final Map mapping = new HashMap<>(); + + AppClassLoader(File apk) { super(new DynamicClassLoader(apk)); } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - String clz = DynLoad.componentMap.get(name); + String clz = mapping.get(name); name = clz != null ? clz : name; return super.loadClass(name, resolve); } + + void updateComponentMap(PackageInfo stub, PackageInfo app) { + { + var src = stub.activities; + var dest = app.activities; + + final ActivityInfo sa; + final ActivityInfo da; + final ActivityInfo sb; + final ActivityInfo db; + if (src[0].exported) { + sa = src[0]; + sb = src[1]; + } else { + sa = src[1]; + sb = src[0]; + } + if (dest[0].exported) { + da = dest[0]; + db = dest[1]; + } else { + da = dest[1]; + db = dest[0]; + } + mapping.put(sa.name, da.name); + mapping.put(sb.name, db.name); + } + + { + var src = stub.services; + var dest = app.services; + + final ServiceInfo sa; + final ServiceInfo da; + final ServiceInfo sb; + final ServiceInfo db; + if (JobService.PERMISSION_BIND.equals(src[0].permission)) { + sa = src[0]; + sb = src[1]; + } else { + sa = src[1]; + sb = src[0]; + } + if (JobService.PERMISSION_BIND.equals(dest[0].permission)) { + da = dest[0]; + db = dest[1]; + } else { + da = dest[1]; + db = dest[0]; + } + mapping.put(sa.name, da.name); + mapping.put(sb.name, db.name); + } + + { + var src = stub.receivers; + var dest = app.receivers; + mapping.put(src[0].name, dest[0].name); + } + + { + var src = stub.providers; + var dest = app.providers; + mapping.put(src[0].name, dest[0].name); + } + } } -class RedirectClassLoader extends ClassLoader { +class StubClassLoader extends ClassLoader { - private final Map> mapping; + private final Map> mapping = new HashMap<>(); - RedirectClassLoader(Map> m) { - super(RedirectClassLoader.class.getClassLoader()); - mapping = m; + StubClassLoader(PackageInfo info) { + super(StubClassLoader.class.getClassLoader()); + for (var c : info.activities) { + mapping.put(c.name, DownloadActivity.class); + } + for (var c : info.services) { + mapping.put(c.name, DummyService.class); + } + for (var c : info.providers) { + mapping.put(c.name, DummyProvider.class); + } + for (var c : info.receivers) { + mapping.put(c.name, DummyReceiver.class); + } } @Override @@ -39,12 +130,15 @@ class RedirectClassLoader extends ClassLoader { class DelegateClassLoader extends ClassLoader { + // The active classloader + static ClassLoader cl = DelegateClassLoader.class.getClassLoader(); + DelegateClassLoader() { super(null); } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { - return DynLoad.loader.loadClass(name); + return cl.loadClass(name); } } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java index b396ae8e7..2b99f9336 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java @@ -9,7 +9,6 @@ import android.content.BroadcastReceiver; import android.content.ContentProvider; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.os.Process; import com.topjohnwu.magisk.dummy.DummyProvider; import com.topjohnwu.magisk.dummy.DummyReceiver; @@ -26,11 +25,6 @@ public class DelegateComponentFactory extends AppComponentFactory { @Override public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo info) { - if (Process.myUid() == 0) { - // Do not do anything in root process - return cl; - } - DynLoad.loadApk(info); return new DelegateClassLoader(); } @@ -43,7 +37,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public Activity instantiateActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (receiver != null) - return receiver.instantiateActivity(DynLoad.loader, className, intent); + return receiver.instantiateActivity(DelegateClassLoader.cl, className, intent); return create(className, DownloadActivity.class); } @@ -51,7 +45,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public BroadcastReceiver instantiateReceiver(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (receiver != null) - return receiver.instantiateReceiver(DynLoad.loader, className, intent); + return receiver.instantiateReceiver(DelegateClassLoader.cl, className, intent); return create(className, DummyReceiver.class); } @@ -59,7 +53,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public Service instantiateService(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (receiver != null) - return receiver.instantiateService(DynLoad.loader, className, intent); + return receiver.instantiateService(DelegateClassLoader.cl, className, intent); return create(className, DummyService.class); } @@ -67,7 +61,7 @@ public class DelegateComponentFactory extends AppComponentFactory { public ContentProvider instantiateProvider(ClassLoader cl, String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if (receiver != null) - return receiver.instantiateProvider(DynLoad.loader, className); + return receiver.instantiateProvider(DelegateClassLoader.cl, className); return create(className, DummyProvider.class); } @@ -75,7 +69,7 @@ public class DelegateComponentFactory extends AppComponentFactory { throws IllegalAccessException, InstantiationException { try { // noinspection unchecked - return (T) DynLoad.loader.loadClass(name).newInstance(); + return (T) DelegateClassLoader.cl.loadClass(name).newInstance(); } catch (ClassNotFoundException e) { return fallback.newInstance(); } diff --git a/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java b/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java index ad891a13e..3ca5b91a5 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java @@ -20,7 +20,8 @@ public class DelegateRootService extends ContextWrapper { @Override protected void attachBaseContext(Context base) { - if (!DynLoad.loadApk(base)) + ClassLoader loader = DynLoad.loadApk(base); + if (loader == null) return; try { @@ -29,7 +30,7 @@ public class DelegateRootService extends ContextWrapper { File apk = StubApk.current(base); PackageManager pm = base.getPackageManager(); PackageInfo pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), 0); - DynLoad.loader.loadClass(pkgInfo.applicationInfo.className) + loader.loadClass(pkgInfo.applicationInfo.className) .getConstructor(Object.class) .newInstance(data.getObject()); diff --git a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index 8272da492..840f253cf 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -49,7 +49,7 @@ public class DownloadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (DynLoad.isDynLoader()) { + if (DelegateClassLoader.cl instanceof AppClassLoader) { // For some reason activity is created before Application.attach(), // relaunch the activity using the same intent finishAffinity(); diff --git a/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java b/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java index 857bacbf6..3514bcb9b 100644 --- a/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java +++ b/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java @@ -4,21 +4,13 @@ import static com.topjohnwu.magisk.BuildConfig.APPLICATION_ID; import android.app.AppComponentFactory; import android.app.Application; -import android.app.job.JobService; import android.content.Context; import android.content.ContextWrapper; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.pm.ServiceInfo; import android.os.Build; -import android.os.Environment; import android.util.Log; -import com.topjohnwu.magisk.dummy.DummyProvider; -import com.topjohnwu.magisk.dummy.DummyReceiver; -import com.topjohnwu.magisk.dummy.DummyService; import com.topjohnwu.magisk.utils.APKInstall; import java.io.File; @@ -28,7 +20,6 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; -import java.util.Map; import io.michaelrocks.paranoid.Obfuscate; @@ -36,21 +27,12 @@ import io.michaelrocks.paranoid.Obfuscate; @SuppressWarnings("ResultOfMethodCallIgnored") public class DynLoad { - // The current active classloader - static ClassLoader loader = DynLoad.class.getClassLoader(); static Object componentFactory; - static Map componentMap = new HashMap<>(); - - private static boolean loadedApk = false; static StubApk.Data createApkData() { var data = new StubApk.Data(); data.setVersion(BuildConfig.STUB_VERSION); - Map map = new HashMap<>(); - for (var e : componentMap.entrySet()) { - map.put(e.getValue(), e.getKey()); - } - data.setClassToComponent(map); + data.setClassToComponent(new HashMap<>()); data.setRootService(DelegateRootService.class); return data; } @@ -65,14 +47,10 @@ public class DynLoad { } catch (Exception ignored) { /* Impossible */ } } - // Dynamically load APK from internal or external storage - static void loadApk(ApplicationInfo info) { - if (loadedApk) - return; - loadedApk = true; - - File apk = StubApk.current(info); - File update = StubApk.update(info); + // Dynamically load APK from internal, external storage, or previous app + static AppClassLoader loadApk(Context context) { + File apk = StubApk.current(context); + File update = StubApk.update(context); if (update.exists()) { // Rename from update @@ -80,20 +58,8 @@ public class DynLoad { } // Copy from external for easier development - if (BuildConfig.DEBUG) copy_from_ext: { - final File dir; - try { - var dirs = (File[]) Environment.class - .getMethod("buildExternalStorageAppFilesDirs", String.class) - .invoke(null, info.packageName); - if (dirs == null) - break copy_from_ext; - dir = dirs[0]; - } catch (ReflectiveOperationException e) { - Log.e(DynLoad.class.getSimpleName(), "", e); - break copy_from_ext; - } - File external = new File(dir, "magisk.apk"); + if (BuildConfig.DEBUG) { + File external = new File(context.getExternalFilesDir(null), "magisk.apk"); if (external.exists()) { try { var in = new FileInputStream(external); @@ -111,20 +77,11 @@ public class DynLoad { } if (apk.exists()) { - loader = new InjectedClassLoader(apk); + return new AppClassLoader(apk); } - } - - // Dynamically load APK from internal, external storage, or previous app - static boolean loadApk(Context context) { - // Trigger folder creation - context.getExternalFilesDir(null); - - loadApk(context.getApplicationInfo()); // If no APK is loaded, attempt to copy from previous app - if (!isDynLoader() && !context.getPackageName().equals(APPLICATION_ID)) { - File apk = StubApk.current(context); + if (!context.getPackageName().equals(APPLICATION_ID)) { try { var info = context.getPackageManager().getApplicationInfo(APPLICATION_ID, 0); var src = new FileInputStream(info.sourceDir); @@ -132,7 +89,7 @@ public class DynLoad { try (src; out) { APKInstall.transfer(src, out); } - loader = new InjectedClassLoader(apk); + return new AppClassLoader(apk); } catch (PackageManager.NameNotFoundException ignored) { } catch (IOException e) { Log.e(DynLoad.class.getSimpleName(), "", e); @@ -140,7 +97,7 @@ public class DynLoad { } } - return isDynLoader(); + return null; } // Dynamically load APK and create the Application instance from the loaded APK @@ -161,32 +118,37 @@ public class DynLoad { throw new RuntimeException(e); } - if (!loadApk(context)) { - loader = new RedirectClassLoader(createInternalMap(info)); - return null; - } - File apk = StubApk.current(context); - PackageManager pm = context.getPackageManager(); - try { - var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags); + + final var cl = loadApk(context); + if (cl != null) try { + var pkgInfo = context.getPackageManager() + .getPackageArchiveInfo(apk.getPath(), flags); + cl.updateComponentMap(info, pkgInfo); + var appInfo = pkgInfo.applicationInfo; - updateComponentMap(info, pkgInfo); + var data = createApkData(); + var map = data.getClassToComponent(); + // Create the inverse mapping (class to component name) + for (var e : cl.mapping.entrySet()) { + map.put(e.getValue(), e.getKey()); + } // Create the receiver Application - var data = createApkData(); - var app = (Application) loader.loadClass(appInfo.className) + var app = (Application) cl.loadClass(appInfo.className) .getConstructor(Object.class) .newInstance(data.getObject()); // Create the receiver component factory if (Build.VERSION.SDK_INT >= 28 && componentFactory != null) { - Object factory = loader.loadClass(appInfo.appComponentFactory).newInstance(); + Object factory = cl.loadClass(appInfo.appComponentFactory).newInstance(); var delegate = (DelegateComponentFactory) componentFactory; delegate.receiver = (AppComponentFactory) factory; } + DelegateClassLoader.cl = cl; + // Send real application to attachBaseContext attachContext(app, context); @@ -196,97 +158,12 @@ public class DynLoad { apk.delete(); } + DelegateClassLoader.cl = new StubClassLoader(info); return null; } - private static Map> createInternalMap(PackageInfo info) { - Map> map = new HashMap<>(); - for (var c : info.activities) { - map.put(c.name, DownloadActivity.class); - } - for (var c : info.services) { - map.put(c.name, DummyService.class); - } - for (var c : info.providers) { - map.put(c.name, DummyProvider.class); - } - for (var c : info.receivers) { - map.put(c.name, DummyReceiver.class); - } - return map; - } - - private static void updateComponentMap(PackageInfo from, PackageInfo to) { - { - var src = from.activities; - var dest = to.activities; - - final ActivityInfo sa; - final ActivityInfo da; - final ActivityInfo sb; - final ActivityInfo db; - if (src[0].exported) { - sa = src[0]; - sb = src[1]; - } else { - sa = src[1]; - sb = src[0]; - } - if (dest[0].exported) { - da = dest[0]; - db = dest[1]; - } else { - da = dest[1]; - db = dest[0]; - } - componentMap.put(sa.name, da.name); - componentMap.put(sb.name, db.name); - } - - { - var src = from.services; - var dest = to.services; - - final ServiceInfo sa; - final ServiceInfo da; - final ServiceInfo sb; - final ServiceInfo db; - if (JobService.PERMISSION_BIND.equals(src[0].permission)) { - sa = src[0]; - sb = src[1]; - } else { - sa = src[1]; - sb = src[0]; - } - if (JobService.PERMISSION_BIND.equals(dest[0].permission)) { - da = dest[0]; - db = dest[1]; - } else { - da = dest[1]; - db = dest[0]; - } - componentMap.put(sa.name, da.name); - componentMap.put(sb.name, db.name); - } - - { - var src = from.receivers; - var dest = to.receivers; - componentMap.put(src[0].name, dest[0].name); - } - - { - var src = from.providers; - var dest = to.providers; - componentMap.put(src[0].name, dest[0].name); - } - } - - static boolean isDynLoader() { - return loader instanceof InjectedClassLoader; - } - // Replace LoadedApk mClassLoader + @SuppressWarnings("ConstantConditions") private static void replaceClassLoader(Context context) { // Get ContextImpl while (context instanceof ContextWrapper) {