mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-11-23 18:15: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?.invoke(it)
|
||||||
permissionCallback = null
|
permissionCallback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var installCallback: ((Boolean) -> Unit)? = null
|
||||||
private val requestInstall = registerForActivityResult(RequestInstall()) {
|
private val requestInstall = registerForActivityResult(RequestInstall()) {
|
||||||
permissionCallback?.invoke(it)
|
installCallback?.invoke(it)
|
||||||
permissionCallback = null
|
installCallback = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private var contentCallback: ContentResultCallback? = null
|
private var contentCallback: ContentResultCallback? = null
|
||||||
@ -93,10 +95,11 @@ abstract class BaseActivity : AppCompatActivity() {
|
|||||||
callback(true)
|
callback(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
permissionCallback = callback
|
|
||||||
if (permission == REQUEST_INSTALL_PACKAGES) {
|
if (permission == REQUEST_INSTALL_PACKAGES) {
|
||||||
|
installCallback = callback
|
||||||
requestInstall.launch(Unit)
|
requestInstall.launch(Unit)
|
||||||
} else {
|
} else {
|
||||||
|
permissionCallback = callback
|
||||||
requestPermission.launch(permission)
|
requestPermission.launch(permission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,15 +101,9 @@ class DownloadService : NotificationService() {
|
|||||||
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
zf.getInputStream(zf.getEntry("assets/stub.apk")).writeTo(apk)
|
||||||
|
|
||||||
// Patch and install
|
// Patch and install
|
||||||
val session = APKInstall.startSession(this)
|
subject.intent = HideAPK.upgrade(this, apk) ?:
|
||||||
session.openStream(this).use {
|
throw IOException("HideAPK patch error")
|
||||||
val label = applicationInfo.nonLocalizedLabel
|
|
||||||
if (!HideAPK.patch(this, apk, it, packageName, label)) {
|
|
||||||
throw IOException("HideAPK patch error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
apk.delete()
|
apk.delete()
|
||||||
subject.intent = session.waitIntent()
|
|
||||||
} else {
|
} else {
|
||||||
ActivityTracker.foreground?.let {
|
ActivityTracker.foreground?.let {
|
||||||
// Relaunch the process if we are foreground
|
// Relaunch the process if we are foreground
|
||||||
|
@ -4,6 +4,7 @@ import android.app.Activity
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.WorkerThread
|
||||||
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
import com.topjohnwu.magisk.BuildConfig.APPLICATION_ID
|
||||||
import com.topjohnwu.magisk.R
|
import com.topjohnwu.magisk.R
|
||||||
import com.topjohnwu.magisk.StubApk
|
import com.topjohnwu.magisk.StubApk
|
||||||
@ -28,6 +29,7 @@ import java.io.FileOutputStream
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
import kotlin.random.asKotlinRandom
|
||||||
|
|
||||||
object HideAPK {
|
object HideAPK {
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ object HideAPK {
|
|||||||
|
|
||||||
// Some arbitrary limit
|
// Some arbitrary limit
|
||||||
const val MAX_LABEL_LENGTH = 32
|
const val MAX_LABEL_LENGTH = 32
|
||||||
|
const val PLACEHOLDER = "COMPONENT_PLACEHOLDER"
|
||||||
|
|
||||||
private fun genPackageName(): String {
|
private fun genPackageName(): String {
|
||||||
val random = SecureRandom()
|
val random = SecureRandom()
|
||||||
@ -61,7 +64,61 @@ object HideAPK {
|
|||||||
return builder.toString()
|
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,
|
context: Context,
|
||||||
apk: File, out: OutputStream,
|
apk: File, out: OutputStream,
|
||||||
pkg: String, label: CharSequence
|
pkg: String, label: CharSequence
|
||||||
@ -72,12 +129,15 @@ object HideAPK {
|
|||||||
JarMap.open(apk, true).use { jar ->
|
JarMap.open(apk, true).use { jar ->
|
||||||
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
val je = jar.getJarEntry(ANDROID_MANIFEST)
|
||||||
val xml = AXML(jar.getRawData(je))
|
val xml = AXML(jar.getRawData(je))
|
||||||
|
val generator = classNameGenerator()
|
||||||
|
|
||||||
if (!xml.patchStrings {
|
if (!xml.patchStrings {
|
||||||
for (i in it.indices) {
|
for (i in it.indices) {
|
||||||
val s = it[i]
|
val s = it[i]
|
||||||
if (s.contains(APPLICATION_ID)) {
|
if (s.contains(APPLICATION_ID)) {
|
||||||
it[i] = s.replace(APPLICATION_ID, pkg)
|
it[i] = s.replace(APPLICATION_ID, pkg)
|
||||||
|
} else if (s.contains(PLACEHOLDER)) {
|
||||||
|
it[i] = generator.next()
|
||||||
} else if (s == origLabel) {
|
} else if (s == origLabel) {
|
||||||
it[i] = label.toString()
|
it[i] = label.toString()
|
||||||
}
|
}
|
||||||
@ -193,4 +253,17 @@ object HideAPK {
|
|||||||
}
|
}
|
||||||
if (!success) onFailure.run()
|
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.forEach
|
||||||
import androidx.core.view.isGone
|
import androidx.core.view.isGone
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
import com.topjohnwu.magisk.MainDirections
|
import com.topjohnwu.magisk.MainDirections
|
||||||
import com.topjohnwu.magisk.R
|
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.Info
|
||||||
import com.topjohnwu.magisk.core.isRunningAsStub
|
import com.topjohnwu.magisk.core.isRunningAsStub
|
||||||
import com.topjohnwu.magisk.core.model.module.LocalModule
|
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.databinding.ActivityMainMd2Binding
|
||||||
import com.topjohnwu.magisk.ktx.startAnimations
|
import com.topjohnwu.magisk.ktx.startAnimations
|
||||||
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
import com.topjohnwu.magisk.ui.home.HomeFragmentDirections
|
||||||
import com.topjohnwu.magisk.utils.Utils
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
import com.topjohnwu.magisk.view.MagiskDialog
|
import com.topjohnwu.magisk.view.MagiskDialog
|
||||||
import com.topjohnwu.magisk.view.Shortcuts
|
import com.topjohnwu.magisk.view.Shortcuts
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class MainViewModel : BaseViewModel()
|
class MainViewModel : BaseViewModel()
|
||||||
@ -59,6 +63,7 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
|
|||||||
setContentView()
|
setContentView()
|
||||||
showUnsupportedMessage()
|
showUnsupportedMessage()
|
||||||
askForHomeShortcut()
|
askForHomeShortcut()
|
||||||
|
checkStubComponent()
|
||||||
|
|
||||||
// Ask permission to post notifications for background update check
|
// Ask permission to post notifications for background update check
|
||||||
if (Config.checkUpdate) {
|
if (Config.checkUpdate) {
|
||||||
@ -227,4 +232,22 @@ class MainActivity : SplashActivity<ActivityMainMd2Binding>() {
|
|||||||
}.show()
|
}.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(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<provider
|
|<provider
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_0"
|
||||||
| android:authorities="${'$'}{applicationId}.provider"
|
| android:authorities="${'$'}{applicationId}.provider"
|
||||||
| android:directBootAware="true"
|
| android:directBootAware="true"
|
||||||
| android:exported="false"
|
| android:exported="false"
|
||||||
@ -92,7 +92,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
|||||||
cmpList.add(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<receiver
|
|<receiver
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_1"
|
||||||
| android:exported="false">
|
| android:exported="false">
|
||||||
| <intent-filter>
|
| <intent-filter>
|
||||||
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||||
@ -111,7 +111,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
|||||||
cmpList.add(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<activity
|
|<activity
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_2"
|
||||||
| android:exported="true">
|
| android:exported="true">
|
||||||
| <intent-filter>
|
| <intent-filter>
|
||||||
| <action android:name="android.intent.action.MAIN" />
|
| <action android:name="android.intent.action.MAIN" />
|
||||||
@ -123,7 +123,7 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
|||||||
cmpList.add(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<activity
|
|<activity
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_3"
|
||||||
| android:directBootAware="true"
|
| android:directBootAware="true"
|
||||||
| android:exported="false"
|
| android:exported="false"
|
||||||
| android:taskAffinity=""
|
| android:taskAffinity=""
|
||||||
@ -138,56 +138,49 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
|||||||
cmpList.add(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<service
|
|<service
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_4"
|
||||||
| android:exported="false" />""".ind(2)
|
| android:exported="false" />""".ind(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
cmpList.add(
|
cmpList.add(
|
||||||
"""
|
"""
|
||||||
|<service
|
|<service
|
||||||
| android:name="%s"
|
| android:name="x.COMPONENT_PLACEHOLDER_5"
|
||||||
| android:exported="false"
|
| android:exported="false"
|
||||||
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
|
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
|
||||||
)
|
)
|
||||||
|
|
||||||
val names = mutableListOf<String>()
|
val classNameGenerator = sequence {
|
||||||
names.addAll(c1)
|
fun notJavaKeyword(name: String) = when (name) {
|
||||||
names.addAll(c2.subList(0, 10))
|
"do", "if", "for", "int", "new", "try" -> false
|
||||||
names.addAll(c3.subList(0, 10))
|
else -> true
|
||||||
names.shuffle(RANDOM)
|
}
|
||||||
|
|
||||||
val pkgNames = names
|
fun List<String>.process() = asSequence()
|
||||||
// Distinct by lower case to support case insensitive file systems
|
.filter(::notJavaKeyword)
|
||||||
.distinctBy { it.toLowerCase(Locale.ROOT) }
|
// Distinct by lower case to support case insensitive file systems
|
||||||
// Old Android does not support capitalized package names
|
.distinctBy { it.toLowerCase(Locale.ROOT) }
|
||||||
// Check Android 7.0.0 PackageParser#buildClassName
|
|
||||||
.map { it.decapitalize(Locale.ROOT) }
|
|
||||||
|
|
||||||
fun isJavaKeyword(name: String) = when (name) {
|
val names = mutableListOf<String>()
|
||||||
"do", "if", "for", "int", "new", "try" -> true
|
names.addAll(c1)
|
||||||
else -> false
|
names.addAll(c2.process().take(30))
|
||||||
}
|
names.addAll(c3.process().take(30))
|
||||||
|
names.shuffle(RANDOM)
|
||||||
|
|
||||||
val cmps = mutableListOf<String>()
|
while (true) {
|
||||||
val usedNames = mutableListOf<String>()
|
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 {
|
fun genClass(type: String): String {
|
||||||
var pkgName: String
|
val clzName = classNameGenerator.next()
|
||||||
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()
|
|
||||||
val (pkg, name) = clzName.split('.')
|
val (pkg, name) = clzName.split('.')
|
||||||
val pkgDir = File(outDir, pkg)
|
val pkgDir = File(outDir, pkg)
|
||||||
pkgDir.mkdirs()
|
pkgDir.mkdirs()
|
||||||
@ -195,22 +188,18 @@ fun genStubManifest(srcDir: File, outDir: File): String {
|
|||||||
it.println("package $pkg;")
|
it.println("package $pkg;")
|
||||||
it.println("public class $name extends com.topjohnwu.magisk.$type {}")
|
it.println("public class $name extends com.topjohnwu.magisk.$type {}")
|
||||||
}
|
}
|
||||||
|
return clzName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate 2 non redirect-able classes
|
// Generate 2 non redirect-able classes
|
||||||
genClass("DelegateComponentFactory")
|
val factory = genClass("DelegateComponentFactory")
|
||||||
genClass("DelegateApplication")
|
val app = genClass("DelegateApplication")
|
||||||
|
|
||||||
for (gen in cmpList) {
|
|
||||||
val name = genCmpName()
|
|
||||||
cmps.add(gen.format(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shuffle the order of the components
|
// Shuffle the order of the components
|
||||||
cmps.shuffle(RANDOM)
|
cmpList.shuffle(RANDOM)
|
||||||
|
|
||||||
val xml = File(srcDir, "AndroidManifest.xml").readText()
|
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) {
|
fun genEncryptedResources(res: InputStream, outDir: File) {
|
||||||
|
Loading…
Reference in New Issue
Block a user