mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-27 12:05:30 +00:00
Dynamically generate component names at runtime
This commit is contained in:
parent
71b0c8b42b
commit
357d913f18
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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<String>()
|
||||
val c2 = mutableListOf<String>()
|
||||
val c3 = mutableListOf<String>()
|
||||
val random = SecureRandom()
|
||||
val kRandom = random.asKotlinRandom()
|
||||
|
||||
fun <T> chain(vararg iters: Iterable<T>) = 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<String>.process() = asSequence().filter(::notJavaKeyword)
|
||||
|
||||
val names = mutableListOf<String>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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<ActivityMainMd2Binding>() {
|
||||
setContentView()
|
||||
showUnsupportedMessage()
|
||||
askForHomeShortcut()
|
||||
checkStubComponent()
|
||||
|
||||
// Ask permission to post notifications for background update check
|
||||
if (Config.checkUpdate) {
|
||||
@ -227,4 +232,22 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
cmpList.add(
|
||||
"""
|
||||
|<provider
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_0"
|
||||
| android:authorities="${'$'}{applicationId}.provider"
|
||||
| android:directBootAware="true"
|
||||
| android:exported="false"
|
||||
@ -92,7 +92,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
cmpList.add(
|
||||
"""
|
||||
|<receiver
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_1"
|
||||
| android:exported="false">
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
@ -111,7 +111,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
cmpList.add(
|
||||
"""
|
||||
|<activity
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_2"
|
||||
| android:exported="true">
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.MAIN" />
|
||||
@ -123,7 +123,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
cmpList.add(
|
||||
"""
|
||||
|<activity
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_3"
|
||||
| android:directBootAware="true"
|
||||
| android:exported="false"
|
||||
| android:taskAffinity=""
|
||||
@ -138,56 +138,49 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
||||
cmpList.add(
|
||||
"""
|
||||
|<service
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_4"
|
||||
| android:exported="false" />""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add(
|
||||
"""
|
||||
|<service
|
||||
| android:name="%s"
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_5"
|
||||
| android:exported="false"
|
||||
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
|
||||
)
|
||||
|
||||
val names = mutableListOf<String>()
|
||||
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<String>.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<String>()
|
||||
names.addAll(c1)
|
||||
names.addAll(c2.process().take(30))
|
||||
names.addAll(c3.process().take(30))
|
||||
names.shuffle(RANDOM)
|
||||
|
||||
val cmps = mutableListOf<String>()
|
||||
val usedNames = mutableListOf<String>()
|
||||
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user