Do not use string resources for app label

This not only simplifies hiding stub APKs (no resource IDs involved),
but also opens the opportunity to allow users to customize whatever
app name they want after it is hidden.
This commit is contained in:
topjohnwu 2019-10-17 04:47:46 -04:00
parent 40eda05a30
commit 78daa2eb62
6 changed files with 50 additions and 49 deletions

View File

@ -32,7 +32,7 @@
-keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker -keep,allowobfuscation class * extends com.topjohnwu.magisk.base.DelegateWorker
# BootSigner # BootSigner
-keepclassmembers class com.topjohnwu.signing.BootSigner { *; } -keep class a.a { *; }
# Strip logging # Strip logging
-assumenosideeffects class timber.log.Timber.Tree { *; } -assumenosideeffects class timber.log.Timber.Tree { *; }

View File

@ -1,18 +1,21 @@
package a; package a;
import androidx.annotation.Keep;
import androidx.core.app.AppComponentFactory; import androidx.core.app.AppComponentFactory;
import com.topjohnwu.magisk.utils.PatchAPK; import com.topjohnwu.magisk.utils.PatchAPK;
import com.topjohnwu.signing.BootSigner; import com.topjohnwu.signing.BootSigner;
@Keep
public class a extends AppComponentFactory { public class a extends AppComponentFactory {
@Deprecated
public static boolean patchAPK(String in, String out, String pkg) { public static boolean patchAPK(String in, String out, String pkg) {
return PatchAPK.patch(in, out, pkg); return PatchAPK.patch(in, out, pkg);
} }
public static boolean patchAPK(String in, String out, String pkg, String label) {
return PatchAPK.patch(in, out, pkg, label);
}
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
BootSigner.main(args); BootSigner.main(args);
} }

View File

@ -4,7 +4,6 @@ import com.topjohnwu.magisk.BuildConfig
import com.topjohnwu.magisk.Config import com.topjohnwu.magisk.Config
import com.topjohnwu.magisk.R import com.topjohnwu.magisk.R
import com.topjohnwu.magisk.cmp import com.topjohnwu.magisk.cmp
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Restore
import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade import com.topjohnwu.magisk.model.entity.internal.Configuration.APK.Upgrade
import com.topjohnwu.magisk.model.entity.internal.DownloadSubject import com.topjohnwu.magisk.model.entity.internal.DownloadSubject
@ -12,7 +11,6 @@ import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.utils.PatchAPK import com.topjohnwu.magisk.utils.PatchAPK
import com.topjohnwu.magisk.utils.Utils import com.topjohnwu.magisk.utils.Utils
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import timber.log.Timber
import java.io.File import java.io.File
private fun RemoteFileService.patchPackage(apk: File, id: Int) { private fun RemoteFileService.patchPackage(apk: File, id: Int) {
@ -24,17 +22,7 @@ private fun RemoteFileService.patchPackage(apk: File, id: Int) {
.setContentText("") .setContentText("")
} }
val patched = File(apk.parent, "patched.apk") val patched = File(apk.parent, "patched.apk")
try { PatchAPK.patch(apk, patched, packageName, applicationInfo.nonLocalizedLabel.toString())
// Try using the new APK to patch itself
val loader = DynamicClassLoader(apk)
loader.loadClass("a.a")
.getMethod("patchAPK", String::class.java, String::class.java, String::class.java)
.invoke(null, apk.path, patched.path, packageName)
} catch (e: Exception) {
Timber.e(e)
// Fallback to use the current implementation
PatchAPK.patch(apk.path, patched.path, packageName)
}
apk.delete() apk.delete()
patched.renameTo(apk) patched.renameTo(apk)
} }

View File

