diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/RootUtils.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/RootUtils.kt index 8d43aa861..b4a209355 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/utils/RootUtils.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/utils/RootUtils.kt @@ -21,7 +21,9 @@ class RootUtils(stub: Any?) : RootService() { private val className: String = stub?.javaClass?.name ?: javaClass.name private lateinit var am: ActivityManager - constructor() : this(null) { + constructor() : this(null) + + init { Timber.plant(object : Timber.DebugTree() { override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { super.log(priority, "Magisk", message, t) diff --git a/app/stub/proguard-rules.pro b/app/stub/proguard-rules.pro index 67a932339..e7d4ef071 100644 --- a/app/stub/proguard-rules.pro +++ b/app/stub/proguard-rules.pro @@ -29,4 +29,4 @@ -allowaccessmodification -keepclassmembers class com.topjohnwu.magisk.dummy.* { (); } -keepclassmembers class com.topjohnwu.magisk.DownloadActivity { (); } --keepclassmembers class com.topjohnwu.magisk.DelegateRootService { (); } +-keepclassmembers class com.topjohnwu.magisk.StubRootService { (); } diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java b/app/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java index 02badde13..a1f65c609 100644 --- a/app/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java +++ b/app/stub/src/main/java/com/topjohnwu/magisk/ClassLoaders.java @@ -1,26 +1,23 @@ 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; // Wrap the actual classloader as we only want to resolve classname // mapping when loading from platform (via LoadedApk.mClassLoader) -class AppClassLoader extends ClassLoader { - final Map mapping = new HashMap<>(); +class MappingClassLoader extends ClassLoader { - AppClassLoader(File apk) { - super(new DynamicClassLoader(apk)); + private final Map mapping; + + MappingClassLoader(ClassLoader parent, Map m) { + super(parent); + mapping = m; } @Override @@ -29,72 +26,6 @@ class AppClassLoader extends ClassLoader { 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 StubClassLoader extends ClassLoader { diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java b/app/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java deleted file mode 100644 index 8b0dfd258..000000000 --- a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateApplication.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.topjohnwu.magisk; - -import android.app.Application; -import android.content.Context; -import android.content.res.Configuration; - -public class DelegateApplication extends Application { - - private Application receiver; - - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - receiver = DynLoad.createAndSetupApp(this); - } - - @Override - public void onCreate() { - super.onCreate(); - if (receiver != null) - receiver.onCreate(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (receiver != null) - receiver.onConfigurationChanged(newConfig); - } -} diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java b/app/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java index a6c78a384..4738f5f03 100644 --- a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java +++ b/app/stub/src/main/java/com/topjohnwu/magisk/DelegateComponentFactory.java @@ -30,7 +30,7 @@ public class DelegateComponentFactory extends AppComponentFactory { @Override public Application instantiateApplication(ClassLoader cl, String className) { - return new DelegateApplication(); + return new StubApplication(); } @Override diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java b/app/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java index 5cbd8b96a..979a20c94 100644 --- a/app/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java +++ b/app/stub/src/main/java/com/topjohnwu/magisk/DownloadActivity.java @@ -60,14 +60,6 @@ public class DownloadActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if (DynLoad.activeClassLoader instanceof AppClassLoader) { - // For some reason activity is created before Application.attach(), - // relaunch the activity using the same intent - finishAffinity(); - startActivity(getIntent()); - return; - } - themed = new ContextThemeWrapper(this, android.R.style.Theme_DeviceDefault); // Only download and dynamic load full APK if hidden @@ -187,7 +179,7 @@ public class DownloadActivity extends Activity { } else { File res = new File(getCodeCacheDir(), "res.apk"); try (var out = new ZipOutputStream(new FileOutputStream(res))) { - // AndroidManifest.xml is reuqired on Android 6-, and directory support is broken on Android 9-10 + // AndroidManifest.xml is required on Android 6-, and directory support is broken on Android 9-10 out.putNextEntry(new ZipEntry("AndroidManifest.xml")); try (var stubApk = new ZipFile(getPackageCodePath())) { APKInstall.transfer(stubApk.getInputStream(stubApk.getEntry("AndroidManifest.xml")), out); diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java b/app/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java index 9569cddda..4fb2beeb2 100644 --- a/app/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java +++ b/app/stub/src/main/java/com/topjohnwu/magisk/DynLoad.java @@ -4,14 +4,18 @@ 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.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; import android.os.Build; import android.util.Log; import com.topjohnwu.magisk.utils.APKInstall; +import com.topjohnwu.magisk.utils.DynamicClassLoader; import java.io.File; import java.io.FileInputStream; @@ -20,6 +24,7 @@ import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; +import java.util.Map; @SuppressWarnings("ResultOfMethodCallIgnored") public class DynLoad { @@ -31,7 +36,7 @@ public class DynLoad { var data = new StubApk.Data(); data.setVersion(BuildConfig.STUB_VERSION); data.setClassToComponent(new HashMap<>()); - data.setRootService(DelegateRootService.class); + data.setRootService(StubRootService.class); return data; } @@ -46,7 +51,7 @@ public class DynLoad { } // Dynamically load APK from internal, external storage, or previous app - static AppClassLoader loadApk(Context context) { + static DynamicClassLoader loadApk(Context context) { File apk = StubApk.current(context); File update = StubApk.update(context); @@ -82,7 +87,7 @@ public class DynLoad { if (apk.exists()) { apk.setReadOnly(); - return new AppClassLoader(apk); + return new DynamicClassLoader(apk); } // If no APK is loaded, attempt to copy from previous app @@ -96,7 +101,7 @@ public class DynLoad { try (src; out) { APKInstall.transfer(src, out); } - return new AppClassLoader(apk); + return new DynamicClassLoader(apk); } catch (PackageManager.NameNotFoundException ignored) { } catch (IOException e) { Log.e(DynLoad.class.getSimpleName(), "", e); @@ -107,8 +112,8 @@ public class DynLoad { return null; } - // Dynamically load APK and create the Application instance from the loaded APK - static Application createAndSetupApp(Application context) { + // Dynamically load APK and initialize the application + static void loadAndInitializeApp(Application context) { // On API >= 29, AppComponentFactory will replace the ClassLoader for us if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) replaceClassLoader(context); @@ -120,10 +125,10 @@ public class DynLoad { | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; var pm = context.getPackageManager(); - final PackageInfo info; + final PackageInfo stubInfo; try { // noinspection WrongConstant - info = pm.getPackageInfo(context.getPackageName(), flags); + stubInfo = pm.getPackageInfo(context.getPackageName(), flags); } catch (PackageManager.NameNotFoundException e) { // Impossible throw new RuntimeException(e); @@ -134,20 +139,19 @@ public class DynLoad { final var cl = loadApk(context); if (cl != null) try { // noinspection WrongConstant - var pkgInfo = pm.getPackageArchiveInfo(apk.getPath(), flags); - cl.updateComponentMap(info, pkgInfo); - - var appInfo = pkgInfo.applicationInfo; + var apkInfo = pm.getPackageArchiveInfo(apk.getPath(), flags); + var mapping = generateMapping(stubInfo, apkInfo); var data = createApkData(); var map = data.getClassToComponent(); // Create the inverse mapping (class to component name) - for (var e : cl.mapping.entrySet()) { + for (var e : mapping.entrySet()) { map.put(e.getValue(), e.getKey()); } - // Create the receiver Application - var app = (Application) cl.loadClass(appInfo.className) + var appInfo = apkInfo.applicationInfo; + // Create the receiver Application with proper constructor + var app = cl.loadClass(appInfo.className) .getConstructor(Object.class) .newInstance(data.getObject()); @@ -162,19 +166,17 @@ public class DynLoad { } } - activeClassLoader = cl; + activeClassLoader = new MappingClassLoader(cl, mapping); - // Send real application to attachBaseContext + // Call Application.attachBaseContext attachContext(app, context); - - return app; } catch (Exception e) { Log.e(DynLoad.class.getSimpleName(), "", e); apk.delete(); + } else { + // Dynamic loading failed, use normal stub classloader + activeClassLoader = new StubClassLoader(stubInfo); } - - activeClassLoader = new StubClassLoader(info); - return null; } // Replace LoadedApk mClassLoader @@ -198,4 +200,72 @@ public class DynLoad { Log.e(DynLoad.class.getSimpleName(), "", e); } } + + private static Map generateMapping(PackageInfo stub, PackageInfo app) { + var mapping = new HashMap(); + { + 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); + } + return mapping; + } } diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/StubApplication.java b/app/stub/src/main/java/com/topjohnwu/magisk/StubApplication.java new file mode 100644 index 000000000..58b4d2ae3 --- /dev/null +++ b/app/stub/src/main/java/com/topjohnwu/magisk/StubApplication.java @@ -0,0 +1,12 @@ +package com.topjohnwu.magisk; + +import android.app.Application; +import android.content.Context; + +public class StubApplication extends Application { + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + DynLoad.loadAndInitializeApp(this); + } +} diff --git a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java b/app/stub/src/main/java/com/topjohnwu/magisk/StubRootService.java similarity index 88% rename from app/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java rename to app/stub/src/main/java/com/topjohnwu/magisk/StubRootService.java index 06db8b56b..9e982dba1 100644 --- a/app/stub/src/main/java/com/topjohnwu/magisk/DelegateRootService.java +++ b/app/stub/src/main/java/com/topjohnwu/magisk/StubRootService.java @@ -9,9 +9,9 @@ import android.util.Log; import java.io.File; import java.lang.reflect.Constructor; -public class DelegateRootService extends ContextWrapper { +public class StubRootService extends ContextWrapper { - public DelegateRootService() { + public StubRootService() { super(null); } @@ -37,7 +37,7 @@ public class DelegateRootService extends ContextWrapper { Object service = ctor.newInstance(this); DynLoad.attachContext(service, base); } catch (Exception e) { - Log.e(DelegateRootService.class.getSimpleName(), "", e); + Log.e(StubRootService.class.getSimpleName(), "", e); } } } diff --git a/buildSrc/src/main/java/Codegen.kt b/buildSrc/src/main/java/Codegen.kt index a95d8d9e2..4e9a7065f 100644 --- a/buildSrc/src/main/java/Codegen.kt +++ b/buildSrc/src/main/java/Codegen.kt @@ -2,13 +2,20 @@ import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property -import org.gradle.api.tasks.* +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream import java.security.SecureRandom -import java.util.* +import java.util.Random import javax.crypto.Cipher import javax.crypto.CipherOutputStream import javax.crypto.spec.IvParameterSpec @@ -169,7 +176,7 @@ abstract class ManifestUpdater: DefaultTask() { |