diff --git a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt index 78ef64ce4..9745eb5ed 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/base/BaseActivity.kt @@ -34,9 +34,11 @@ abstract class BaseActivity : AppCompatActivity() { permissionCallback?.invoke(it) permissionCallback = null } + + private var installCallback: ((Boolean) -> Unit)? = null private val requestInstall = registerForActivityResult(RequestInstall()) { - permissionCallback?.invoke(it) - permissionCallback = null + installCallback?.invoke(it) + installCallback = null } private var contentCallback: ContentResultCallback? = null @@ -93,10 +95,11 @@ abstract class BaseActivity : AppCompatActivity() { callback(true) return } - permissionCallback = callback if (permission == REQUEST_INSTALL_PACKAGES) { + installCallback = callback requestInstall.launch(Unit) } else { + permissionCallback = callback requestPermission.launch(permission) } } diff --git a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt index 2c726f436..76cc8e406 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/download/DownloadService.kt @@ -101,15 +101,9 @@ class DownloadService : NotificationService() { zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk) // Patch and install - val session = APKInstall.startSession(this) - session.openStream(this).use { - val label = applicationInfo.nonLocalizedLabel - if (!HideAPK.patch(this, apk, it, packageName, label)) { - throw IOException("HideAPK patch error") - } - } + subject.intent = HideAPK.upgrade(this, apk) ?: + throw IOException("HideAPK patch error") apk.delete() - subject.intent = session.waitIntent() } else { ActivityTracker.foreground?.let { // Relaunch the process if we are foreground diff --git a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt index 415665d32..500d5fb57 100644 --- a/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt +++ b/app/src/main/java/com/topjohnwu/magisk/core/tasks/HideAPK.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.widget.Toast +import androidx.annotation.WorkerThread import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID import com.topjohnwu.magisk.R import com.topjohnwu.magisk.StubApk @@ -28,6 +29,7 @@ import java.io.FileOutputStream import java.io.IOException import java.io.OutputStream import java.security.SecureRandom +import kotlin.random.asKotlinRandom object HideAPK { @@ -37,6 +39,7 @@ object HideAPK { // Some arbitrary limit const val MAX_LABEL_LENGTH = 32 + const val PLACEHOLDER = "COMPONENT_PLACEHOLDER" private fun genPackageName(): String { val random = SecureRandom() @@ -61,7 +64,61 @@ object HideAPK { return builder.toString() } - fun patch( + private fun classNameGenerator() = sequence { + val c1 = mutableListOf() + val c2 = mutableListOf() + val c3 = mutableListOf() + val random = SecureRandom() + val kRandom = random.asKotlinRandom() + + fun chain(vararg iters: Iterable) = sequence { + iters.forEach { it.forEach { v -> yield(v) } } + } + + for (a in chain('a'..'z', 'A'..'Z')) { + if (a != 'a' && a != 'A') { + c1.add("$a") + } + for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) { + c2.add("$a$b") + for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) { + c3.add("$a$b$c") + } + } + } + + c1.shuffle(random) + c2.shuffle(random) + c3.shuffle(random) + + fun notJavaKeyword(name: String) = when (name) { + "do", "if", "for", "int", "new", "try" -> false + else -> true + } + + fun List.process() = asSequence().filter(::notJavaKeyword) + + val names = mutableListOf() + names.addAll(c1) + names.addAll(c2.process().take(30)) + names.addAll(c3.process().take(30)) + + while (true) { + val seg = 2 + random.nextInt(4) + val cls = StringBuilder() + for (i in 0 until seg) { + cls.append(names.random(kRandom)) + if (i != seg - 1) + cls.append('.') + } + // Old Android does not support capitalized package names + // Check Android 7.0.0 PackageParser#buildClassName + cls[0] = cls[0].lowercaseChar() + yield(cls.toString()) + } + }.distinct().iterator() + + private fun patch( context: Context, apk: File, out: OutputStream, pkg: String, label: CharSequence @@ -72,12 +129,15 @@ object HideAPK { JarMap.open(apk, true).use { jar -> val je = jar.getJarEntry(ANDROID_MANIFEST) val xml = AXML(jar.getRawData(je)) + val generator = classNameGenerator() if (!xml.patchStrings { for (i in it.indices) { val s = it[i] if (s.contains(APPLICATION_ID)) { it[i] = s.replace(APPLICATION_ID, pkg) + } else if (s.contains(PLACEHOLDER)) { + it[i] = generator.next() } else if (s == origLabel) { it[i] = label.toString() } @@ -193,4 +253,17 @@ object HideAPK { } if (!success) onFailure.run() } + + @WorkerThread + fun upgrade(context: Context, apk: File): Intent? { + val label = context.applicationInfo.nonLocalizedLabel + val pkg = context.packageName + val session = APKInstall.startSession(context) + session.openStream(context).use { + if (!patch(context, apk, it, pkg, label)) { + return null + } + } + return session.waitIntent() + } } diff --git a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt index 6dd1de199..0a81e17c3 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt +++ b/app/src/main/java/com/topjohnwu/magisk/ui/MainActivity.kt @@ -12,6 +12,7 @@ import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.forEach import androidx.core.view.isGone import androidx.core.view.isVisible +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import com.topjohnwu.magisk.MainDirections import com.topjohnwu.magisk.R @@ -22,12 +23,15 @@ import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.Info import com.topjohnwu.magisk.core.isRunningAsStub import com.topjohnwu.magisk.core.model.module.LocalModule +import com.topjohnwu.magisk.core.tasks.HideAPK import com.topjohnwu.magisk.databinding.ActivityMainMd2Binding import com.topjohnwu.magisk.ktx.startAnimations import com.topjohnwu.magisk.ui.home.HomeFragmentDirections import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.view.MagiskDialog import com.topjohnwu.magisk.view.Shortcuts +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import java.io.File class MainViewModel : BaseViewModel() @@ -59,6 +63,7 @@ class MainActivity : SplashActivity() { setContentView() showUnsupportedMessage() askForHomeShortcut() + checkStubComponent() // Ask permission to post notifications for background update check if (Config.checkUpdate) { @@ -227,4 +232,22 @@ class MainActivity : SplashActivity() { }.show() } } + + @SuppressLint("InlinedApi") + private fun checkStubComponent() { + if (intent.component?.className?.contains(HideAPK.PLACEHOLDER) == true) { + // The stub APK was not properly patched, re-apply our changes + withPermission(Manifest.permission.REQUEST_INSTALL_PACKAGES) { granted -> + if (granted) { + lifecycleScope.launch(Dispatchers.IO) { + val apk = File(applicationInfo.sourceDir) + HideAPK.upgrade(this@MainActivity, apk)?.let { + startActivity(it) + } + } + } + } + } + } + } diff --git a/buildSrc/src/main/java/Codegen.kt b/buildSrc/src/main/java/Codegen.kt index e535a9dc9..bd022088e 100644 --- a/buildSrc/src/main/java/Codegen.kt +++ b/buildSrc/src/main/java/Codegen.kt @@ -82,7 +82,7 @@ fun genStubManifest(srcDir: File, outDir: File): String { cmpList.add( """ | | | @@ -111,7 +111,7 @@ fun genStubManifest(srcDir: File, outDir: File): String { cmpList.add( """ | | | @@ -123,7 +123,7 @@ fun genStubManifest(srcDir: File, outDir: File): String { cmpList.add( """ |""".ind(2) ) cmpList.add( """ |""".ind(2) ) - val names = mutableListOf() - names.addAll(c1) - names.addAll(c2.subList(0, 10)) - names.addAll(c3.subList(0, 10)) - names.shuffle(RANDOM) + val classNameGenerator = sequence { + fun notJavaKeyword(name: String) = when (name) { + "do", "if", "for", "int", "new", "try" -> false + else -> true + } - val pkgNames = names - // Distinct by lower case to support case insensitive file systems - .distinctBy { it.toLowerCase(Locale.ROOT) } - // Old Android does not support capitalized package names - // Check Android 7.0.0 PackageParser#buildClassName - .map { it.decapitalize(Locale.ROOT) } + fun List.process() = asSequence() + .filter(::notJavaKeyword) + // Distinct by lower case to support case insensitive file systems + .distinctBy { it.toLowerCase(Locale.ROOT) } - fun isJavaKeyword(name: String) = when (name) { - "do", "if", "for", "int", "new", "try" -> true - else -> false - } + val names = mutableListOf() + names.addAll(c1) + names.addAll(c2.process().take(30)) + names.addAll(c3.process().take(30)) + names.shuffle(RANDOM) - val cmps = mutableListOf() - val usedNames = mutableListOf() + while (true) { + val cls = StringBuilder() + cls.append(names.random(kRANDOM)) + cls.append('.') + cls.append(names.random(kRANDOM)) + // Old Android does not support capitalized package names + // Check Android 7.0.0 PackageParser#buildClassName + cls[0] = cls[0].toLowerCase() + yield(cls.toString()) + } + }.distinct().iterator() - fun genCmpName(): String { - var pkgName: String - do { - pkgName = pkgNames.random(kRANDOM) - } while (isJavaKeyword(pkgName)) - - var clzName: String - do { - clzName = names.random(kRANDOM) - } while (isJavaKeyword(clzName)) - val cmp = "${pkgName}.${clzName}" - usedNames.add(cmp) - return cmp - } - - fun genClass(type: String) { - val clzName = genCmpName() + fun genClass(type: String): String { + val clzName = classNameGenerator.next() val (pkg, name) = clzName.split('.') val pkgDir = File(outDir, pkg) pkgDir.mkdirs() @@ -195,22 +188,18 @@ fun genStubManifest(srcDir: File, outDir: File): String { it.println("package $pkg;") it.println("public class $name extends com.topjohnwu.magisk.$type {}") } + return clzName } // Generate 2 non redirect-able classes - genClass("DelegateComponentFactory") - genClass("DelegateApplication") - - for (gen in cmpList) { - val name = genCmpName() - cmps.add(gen.format(name)) - } + val factory = genClass("DelegateComponentFactory") + val app = genClass("DelegateApplication") // Shuffle the order of the components - cmps.shuffle(RANDOM) + cmpList.shuffle(RANDOM) val xml = File(srcDir, "AndroidManifest.xml").readText() - return xml.format(usedNames[0], usedNames[1], cmps.joinToString("\n\n")) + return xml.format(factory, app, cmpList.joinToString("\n\n")) } fun genEncryptedResources(res: InputStream, outDir: File) {