@ -67,7 +67,8 @@ class SettingsFragment : BasePreferenceFragment() {
val suCategory = findPreference<PreferenceCategory>("superuser")!! val suCategory = findPreference<PreferenceCategory>("superuser")!!
val hideManager = findPreference<Preference>("hide")!! val hideManager = findPreference<Preference>("hide")!!
hideManager.setOnPreferenceClickListener { hideManager.setOnPreferenceClickListener {
PatchAPK.hideManager(requireContext()) // TODO: Add UI to allow user to customize app name
PatchAPK.hideManager(requireContext(), "Manager")
true true
} }
val restoreManager = findPreference<Preference>("restore") val restoreManager = findPreference<Preference>("restore")

View File

@ -3,6 +3,7 @@ package com.topjohnwu.magisk.utils
import android.content.Context import android.content.Context
import android.widget.Toast import android.widget.Toast
import com.topjohnwu.magisk.* import com.topjohnwu.magisk.*
import com.topjohnwu.magisk.extensions.DynamicClassLoader
import com.topjohnwu.magisk.extensions.subscribeK import com.topjohnwu.magisk.extensions.subscribeK
import com.topjohnwu.magisk.ui.SplashActivity import com.topjohnwu.magisk.ui.SplashActivity
import com.topjohnwu.magisk.view.Notifications import com.topjohnwu.magisk.view.Notifications
@ -46,52 +47,38 @@ object PatchAPK {
} }
private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean { private fun findAndPatch(xml: ByteArray, from: String, to: String): Boolean {
if (from.length != to.length) if (to.length > from.length)
return false return false
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer() val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asCharBuffer()
val offList = mutableListOf<Int>() val offList = mutableListOf<Int>()
var i = 0 var i = 0
while (i < buf.length - from.length) { loop@ while (i < buf.length - from.length) {
var match = true for (j in from.indices) {
for (j in 0 until from.length) {
if (buf.get(i + j) != from[j]) { if (buf.get(i + j) != from[j]) {
match = false ++i
break continue@loop
} }
} }
if (match) {
offList.add(i) offList.add(i)
i += from.length i += from.length
} }
++i
}
if (offList.isEmpty()) if (offList.isEmpty())
return false return false
val toBuf = to.toCharArray().copyOf(from.length)
for (off in offList) { for (off in offList) {
buf.position(off) buf.position(off)
buf.put(to) buf.put(toBuf)
} }
return true return true
} }
private fun findAndPatch(xml: ByteArray, a: Int, b: Int): Boolean { private fun patchAndHide(context: Context, label: String): Boolean {
val buf = ByteBuffer.wrap(xml).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
val len = xml.size / 4
for (i in 0 until len) {
if (buf.get(i) == a) {
buf.put(i, b)
return true
}
}
return false
}
private fun patchAndHide(context: Context): Boolean {
// Generate a new app with random package name // Generate a new app with random package name
val repack = File(context.filesDir, "patched.apk") val repack = File(context.filesDir, "patched.apk")
val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length) val pkg = genPackageName("com.", BuildConfig.APPLICATION_ID.length)
if (!patch(context.packageCodePath, repack.path, pkg)) if (!patch(context.packageCodePath, repack.path, pkg, label))
return false return false
// Install the application // Install the application
@ -107,14 +94,15 @@ object PatchAPK {
} }
@JvmStatic @JvmStatic
fun patch(apk: String, out: String, pkg: String): Boolean { @JvmOverloads
fun patch(apk: String, out: String, pkg: String, label: String = "Manager"): Boolean {
try { try {
val jar = JarMap(apk) val jar = JarMap(apk)
val je = jar.getJarEntry(Const.ANDROID_MANIFEST) val je = jar.getJarEntry(Const.ANDROID_MANIFEST)
val xml = jar.getRawData(je) val xml = jar.getRawData(je)
if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) || if (!findAndPatch(xml, BuildConfig.APPLICATION_ID, pkg) ||
!findAndPatch(xml, R.string.app_name, R.string.re_app_name)) !findAndPatch(xml, "Magisk Manager", label))
return false return false
// Write apk changes // Write apk changes
@ -128,11 +116,32 @@ object PatchAPK {
return true return true
} }
fun hideManager(context: Context) { fun patch(apk: File, out: File, pkg: String, label: String): Boolean {
try {
// Try using the new APK to patch itself
val loader = DynamicClassLoader(apk)
val cls = loader.loadClass("a.a")
for (m in cls.declaredMethods) {
val pars = m.parameterTypes
if (pars.size == 4 && pars[0] == String::class.java) {
return m.invoke(null, apk.path, out.path, pkg, label) as Boolean
}
}
throw Exception("No matching method found")
} catch (e: Exception) {
Timber.e(e)
// Fallback to use the current implementation
patch(apk.path, out.path, pkg, label)
}
return false
}
fun hideManager(context: Context, label: String) {
Completable.fromAction { Completable.fromAction {
val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title)) val progress = Notifications.progress(context, context.getString(R.string.hide_manager_title))
Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build()) Notifications.mgr.notify(Const.ID.HIDE_MANAGER_NOTIFICATION_ID, progress.build())
if (!patchAndHide(context)) if (!patchAndHide(context, label))
Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG) Utils.toast(R.string.hide_manager_fail_toast, Toast.LENGTH_LONG)
Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID) Notifications.mgr.cancel(Const.ID.HIDE_MANAGER_NOTIFICATION_ID)
}.subscribeK() }.subscribeK()

View File

@ -8,7 +8,7 @@
<application <application
android:icon="@drawable/ic_launcher" android:icon="@drawable/ic_launcher"
android:installLocation="internalOnly" android:installLocation="internalOnly"
android:label="@string/app_name" android:label="Magisk Manager"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity